codecortex 0.25.0__tar.gz → 0.26.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.
- {codecortex-0.25.0/src/codecortex.egg-info → codecortex-0.26.0}/PKG-INFO +1 -1
- {codecortex-0.25.0 → codecortex-0.26.0/src/codecortex.egg-info}/PKG-INFO +1 -1
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codecortex.egg-info/SOURCES.txt +3 -0
- codecortex-0.26.0/src/codeintel/__init__.py +1 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/graph_answer.py +26 -7
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/graph_changed.py +50 -11
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/graph_confidence.py +52 -2
- codecortex-0.26.0/src/codeintel/graph_dispatch.py +1195 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/graph_edges.py +6 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/graph_ops.py +119 -47
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/graph_render.py +5 -0
- codecortex-0.26.0/tests/test_bench_graphify.py +268 -0
- codecortex-0.26.0/tests/test_dispatch_callers.py +1695 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_docs_trust_claims.py +13 -1
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_summary_integrity.py +17 -0
- codecortex-0.25.0/src/codeintel/__init__.py +0 -1
- {codecortex-0.25.0 → codecortex-0.26.0}/LICENSE +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/README.md +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/pyproject.toml +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/setup.cfg +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codecortex.egg-info/dependency_links.txt +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codecortex.egg-info/entry_points.txt +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codecortex.egg-info/requires.txt +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codecortex.egg-info/top_level.txt +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/__main__.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/agent_prompt.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/auth.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/c4.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/c4_check.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/c4_layers.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/cache.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/changed_range.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/commands/__init__.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/commands/_common.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/commands/c4.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/commands/doctor.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/commands/gen_token.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/commands/graph.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/commands/index.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/commands/install.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/commands/map.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/commands/prompt.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/commands/query.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/commands/reset.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/commands/serve.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/commands/serve_http.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/commands/setup.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/commands/status.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/commands/uninstall.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/config.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/containment.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/doctor.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/filelock.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/gateway.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/graph_backend.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/graph_resolution.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/graph_targets.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/grapher.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/http_server.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/indexer.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/injector.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/installer.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/lang_config.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/loc.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/logconfig.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/mapper.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/metrics.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/onboarding.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/outcome.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/paths.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/per_thread.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/policy.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/progress.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/provider.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/providers/__init__.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/providers/graph.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/providers/lsp.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/providers/none.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/providers/semantic.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/py.typed +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/query_ops.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/redact.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/reindexer.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/reset.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/searcher.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/semantic_db.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/server.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/source_kind.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/symbol_diff.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/term.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/verify.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/viewer/__init__.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/viewer/graph_template.html +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/src/codeintel/wire_text.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_agent_prompt.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_backend_failure_detail.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_backend_reaper.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_background_index_failure.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_bench_corpus_typed.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_bench_gate.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_bench_oracle.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_bench_oracle_ts.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_c4.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_c4_check.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_c4_layers.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_c4_model.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_c4_output.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_cache.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_changed_range.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_chunking.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_cli_commands.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_cli_help.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_cli_install_dry_run.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_cli_install_offer_injection.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_cold_process.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_config.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_corpus.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_could_not_ask_semantic.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_docs_ci_claims.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_docs_deadcode_withdrawal.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_doctor.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_e2e.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_edge_confidence.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_edge_endpoint_cap.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_engine_adoption.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_enterprise.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_gateway.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_graph_failure_population.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_graph_provider.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_graph_real.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_graph_stdin.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_grapher.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_hard_exit.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_hardening.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_http_auth.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_http_server.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_incompleteness.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_index_failure_reason.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_index_incompleteness.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_index_progress.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_indexer_binary.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_installer.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_integration.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_lang_config.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_loc_census.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_lsp_language_coverage.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_lsp_provider.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_lsp_real.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_mapper.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_mcp_handshake.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_mcp_server.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_missing_target.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_model_dimension.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_never_raise.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_null_reason_and_ripple.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_onboarding.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_per_thread.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_query_budget.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_rbac.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_redaction_boundary.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_reindex_lock.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_reindexer.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_rerank.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_reset.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_semantic_provider.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_source_kind.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_staleness.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_symbol_context.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_symbol_diff.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_term.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_treesitter.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_uninstall.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_upgrade_path.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_verify.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_verify_call.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_version_skew.py +0 -0
- {codecortex-0.25.0 → codecortex-0.26.0}/tests/test_wire_text.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codecortex
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.26.0
|
|
4
4
|
Summary: Local-first, MCP-native code-intelligence server — graph, LSP, and semantic search behind one safe code.query tool for coding agents.
|
|
5
5
|
Author: Shammai Hamilton
|
|
6
6
|
License-Expression: MIT
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codecortex
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.26.0
|
|
4
4
|
Summary: Local-first, MCP-native code-intelligence server — graph, LSP, and semantic search behind one safe code.query tool for coding agents.
|
|
5
5
|
Author: Shammai Hamilton
|
|
6
6
|
License-Expression: MIT
|
|
@@ -25,6 +25,7 @@ src/codeintel/graph_answer.py
|
|
|
25
25
|
src/codeintel/graph_backend.py
|
|
26
26
|
src/codeintel/graph_changed.py
|
|
27
27
|
src/codeintel/graph_confidence.py
|
|
28
|
+
src/codeintel/graph_dispatch.py
|
|
28
29
|
src/codeintel/graph_edges.py
|
|
29
30
|
src/codeintel/graph_ops.py
|
|
30
31
|
src/codeintel/graph_render.py
|
|
@@ -90,6 +91,7 @@ tests/test_backend_reaper.py
|
|
|
90
91
|
tests/test_background_index_failure.py
|
|
91
92
|
tests/test_bench_corpus_typed.py
|
|
92
93
|
tests/test_bench_gate.py
|
|
94
|
+
tests/test_bench_graphify.py
|
|
93
95
|
tests/test_bench_oracle.py
|
|
94
96
|
tests/test_bench_oracle_ts.py
|
|
95
97
|
tests/test_c4.py
|
|
@@ -108,6 +110,7 @@ tests/test_cold_process.py
|
|
|
108
110
|
tests/test_config.py
|
|
109
111
|
tests/test_corpus.py
|
|
110
112
|
tests/test_could_not_ask_semantic.py
|
|
113
|
+
tests/test_dispatch_callers.py
|
|
111
114
|
tests/test_docs_ci_claims.py
|
|
112
115
|
tests/test_docs_deadcode_withdrawal.py
|
|
113
116
|
tests/test_docs_trust_claims.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.26.0"
|
|
@@ -605,7 +605,7 @@ class AnswerRendering:
|
|
|
605
605
|
guessed_by.add(str(r.get("strategy") or "").strip())
|
|
606
606
|
unverified += 1
|
|
607
607
|
continue
|
|
608
|
-
if evidence in ("lsp", "import", "same-module"):
|
|
608
|
+
if evidence in ("lsp", "import", "same-module", "self-mro"):
|
|
609
609
|
r["_evidence"] = evidence
|
|
610
610
|
if evidence == "same-module":
|
|
611
611
|
# `same_module` is a bare-name lookup inside the caller's own file, and it
|
|
@@ -806,7 +806,8 @@ class AnswerRendering:
|
|
|
806
806
|
tally = " · ".join(f"{n} {b}" for b, n in present)
|
|
807
807
|
return (
|
|
808
808
|
f"**{tally}.** The heading counts rows, not confirmed {unit}s — only the `{_RESOLVED}` "
|
|
809
|
-
f"rows were bound by an import, a language server
|
|
809
|
+
f"rows were bound by an import, a language server, the caller's own module scope or, for "
|
|
810
|
+
f"a `self.` call, the caller's own class hierarchy (`self_mro`, over INHERITS edges). "
|
|
810
811
|
f"Per-row detail below.\n"
|
|
811
812
|
)
|
|
812
813
|
|
|
@@ -905,10 +906,11 @@ class AnswerRendering:
|
|
|
905
906
|
structured row and the printed row cannot disagree about a fact.
|
|
906
907
|
|
|
907
908
|
`verified` is the field to filter on and the only one that is a VERDICT: true exactly when
|
|
908
|
-
a binding was followed — through an import, a language server,
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
909
|
+
a binding was followed — through an import, a language server, (for a bare call to a symbol
|
|
910
|
+
the caller's own module defines) the module's own scope, or (for a `self.m()` call) the
|
|
911
|
+
caller's own class hierarchy, over INHERITS edges. `why` says WHICH, per strategy, because
|
|
912
|
+
the four are different strengths of claim and one sentence for the bucket was false for one
|
|
913
|
+
of them. An unstamped row is not verified — silence from the
|
|
912
914
|
backend is not evidence — and it is not `possible` either, which is why `evidence` keeps
|
|
913
915
|
all three states rather than collapsing to a boolean pair.
|
|
914
916
|
|
|
@@ -926,7 +928,7 @@ class AnswerRendering:
|
|
|
926
928
|
confidence = row.get("_low_confidence")
|
|
927
929
|
scope = row.get("_module_scope")
|
|
928
930
|
edge = str(row.get("type(c)") or "").strip()
|
|
929
|
-
|
|
931
|
+
out: dict[str, Any] = {
|
|
930
932
|
"relation": unit,
|
|
931
933
|
"name": str(row.get(name_key) or ""),
|
|
932
934
|
"qualified_name": _strip_project_prefix(
|
|
@@ -940,6 +942,16 @@ class AnswerRendering:
|
|
|
940
942
|
"confidence": float(confidence) if confidence is not None else None,
|
|
941
943
|
"why": _why(row, bucket),
|
|
942
944
|
}
|
|
945
|
+
via = row.get("_via")
|
|
946
|
+
if via:
|
|
947
|
+
# Only on a row that reached the symbol through a base, so every other answer keeps the
|
|
948
|
+
# exact row shape it always had. `via` is the base method the call was actually resolved
|
|
949
|
+
# to; `via_kind` says whether that base is a Protocol the symbol satisfies structurally or
|
|
950
|
+
# a class it inherits from. Neither changes `verified`, which stays false: a call to the
|
|
951
|
+
# base is not a binding to this override.
|
|
952
|
+
out["via"] = str(via)
|
|
953
|
+
out["via_kind"] = str(row.get("_via_kind") or "")
|
|
954
|
+
return out
|
|
943
955
|
|
|
944
956
|
def _record_rows(self, rendered: list[dict], row_keys: tuple[str, str, str], unit: str,
|
|
945
957
|
*, row_cap_hit: bool, withheld: int) -> None:
|
|
@@ -1046,6 +1058,13 @@ class AnswerRendering:
|
|
|
1046
1058
|
if ev["verified"] or ev["possible"]:
|
|
1047
1059
|
lines.append(f"> Verified {noun}s: {ev['verified']} · "
|
|
1048
1060
|
f"possible: {ev['possible']} · unstated: {ev['unstated']}")
|
|
1061
|
+
# Said here because `possible` alone reads as "weak evidence about this symbol", and a row
|
|
1062
|
+
# that called the BASE method is a different claim: it may be the only production caller
|
|
1063
|
+
# there is, and no binding ties it to the symbol asked about.
|
|
1064
|
+
through = sum(1 for r in self._pending_rows if r.get("via"))
|
|
1065
|
+
if through:
|
|
1066
|
+
lines.append(f"> Of the possible {noun}s, {through} call a base type or Protocol rather "
|
|
1067
|
+
"than this symbol, and reach it only by dispatch")
|
|
1049
1068
|
if ev["truncated"]:
|
|
1050
1069
|
total = ev["total"]
|
|
1051
1070
|
shown = (f"{ev['returned']} shown, {total} in total" if total is not None
|
|
@@ -111,6 +111,9 @@ class _Lookup:
|
|
|
111
111
|
detail: str = ""
|
|
112
112
|
untouched: list[dict] = field(default_factory=list)
|
|
113
113
|
also: list[dict] = field(default_factory=list)
|
|
114
|
+
# Callers of a BASE method (`graph_dispatch.py`). Kept out of the two lists above: whether the
|
|
115
|
+
# diff touched such a caller says nothing about whether its author considered THIS symbol.
|
|
116
|
+
through: list[dict] = field(default_factory=list)
|
|
114
117
|
mentions: list[dict] = field(default_factory=list)
|
|
115
118
|
quiet: int = 0 # mentions that are only comments or definitions
|
|
116
119
|
withheld: int = 0 # rows found and not printed
|
|
@@ -396,15 +399,19 @@ class ChangedSince(AnswerRendering):
|
|
|
396
399
|
if rows:
|
|
397
400
|
lk.state = "ok"
|
|
398
401
|
for r in rows:
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
+
row = dict(r, module_scope_in_diff=bool(
|
|
403
|
+
r.get("module_scope") and str(r.get("file") or "") in files))
|
|
404
|
+
if r.get("via"):
|
|
405
|
+
lk.through.append(row)
|
|
406
|
+
else:
|
|
407
|
+
(lk.also if self._caller_is_changed(r, touched) else lk.untouched).append(row)
|
|
402
408
|
shown = self._RANGE_ROWS_SHOWN
|
|
403
409
|
# Two different ways a known row goes unprinted, and both are WITHHELD: the rows this
|
|
404
410
|
# group does not print (its own per-list limit), and the ones `callers` itself kept back
|
|
405
411
|
# (its distinct-caller cap) — which it counts and does not publish. Counting only the
|
|
406
412
|
# first made "120 callers, 50 kept" read as "50 in total" in the envelope.
|
|
407
413
|
lk.withheld = (max(0, len(lk.untouched) - shown) + max(0, len(lk.also) - shown)
|
|
414
|
+
+ sum(max(0, len(rs) - shown) for rs in _by_base(lk.through).values())
|
|
408
415
|
+ omitted)
|
|
409
416
|
lk.complete = not lk.cap_hit and not lk.gaps
|
|
410
417
|
if miss is not None:
|
|
@@ -638,7 +645,9 @@ class ChangedSince(AnswerRendering):
|
|
|
638
645
|
if lk.source == "text":
|
|
639
646
|
return [dict(r, group_class=klass) for r in lk.mentions[:shown]]
|
|
640
647
|
out: list[dict[str, Any]] = []
|
|
641
|
-
|
|
648
|
+
lists = [("untouched", lk.untouched), ("also-changed", lk.also),
|
|
649
|
+
*(("through-base", rs) for rs in _by_base(lk.through).values())]
|
|
650
|
+
for status, rows in lists:
|
|
642
651
|
out.extend(dict(r, changed_symbol=e.qn, change=e.change.change,
|
|
643
652
|
caller_status=status, group_class=klass) for r in rows[:shown])
|
|
644
653
|
return out
|
|
@@ -653,7 +662,7 @@ class ChangedSince(AnswerRendering):
|
|
|
653
662
|
graph lookup produced is `advisory`."""
|
|
654
663
|
if lk.source == "text":
|
|
655
664
|
return _DISCOVERY
|
|
656
|
-
rows = lk.untouched + lk.also
|
|
665
|
+
rows = lk.untouched + lk.also + lk.through
|
|
657
666
|
if (lk.state == "ok" and lk.complete and rows and not lk.withheld
|
|
658
667
|
and all(r.get("verified") is True for r in rows)):
|
|
659
668
|
return _EVIDENCE
|
|
@@ -670,11 +679,16 @@ class ChangedSince(AnswerRendering):
|
|
|
670
679
|
raw["_module_scope"] = row.get("file")
|
|
671
680
|
if row.get("evidence") == _NAME_MATCHED:
|
|
672
681
|
raw["_low_confidence"] = row.get("confidence")
|
|
682
|
+
if row.get("via"):
|
|
683
|
+
# A caller of a BASE method (see `graph_dispatch.py`): the badge says which kind of
|
|
684
|
+
# base, and its strategy — whatever it is — is the account of how the call reached
|
|
685
|
+
# the base, so the `same_module` reading below must not be applied to it.
|
|
686
|
+
raw["_via"], raw["_via_kind"] = row.get("via"), row.get("via_kind")
|
|
673
687
|
# A `same_module` edge is only ever name-matched because the call-site check found it
|
|
674
688
|
# written through a receiver (see `_same_module_call`), so the strategy IS the verdict.
|
|
675
689
|
# Without it the badge reads `[?0.90]` — the backend's high score behind a question
|
|
676
690
|
# mark, which is the reading `callers` prints "qualified call" to prevent.
|
|
677
|
-
|
|
691
|
+
elif _evidence_class(str(row.get("strategy") or "")) == "same-module":
|
|
678
692
|
raw["_same_module"] = _SAME_MODULE_FOREIGN
|
|
679
693
|
line = self._display(raw, "a.name", "a.qualified_name", "a.file_path")
|
|
680
694
|
if row.get("module_scope_in_diff"):
|
|
@@ -702,13 +716,24 @@ class ChangedSince(AnswerRendering):
|
|
|
702
716
|
out = [self._group_heading(lk)]
|
|
703
717
|
shown = self._RANGE_ROWS_SHOWN
|
|
704
718
|
if lk.state == "ok":
|
|
705
|
-
|
|
706
|
-
("Callers this diff did NOT touch — these may break
|
|
707
|
-
|
|
708
|
-
|
|
719
|
+
lists = [
|
|
720
|
+
(f"**Callers this diff did NOT touch — these may break ({len(lk.untouched)}):**",
|
|
721
|
+
lk.untouched),
|
|
722
|
+
(f"**Callers also changed in this diff — probably updated together ({len(lk.also)}):**",
|
|
723
|
+
lk.also),
|
|
724
|
+
# Apart from the two above, and one list per base: a caller of the BASE is not a
|
|
725
|
+
# caller of this symbol, and whether the diff touched it does not say that its
|
|
726
|
+
# author considered this override. It may be the only production caller there is.
|
|
727
|
+
*((f"**Callers through `{_short_base(via)}` — they call the "
|
|
728
|
+
f"{'base class' if rs[0].get('via_kind') == 'base' else 'Protocol'}, not this symbol, "
|
|
729
|
+
"and reach it only by dispatch; never counted as verified callers "
|
|
730
|
+
f"({len(rs)}):**", rs)
|
|
731
|
+
for via, rs in _by_base(lk.through).items()),
|
|
732
|
+
]
|
|
733
|
+
for title, rows in lists:
|
|
709
734
|
if not rows:
|
|
710
735
|
continue
|
|
711
|
-
out.append(
|
|
736
|
+
out.append(title)
|
|
712
737
|
out.extend(self._caller_line(r) for r in rows[:shown])
|
|
713
738
|
if len(rows) > shown:
|
|
714
739
|
out.append(f"… (+{len(rows) - shown} more, not shown)")
|
|
@@ -854,6 +879,20 @@ class ChangedSince(AnswerRendering):
|
|
|
854
879
|
|
|
855
880
|
# ----------------------------------------------------------------------------- module helpers
|
|
856
881
|
|
|
882
|
+
def _by_base(rows: list[dict]) -> dict[str, list[dict]]:
|
|
883
|
+
"""Rows that reached a symbol through a base, grouped by the base method they called, in the
|
|
884
|
+
order the bases first appear."""
|
|
885
|
+
out: dict[str, list[dict]] = {}
|
|
886
|
+
for r in rows:
|
|
887
|
+
out.setdefault(str(r.get("via") or ""), []).append(r)
|
|
888
|
+
return out
|
|
889
|
+
|
|
890
|
+
|
|
891
|
+
def _short_base(via: str) -> str:
|
|
892
|
+
"""`Base.m` from the qualified name of a base method."""
|
|
893
|
+
return ".".join(via.split(".")[-2:])
|
|
894
|
+
|
|
895
|
+
|
|
857
896
|
def _diff_change(root: str, base_sha: str, change: FileChange) -> FileSymbolDiff:
|
|
858
897
|
"""One file's symbol diff. A side that SHOULD exist and cannot be read is `unparsable`, not
|
|
859
898
|
absent: passing `None` for it would classify every definition in the file as added or removed."""
|
|
@@ -68,6 +68,11 @@ def _evidence_class(strategy: str) -> str:
|
|
|
68
68
|
return "import"
|
|
69
69
|
if st.startswith("same_module"):
|
|
70
70
|
return "same-module"
|
|
71
|
+
# Not a strategy the backend reports: the label this project gives an edge it RE-RESOLVED from the
|
|
72
|
+
# class hierarchy (`self.m()` bound through the caller's own ancestry — `graph_dispatch.py`). It
|
|
73
|
+
# is classified here, with the others, so every reader of `strategy` agrees it is a resolution.
|
|
74
|
+
if st.startswith("self_mro"):
|
|
75
|
+
return "self-mro"
|
|
71
76
|
if st.startswith("language_rule"):
|
|
72
77
|
return "language-rule"
|
|
73
78
|
if st.startswith("unresolved"):
|
|
@@ -227,7 +232,7 @@ def _edge_strength(row: dict) -> tuple[int, float]:
|
|
|
227
232
|
"""
|
|
228
233
|
evidence = _evidence_class(str(row.get("strategy") or ""))
|
|
229
234
|
conf = _edge_confidence(row)
|
|
230
|
-
if evidence in ("lsp", "import"):
|
|
235
|
+
if evidence in ("lsp", "import", "self-mro"):
|
|
231
236
|
rank = 0
|
|
232
237
|
elif evidence == "same-module":
|
|
233
238
|
rank = 2 if _same_module_call(row) == _SAME_MODULE_FOREIGN else 0
|
|
@@ -246,6 +251,12 @@ def _callee_for_display(callee: str) -> str:
|
|
|
246
251
|
return flat if len(flat) <= 60 else flat[:57] + "..."
|
|
247
252
|
|
|
248
253
|
|
|
254
|
+
def _via_kind(row: dict) -> str:
|
|
255
|
+
"""`protocol`, `protocol-undecided` or `base`: what kind of base method a `via` row called."""
|
|
256
|
+
kind = str(row.get("_via_kind") or "")
|
|
257
|
+
return kind if kind in ("protocol", "protocol-undecided") else "base"
|
|
258
|
+
|
|
259
|
+
|
|
249
260
|
def _why(row: dict, bucket: str) -> str:
|
|
250
261
|
"""One sentence saying what ACTUALLY happened to produce this row's bucket.
|
|
251
262
|
|
|
@@ -256,8 +267,37 @@ def _why(row: dict, bucket: str) -> str:
|
|
|
256
267
|
stronger.
|
|
257
268
|
"""
|
|
258
269
|
strategy = str(row.get("strategy") or "").strip()
|
|
270
|
+
via = str(row.get("_via") or "")
|
|
271
|
+
if via:
|
|
272
|
+
# A caller of a BASE method listed under a symbol that overrides it. Whatever the edge's own
|
|
273
|
+
# strategy was, it bound the call to `via` and not to this symbol, so the row is never
|
|
274
|
+
# `resolved` for the symbol asked about; the strategy is kept here as the account of how it
|
|
275
|
+
# reached `via`, because that is the part of the claim the backend did make.
|
|
276
|
+
kind = "base class" if _via_kind(row) == "base" else "Protocol"
|
|
277
|
+
how = (f"the receiver's declared type (`{strategy}`)" if strategy == "field_type_hint"
|
|
278
|
+
else f"`{strategy}`" if strategy else "an edge the backend left unlabelled")
|
|
279
|
+
text = (f"resolved to the {kind} method `{via}` by {how}, not to this symbol; it reaches this "
|
|
280
|
+
"override only by dispatch, when the object is an instance of this symbol's class, "
|
|
281
|
+
"so no binding the index records ties it to this symbol")
|
|
282
|
+
# Each of these is a way the claim is weaker than the line above says, stated on the row so
|
|
283
|
+
# it is not only in a heading three screens up.
|
|
284
|
+
if _via_kind(row) == "protocol-undecided":
|
|
285
|
+
text += ("; and whether that class satisfies the Protocol is undecided — an attribute or "
|
|
286
|
+
"property could supply a member it lacks as a method")
|
|
287
|
+
if row.get("_via_how") == "base_classes":
|
|
288
|
+
text += ("; and the link from this symbol's class to that base was found by resolving the "
|
|
289
|
+
"base-class NAME its statement wrote, not from an INHERITS edge")
|
|
290
|
+
if row.get("_via_super"):
|
|
291
|
+
text += (f"; it is a `super()` call inside `{row['_via_super']}`, a class that descends from "
|
|
292
|
+
"this symbol's class, so it can reach this symbol")
|
|
293
|
+
elif row.get("_via_super_unplaced"):
|
|
294
|
+
text += ("; it is a `super()` call from a class the index could not place in this symbol's "
|
|
295
|
+
"hierarchy, so whether it can reach this symbol was not decided")
|
|
296
|
+
return text
|
|
259
297
|
if bucket == _RESOLVED:
|
|
260
298
|
evidence = str(row.get("_evidence") or "") or _evidence_class(strategy)
|
|
299
|
+
if evidence == "self-mro":
|
|
300
|
+
return str(row.get("_self_mro") or "resolved through the caller's own class hierarchy")
|
|
261
301
|
if evidence == "lsp":
|
|
262
302
|
return f"a language server resolved the call to this symbol ({strategy})"
|
|
263
303
|
if evidence == "import":
|
|
@@ -295,8 +335,12 @@ def _why(row: dict, bucket: str) -> str:
|
|
|
295
335
|
return (f"matched by name inside the caller's own module, but the call is written "
|
|
296
336
|
f"`{callee}` — through a receiver, not as this module's own symbol — so the "
|
|
297
337
|
f"match is a guess about what the receiver is ({strategy})")
|
|
298
|
-
|
|
338
|
+
text = (f"matched by name ({strategy})" if strategy
|
|
299
339
|
else "matched by bare symbol name, not by following a binding")
|
|
340
|
+
# A `self.m()` the class hierarchy would bind if one of its links were an edge: said here so
|
|
341
|
+
# a reader who sees a `self.` call still badged knows the rule looked and why it declined.
|
|
342
|
+
note = str(row.get("_hierarchy_note") or "")
|
|
343
|
+
return f"{text}; {note}" if note else text
|
|
300
344
|
if bucket == _UNSTATED:
|
|
301
345
|
return "the backend reported no provenance for this edge"
|
|
302
346
|
return "unclassified"
|
|
@@ -311,6 +355,12 @@ def _confidence_badge(row: dict) -> str:
|
|
|
311
355
|
if "_low_confidence" not in row:
|
|
312
356
|
return ""
|
|
313
357
|
conf = row.get("_low_confidence")
|
|
358
|
+
if row.get("_via"):
|
|
359
|
+
# The backend's score is for the edge to the BASE method — kept on show as detail, as every
|
|
360
|
+
# other badge does — but the verdict is about this symbol, and the glyph says which kind of
|
|
361
|
+
# base the call went through, since that is what a reader scanning the list needs to know.
|
|
362
|
+
kind = _via_kind(row)
|
|
363
|
+
return f" [?via {kind}]" if conf is None else f" [?via {kind} {float(conf):.2f}]"
|
|
314
364
|
# A `same_module` edge this module refused to trust keeps the backend's own score on show — the
|
|
315
365
|
# number is the backend's and is still detail — but the verdict is OURS, and a bare `[?0.90]`
|
|
316
366
|
# reads as high confidence behind a question mark. Say what the doubt is about.
|