code-review-graph-codeblackwell 2.3.6.post1__tar.gz → 2.4.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 (104) hide show
  1. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/PKG-INFO +3 -3
  2. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/README.md +2 -2
  3. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/graph.py +45 -1
  4. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/incremental.py +225 -1
  5. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/main.py +5 -0
  6. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/parser.py +1143 -42
  7. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/tools/query.py +37 -1
  8. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/tools/review.py +8 -0
  9. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/visualization.py +28 -7
  10. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/docs/COMMANDS.md +3 -1
  11. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/docs/FEATURES.md +1 -1
  12. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/docs/USAGE.md +1 -1
  13. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/docs/schema.md +27 -0
  14. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/pyproject.toml +1 -1
  15. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/.beads/README.md +0 -0
  16. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/.beads/hooks/post-checkout +0 -0
  17. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/.beads/hooks/post-merge +0 -0
  18. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/.beads/hooks/pre-commit +0 -0
  19. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/.beads/hooks/pre-push +0 -0
  20. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/.beads/hooks/prepare-commit-msg +0 -0
  21. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/.gitignore +0 -0
  22. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/LICENSE +0 -0
  23. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code-review-graph-vscode/LICENSE +0 -0
  24. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code-review-graph-vscode/README.md +0 -0
  25. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/__init__.py +0 -0
  26. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/__main__.py +0 -0
  27. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/analysis.py +0 -0
  28. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/changes.py +0 -0
  29. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/cli.py +0 -0
  30. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/communities.py +0 -0
  31. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/constants.py +0 -0
  32. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/context_savings.py +0 -0
  33. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/custom_languages.py +0 -0
  34. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/daemon.py +0 -0
  35. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/daemon_cli.py +0 -0
  36. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/embeddings.py +0 -0
  37. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/enrich.py +0 -0
  38. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/eval/__init__.py +0 -0
  39. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/eval/benchmarks/__init__.py +0 -0
  40. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/eval/benchmarks/agent_baseline.py +0 -0
  41. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/eval/benchmarks/build_performance.py +0 -0
  42. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/eval/benchmarks/flow_completeness.py +0 -0
  43. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/eval/benchmarks/impact_accuracy.py +0 -0
  44. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/eval/benchmarks/multi_hop_retrieval.py +0 -0
  45. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/eval/benchmarks/search_quality.py +0 -0
  46. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/eval/benchmarks/token_efficiency.py +0 -0
  47. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/eval/configs/code-review-graph.yaml +0 -0
  48. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/eval/configs/express.yaml +0 -0
  49. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/eval/configs/fastapi.yaml +0 -0
  50. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/eval/configs/flask.yaml +0 -0
  51. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/eval/configs/gin.yaml +0 -0
  52. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/eval/configs/httpx.yaml +0 -0
  53. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/eval/reporter.py +0 -0
  54. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/eval/runner.py +0 -0
  55. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/eval/scorer.py +0 -0
  56. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/eval/token_benchmark.py +0 -0
  57. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/exports.py +0 -0
  58. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/flows.py +0 -0
  59. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/graph_diff.py +0 -0
  60. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/hints.py +0 -0
  61. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/jedi_resolver.py +0 -0
  62. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/memory.py +0 -0
  63. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/migrations.py +0 -0
  64. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/postprocessing.py +0 -0
  65. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/prompts.py +0 -0
  66. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/refactor.py +0 -0
  67. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/registry.py +0 -0
  68. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/rescript_resolver.py +0 -0
  69. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/search.py +0 -0
  70. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/skills.py +0 -0
  71. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/spring_resolver.py +0 -0
  72. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/temporal_resolver.py +0 -0
  73. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/token_benchmark.py +0 -0
  74. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/tools/__init__.py +0 -0
  75. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/tools/_common.py +0 -0
  76. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/tools/analysis_tools.py +0 -0
  77. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/tools/build.py +0 -0
  78. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/tools/community_tools.py +0 -0
  79. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/tools/context.py +0 -0
  80. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/tools/docs.py +0 -0
  81. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/tools/flows_tools.py +0 -0
  82. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/tools/refactor_tools.py +0 -0
  83. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/tools/registry_tools.py +0 -0
  84. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/tsconfig_resolver.py +0 -0
  85. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/code_review_graph/wiki.py +0 -0
  86. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/docs/CUSTOM_LANGUAGES.md +0 -0
  87. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/docs/FAQ.md +0 -0
  88. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/docs/GITHUB_ACTION.md +0 -0
  89. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/docs/INDEX.md +0 -0
  90. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/docs/LEGAL.md +0 -0
  91. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/docs/LLM-OPTIMIZED-REFERENCE.md +0 -0
  92. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/docs/REPRODUCING.md +0 -0
  93. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/docs/ROADMAP.md +0 -0
  94. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/docs/TROUBLESHOOTING.md +0 -0
  95. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/docs/architecture.md +0 -0
  96. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/hooks/hooks.json +0 -0
  97. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/hooks/session-start.sh +0 -0
  98. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/skills/build-graph/SKILL.md +0 -0
  99. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/skills/debug-issue/SKILL.md +0 -0
  100. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/skills/explore-codebase/SKILL.md +0 -0
  101. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/skills/refactor-safely/SKILL.md +0 -0
  102. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/skills/review-changes/SKILL.md +0 -0
  103. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/skills/review-delta/SKILL.md +0 -0
  104. {code_review_graph_codeblackwell-2.3.6.post1 → code_review_graph_codeblackwell-2.4.0}/skills/review-pr/SKILL.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-review-graph-codeblackwell
