codmap 0.0.24__tar.gz → 0.0.26__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.
- {codmap-0.0.24/codmap.egg-info → codmap-0.0.26}/PKG-INFO +2 -2
- {codmap-0.0.24 → codmap-0.0.26}/README.md +1 -1
- {codmap-0.0.24 → codmap-0.0.26}/codemap/arch.py +22 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/extract/behavior.py +5 -1
- {codmap-0.0.24 → codmap-0.0.26/codmap.egg-info}/PKG-INFO +2 -2
- {codmap-0.0.24 → codmap-0.0.26}/codmap.egg-info/SOURCES.txt +2 -0
- {codmap-0.0.24 → codmap-0.0.26}/pyproject.toml +1 -1
- codmap-0.0.26/tests/test_r1c75_reexported_class_calls.py +58 -0
- codmap-0.0.26/tests/test_r1c76_contract_table_typo.py +68 -0
- {codmap-0.0.24 → codmap-0.0.26}/LICENSE +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/__init__.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/apidiff.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/cli.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/diagnostics.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/extract/__init__.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/extract/attrflow.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/extract/dataflow.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/extract/dispatch.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/extract/griffe_extractor.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/extract/gsource.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/extract/probe.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/extract/roots.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/extract/union.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/freshness.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/incremental.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/integrations/__init__.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/integrations/base.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/integrations/cocoindex.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/integrations/gate.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/integrations/gitnexus.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/integrations/registry.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/integrations/transport.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/model.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/provenance.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/query.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/scope.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/serve/__init__.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/serve/_scip_pb2.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/serve/api_surface.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/serve/apidiff.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/serve/architecture.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/serve/audit.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/serve/check.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/serve/ctags.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/serve/currency.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/serve/impact.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/serve/limits.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/serve/livingdocs.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/serve/mcp_server.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/serve/mermaid.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/serve/pack.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/serve/rag.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/serve/review.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/serve/scip.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/serve/semantic.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/serve/server.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/serve/session.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/serve/subsystems.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/serve/vault.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/store.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/tomlio.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codemap/watch.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codmap.egg-info/dependency_links.txt +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codmap.egg-info/entry_points.txt +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codmap.egg-info/requires.txt +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/codmap.egg-info/top_level.txt +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/setup.cfg +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_epistemic.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_gitnexus_router.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_impact_depth.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_integrations.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_issue3_serve_freshness.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_livingdocs.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_m0_api_surface.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_m11_argcontract.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_m12_dataflow.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_m15_review.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_m16_architecture.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_m17_mcp.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_m18_freshness.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_m1_5_semantics.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_m1_query.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_m2_views.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_m32_watch.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_m3_serve.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_m4_behavior.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_m5_deep.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_m6_repo_scope.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_m7_dispatch.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_m9_family.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_pack_honesty.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c13_callgraph_accuracy.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c13_grep_vs_graph.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c16_semantic.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c20_attribute_edges.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c21_flat_layout.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c22_source_visible_refs.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c23_hard_python.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c24_test_mapping.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c25_provenance.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c26_deep_union.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c27_config_honesty.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c28_limit_envelope.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c29_lazy_imports.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c2_ctags.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c30_local_import_calls.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c30f1_reexport_calls.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c30f2_check_scope.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c31_path_origin.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c32_report_json.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c33_query_signature.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c34_signature_kinds.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c35_contract_location.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c36_target_resolution.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c37_rule_mutation.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c38_tool_drift.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c39_edge_resolution.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c3_arch_contract.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c3_dogfood.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c40_flow_reach.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c41_scope_membership.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c42_deep_tier_stability.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c43_incremental_splice.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c44_absent_answers.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c45_repeat_union.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c46_shadowed_definitions.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c47_incremental_resample.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c48_type_checking_imports.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c49_type_only_cycles.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c4_complexity.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c50_flow_entry_points.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c53_narrowing_declared.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c54_deterministic_rendering.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c55_override_grade.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c56_stub_files.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c57_facade_package.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c58_cycle_tangles.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c59_completeness_drop.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c5_apidiff.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c60_nested_definitions.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c62_phantom_rules.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c63_answer_format.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c64_dependency_probe.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c66_pack_direction.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c68_dotted_imports.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c69_contract_validation.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c6_pack.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c70_check_graph_currency.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c71_attribute_chain_calls.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c72_flat_inference_in_packages.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c73_cli_version.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c74_call_site_conservation.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c7_edge_vocab.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c8_deadcode.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_r1c9_incremental.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_scip_export.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_scope.py +0 -0
- {codmap-0.0.24 → codmap-0.0.26}/tests/test_subsystems.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codmap
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.26
|
|
4
4
|
Summary: Static analyzer that turns a package's source into a queryable code graph.
|
|
5
5
|
Author-email: kogriv <kogriv@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -44,7 +44,7 @@ and a **SCIP index** for interop with Sourcegraph / Glean and other precise-code
|
|
|
44
44
|
|
|
45
45
|
[](https://github.com/kogriv/codemap/actions/workflows/ci.yml)
|
|
46
46
|
|
|
47
|
-
**Status:** 🟢 M0–M20 implemented + research track (R1/R2) — schema 0.13, **
|
|
47
|
+
**Status:** 🟢 M0–M20 implemented + research track (R1/R2) — schema 0.13, **1136 tests with no failures on
|
|
48
48
|
Python 3.11–3.14** ([in CI](docs/ci.md): the full suite including the dogfood pass, a determinism check, a
|
|
49
49
|
wheel smoke test, and ctags/SCIP interop against the real CLIs), warm serve surface with 31 ops (28 exposed
|
|
50
50
|
as MCP tools), and SCIP export. See **[DESIGN.md](DESIGN.md)** (product design &
|
|
@@ -8,7 +8,7 @@ and a **SCIP index** for interop with Sourcegraph / Glean and other precise-code
|
|
|
8
8
|
|
|
9
9
|
[](https://github.com/kogriv/codemap/actions/workflows/ci.yml)
|
|
10
10
|
|
|
11
|
-
**Status:** 🟢 M0–M20 implemented + research track (R1/R2) — schema 0.13, **
|
|
11
|
+
**Status:** 🟢 M0–M20 implemented + research track (R1/R2) — schema 0.13, **1136 tests with no failures on
|
|
12
12
|
Python 3.11–3.14** ([in CI](docs/ci.md): the full suite including the dogfood pass, a determinism check, a
|
|
13
13
|
wheel smoke test, and ctags/SCIP interop against the real CLIs), warm serve surface with 31 ops (28 exposed
|
|
14
14
|
as MCP tools), and SCIP export. See **[DESIGN.md](DESIGN.md)** (product design &
|
|
@@ -101,9 +101,31 @@ def load_contract(root: str | Path = ".") -> ArchitectureContract:
|
|
|
101
101
|
data, error = read_toml(path)
|
|
102
102
|
if error:
|
|
103
103
|
return ArchitectureContract(error=error, path=str(path))
|
|
104
|
+
near = _near_miss_tables(data)
|
|
105
|
+
if near:
|
|
106
|
+
return ArchitectureContract(error=near, path=str(path))
|
|
104
107
|
return replace(parse_contract(data.get("architecture", {})), path=str(path))
|
|
105
108
|
|
|
106
109
|
|
|
110
|
+
def _near_miss_tables(data: dict) -> str | None:
|
|
111
|
+
"""R1-C76 (design ``contract_table_typo.md``): a table named like ``[architecture]`` but not it.
|
|
112
|
+
|
|
113
|
+
``[architectur]`` used to read as "no contract", exit 0: R1-C69 closed the schema inside the
|
|
114
|
+
table, and the table name stayed the one typo that turned a gate green. Refused even beside a
|
|
115
|
+
real ``[architecture]`` (D2): its rules would never run. Other tables are not checked (D3).
|
|
116
|
+
"""
|
|
117
|
+
import difflib
|
|
118
|
+
|
|
119
|
+
names = [k for k, v in data.items()
|
|
120
|
+
if k != "architecture" and isinstance(v, dict)
|
|
121
|
+
and difflib.SequenceMatcher(None, k.lower(), "architecture").ratio() >= 0.8]
|
|
122
|
+
if not names:
|
|
123
|
+
return None
|
|
124
|
+
listed = ", ".join(f"`[{k}]`" for k in names)
|
|
125
|
+
return (f"{listed} looks like a misspelling of `[architecture]`; codemap reads only "
|
|
126
|
+
"`[architecture]`, so its rules would never run")
|
|
127
|
+
|
|
128
|
+
|
|
107
129
|
#: R1-C69: the closed schema of ``[architecture]``. A key outside it is an error, not a skip.
|
|
108
130
|
_FLAGS = ("no_cycles", "no_lazy_cycles", "no_type_only_cycles", "exhaustive", "no_phantom_rules")
|
|
109
131
|
_KNOWN_KEYS = ("layers", "independent", "forbidden") + _FLAGS
|
|
@@ -682,7 +682,11 @@ def _resolve(call, modpath, class_prefix, imports, modmembers, members, reexport
|
|
|
682
682
|
tgt = imports[head]
|
|
683
683
|
if not _is_internal(tgt, modpath):
|
|
684
684
|
return f"{tgt}.{'.'.join(parts)}", "external"
|
|
685
|
-
|
|
685
|
+
# R1-C75 (design ``reexported_class_calls.md``): the head first. A class bound through
|
|
686
|
+
# a package re-export (`from pkg import Cls`) is no node under the package's name, so
|
|
687
|
+
# `Cls.method()` died on its first hop while `Cls()` resolved. Unchanged for a head that
|
|
688
|
+
# is a node or names no re-export.
|
|
689
|
+
cur = _follow_reexport(tgt, reexports or {}, nodes)
|
|
686
690
|
for part in parts:
|
|
687
691
|
cur = _follow_reexport(f"{cur}.{part}", reexports or {}, nodes)
|
|
688
692
|
if len(parts) == 1 or cur in nodes:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codmap
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.26
|
|
4
4
|
Summary: Static analyzer that turns a package's source into a queryable code graph.
|
|
5
5
|
Author-email: kogriv <kogriv@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -44,7 +44,7 @@ and a **SCIP index** for interop with Sourcegraph / Glean and other precise-code
|
|
|
44
44
|
|
|
45
45
|
[](https://github.com/kogriv/codemap/actions/workflows/ci.yml)
|
|
46
46
|
|
|
47
|
-
**Status:** 🟢 M0–M20 implemented + research track (R1/R2) — schema 0.13, **
|
|
47
|
+
**Status:** 🟢 M0–M20 implemented + research track (R1/R2) — schema 0.13, **1136 tests with no failures on
|
|
48
48
|
Python 3.11–3.14** ([in CI](docs/ci.md): the full suite including the dogfood pass, a determinism check, a
|
|
49
49
|
wheel smoke test, and ctags/SCIP interop against the real CLIs), warm serve surface with 31 ops (28 exposed
|
|
50
50
|
as MCP tools), and SCIP export. See **[DESIGN.md](DESIGN.md)** (product design &
|
|
@@ -146,6 +146,8 @@ tests/test_r1c71_attribute_chain_calls.py
|
|
|
146
146
|
tests/test_r1c72_flat_inference_in_packages.py
|
|
147
147
|
tests/test_r1c73_cli_version.py
|
|
148
148
|
tests/test_r1c74_call_site_conservation.py
|
|
149
|
+
tests/test_r1c75_reexported_class_calls.py
|
|
150
|
+
tests/test_r1c76_contract_table_typo.py
|
|
149
151
|
tests/test_r1c7_edge_vocab.py
|
|
150
152
|
tests/test_r1c8_deadcode.py
|
|
151
153
|
tests/test_r1c9_incremental.py
|
|
@@ -9,7 +9,7 @@ build-backend = "setuptools.build_meta"
|
|
|
9
9
|
# version. README says this plainly, since `pip install codmap` / `import codemap` is a
|
|
10
10
|
# seam a reader will otherwise hit by surprise.
|
|
11
11
|
name = "codmap"
|
|
12
|
-
version = "0.0.
|
|
12
|
+
version = "0.0.26"
|
|
13
13
|
description = "Static analyzer that turns a package's source into a queryable code graph."
|
|
14
14
|
readme = "README.md"
|
|
15
15
|
# M20/D1: measured, not assumed. Each release of the declared range runs the full suite in
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""R1-C75 — a call through a class name that came in by a package re-export.
|
|
2
|
+
|
|
3
|
+
`from pkg.indicators import LibraryManager` + `LibraryManager.create_indicator()` was unresolved in the
|
|
4
|
+
fast tier, while the same call after a direct import of the defining module resolved, and so did a bare
|
|
5
|
+
`LibraryManager()` through the re-export. Design: docs/design/reexported_class_calls.md.
|
|
6
|
+
"""
|
|
7
|
+
from codemap.extract import extract
|
|
8
|
+
|
|
9
|
+
FILES = {
|
|
10
|
+
"__init__.py": "",
|
|
11
|
+
"indicators/__init__.py": "from .library.manager import LibraryManager\n",
|
|
12
|
+
"indicators/library/__init__.py": "",
|
|
13
|
+
"indicators/library/manager.py": (
|
|
14
|
+
"class LibraryManager:\n"
|
|
15
|
+
" @classmethod\n def create_indicator(cls, name):\n return name\n\n"
|
|
16
|
+
" @staticmethod\n def load_all():\n return 1\n"),
|
|
17
|
+
"analysis/__init__.py": "",
|
|
18
|
+
"analysis/s/__init__.py": "",
|
|
19
|
+
"analysis/s/direct.py": (
|
|
20
|
+
"from ...indicators.library.manager import LibraryManager\n\n\n"
|
|
21
|
+
"def direct():\n return LibraryManager.create_indicator('x')\n"),
|
|
22
|
+
"analysis/s/viare.py": (
|
|
23
|
+
"from ...indicators import LibraryManager\n\n\n"
|
|
24
|
+
"def via_reexport():\n return LibraryManager.create_indicator('x'), LibraryManager.load_all()\n\n\n"
|
|
25
|
+
"def construct():\n return LibraryManager()\n"),
|
|
26
|
+
"analysis/s/absre.py": (
|
|
27
|
+
"from rpkg.indicators import LibraryManager\n\n\n"
|
|
28
|
+
"def absolute_reexport():\n return LibraryManager.create_indicator('x')\n"),
|
|
29
|
+
}
|
|
30
|
+
M = "rpkg.indicators.library.manager.LibraryManager"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _graph(tmp_path):
|
|
34
|
+
root = tmp_path / "rpkg"
|
|
35
|
+
for rel, body in FILES.items():
|
|
36
|
+
p = root / rel
|
|
37
|
+
p.parent.mkdir(parents=True, exist_ok=True)
|
|
38
|
+
p.write_text(body)
|
|
39
|
+
return extract(str(root))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _calls(g, src):
|
|
43
|
+
return sorted(e.target for e in g.edges if e.type == "calls" and e.source == src)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_a_method_of_a_reexported_class_resolves(tmp_path):
|
|
47
|
+
g = _graph(tmp_path)
|
|
48
|
+
assert _calls(g, "rpkg.analysis.s.viare.via_reexport") == [f"{M}.create_indicator", f"{M}.load_all"]
|
|
49
|
+
assert _calls(g, "rpkg.analysis.s.absre.absolute_reexport") == [f"{M}.create_indicator"]
|
|
50
|
+
for fn, n in (("viare.via_reexport", 2), ("absre.absolute_reexport", 1)):
|
|
51
|
+
c = g.nodes[f"rpkg.analysis.s.{fn}"].extras["calls"]
|
|
52
|
+
assert (c["resolved"], c["unresolved"]) == (n, 0)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_the_direct_import_and_the_bare_constructor_are_unchanged(tmp_path):
|
|
56
|
+
g = _graph(tmp_path)
|
|
57
|
+
assert _calls(g, "rpkg.analysis.s.direct.direct") == [f"{M}.create_indicator"]
|
|
58
|
+
assert _calls(g, "rpkg.analysis.s.viare.construct") == [M]
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""R1-C76 — a misspelled `[architecture]` table is refused, not read as "no contract".
|
|
2
|
+
|
|
3
|
+
Found by asking codemap the question its import-linter card asked of import-linter (zero contracts pass).
|
|
4
|
+
Design `docs/design/contract_table_typo.md`.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
from codemap import arch
|
|
12
|
+
from codemap.cli import main
|
|
13
|
+
from codemap.extract import extract
|
|
14
|
+
from codemap.query import Query
|
|
15
|
+
from codemap.serve.check import build_check
|
|
16
|
+
|
|
17
|
+
RULE = 'forbidden = [{from="core", to="analysis"}]\n'
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@pytest.fixture(scope="module")
|
|
21
|
+
def pkg(tmp_path_factory):
|
|
22
|
+
root = tmp_path_factory.mktemp("r1c76") / "tpkg"
|
|
23
|
+
for d in (root, root / "core", root / "analysis"):
|
|
24
|
+
d.mkdir(parents=True, exist_ok=True)
|
|
25
|
+
(d / "__init__.py").write_text("")
|
|
26
|
+
(root / "analysis" / "b.py").write_text("X = 1\n")
|
|
27
|
+
(root / "core" / "a.py").write_text("from tpkg.analysis import b\n")
|
|
28
|
+
return root
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _check(tmp_path, pkg, toml, capsys, *extra) -> tuple[int, str]:
|
|
32
|
+
if toml is not None:
|
|
33
|
+
(tmp_path / "codemap.toml").write_text(toml)
|
|
34
|
+
rc = main(["check", "--build", str(pkg), "--root", str(tmp_path), *extra])
|
|
35
|
+
return rc, capsys.readouterr().out
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@pytest.mark.parametrize("toml, name", [
|
|
39
|
+
(f"[architectur]\n{RULE}", "architectur"),
|
|
40
|
+
(f"[Architecture]\n{RULE}", "Architecture"),
|
|
41
|
+
(f"[archtecture]\n{RULE}", "archtecture"),
|
|
42
|
+
(f"[architecture]\nno_cycles = true\n\n[architectur]\n{RULE}", "architectur"),
|
|
43
|
+
], ids=["A1", "A2-case", "A2-letter", "A3-both"])
|
|
44
|
+
def test_a_near_miss_table_is_refused(tmp_path, pkg, capsys, toml, name):
|
|
45
|
+
rc, out = _check(tmp_path, pkg, toml, capsys)
|
|
46
|
+
assert rc == 2, out
|
|
47
|
+
assert f"`[{name}]`" in out and "`[architecture]`" in out
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_mcp_check_carries_the_refusal(tmp_path, pkg):
|
|
51
|
+
(tmp_path / "codemap.toml").write_text(f"[architectur]\n{RULE}")
|
|
52
|
+
c = arch.load_contract(tmp_path)
|
|
53
|
+
q = Query(extract(str(pkg)))
|
|
54
|
+
payload = build_check(q, c, arch.check_contract(q, c))
|
|
55
|
+
assert payload["ok"] is False
|
|
56
|
+
assert "`[architectur]`" in payload["contract_error"]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_what_was_valid_or_absent_answers_as_before(tmp_path, pkg, capsys):
|
|
60
|
+
rc, out = _check(tmp_path, pkg, f"[architecture]\n{RULE}", capsys)
|
|
61
|
+
assert rc == 2 and "1 rule(s) broken" in out # a real violation, as before
|
|
62
|
+
rc, out = _check(tmp_path, pkg, '[integrations]\nenabled = []\n', capsys)
|
|
63
|
+
assert rc == 0 and "No `[architecture]` contract found" in out # unrelated table: still absent
|
|
64
|
+
with pytest.raises(SystemExit, match="no \\[architecture\\] contract found"):
|
|
65
|
+
_check(tmp_path, pkg, None, capsys, "--require-contract") # still opt-in for absence (exit 1)
|
|
66
|
+
(tmp_path / "codemap.toml").unlink()
|
|
67
|
+
rc, out = _check(tmp_path, pkg, None, capsys)
|
|
68
|
+
assert rc == 0 and "No `[architecture]` contract found" in out # no file at all
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|