coderadar-rs 0.7.0__tar.gz → 0.7.2__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.
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/Cargo.lock +1 -1
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/Cargo.toml +1 -1
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/PKG-INFO +38 -9
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/README.md +37 -8
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/lib.rs +11 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/py_agent/src/coderadar/__init__.py +5 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/py_agent/src/coderadar/cli.py +18 -5
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/py_agent/src/coderadar/mcp/lazy.py +14 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/py_agent/src/coderadar/mcp/roots.py +28 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/py_agent/src/coderadar/mcp/server.py +180 -30
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/py_agent/src/coderadar/query/cache.py +6 -2
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/pyproject.toml +1 -1
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/LICENSE +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/Cargo.toml +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/queries/bash.scm +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/queries/c.scm +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/queries/clojure.scm +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/queries/cmake.scm +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/queries/cpp.scm +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/queries/csharp.scm +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/queries/dart.scm +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/queries/dockerfile.scm +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/queries/elisp.scm +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/queries/elixir.scm +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/queries/erlang.scm +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/queries/fsharp.scm +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/queries/go.scm +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/queries/graphql.scm +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/queries/groovy.scm +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/queries/haskell.scm +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/queries/hcl.scm +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/queries/java.scm +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/queries/javascript.scm +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/queries/julia.scm +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/queries/kotlin.scm +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/queries/lua.scm +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/queries/nix.scm +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/queries/objc.scm +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/queries/ocaml.scm +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/queries/perl.scm +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/queries/php.scm +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/queries/powershell.scm +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/queries/protobuf.scm +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/queries/python.scm +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/queries/r.scm +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/queries/ruby.scm +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/queries/rust.scm +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/queries/scala.scm +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/queries/shell.scm +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/queries/sql.scm +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/queries/swift.scm +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/queries/systemverilog.scm +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/queries/typescript.scm +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/queries/verilog.scm +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/queries/zig.scm +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/extract/decorators.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/extract/docstring.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/extract/mod.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/extract/single_pass.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/extract/spans.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/extract/tagger.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/extract/walker.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/fs/git.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/fs/mod.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/fs/watcher.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/graph/call_graph.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/graph/config.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/graph/embeddings.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/graph/import_graph.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/graph/indexing.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/graph/inheritance.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/graph/mod.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/graph/module_resolution.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/graph/mro.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/graph/persistence.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/graph/projection_ops.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/graph/resolve_calls.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/graph/tests/call_graph_tests.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/graph/tests/embedding_tests.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/graph/tests/import_graph_tests.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/graph/tests/indexing_tests.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/graph/tests/inheritance_tests.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/graph/tests/mod.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/graph/tests/mro_tests.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/graph/tests/persistence_tests.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/graph/tests/projection_tests.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/graph/tests/query_compile_tests.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/graph/tests/traversal_tests.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/graph/traversal.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/mutation/edit.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/mutation/indent.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/mutation/mod.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/mutation/write_guard.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/query/exec.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/query/grammar.pest +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/query/grammar.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/query/mod.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/resolve/cache.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/resolve/import_graph.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/resolve/mod.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/resolve/orchestrator.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/resolve/signature.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/smells/engine.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/smells/metrics.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/smells/mod.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/smells/rule.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/smells/rules/brain_method.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/smells/rules/data_class.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/smells/rules/deep_nesting.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/smells/rules/excessive_returns.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/smells/rules/god_class.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/smells/rules/high_cyclomatic_complexity.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/smells/rules/long_method.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/smells/rules/long_parameter_list.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/smells/rules/mod.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/smells/rules/too_many_fields.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/smells/types.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/storage.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/types.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/core_indexer/src/update/wal.rs +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/py_agent/src/coderadar/agent/__init__.py +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/py_agent/src/coderadar/agent/graphrag.py +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/py_agent/src/coderadar/config.py +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/py_agent/src/coderadar/embedding/__init__.py +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/py_agent/src/coderadar/embedding/dedup.py +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/py_agent/src/coderadar/lsp/__init__.py +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/py_agent/src/coderadar/lsp/pool.py +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/py_agent/src/coderadar/mcp/__init__.py +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/py_agent/src/coderadar/mcp/lifecycle.py +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/py_agent/src/coderadar/mcp/startup.py +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/py_agent/src/coderadar/mutation/__init__.py +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/py_agent/src/coderadar/mutation/tool_router.py +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/py_agent/src/coderadar/query/__init__.py +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/py_agent/src/coderadar/query/executor.py +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/py_agent/src/coderadar/query/planner.py +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/py_agent/src/coderadar/resolvers/__init__.py +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/py_agent/src/coderadar/resolvers/actix.py +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/py_agent/src/coderadar/resolvers/aspnet.py +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/py_agent/src/coderadar/resolvers/base.py +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/py_agent/src/coderadar/resolvers/django.py +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/py_agent/src/coderadar/resolvers/exports.py +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/py_agent/src/coderadar/resolvers/express.py +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/py_agent/src/coderadar/resolvers/fastapi.py +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/py_agent/src/coderadar/resolvers/flask.py +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/py_agent/src/coderadar/resolvers/go.py +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/py_agent/src/coderadar/resolvers/laravel.py +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/py_agent/src/coderadar/resolvers/nestjs.py +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/py_agent/src/coderadar/resolvers/rails.py +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/py_agent/src/coderadar/resolvers/reactrouter.py +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/py_agent/src/coderadar/resolvers/resolution.py +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/py_agent/src/coderadar/resolvers/springboot.py +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/py_agent/src/coderadar/resolvers/vuerouter.py +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/py_agent/src/coderadar/visualizers/__init__.py +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/py_agent/src/coderadar/visualizers/call_graph.py +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/py_agent/src/coderadar/visualizers/graphviz_viz.py +0 -0
- {coderadar_rs-0.7.0 → coderadar_rs-0.7.2}/py_agent/src/coderadar/visualizers/mermaid.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: coderadar-rs
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.2
|
|
4
4
|
Classifier: Development Status :: 4 - Beta
|
|
5
5
|
Classifier: Intended Audience :: Developers
|
|
6
6
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -32,7 +32,7 @@ License: MIT
|
|
|
32
32
|
Requires-Python: >=3.10
|
|
33
33
|
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
34
34
|
|
|
35
|
-
# CodeRadar v0.7.
|
|
35
|
+
# CodeRadar v0.7.2
|
|
36
36
|
|
|
37
37
|
[](https://github.com/opticsWolf/coderadar/actions/workflows/ci.yml)
|
|
38
38
|
[](https://pypi.org/project/coderadar-rs/)
|
|
@@ -88,8 +88,8 @@ Rust Core (ProjectedGraph, Tree-sitter 41-lang, Parallel Extraction,
|
|
|
88
88
|
| Metric | Value |
|
|
89
89
|
|--------|-------|
|
|
90
90
|
| **Languages indexed** | 41 (12 Tier 1, 29 Tier 2, 330+ Tier 3) |
|
|
91
|
-
| **Tests** |
|
|
92
|
-
| **MCP Tools** |
|
|
91
|
+
| **Tests** | 883 (250 Rust + 633 Python) |
|
|
92
|
+
| **MCP Tools** | 19 (explore, search, node, affected, resolve, query, search_similar, module_children, as_of, traverse, get_smells, replace_body, update_signature, rename, create_entity, compute_embeddings, reindex, update_file, set_project) |
|
|
93
93
|
| **Query surface** | Pest structural + Macrame agent traversals + vector search |
|
|
94
94
|
| **Frameworks** | Django, Flask, FastAPI, Go, Actix, Express, Spring Boot, Laravel, ASP.NET, Rails, NestJS, Vue Router, React Router |
|
|
95
95
|
| **Agents** | MCP server over stdio — finds the project root, indexes in the background, and exits with its client |
|
|
@@ -209,9 +209,15 @@ not assume the cwd is the project:
|
|
|
209
209
|
- **Saying where it looked.** A "no index" reply names the directory being
|
|
210
210
|
served and how that directory was chosen, so an agent pointed at the wrong
|
|
211
211
|
project can say so.
|
|
212
|
-
- **One project
|
|
213
|
-
|
|
214
|
-
|
|
212
|
+
- **One project at a time.** Every tool takes an optional `project_path`; a path
|
|
213
|
+
inside the served project (a file, a subdirectory, or the root itself) is
|
|
214
|
+
accepted via nearest-marker resolution; another project is refused with the
|
|
215
|
+
reason, not quietly answered from the wrong codebase.
|
|
216
|
+
- **Switching projects without restarting.** `codegraph_set_project` re-runs
|
|
217
|
+
startup against a new root from inside a tool call: that project's config
|
|
218
|
+
and mutation policy take effect, indexing restarts in the background, and an
|
|
219
|
+
explicit switch outranks the client's declared workspace for the rest of the
|
|
220
|
+
connection.
|
|
215
221
|
- **Not outliving the client.** Handshake timeout, parent-process watchdog, and
|
|
216
222
|
teardown when stdin closes.
|
|
217
223
|
|
|
@@ -259,6 +265,29 @@ CodeRadar detects and extracts framework-specific patterns that tree-sitter can'
|
|
|
259
265
|
|
|
260
266
|
Framework edges are registered in the Rust graph — agents can trace from URL patterns to handler functions via `callers_of()` / `callees_of()`.
|
|
261
267
|
|
|
268
|
+
## v0.7.2 Feature Highlights
|
|
269
|
+
|
|
270
|
+
- **Runtime project switching** — the new `codegraph_set_project` MCP tool
|
|
271
|
+
(19 total) re-runs startup against a new root from inside a tool call:
|
|
272
|
+
that project's `.coderadar.toml` config and mutation policy take effect,
|
|
273
|
+
indexing restarts in the background, and an explicit switch outranks the
|
|
274
|
+
client's declared workspace for the rest of the connection. Unmarked roots
|
|
275
|
+
require `confirm=true`; switching to the current root is a no-op that says
|
|
276
|
+
so.
|
|
277
|
+
- **`project_path` reads like agents mean it** — a file, subdirectory, or
|
|
278
|
+
root inside the served project is accepted via nearest-marker walk-up
|
|
279
|
+
(`resolve_selector`), replacing a byte-exact root comparison that refused
|
|
280
|
+
editor-tab paths and explored directories. Windows drive-letter casing can
|
|
281
|
+
no longer split one directory in two.
|
|
282
|
+
- **Refusals name the way out** — "wrong project" replies point at
|
|
283
|
+
`codegraph_set_project` instead of telling the agent to edit mcp.json and
|
|
284
|
+
restart the server.
|
|
285
|
+
- **One writable project at a time, unchanged** — mutation confinement
|
|
286
|
+
follows the switched root automatically: `analyze(root)` re-tightens
|
|
287
|
+
`INDEXED_ROOT`, so policy, stale-write hashes and rollback need no
|
|
288
|
+
redesign. E2E tests prove an escape-path mutation into the previous
|
|
289
|
+
project never touches disk.
|
|
290
|
+
|
|
262
291
|
## v0.7.0 Feature Highlights
|
|
263
292
|
|
|
264
293
|
The v0.7 improvement plan, start to finish — write-path correctness, temporal
|
|
@@ -456,7 +485,7 @@ py_agent/src/coderadar/ # Python layer
|
|
|
456
485
|
lsp/ # Persistent LSP warm pool
|
|
457
486
|
mutation/ # Tool router for LLM
|
|
458
487
|
mcp/ # MCP server
|
|
459
|
-
server.py #
|
|
488
|
+
server.py # 19 tools + guidance
|
|
460
489
|
roots.py # project-root ladder and marker walk-up
|
|
461
490
|
startup.py # background index, ensure_ready()
|
|
462
491
|
lazy.py # roots/list retry on the first tool call
|
|
@@ -465,7 +494,7 @@ py_agent/src/coderadar/ # Python layer
|
|
|
465
494
|
visualizers/ # Mermaid + Graphviz (SCC cycle highlighting)
|
|
466
495
|
|
|
467
496
|
docs/ # Specifications + code review + performance roadmap
|
|
468
|
-
tests/ #
|
|
497
|
+
tests/ # 633 Python tests (E2E, mutation E2E, MCP, smells,
|
|
469
498
|
# framework resolvers, ingest parity, benchmarks)
|
|
470
499
|
mcp/ # Root resolution, background init, lifecycle, project_path
|
|
471
500
|
```
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# CodeRadar v0.7.
|
|
1
|
+
# CodeRadar v0.7.2
|
|
2
2
|
|
|
3
3
|
[](https://github.com/opticsWolf/coderadar/actions/workflows/ci.yml)
|
|
4
4
|
[](https://pypi.org/project/coderadar-rs/)
|
|
@@ -54,8 +54,8 @@ Rust Core (ProjectedGraph, Tree-sitter 41-lang, Parallel Extraction,
|
|
|
54
54
|
| Metric | Value |
|
|
55
55
|
|--------|-------|
|
|
56
56
|
| **Languages indexed** | 41 (12 Tier 1, 29 Tier 2, 330+ Tier 3) |
|
|
57
|
-
| **Tests** |
|
|
58
|
-
| **MCP Tools** |
|
|
57
|
+
| **Tests** | 883 (250 Rust + 633 Python) |
|
|
58
|
+
| **MCP Tools** | 19 (explore, search, node, affected, resolve, query, search_similar, module_children, as_of, traverse, get_smells, replace_body, update_signature, rename, create_entity, compute_embeddings, reindex, update_file, set_project) |
|
|
59
59
|
| **Query surface** | Pest structural + Macrame agent traversals + vector search |
|
|
60
60
|
| **Frameworks** | Django, Flask, FastAPI, Go, Actix, Express, Spring Boot, Laravel, ASP.NET, Rails, NestJS, Vue Router, React Router |
|
|
61
61
|
| **Agents** | MCP server over stdio — finds the project root, indexes in the background, and exits with its client |
|
|
@@ -175,9 +175,15 @@ not assume the cwd is the project:
|
|
|
175
175
|
- **Saying where it looked.** A "no index" reply names the directory being
|
|
176
176
|
served and how that directory was chosen, so an agent pointed at the wrong
|
|
177
177
|
project can say so.
|
|
178
|
-
- **One project
|
|
179
|
-
|
|
180
|
-
|
|
178
|
+
- **One project at a time.** Every tool takes an optional `project_path`; a path
|
|
179
|
+
inside the served project (a file, a subdirectory, or the root itself) is
|
|
180
|
+
accepted via nearest-marker resolution; another project is refused with the
|
|
181
|
+
reason, not quietly answered from the wrong codebase.
|
|
182
|
+
- **Switching projects without restarting.** `codegraph_set_project` re-runs
|
|
183
|
+
startup against a new root from inside a tool call: that project's config
|
|
184
|
+
and mutation policy take effect, indexing restarts in the background, and an
|
|
185
|
+
explicit switch outranks the client's declared workspace for the rest of the
|
|
186
|
+
connection.
|
|
181
187
|
- **Not outliving the client.** Handshake timeout, parent-process watchdog, and
|
|
182
188
|
teardown when stdin closes.
|
|
183
189
|
|
|
@@ -225,6 +231,29 @@ CodeRadar detects and extracts framework-specific patterns that tree-sitter can'
|
|
|
225
231
|
|
|
226
232
|
Framework edges are registered in the Rust graph — agents can trace from URL patterns to handler functions via `callers_of()` / `callees_of()`.
|
|
227
233
|
|
|
234
|
+
## v0.7.2 Feature Highlights
|
|
235
|
+
|
|
236
|
+
- **Runtime project switching** — the new `codegraph_set_project` MCP tool
|
|
237
|
+
(19 total) re-runs startup against a new root from inside a tool call:
|
|
238
|
+
that project's `.coderadar.toml` config and mutation policy take effect,
|
|
239
|
+
indexing restarts in the background, and an explicit switch outranks the
|
|
240
|
+
client's declared workspace for the rest of the connection. Unmarked roots
|
|
241
|
+
require `confirm=true`; switching to the current root is a no-op that says
|
|
242
|
+
so.
|
|
243
|
+
- **`project_path` reads like agents mean it** — a file, subdirectory, or
|
|
244
|
+
root inside the served project is accepted via nearest-marker walk-up
|
|
245
|
+
(`resolve_selector`), replacing a byte-exact root comparison that refused
|
|
246
|
+
editor-tab paths and explored directories. Windows drive-letter casing can
|
|
247
|
+
no longer split one directory in two.
|
|
248
|
+
- **Refusals name the way out** — "wrong project" replies point at
|
|
249
|
+
`codegraph_set_project` instead of telling the agent to edit mcp.json and
|
|
250
|
+
restart the server.
|
|
251
|
+
- **One writable project at a time, unchanged** — mutation confinement
|
|
252
|
+
follows the switched root automatically: `analyze(root)` re-tightens
|
|
253
|
+
`INDEXED_ROOT`, so policy, stale-write hashes and rollback need no
|
|
254
|
+
redesign. E2E tests prove an escape-path mutation into the previous
|
|
255
|
+
project never touches disk.
|
|
256
|
+
|
|
228
257
|
## v0.7.0 Feature Highlights
|
|
229
258
|
|
|
230
259
|
The v0.7 improvement plan, start to finish — write-path correctness, temporal
|
|
@@ -422,7 +451,7 @@ py_agent/src/coderadar/ # Python layer
|
|
|
422
451
|
lsp/ # Persistent LSP warm pool
|
|
423
452
|
mutation/ # Tool router for LLM
|
|
424
453
|
mcp/ # MCP server
|
|
425
|
-
server.py #
|
|
454
|
+
server.py # 19 tools + guidance
|
|
426
455
|
roots.py # project-root ladder and marker walk-up
|
|
427
456
|
startup.py # background index, ensure_ready()
|
|
428
457
|
lazy.py # roots/list retry on the first tool call
|
|
@@ -431,7 +460,7 @@ py_agent/src/coderadar/ # Python layer
|
|
|
431
460
|
visualizers/ # Mermaid + Graphviz (SCC cycle highlighting)
|
|
432
461
|
|
|
433
462
|
docs/ # Specifications + code review + performance roadmap
|
|
434
|
-
tests/ #
|
|
463
|
+
tests/ # 633 Python tests (E2E, mutation E2E, MCP, smells,
|
|
435
464
|
# framework resolvers, ingest parity, benchmarks)
|
|
436
465
|
mcp/ # Root resolution, background init, lifecycle, project_path
|
|
437
466
|
```
|
|
@@ -1580,6 +1580,17 @@ fn graph_stats(py: Python<'_>) -> PyResult<PyObject> {
|
|
|
1580
1580
|
// Unix seconds of the last projection commit; 0.0 means never indexed.
|
|
1581
1581
|
// The MCP staleness banner reads this.
|
|
1582
1582
|
dict.set_item("indexed_at", graph.indexed_at())?;
|
|
1583
|
+
// Where the loaded graph was walked from. Callers compare it against
|
|
1584
|
+
// their own cwd: a graph belongs to one directory, and reusing dir
|
|
1585
|
+
// A's graph to answer about dir B is how stale, wrong-tree answers
|
|
1586
|
+
// look exactly like fresh ones.
|
|
1587
|
+
dict.set_item(
|
|
1588
|
+
"indexed_root",
|
|
1589
|
+
INDEXED_ROOT
|
|
1590
|
+
.read()
|
|
1591
|
+
.clone()
|
|
1592
|
+
.map(|p| p.to_string_lossy().into_owned()),
|
|
1593
|
+
)?;
|
|
1583
1594
|
Ok(dict.into())
|
|
1584
1595
|
})
|
|
1585
1596
|
}
|
|
@@ -14,6 +14,11 @@ v3.6 Architecture:
|
|
|
14
14
|
|
|
15
15
|
from __future__ import annotations
|
|
16
16
|
|
|
17
|
+
#: The single version constant. pyproject.toml, Cargo.toml and this value
|
|
18
|
+
#: move together on a release; the CLI banner, the MCP handshake and the
|
|
19
|
+
#: tests read from here so no string is hardcoded twice.
|
|
20
|
+
__version__ = "0.7.2"
|
|
21
|
+
|
|
17
22
|
import json
|
|
18
23
|
from pathlib import Path
|
|
19
24
|
from typing import Any, Dict, Iterator, List, Literal, Optional, Union
|
|
@@ -11,6 +11,8 @@ import click
|
|
|
11
11
|
from rich.console import Console
|
|
12
12
|
from rich.table import Table
|
|
13
13
|
|
|
14
|
+
from . import __version__
|
|
15
|
+
|
|
14
16
|
console = Console()
|
|
15
17
|
|
|
16
18
|
|
|
@@ -125,25 +127,36 @@ def _ensure_graph(path: str = "."):
|
|
|
125
127
|
— advice the user had just taken. Cross-process cold start from the
|
|
126
128
|
Macrame ledger (`load_snapshot`) is Phase 3B; until then the honest
|
|
127
129
|
behaviour is to index here rather than to send the user in a circle.
|
|
130
|
+
|
|
131
|
+
A loaded graph is only reused when it belongs to *this* directory: the
|
|
132
|
+
core records the root each index walked, and a graph from somewhere else
|
|
133
|
+
answers every question about this tree wrongly while looking completely
|
|
134
|
+
healthy. Different root — or no graph — means index here.
|
|
128
135
|
"""
|
|
129
136
|
import coderadar
|
|
130
137
|
from coderadar._core import graph_stats
|
|
131
138
|
|
|
132
139
|
graph = coderadar.CodeGraph()
|
|
133
140
|
try:
|
|
134
|
-
graph_stats()
|
|
135
|
-
|
|
141
|
+
stats = graph_stats()
|
|
142
|
+
root = stats.get("indexed_root")
|
|
143
|
+
if root:
|
|
144
|
+
# std::fs::canonicalize emits Windows verbatim paths (\\?\C:\…);
|
|
145
|
+
# pathlib compares them as a different directory, so strip first.
|
|
146
|
+
root_str = str(root).removeprefix("\\\\?\\")
|
|
147
|
+
if Path(root_str).resolve() == Path(path).resolve():
|
|
148
|
+
return graph
|
|
136
149
|
except RuntimeError:
|
|
137
150
|
pass
|
|
138
151
|
|
|
139
152
|
_activate(path)
|
|
140
|
-
console.print("[dim]No graph
|
|
153
|
+
console.print("[dim]No graph for this directory — indexing...[/dim]")
|
|
141
154
|
coderadar.analyze(path)
|
|
142
155
|
return graph
|
|
143
156
|
|
|
144
157
|
|
|
145
158
|
@click.group()
|
|
146
|
-
@click.version_option(version=
|
|
159
|
+
@click.version_option(version=__version__, prog_name="coderadar",
|
|
147
160
|
message="coderadar %(version)s (spec v3.6)")
|
|
148
161
|
def main():
|
|
149
162
|
"""CodeRadar — live semantic graph of your codebase.
|
|
@@ -672,7 +685,7 @@ def serve(project_path: str | None):
|
|
|
672
685
|
"""Start the CodeRadar MCP server over stdio.
|
|
673
686
|
|
|
674
687
|
Connect an MCP client (Claude Code, Cursor, etc.) to this server to get
|
|
675
|
-
code intelligence over the indexed project —
|
|
688
|
+
code intelligence over the indexed project — 19 tools covering search,
|
|
676
689
|
exploration, structural and temporal queries, code smells, and the
|
|
677
690
|
plan/review/apply mutation pipeline.
|
|
678
691
|
|
|
@@ -74,6 +74,20 @@ class LazyRootRetry:
|
|
|
74
74
|
|
|
75
75
|
# ── action ───────────────────────────────────────────────────────────
|
|
76
76
|
|
|
77
|
+
def mark_user_chosen(self, resolved: ResolvedRoot) -> None:
|
|
78
|
+
"""Record an explicit ``codegraph_set_project`` choice.
|
|
79
|
+
|
|
80
|
+
An explicit tool call is the strongest signal there is — stronger
|
|
81
|
+
than a marker found at startup, and much stronger than the client's
|
|
82
|
+
declared workspace. The retry is marked attempted so the lazy
|
|
83
|
+
roots/list dance can never fire again: an agent that named the
|
|
84
|
+
project it wants does not want middleware second-guessing that with
|
|
85
|
+
whatever the host happens to declare as its workspace root.
|
|
86
|
+
"""
|
|
87
|
+
with self._lock:
|
|
88
|
+
self._attempted = True
|
|
89
|
+
self.resolved = resolved
|
|
90
|
+
|
|
77
91
|
async def attempt(self, session: Any) -> bool:
|
|
78
92
|
"""Ask, re-resolve, and move if the answer is better. Returns whether
|
|
79
93
|
anything changed."""
|
|
@@ -42,6 +42,8 @@ MARKERS = (".coderadar", ".coderadar.toml")
|
|
|
42
42
|
CLIENT_ROOT = "client root"
|
|
43
43
|
PATH_FLAG = "--path"
|
|
44
44
|
CWD = "cwd"
|
|
45
|
+
#: Source name for a root named by a tool call's ``project_path`` argument.
|
|
46
|
+
PROJECT_PATH = "project_path"
|
|
45
47
|
|
|
46
48
|
|
|
47
49
|
@dataclass(frozen=True)
|
|
@@ -220,6 +222,32 @@ def resolve_from_client_roots(uris: Iterable[str]) -> Optional[ResolvedRoot]:
|
|
|
220
222
|
return None
|
|
221
223
|
|
|
222
224
|
|
|
225
|
+
def resolve_selector(project_path: str) -> Optional[ResolvedRoot]:
|
|
226
|
+
"""Resolve a tool call's ``project_path`` argument the way agents mean it.
|
|
227
|
+
|
|
228
|
+
Agents rarely pass a project *root*: they pass a source file they can see
|
|
229
|
+
in an editor, a subdirectory they explored earlier, or a path with a
|
|
230
|
+
trailing component that only looks redundant. All of them mean "the
|
|
231
|
+
project this thing belongs to", which is exactly what the walk-up finds —
|
|
232
|
+
the same rule the startup ladder applies to its own candidates. A bare
|
|
233
|
+
directory with no marker anywhere above it is still answered, but with
|
|
234
|
+
``marker=None`` so the caller says out loud that nothing confirmed it.
|
|
235
|
+
|
|
236
|
+
Returns None only when no part of the path names a usable directory —
|
|
237
|
+
the same rule ``_canonical`` applies for the ladder: a nonexistent name
|
|
238
|
+
under a real directory is read as a file of that directory, but a path
|
|
239
|
+
whose every component is missing is a caller error worth naming, not a
|
|
240
|
+
project to guess about.
|
|
241
|
+
"""
|
|
242
|
+
resolved = _canonical(Path(project_path))
|
|
243
|
+
if resolved is None:
|
|
244
|
+
return None
|
|
245
|
+
marker = find_marker(resolved)
|
|
246
|
+
if marker is not None:
|
|
247
|
+
return ResolvedRoot(path=marker.parent, source=PROJECT_PATH, marker=marker)
|
|
248
|
+
return ResolvedRoot(path=resolved, source=PROJECT_PATH, marker=None)
|
|
249
|
+
|
|
250
|
+
|
|
223
251
|
def adopt_project_root(resolved: ResolvedRoot) -> Path:
|
|
224
252
|
"""Make the resolved root the process cwd, and report the old one.
|
|
225
253
|
|
|
@@ -21,6 +21,7 @@ import functools
|
|
|
21
21
|
import re
|
|
22
22
|
import os
|
|
23
23
|
from collections import deque
|
|
24
|
+
from pathlib import Path
|
|
24
25
|
from typing import Any, Literal, Optional
|
|
25
26
|
|
|
26
27
|
from mcp.server import MCPServer
|
|
@@ -115,15 +116,19 @@ After editing code, use the mutation pipeline to keep the graph in sync:
|
|
|
115
116
|
- **When a file is flagged "⚠ changed on disk after index sync"**, Read those specific files for accurate content. Every file NOT flagged is fresh — still trust codegraph.
|
|
116
117
|
- **If a project isn't indexed**, stop calling codegraph tools for that project and use built-in tools. Indexing is the user's decision — mention `coderadar init` if it comes up, but don't run it yourself.
|
|
117
118
|
|
|
118
|
-
## One project
|
|
119
|
+
## One project at a time
|
|
119
120
|
|
|
120
|
-
This server serves a single project — the one named in the "no
|
|
121
|
-
"wrong project" messages. Every tool takes an optional
|
|
122
|
-
it when you
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
121
|
+
This server serves a single project at a time — the one named in the "no
|
|
122
|
+
index" and "wrong project" messages. Every tool takes an optional
|
|
123
|
+
`project_path`; pass it when you want to be told, rather than quietly
|
|
124
|
+
answered from the wrong one. A `project_path` inside the served project (a
|
|
125
|
+
file, a subdirectory, or the root itself) is accepted; another project is
|
|
126
|
+
refused with the reason.
|
|
127
|
+
|
|
128
|
+
To work on a different project, call `codegraph_set_project` with any path
|
|
129
|
+
inside it — this server switches there, re-reads that project's config and
|
|
130
|
+
re-indexes in the background. Switching is wholesale: after it, earlier
|
|
131
|
+
event ids from the previous project are no longer valid.
|
|
127
132
|
|
|
128
133
|
If a tool reports that indexing is still in progress, that is not an error —
|
|
129
134
|
the first index walks every source file. Retry in a few seconds rather than
|
|
@@ -140,9 +145,11 @@ def create_server(graph: Any) -> MCPServer:
|
|
|
140
145
|
Every tool is a plain, type-hinted Python function; MCPServer derives
|
|
141
146
|
JSON Schema and routing from the function signature.
|
|
142
147
|
"""
|
|
148
|
+
from coderadar import __version__
|
|
149
|
+
|
|
143
150
|
mcp = MCPServer(
|
|
144
151
|
"CodeRadar",
|
|
145
|
-
version=
|
|
152
|
+
version=__version__,
|
|
146
153
|
instructions=SERVER_INSTRUCTIONS,
|
|
147
154
|
)
|
|
148
155
|
|
|
@@ -698,6 +705,31 @@ def create_server(graph: Any) -> MCPServer:
|
|
|
698
705
|
|
|
699
706
|
return _update_file(graph, file_path, content)
|
|
700
707
|
|
|
708
|
+
# ── codegraph_set_project — switch the served project ───────────
|
|
709
|
+
|
|
710
|
+
@mcp.tool(
|
|
711
|
+
description=(
|
|
712
|
+
"Switch this server to a different project. All other tools then "
|
|
713
|
+
"verify their project_path against this root. Pass any directory, "
|
|
714
|
+
"subdirectory or file inside the target project — the nearest "
|
|
715
|
+
".coderadar marker at or above it defines the root; pass "
|
|
716
|
+
"confirm=true only if the directory has no marker and you want it "
|
|
717
|
+
"served anyway. Config and mutation policy are re-read from the "
|
|
718
|
+
"new project, and indexing restarts in the background. Switching "
|
|
719
|
+
"replaces the graph wholesale: calls racing a switch get the old "
|
|
720
|
+
"or new answer, never a mix."
|
|
721
|
+
),
|
|
722
|
+
annotations={
|
|
723
|
+
"read_only_hint": False,
|
|
724
|
+
"destructive_hint": False,
|
|
725
|
+
"idempotent_hint": False,
|
|
726
|
+
"open_world_hint": True,
|
|
727
|
+
},
|
|
728
|
+
)
|
|
729
|
+
def codegraph_set_project(project_path: str, confirm: bool = False) -> str:
|
|
730
|
+
"""Switch the served project."""
|
|
731
|
+
return _set_project(project_path, confirm)
|
|
732
|
+
|
|
701
733
|
return mcp
|
|
702
734
|
|
|
703
735
|
|
|
@@ -962,6 +994,19 @@ def _trim_to_char_budget(body_lines: list[str], max_chars: int) -> list[str]:
|
|
|
962
994
|
|
|
963
995
|
# ── Tool Implementations ─────────────────────────────────────────────────
|
|
964
996
|
|
|
997
|
+
def _served_root() -> Path:
|
|
998
|
+
"""The root this server currently serves.
|
|
999
|
+
|
|
1000
|
+
The lazy retry handle holds it once startup ran; a directly constructed
|
|
1001
|
+
server (tests, embedders) falls back to the process cwd, by the same
|
|
1002
|
+
reasoning that made serve chdir onto the root.
|
|
1003
|
+
"""
|
|
1004
|
+
from coderadar.mcp import lazy
|
|
1005
|
+
|
|
1006
|
+
retry = lazy.current()
|
|
1007
|
+
return retry.resolved.path if retry is not None else Path.cwd().resolve()
|
|
1008
|
+
|
|
1009
|
+
|
|
965
1010
|
def _wrong_project(project_path: Optional[str]) -> Optional[str]:
|
|
966
1011
|
"""Answer honestly when a tool is asked about a project we are not serving.
|
|
967
1012
|
|
|
@@ -970,39 +1015,44 @@ def _wrong_project(project_path: Optional[str]) -> Optional[str]:
|
|
|
970
1015
|
question nobody asked — about the wrong codebase, with no sign that
|
|
971
1016
|
anything went wrong.
|
|
972
1017
|
|
|
973
|
-
This build
|
|
974
|
-
GLOBAL_GRAPH and `analyze` replaces it wholesale rather than merging.
|
|
975
|
-
|
|
976
|
-
|
|
1018
|
+
This build serves one project at a time: the core keeps a single
|
|
1019
|
+
GLOBAL_GRAPH and `analyze` replaces it wholesale rather than merging. But
|
|
1020
|
+
the argument is read the way agents mean it: the selector walks up from
|
|
1021
|
+
whatever was passed — a file, a subdirectory, or the root itself — to the
|
|
1022
|
+
nearest `.coderadar` marker, so anything inside the served project is
|
|
1023
|
+
accepted. Only a path that lands in some other project is refused, with
|
|
1024
|
+
the reason and the way out rather than a quietly wrong answer.
|
|
977
1025
|
|
|
978
1026
|
Returns None when the call may proceed, or the message to return instead.
|
|
979
1027
|
"""
|
|
980
1028
|
if not project_path:
|
|
981
1029
|
return None
|
|
982
1030
|
|
|
983
|
-
from pathlib import Path
|
|
984
|
-
|
|
985
1031
|
from coderadar.mcp import lazy
|
|
1032
|
+
from coderadar.mcp.roots import resolve_selector
|
|
986
1033
|
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
1034
|
+
selected = resolve_selector(project_path)
|
|
1035
|
+
if selected is None:
|
|
1036
|
+
return (
|
|
1037
|
+
f"`{project_path}` names no readable directory, so no project "
|
|
1038
|
+
"can be selected from it. Pass a directory (or any file inside "
|
|
1039
|
+
"one) and retry."
|
|
1040
|
+
)
|
|
991
1041
|
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
1042
|
+
served = _served_root()
|
|
1043
|
+
# Windows paths keep their drive-letter casing through resolve(); compare
|
|
1044
|
+
# case-insensitively so D:/User and d:/user are one directory everywhere.
|
|
1045
|
+
if os.path.normcase(str(selected.path)) == os.path.normcase(str(served)):
|
|
995
1046
|
return None
|
|
996
1047
|
|
|
997
1048
|
return (
|
|
998
1049
|
f"This server is serving `{served}` and cannot answer for "
|
|
999
|
-
f"`{
|
|
1000
|
-
"One project
|
|
1001
|
-
"in-process graph
|
|
1002
|
-
"
|
|
1003
|
-
"
|
|
1004
|
-
"
|
|
1005
|
-
"`project_path` argument to ask about the one being served."
|
|
1050
|
+
f"`{selected.path}`.\n\n"
|
|
1051
|
+
"One project at a time in this build: the index is a single "
|
|
1052
|
+
"in-process graph. To ask about that other project instead, call "
|
|
1053
|
+
f"`codegraph_set_project` with `{selected.path}` — this server "
|
|
1054
|
+
"switches there and re-indexes. Or drop the `project_path` argument "
|
|
1055
|
+
f"to keep asking about `{served}`."
|
|
1006
1056
|
)
|
|
1007
1057
|
|
|
1008
1058
|
|
|
@@ -1042,7 +1092,8 @@ def _no_index_message() -> str:
|
|
|
1042
1092
|
"Nothing on disk confirmed that directory as a project root — no "
|
|
1043
1093
|
"`.coderadar/` or `.coderadar.toml` was found at or above it.",
|
|
1044
1094
|
"",
|
|
1045
|
-
"If that is the wrong project,
|
|
1095
|
+
"If that is the wrong project, call `codegraph_set_project` with "
|
|
1096
|
+
"the right directory to switch this server there, restart with "
|
|
1046
1097
|
"`coderadar mcp serve --path <project root>`, or run "
|
|
1047
1098
|
"`coderadar init` in the right directory so it can be found "
|
|
1048
1099
|
"automatically.",
|
|
@@ -1052,6 +1103,105 @@ def _no_index_message() -> str:
|
|
|
1052
1103
|
return "\n".join(lines)
|
|
1053
1104
|
|
|
1054
1105
|
|
|
1106
|
+
def _set_project(project_path: str, confirm: bool = False) -> str:
|
|
1107
|
+
"""Switch the served project, and say what happened.
|
|
1108
|
+
|
|
1109
|
+
One project at a time is the shape of the core — a single GLOBAL_GRAPH
|
|
1110
|
+
that `analyze` replaces wholesale — so switching means re-running the
|
|
1111
|
+
whole start-up sequence against the new root: config in, process moved,
|
|
1112
|
+
index restarted. Every step already exists from launch; this orders them
|
|
1113
|
+
for a tool call instead.
|
|
1114
|
+
|
|
1115
|
+
The order matters. Config first, so `[mutation]` policy and excludes are
|
|
1116
|
+
read from the *new* project before anything walks it; chdir second, so
|
|
1117
|
+
entity ids and every cwd-relative helper agree with the root; restart
|
|
1118
|
+
last, so the generation bump cannot race the config swap.
|
|
1119
|
+
|
|
1120
|
+
An explicit tool call outranks everything: the lazy roots/list retry is
|
|
1121
|
+
retired for the rest of the connection, because an agent that named the
|
|
1122
|
+
project it wants must not be second-guessed by whatever the host declares
|
|
1123
|
+
as its workspace.
|
|
1124
|
+
"""
|
|
1125
|
+
from coderadar.config import activate_config
|
|
1126
|
+
from coderadar.mcp import lazy, startup
|
|
1127
|
+
from coderadar.mcp.roots import adopt_project_root, resolve_selector
|
|
1128
|
+
|
|
1129
|
+
selected = resolve_selector(project_path)
|
|
1130
|
+
if selected is None:
|
|
1131
|
+
return (
|
|
1132
|
+
f"`{project_path}` names no readable directory, so no project "
|
|
1133
|
+
"can be selected from it. Pass a directory (or any file inside "
|
|
1134
|
+
"one) and retry."
|
|
1135
|
+
)
|
|
1136
|
+
|
|
1137
|
+
if not selected.confirmed and not confirm:
|
|
1138
|
+
return (
|
|
1139
|
+
f"No `.coderadar/` or `.coderadar.toml` was found at or above "
|
|
1140
|
+
f"`{selected.path}`, so nothing confirms that directory as a "
|
|
1141
|
+
"project root.\n\n"
|
|
1142
|
+
"Run `coderadar init` there if it should be one, or re-call "
|
|
1143
|
+
"with confirm=true to serve it anyway (unmarked roots are "
|
|
1144
|
+
"served as bare guesses, same as at startup)."
|
|
1145
|
+
)
|
|
1146
|
+
|
|
1147
|
+
# Already there? Say so rather than silently re-indexing the same tree.
|
|
1148
|
+
served_now = _served_root()
|
|
1149
|
+
if os.path.normcase(str(selected.path)) == os.path.normcase(str(served_now)):
|
|
1150
|
+
return (
|
|
1151
|
+
f"Already serving `{selected.path}` — nothing to switch. Use "
|
|
1152
|
+
"`codegraph_reindex` to refresh the index."
|
|
1153
|
+
)
|
|
1154
|
+
|
|
1155
|
+
lines = [f"Switched to `{selected.path}`"]
|
|
1156
|
+
if selected.confirmed:
|
|
1157
|
+
lines[0] += f" (marker {selected.marker.name})"
|
|
1158
|
+
else:
|
|
1159
|
+
lines[0] += " — unconfirmed: no marker found, serving on your say-so"
|
|
1160
|
+
|
|
1161
|
+
# 1. Config from the NEW project, before anything reads it.
|
|
1162
|
+
try:
|
|
1163
|
+
activated = activate_config(selected.path)
|
|
1164
|
+
if activated.ignored:
|
|
1165
|
+
lines.append(
|
|
1166
|
+
f"Config: {len(activated.ignored)} setting(s) with no "
|
|
1167
|
+
f"consumer were ignored ({', '.join(activated.ignored[:3])}" +
|
|
1168
|
+
(", ..." if len(activated.ignored) > 3 else "") + ")."
|
|
1169
|
+
)
|
|
1170
|
+
except Exception as exc: # noqa: BLE001 — a broken config must not strand the server on the old project
|
|
1171
|
+
lines.append(
|
|
1172
|
+
f"WARNING: config not applied ({type(exc).__name__}: {exc}) — "
|
|
1173
|
+
"this project runs on defaults, including default mutation "
|
|
1174
|
+
"policy."
|
|
1175
|
+
)
|
|
1176
|
+
structlog.get_logger(__name__).warning(
|
|
1177
|
+
"mcp.set_project.config_failed", root=str(selected.path),
|
|
1178
|
+
error=str(exc))
|
|
1179
|
+
|
|
1180
|
+
# 2. Move the process: entity ids and cwd-relative helpers follow.
|
|
1181
|
+
adopt_project_root(selected)
|
|
1182
|
+
|
|
1183
|
+
# 3. Re-index in the background; ensure_ready makes callers wait or
|
|
1184
|
+
# report progress, never answer from the old graph.
|
|
1185
|
+
index = startup.current()
|
|
1186
|
+
if index is not None:
|
|
1187
|
+
index.restart(".")
|
|
1188
|
+
lines.append("Indexing restarted in the background — tool calls "
|
|
1189
|
+
"will wait for it or report progress.")
|
|
1190
|
+
else:
|
|
1191
|
+
lines.append("No background index handle in this process — call "
|
|
1192
|
+
"`codegraph_reindex` to build the new graph.")
|
|
1193
|
+
|
|
1194
|
+
# 4. The explicit choice outranks the client's workspace forever after.
|
|
1195
|
+
retry = lazy.current()
|
|
1196
|
+
if retry is not None:
|
|
1197
|
+
retry.mark_user_chosen(selected)
|
|
1198
|
+
|
|
1199
|
+
structlog.get_logger(__name__).info(
|
|
1200
|
+
"mcp.project.switched", to=str(selected.path),
|
|
1201
|
+
confirmed=selected.confirmed)
|
|
1202
|
+
return "\n".join(lines)
|
|
1203
|
+
|
|
1204
|
+
|
|
1055
1205
|
NO_EXTENSION_MESSAGE = (
|
|
1056
1206
|
"The CodeRadar native extension is not available in this environment, so "
|
|
1057
1207
|
"no code intelligence can be served.\n\n"
|
|
@@ -29,7 +29,10 @@ class QueryCache:
|
|
|
29
29
|
entry = self._cache.get(key)
|
|
30
30
|
if entry is None:
|
|
31
31
|
return None
|
|
32
|
-
|
|
32
|
+
# >=, not >: a ttl of 0 means "never valid", and on Windows two
|
|
33
|
+
# monotonic() reads around a set() can be equal — a strict compare
|
|
34
|
+
# made ttl=0 entries immortal there while Linux timers masked it.
|
|
35
|
+
if time.monotonic() - entry.timestamp >= self._ttl_seconds:
|
|
33
36
|
del self._cache[key]
|
|
34
37
|
return None
|
|
35
38
|
return entry.value
|
|
@@ -45,9 +48,10 @@ class QueryCache:
|
|
|
45
48
|
|
|
46
49
|
def prune_expired(self) -> int:
|
|
47
50
|
now = time.monotonic()
|
|
51
|
+
# Same >= as get(): ttl=0 expires everything, deterministically.
|
|
48
52
|
expired = [
|
|
49
53
|
k for k, v in self._cache.items()
|
|
50
|
-
if now - v.timestamp
|
|
54
|
+
if now - v.timestamp >= self._ttl_seconds
|
|
51
55
|
]
|
|
52
56
|
for k in expired:
|
|
53
57
|
del self._cache[k]
|