3
- Version: 2.3.6.post1
3
+ Version: 2.4.0
4
4
  Summary: Local-first knowledge graph for token-efficient code review through MCP and CLI. Fork of tirth8205/code-review-graph with additional fixes; CLI and MCP server remain `code-review-graph`.
5
5
  Project-URL: Homepage, https://github.com/CodeBlackwell/code-review-graph-codeblackwell
6
6
  Project-URL: Repository, https://github.com/CodeBlackwell/code-review-graph-codeblackwell
@@ -191,7 +191,7 @@ Large monorepos are where token waste is most painful. The graph cuts through th
191
191
  <img src="diagrams/diagram9_language_coverage.png" alt="Language coverage organized by category: Web, Backend, Systems, Mobile, Scripting, Config, plus Jupyter and Databricks notebook support" width="90%" />
192
192
  </p>
193
193
 
194
- Parser support covers functions, classes, imports, call sites, inheritance, and test detection across the current parser surface, using Tree-sitter where available and targeted fallbacks where needed. Current support includes Python, JavaScript/TypeScript/TSX, Go, Rust, Java, C/C++, C#, Ruby, Kotlin, Swift, PHP, Scala, Solidity, Dart, R, Perl, Lua/Luau, Objective-C, shell scripts, Elixir, Zig, PowerShell, Julia, ReScript, GDScript, Nix, Verilog/SystemVerilog, SQL, Vue/Svelte SFCs, Astro files parsed through the TypeScript parser, Jupyter/Databricks notebooks (`.ipynb`), and Perl XS files (`.xs`).
194
+ Parser support covers functions, classes, imports, call sites, inheritance, and test detection across the current parser surface, using Tree-sitter where available and targeted fallbacks where needed. Current support includes Python, JavaScript/TypeScript/TSX, Go, Rust, Java, C/C++, C#, Ruby, Kotlin, Swift, PHP, Scala, Solidity, Dart, R, Perl, Lua/Luau, Objective-C, shell scripts, Elixir, Zig, PowerShell, Julia, ReScript, GDScript, Nix, Verilog/SystemVerilog, SQL, CSS/SCSS/LESS stylesheets, Vue/Svelte SFCs (including `<style>` blocks), Astro files parsed through the TypeScript parser, Jupyter/Databricks notebooks (`.ipynb`), and Perl XS files (`.xs`).
195
195
 
196
196
  ### Add your own language (no fork needed)
197
197
 
@@ -320,7 +320,7 @@ The benchmark also runs an honest **co-change mode**: the predictor is seeded wi
320
320
  | Feature | Details |
321
321
  |---------|---------|
322
322
  | **Incremental updates** | Re-parses only changed files. Subsequent updates complete in under 2 seconds. |
