codmap 0.0.25__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.
Files changed (158) hide show
  1. {codmap-0.0.25/codmap.egg-info → codmap-0.0.26}/PKG-INFO +2 -2
  2. {codmap-0.0.25 → codmap-0.0.26}/README.md +1 -1
  3. {codmap-0.0.25 → codmap-0.0.26}/codemap/arch.py +22 -0
  4. {codmap-0.0.25 → codmap-0.0.26/codmap.egg-info}/PKG-INFO +2 -2
  5. {codmap-0.0.25 → codmap-0.0.26}/codmap.egg-info/SOURCES.txt +1 -0
  6. {codmap-0.0.25 → codmap-0.0.26}/pyproject.toml +1 -1
  7. codmap-0.0.26/tests/test_r1c76_contract_table_typo.py +68 -0
  8. {codmap-0.0.25 → codmap-0.0.26}/LICENSE +0 -0
  9. {codmap-0.0.25 → codmap-0.0.26}/codemap/__init__.py +0 -0
  10. {codmap-0.0.25 → codmap-0.0.26}/codemap/apidiff.py +0 -0
  11. {codmap-0.0.25 → codmap-0.0.26}/codemap/cli.py +0 -0
  12. {codmap-0.0.25 → codmap-0.0.26}/codemap/diagnostics.py +0 -0
  13. {codmap-0.0.25 → codmap-0.0.26}/codemap/extract/__init__.py +0 -0
  14. {codmap-0.0.25 → codmap-0.0.26}/codemap/extract/attrflow.py +0 -0
  15. {codmap-0.0.25 → codmap-0.0.26}/codemap/extract/behavior.py +0 -0
  16. {codmap-0.0.25 → codmap-0.0.26}/codemap/extract/dataflow.py +0 -0
  17. {codmap-0.0.25 → codmap-0.0.26}/codemap/extract/dispatch.py +0 -0
  18. {codmap-0.0.25 → codmap-0.0.26}/codemap/extract/griffe_extractor.py +0 -0
  19. {codmap-0.0.25 → codmap-0.0.26}/codemap/extract/gsource.py +0 -0
  20. {codmap-0.0.25 → codmap-0.0.26}/codemap/extract/probe.py +0 -0
  21. {codmap-0.0.25 → codmap-0.0.26}/codemap/extract/roots.py +0 -0
  22. {codmap-0.0.25 → codmap-0.0.26}/codemap/extract/union.py +0 -0
  23. {codmap-0.0.25 → codmap-0.0.26}/codemap/freshness.py +0 -0
  24. {codmap-0.0.25 → codmap-0.0.26}/codemap/incremental.py +0 -0
  25. {codmap-0.0.25 → codmap-0.0.26}/codemap/integrations/__init__.py +0 -0
  26. {codmap-0.0.25 → codmap-0.0.26}/codemap/integrations/base.py +0 -0
  27. {codmap-0.0.25 → codmap-0.0.26}/codemap/integrations/cocoindex.py +0 -0
  28. {codmap-0.0.25 → codmap-0.0.26}/codemap/integrations/gate.py +0 -0
  29. {codmap-0.0.25 → codmap-0.0.26}/codemap/integrations/gitnexus.py +0 -0
  30. {codmap-0.0.25 → codmap-0.0.26}/codemap/integrations/registry.py +0 -0
  31. {codmap-0.0.25 → codmap-0.0.26}/codemap/integrations/transport.py +0 -0
  32. {codmap-0.0.25 → codmap-0.0.26}/codemap/model.py +0 -0
  33. {codmap-0.0.25 → codmap-0.0.26}/codemap/provenance.py +0 -0
  34. {codmap-0.0.25 → codmap-0.0.26}/codemap/query.py +0 -0
  35. {codmap-0.0.25 → codmap-0.0.26}/codemap/scope.py +0 -0
  36. {codmap-0.0.25 → codmap-0.0.26}/codemap/serve/__init__.py +0 -0
  37. {codmap-0.0.25 → codmap-0.0.26}/codemap/serve/_scip_pb2.py +0 -0
  38. {codmap-0.0.25 → codmap-0.0.26}/codemap/serve/api_surface.py +0 -0
  39. {codmap-0.0.25 → codmap-0.0.26}/codemap/serve/apidiff.py +0 -0
  40. {codmap-0.0.25 → codmap-0.0.26}/codemap/serve/architecture.py +0 -0
  41. {codmap-0.0.25 → codmap-0.0.26}/codemap/serve/audit.py +0 -0
  42. {codmap-0.0.25 → codmap-0.0.26}/codemap/serve/check.py +0 -0
  43. {codmap-0.0.25 → codmap-0.0.26}/codemap/serve/ctags.py +0 -0
  44. {codmap-0.0.25 → codmap-0.0.26}/codemap/serve/currency.py +0 -0
  45. {codmap-0.0.25 → codmap-0.0.26}/codemap/serve/impact.py +0 -0
  46. {codmap-0.0.25 → codmap-0.0.26}/codemap/serve/limits.py +0 -0
  47. {codmap-0.0.25 → codmap-0.0.26}/codemap/serve/livingdocs.py +0 -0
  48. {codmap-0.0.25 → codmap-0.0.26}/codemap/serve/mcp_server.py +0 -0
  49. {codmap-0.0.25 → codmap-0.0.26}/codemap/serve/mermaid.py +0 -0
  50. {codmap-0.0.25 → codmap-0.0.26}/codemap/serve/pack.py +0 -0
  51. {codmap-0.0.25 → codmap-0.0.26}/codemap/serve/rag.py +0 -0
  52. {codmap-0.0.25 → codmap-0.0.26}/codemap/serve/review.py +0 -0
  53. {codmap-0.0.25 → codmap-0.0.26}/codemap/serve/scip.py +0 -0
  54. {codmap-0.0.25 → codmap-0.0.26}/codemap/serve/semantic.py +0 -0
  55. {codmap-0.0.25 → codmap-0.0.26}/codemap/serve/server.py +0 -0
  56. {codmap-0.0.25 → codmap-0.0.26}/codemap/serve/session.py +0 -0
  57. {codmap-0.0.25 → codmap-0.0.26}/codemap/serve/subsystems.py +0 -0
  58. {codmap-0.0.25 → codmap-0.0.26}/codemap/serve/vault.py +0 -0
  59. {codmap-0.0.25 → codmap-0.0.26}/codemap/store.py +0 -0
  60. {codmap-0.0.25 → codmap-0.0.26}/codemap/tomlio.py +0 -0
  61. {codmap-0.0.25 → codmap-0.0.26}/codemap/watch.py +0 -0
  62. {codmap-0.0.25 → codmap-0.0.26}/codmap.egg-info/dependency_links.txt +0 -0
  63. {codmap-0.0.25 → codmap-0.0.26}/codmap.egg-info/entry_points.txt +0 -0
  64. {codmap-0.0.25 → codmap-0.0.26}/codmap.egg-info/requires.txt +0 -0
  65. {codmap-0.0.25 → codmap-0.0.26}/codmap.egg-info/top_level.txt +0 -0
  66. {codmap-0.0.25 → codmap-0.0.26}/setup.cfg +0 -0
  67. {codmap-0.0.25 → codmap-0.0.26}/tests/test_epistemic.py +0 -0
  68. {codmap-0.0.25 → codmap-0.0.26}/tests/test_gitnexus_router.py +0 -0
  69. {codmap-0.0.25 → codmap-0.0.26}/tests/test_impact_depth.py +0 -0
  70. {codmap-0.0.25 → codmap-0.0.26}/tests/test_integrations.py +0 -0
  71. {codmap-0.0.25 → codmap-0.0.26}/tests/test_issue3_serve_freshness.py +0 -0
  72. {codmap-0.0.25 → codmap-0.0.26}/tests/test_livingdocs.py +0 -0
  73. {codmap-0.0.25 → codmap-0.0.26}/tests/test_m0_api_surface.py +0 -0
  74. {codmap-0.0.25 → codmap-0.0.26}/tests/test_m11_argcontract.py +0 -0
  75. {codmap-0.0.25 → codmap-0.0.26}/tests/test_m12_dataflow.py +0 -0
  76. {codmap-0.0.25 → codmap-0.0.26}/tests/test_m15_review.py +0 -0
  77. {codmap-0.0.25 → codmap-0.0.26}/tests/test_m16_architecture.py +0 -0
  78. {codmap-0.0.25 → codmap-0.0.26}/tests/test_m17_mcp.py +0 -0
  79. {codmap-0.0.25 → codmap-0.0.26}/tests/test_m18_freshness.py +0 -0
  80. {codmap-0.0.25 → codmap-0.0.26}/tests/test_m1_5_semantics.py +0 -0
  81. {codmap-0.0.25 → codmap-0.0.26}/tests/test_m1_query.py +0 -0
  82. {codmap-0.0.25 → codmap-0.0.26}/tests/test_m2_views.py +0 -0
  83. {codmap-0.0.25 → codmap-0.0.26}/tests/test_m32_watch.py +0 -0
  84. {codmap-0.0.25 → codmap-0.0.26}/tests/test_m3_serve.py +0 -0
  85. {codmap-0.0.25 → codmap-0.0.26}/tests/test_m4_behavior.py +0 -0
  86. {codmap-0.0.25 → codmap-0.0.26}/tests/test_m5_deep.py +0 -0
  87. {codmap-0.0.25 → codmap-0.0.26}/tests/test_m6_repo_scope.py +0 -0
  88. {codmap-0.0.25 → codmap-0.0.26}/tests/test_m7_dispatch.py +0 -0
  89. {codmap-0.0.25 → codmap-0.0.26}/tests/test_m9_family.py +0 -0
  90. {codmap-0.0.25 → codmap-0.0.26}/tests/test_pack_honesty.py +0 -0
  91. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c13_callgraph_accuracy.py +0 -0
  92. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c13_grep_vs_graph.py +0 -0
  93. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c16_semantic.py +0 -0
  94. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c20_attribute_edges.py +0 -0
  95. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c21_flat_layout.py +0 -0
  96. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c22_source_visible_refs.py +0 -0
  97. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c23_hard_python.py +0 -0
  98. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c24_test_mapping.py +0 -0
  99. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c25_provenance.py +0 -0
  100. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c26_deep_union.py +0 -0
  101. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c27_config_honesty.py +0 -0
  102. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c28_limit_envelope.py +0 -0
  103. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c29_lazy_imports.py +0 -0
  104. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c2_ctags.py +0 -0
  105. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c30_local_import_calls.py +0 -0
  106. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c30f1_reexport_calls.py +0 -0
  107. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c30f2_check_scope.py +0 -0
  108. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c31_path_origin.py +0 -0
  109. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c32_report_json.py +0 -0
  110. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c33_query_signature.py +0 -0
  111. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c34_signature_kinds.py +0 -0
  112. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c35_contract_location.py +0 -0
  113. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c36_target_resolution.py +0 -0
  114. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c37_rule_mutation.py +0 -0
  115. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c38_tool_drift.py +0 -0
  116. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c39_edge_resolution.py +0 -0
  117. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c3_arch_contract.py +0 -0
  118. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c3_dogfood.py +0 -0
  119. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c40_flow_reach.py +0 -0
  120. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c41_scope_membership.py +0 -0
  121. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c42_deep_tier_stability.py +0 -0
  122. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c43_incremental_splice.py +0 -0
  123. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c44_absent_answers.py +0 -0
  124. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c45_repeat_union.py +0 -0
  125. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c46_shadowed_definitions.py +0 -0
  126. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c47_incremental_resample.py +0 -0
  127. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c48_type_checking_imports.py +0 -0
  128. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c49_type_only_cycles.py +0 -0
  129. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c4_complexity.py +0 -0
  130. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c50_flow_entry_points.py +0 -0
  131. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c53_narrowing_declared.py +0 -0
  132. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c54_deterministic_rendering.py +0 -0
  133. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c55_override_grade.py +0 -0
  134. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c56_stub_files.py +0 -0
  135. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c57_facade_package.py +0 -0
  136. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c58_cycle_tangles.py +0 -0
  137. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c59_completeness_drop.py +0 -0
  138. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c5_apidiff.py +0 -0
  139. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c60_nested_definitions.py +0 -0
  140. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c62_phantom_rules.py +0 -0
  141. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c63_answer_format.py +0 -0
  142. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c64_dependency_probe.py +0 -0
  143. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c66_pack_direction.py +0 -0
  144. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c68_dotted_imports.py +0 -0
  145. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c69_contract_validation.py +0 -0
  146. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c6_pack.py +0 -0
  147. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c70_check_graph_currency.py +0 -0
  148. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c71_attribute_chain_calls.py +0 -0
  149. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c72_flat_inference_in_packages.py +0 -0
  150. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c73_cli_version.py +0 -0
  151. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c74_call_site_conservation.py +0 -0
  152. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c75_reexported_class_calls.py +0 -0
  153. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c7_edge_vocab.py +0 -0
  154. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c8_deadcode.py +0 -0
  155. {codmap-0.0.25 → codmap-0.0.26}/tests/test_r1c9_incremental.py +0 -0
  156. {codmap-0.0.25 → codmap-0.0.26}/tests/test_scip_export.py +0 -0
  157. {codmap-0.0.25 → codmap-0.0.26}/tests/test_scope.py +0 -0
  158. {codmap-0.0.25 → 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.25
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
  [![CI](https://github.com/kogriv/codemap/actions/workflows/ci.yml/badge.svg)](https://github.com/kogriv/codemap/actions/workflows/ci.yml)
46
46
 
47
- **Status:** 🟢 M0–M20 implemented + research track (R1/R2) — schema 0.13, **1130 tests with no failures on
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
  [![CI](https://github.com/kogriv/codemap/actions/workflows/ci.yml/badge.svg)](https://github.com/kogriv/codemap/actions/workflows/ci.yml)
10
10
 
11
- **Status:** 🟢 M0–M20 implemented + research track (R1/R2) — schema 0.13, **1130 tests with no failures on
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codmap
3
- Version: 0.0.25
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
  [![CI](https://github.com/kogriv/codemap/actions/workflows/ci.yml/badge.svg)](https://github.com/kogriv/codemap/actions/workflows/ci.yml)
46
46
 
47
- **Status:** 🟢 M0–M20 implemented + research track (R1/R2) — schema 0.13, **1130 tests with no failures on
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 &
@@ -147,6 +147,7 @@ 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
149
  tests/test_r1c75_reexported_class_calls.py
150
+ tests/test_r1c76_contract_table_typo.py
150
151
  tests/test_r1c7_edge_vocab.py
151
152
  tests/test_r1c8_deadcode.py
152
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.25"
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,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