323
- | **Broad language + notebook support** | Python, JavaScript/TypeScript/TSX, Go, Rust, Java, C/C++, C#, Ruby, Kotlin, Swift, PHP, Scala, Solidity, Dart, R, Perl, Lua/Luau, Objective-C, shell scripts, Elixir, Zig, PowerShell, Julia, ReScript, GDScript, Nix, Verilog/SystemVerilog, SQL, Vue/Svelte SFCs, Astro files parsed through the TypeScript parser, Jupyter/Databricks (.ipynb), and Perl XS (.xs) |
323
+ | **Broad language + notebook support** | Python, JavaScript/TypeScript/TSX, Go, Rust, Java, C/C++, C#, Ruby, Kotlin, Swift, PHP, Scala, Solidity, Dart, R, Perl, Lua/Luau, Objective-C, shell scripts, Elixir, Zig, PowerShell, Julia, ReScript, GDScript, Nix, Verilog/SystemVerilog, SQL, CSS/SCSS/LESS, Vue/Svelte SFCs (including `<style>` blocks), Astro files parsed through the TypeScript parser, Jupyter/Databricks (.ipynb), and Perl XS (.xs) |
324
324
  | **Blast-radius analysis** | Shows which functions, classes, and files are likely affected by a change |
325
325
  | **Auto-update hooks** | Hooks and watch mode can update the graph on file saves and supported commit hooks |
326
326
  | **Semantic search** | Optional vector embeddings via sentence-transformers, Google Gemini, MiniMax, or any OpenAI-compatible endpoint (real OpenAI, Azure, new-api, LiteLLM, vLLM, LocalAI) |
@@ -129,7 +129,7 @@ Large monorepos are where token waste is most painful. The graph cuts through th
129
129
  <img src="diagrams/diagram9_language_coverage.png" alt="Language coverage organized by category: Web, Backend, Systems, Mobile, Scripting, Config, plus Jupyter and Databricks notebook support" width="90%" />
130
130
  </p>
131
131
 
132
- Parser support covers functions, classes, imports, call sites, inheritance, and test detection across the current parser surface, using Tree-sitter where available and targeted fallbacks where needed. Current support includes Python, JavaScript/TypeScript/TSX, Go, Rust, Java, C/C++, C#, Ruby, Kotlin, Swift, PHP, Scala, Solidity, Dart, R, Perl, Lua/Luau, Objective-C, shell scripts, Elixir, Zig, PowerShell, Julia, ReScript, GDScript, Nix, Verilog/SystemVerilog, SQL, Vue/Svelte SFCs, Astro files parsed through the TypeScript parser, Jupyter/Databricks notebooks (`.ipynb`), and Perl XS files (`.xs`).
132
+ Parser support covers functions, classes, imports, call sites, inheritance, and test detection across the current parser surface, using Tree-sitter where available and targeted fallbacks where needed. Current support includes Python, JavaScript/TypeScript/TSX, Go, Rust, Java, C/C++, C#, Ruby, Kotlin, Swift, PHP, Scala, Solidity, Dart, R, Perl, Lua/Luau, Objective-C, shell scripts, Elixir, Zig, PowerShell, Julia, ReScript, GDScript, Nix, Verilog/SystemVerilog, SQL, CSS/SCSS/LESS stylesheets, Vue/Svelte SFCs (including `<style>` blocks), Astro files parsed through the TypeScript parser, Jupyter/Databricks notebooks (`.ipynb`), and Perl XS files (`.xs`).
133
133
 
134
134
  ### Add your own language (no fork needed)
135
135
 
@@ -258,7 +258,7 @@ The benchmark also runs an honest **co-change mode**: the predictor is seeded wi
258
258
  | Feature | Details |
259
259
  |---------|---------|
260
260
  | **Incremental updates** | Re-parses only changed files. Subsequent updates complete in under 2 seconds. |
261
- | **Broad language + notebook support** | Python, JavaScript/TypeScript/TSX, Go, Rust, Java, C/C++, C#, Ruby, Kotlin, Swift, PHP, Scala, Solidity, Dart, R, Perl, Lua/Luau, Objective-C, shell scripts, Elixir, Zig, PowerShell, Julia, ReScript, GDScript, Nix, Verilog/SystemVerilog, SQL, Vue/Svelte SFCs, Astro files parsed through the TypeScript parser, Jupyter/Databricks (.ipynb), and Perl XS (.xs) |
261
+ | **Broad language + notebook support** | Python, JavaScript/TypeScript/TSX, Go, Rust, Java, C/C++, C#, Ruby, Kotlin, Swift, PHP, Scala, Solidity, Dart, R, Perl, Lua/Luau, Objective-C, shell scripts, Elixir, Zig, PowerShell, Julia, ReScript, GDScript, Nix, Verilog/SystemVerilog, SQL, CSS/SCSS/LESS, Vue/Svelte SFCs (including `<style>` blocks), Astro files parsed through the TypeScript parser, Jupyter/Databricks (.ipynb), and Perl XS (.xs) |
262
262
  | **Blast-radius analysis** | Shows which functions, classes, and files are likely affected by a change |
263
263
  | **Auto-update hooks** | Hooks and watch mode can update the graph on file saves and supported commit hooks |
264
264
  | **Semantic search** | Optional vector embeddings via sentence-transformers, Google Gemini, MiniMax, or any OpenAI-compatible endpoint (real OpenAI, Azure, new-api, LiteLLM, vLLM, LocalAI) |
@@ -1,7 +1,8 @@
1
1
  """SQLite-backed knowledge graph storage and query engine.
2
2
 
3
3
  Stores code structure as nodes (File, Class, Function, Type, Test) and
4
- edges (CALLS, IMPORTS_FROM, INHERITS, IMPLEMENTS, CONTAINS, TESTED_BY, DEPENDS_ON, REFERENCES).
4
+ edges (CALLS, IMPORTS_FROM, INHERITS, IMPLEMENTS, CONTAINS, TESTED_BY, DEPENDS_ON,
5
+ REFERENCES, OVERRIDES, STYLES, POTENTIAL_CONFLICT).
5
6
  Supports impact-radius queries and subgraph extraction.
6
7
  """
7
8
 
@@ -1310,6 +1311,32 @@ class GraphStore:
1310
1311
  nodes_by_id=nodes_by_id,
1311
1312
  )
1312
1313
 
1314
+ def delete_edges_by_kind(self, kind: str) -> int:
1315
+ """Delete all edges of the given kind. Returns count of deleted rows."""
1316
+ cur = self._conn.execute("DELETE FROM edges WHERE kind = ?", (kind,))
1317
+ self._invalidate_cache()
1318
+ return cur.rowcount
1319
+
1320
+ def get_nodes_by_extra_pattern(
1321
+ self, pattern: str, kind: str | None = None,
1322
+ ) -> list[GraphNode]:
1323
+ """Find nodes where extra JSON matches a LIKE pattern.
1324
+
1325
+ Args:
1326
+ pattern: SQL LIKE pattern, e.g. '%"css_kind":"selector"%'.
1327
+ kind: Optional node kind filter (Class, Function, etc.).
1328
+ """
1329
+ if kind:
1330
+ rows = self._conn.execute(
1331
+ "SELECT * FROM nodes WHERE extra LIKE ? AND kind = ?",
1332
+ (pattern, kind),
1333
+ ).fetchall()
1334
+ else:
1335
+ rows = self._conn.execute(
1336
+ "SELECT * FROM nodes WHERE extra LIKE ?", (pattern,),
1337
+ ).fetchall()
1338
+ return [self._row_to_node(r) for r in rows]
1339
+
1313
1340
  # --- Internal helpers ---
1314
1341
 
1315
1342
  def _build_networkx_graph(self) -> nx.DiGraph:
@@ -1406,6 +1433,21 @@ def _sanitize_name(s: str, max_len: int = 256) -> str:
1406
1433
  return cleaned[:max_len]
1407
1434
 
1408
1435
 
1436
+ def _sanitize_extra(extra: dict) -> dict:
1437
+ """Sanitize string values in extra dict for safe display."""
1438
+ if not extra:
1439
+ return {}
1440
+
1441
+ def _sanitize_value(v): # type: ignore[no-untyped-def]
1442
+ if isinstance(v, str):
1443
+ return _sanitize_name(v)
1444
+ if isinstance(v, list):
1445
+ return [_sanitize_value(item) for item in v]
1446
+ return v
1447
+
1448
+ return {k: _sanitize_value(v) for k, v in extra.items()}
1449
+
1450
+
1409
1451
  def node_to_dict(n: GraphNode) -> dict:
1410
1452
  return {
1411
1453
  "id": n.id, "kind": n.kind, "name": _sanitize_name(n.name),
@@ -1414,6 +1456,7 @@ def node_to_dict(n: GraphNode) -> dict:
1414
1456
  "language": n.language,
1415
1457
  "parent_name": _sanitize_name(n.parent_name) if n.parent_name else n.parent_name,
1416
1458
  "is_test": n.is_test,
1459
+ "extra": _sanitize_extra(n.extra),
1417
1460
  }
1418
1461
 
1419
1462
 
@@ -1424,4 +1467,5 @@ def edge_to_dict(e: GraphEdge) -> dict:
1424
1467
  "target": _sanitize_name(e.target_qualified),
1425
1468
  "file_path": e.file_path, "line": e.line,
1426
1469
  "confidence": e.confidence, "confidence_tier": e.confidence_tier,
1470
+ "extra": _sanitize_extra(e.extra),
1427
1471
  }
@@ -82,6 +82,30 @@ def _run_spring_resolver(store: GraphStore) -> Optional[dict]:
82
82
  return None
83
83
 
84
84
 
85
+ def _run_css_resolver(store: GraphStore) -> Optional[dict]:
86
+ """Run cross-file CSS linking + conflict detection, swallowing any
87
+ failure so build never fails because of it. Returns stats or None.
88
+ """
89
+ try:
90
+ selectors = _get_css_selectors(store)
91
+ styles_count = link_css_styles(store, selectors)
92
+ conflicts_count = detect_cross_file_conflicts(store, selectors)
93
+ logger.info(
94
+ "CSS linking: %d STYLES edges, %d POTENTIAL_CONFLICT edges",
95
+ styles_count, conflicts_count,
96
+ )
97
+ return {"styles_edges": styles_count, "conflict_edges": conflicts_count}
98
+ except Exception as exc: # noqa: BLE001 - best-effort post-pass
99
+ logger.warning("CSS resolver failed: %s", exc)
100
+ return None
101
+
102
+
103
+ # File extensions whose changes can affect STYLES/POTENTIAL_CONFLICT edges.
104
+ _CSS_RELEVANT_EXTENSIONS = (
105
+ ".css", ".scss", ".less", ".vue", ".svelte", ".tsx", ".jsx", ".ts", ".js",
106
+ )
107
+
108
+
85
109
  def _run_temporal_resolver(store: GraphStore) -> Optional[dict]:
86
110
  """Run the Temporal workflow/activity call resolver, swallowing any failure so
87
111
  build never fails because of it. Returns stats or None on error.
@@ -728,7 +752,9 @@ def _single_hop_dependents(store: GraphStore, file_path: str) -> set[str]:
728
752
  nodes = store.get_nodes_by_file(file_path)
729
753
  for node in nodes:
730
754
  for e in store.get_edges_by_target(node.qualified_name):
731
- if e.kind in ("CALLS", "IMPORTS_FROM", "INHERITS", "IMPLEMENTS"):
755
+ if e.kind in (
756
+ "CALLS", "IMPORTS_FROM", "INHERITS", "IMPLEMENTS", "STYLES",
757
+ ):
732
758
  dependents.add(e.file_path)
733
759
 
734
760
  dependents.discard(file_path)
@@ -817,6 +843,194 @@ def _parse_single_file(
817
843
  return (rel_path, [], [], str(e), "")
818
844
 
819
845
 
846
+ def _get_css_selectors(store: GraphStore) -> list:
847
+ """Fetch all CSS selector nodes (css_kind == "selector")."""
848
+ return store.get_nodes_by_extra_pattern(
849
+ '%"css_kind":%"selector"%', kind="Class",
850
+ )
851
+
852
+
853
+ def link_css_styles(store: GraphStore, selectors: list | None = None) -> int:
854
+ """Create STYLES edges between class references and CSS selectors.
855
+
856
+ Queries the graph for:
857
+ 1. All CSS selector nodes (css_kind == "selector")
858
+ 2. All nodes with css_classes in extra (JSX functions, Vue/Svelte files)
859
+
860
+ Creates STYLES edges from the referencing component/function to
861
+ the matching CSS selector node.
862
+
863
+ Returns count of STYLES edges created.
864
+ """
865
+ from .parser import CodeParser, EdgeInfo # noqa: F811 (avoid circular)
866
+
867
+ # Clean up previous STYLES edges
868
+ store.delete_edges_by_kind("STYLES")
869
+
870
+ # Build selector index: bare class name → list of (qualified_name, file_path)
871
+ if selectors is None:
872
+ selectors = _get_css_selectors(store)
873
+ selector_index: dict[str, list[tuple[str, str]]] = {}
874
+ for sel in selectors:
875
+ sel_name = sel.name.strip()
876
+ # Only index class selectors (starting with .)
877
+ if sel_name.startswith("."):
878
+ bare = sel_name.lstrip(".")
879
+ selector_index.setdefault(bare, []).append(
880
+ (sel.qualified_name, sel.file_path),
881
+ )
882
+ # For compound selectors, also index by last class segment
883
+ parts = sel_name.split()
884
+ if len(parts) > 1:
885
+ last = parts[-1].strip()
886
+ if last.startswith("."):
887
+ bare = last.lstrip(".")
888
+ selector_index.setdefault(bare, []).append(
889
+ (sel.qualified_name, sel.file_path),
890
+ )
891
+
892
+ if not selector_index:
893
+ store.commit()
894
+ return 0
895
+
896
+ count = 0
897
+
898
+ # Link static css_classes references
899
+ class_nodes = store.get_nodes_by_extra_pattern('%"css_classes"%')
900
+ for node in class_nodes:
901
+ classes = node.extra.get("css_classes", [])
902
+ for cls_name in classes:
903
+ matches = selector_index.get(cls_name, [])
904
+ for sel_qn, sel_file in matches:
905
+ store.upsert_edge(EdgeInfo(
906
+ kind="STYLES",
907
+ source=node.qualified_name,
908
+ target=sel_qn,
909
+ file_path=node.file_path,
910
+ line=node.line_start or 0,
911
+ extra={
912
+ "class_name": cls_name,
913
+ "resolution": "static",
914
+ },
915
+ ))
916
+ count += 1
917
+
918
+ # Link CSS Module references
919
+ module_nodes = store.get_nodes_by_extra_pattern('%"css_module_refs"%')
920
+ # Map of file_path → {import name: module path}, built once for all nodes
921
+ file_mod_imports: dict[str, dict[str, str]] = {}
922
+ if module_nodes:
923
+ for fn in store.get_nodes_by_extra_pattern(
924
+ '%"css_module_imports"%', kind="File",
925
+ ):
926
+ file_mod_imports[fn.file_path] = fn.extra.get(
927
+ "css_module_imports", {},
928
+ )
929
+ for node in module_nodes:
930
+ refs = node.extra.get("css_module_refs", [])
931
+ mod_imports = file_mod_imports.get(node.file_path, {})
932
+ for ref in refs:
933
+ imp_name = ref.get("import", "")
934
+ prop_name = ref.get("property", "")
935
+ if not imp_name or not prop_name:
936
+ continue
937
+ # Find the module path
938
+ if imp_name not in mod_imports:
939
+ continue
940
+ # Convert camelCase to kebab-case for selector lookup
941
+ kebab = CodeParser._camel_to_kebab(prop_name)
942
+ matches = selector_index.get(kebab, [])
943
+ for sel_qn, sel_file in matches:
944
+ store.upsert_edge(EdgeInfo(
945
+ kind="STYLES",
946
+ source=node.qualified_name,
947
+ target=sel_qn,
948
+ file_path=node.file_path,
949
+ line=node.line_start or 0,
950
+ extra={
951
+ "class_name": kebab,
952
+ "resolution": "css_module",
953
+ "import_name": imp_name,
954
+ "property": prop_name,
955
+ },
956
+ ))
957
+ count += 1
958
+
959
+ store.commit()
960
+ return count
961
+
962
+
963
+ def detect_cross_file_conflicts(
964
+ store: GraphStore, selectors: list | None = None,
965
+ ) -> int:
966
+ """Detect potential CSS conflicts across files.
967
+
968
+ Scans for CSS selectors across different files that target the same
969
+ class name. Creates POTENTIAL_CONFLICT edges when found.
970
+
971
+ Returns count of conflict edges created.
972
+ """
973
+ from .parser import EdgeInfo # noqa: F811
974
+
975
+ store.delete_edges_by_kind("POTENTIAL_CONFLICT")
976
+
977
+ if selectors is None:
978
+ selectors = _get_css_selectors(store)
979
+
980
+ # Group by bare class name
981
+ by_class: dict[str, list[tuple[str, str, list]]] = {}
982
+ for sel in selectors:
983
+ sel_name = sel.name.strip()
984
+ if not sel_name.startswith("."):
985
+ continue
986
+ bare = sel_name.lstrip(".")
987
+ specificity = sel.extra.get("specificity", [0, 0, 0])
988
+ by_class.setdefault(bare, []).append(
989
+ (sel.qualified_name, sel.file_path, specificity),
990
+ )
991
+
992
+ count = 0
993
+ max_pairs_per_class = 50
994
+
995
+ for bare, entries in by_class.items():
996
+ # Only flag if selectors appear in 2+ different files
997
+ files_seen = {fp for _, fp, _ in entries}
998
+ if len(files_seen) < 2:
999
+ continue
1000
+
1001
+ pairs = 0
1002
+ for i, (src_qn, src_fp, src_spec) in enumerate(entries):
1003
+ for tgt_qn, tgt_fp, tgt_spec in entries[i + 1:]:
1004
+ if src_fp == tgt_fp:
1005
+ continue
1006
+ if pairs >= max_pairs_per_class:
1007
+ break
1008
+ # Conflict confidence label; keyed as conflict_confidence
1009
+ # because extra["confidence"] is reserved for the numeric
1010
+ # edge-confidence score read by GraphStore.upsert_edge.
1011
+ conflict_confidence = "high" # same bare class name
1012
+ store.upsert_edge(EdgeInfo(
1013
+ kind="POTENTIAL_CONFLICT",
1014
+ source=src_qn,
1015
+ target=tgt_qn,
1016
+ file_path="<cross-file>",
1017
+ line=0,
1018
+ extra={
1019
+ "class_name": f".{bare}",
1020
+ "source_specificity": src_spec,
1021
+ "target_specificity": tgt_spec,
1022
+ "conflict_confidence": conflict_confidence,
1023
+ },
1024
+ ))
1025
+ count += 1
1026
+ pairs += 1
1027
+ if pairs >= max_pairs_per_class:
1028
+ break
1029
+
1030
+ store.commit()
1031
+ return count
1032
+
1033
+
820
1034
  def full_build(
821
1035
  repo_root: Path,
822
1036
  store: GraphStore,
@@ -901,6 +1115,7 @@ def full_build(
901
1115
  _store_vcs_metadata(repo_root, store)
902
1116
  store.commit()
903
1117
 
1118
+ css_stats = _run_css_resolver(store)
904
1119
  rescript_stats = _run_rescript_resolver(store)
905
1120
  spring_stats = _run_spring_resolver(store)
906
1121
  temporal_stats = _run_temporal_resolver(store)
@@ -917,6 +1132,8 @@ def full_build(
917
1132
  "total_nodes": total_nodes,
918
1133
  "total_edges": total_edges,
919
1134
  "disambiguated_nodes": disambiguated,
1135
+ "styles_edges": (css_stats or {}).get("styles_edges"),
1136
+ "conflict_edges": (css_stats or {}).get("conflict_edges"),
920
1137
  "errors": errors,
921
1138
  "rescript_resolution": rescript_stats,
922
1139
  "spring_resolution": spring_stats,
@@ -1040,6 +1257,11 @@ def incremental_update(
1040
1257
  store.commit()
1041
1258
 
1042
1259
  # Only re-run language-specific resolvers when the relevant files changed.
1260
+ css_changed = any(
1261
+ rp.endswith(_CSS_RELEVANT_EXTENSIONS) for rp in all_files
1262
+ )
1263
+ css_stats = _run_css_resolver(store) if css_changed else None
1264
+
1043
1265
  rescript_changed = any(
1044
1266
  rp.endswith((".res", ".resi")) for rp in all_files
1045
1267
  )
@@ -1058,6 +1280,8 @@ def incremental_update(
1058
1280
  "total_nodes": total_nodes,
1059
1281
  "total_edges": total_edges,
1060
1282
  "disambiguated_nodes": disambiguated,
1283
+ "styles_edges": (css_stats or {}).get("styles_edges"),
1284
+ "conflict_edges": (css_stats or {}).get("conflict_edges"),
1061
1285
  "changed_files": list(changed_files),
1062
1286
  "dependent_files": list(dependent_files),
1063
1287
  "errors": errors,
@@ -229,6 +229,11 @@ def query_graph_tool(
229
229
  - children_of: Find nodes contained in a file or class
230
230
  - tests_for: Find tests for the target
231
231
  - inheritors_of: Find classes inheriting from the target
232
+ - overrides_of: Find CSS selectors that the target overrides
233
+ - overridden_by: Find CSS selectors that override the target
234
+ - styles_of: Find CSS selectors that style a component or function
235
+ - styled_by: Find components/functions using a given CSS selector
236
+ - conflicts_of: Find potential cross-file CSS conflicts for a selector
232
237
  - file_summary: Get all nodes in a file
233
238
 
234
239
  Args: