codeclone 2.0.0b1__tar.gz → 2.0.0b2__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 (140) hide show
  1. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/PKG-INFO +3 -3
  2. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/README.md +1 -1
  3. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/_html_css.py +71 -21
  4. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/_html_report/_sections/_meta.py +19 -14
  5. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone.egg-info/PKG-INFO +3 -3
  6. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone.egg-info/requires.txt +1 -1
  7. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/pyproject.toml +2 -2
  8. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/tests/test_html_report.py +90 -6
  9. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/tests/test_report.py +1 -1
  10. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/tests/test_report_contract_coverage.py +2 -2
  11. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/LICENSE +0 -0
  12. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/__init__.py +0 -0
  13. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/_cli_args.py +0 -0
  14. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/_cli_baselines.py +0 -0
  15. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/_cli_config.py +0 -0
  16. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/_cli_gating.py +0 -0
  17. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/_cli_meta.py +0 -0
  18. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/_cli_paths.py +0 -0
  19. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/_cli_reports.py +0 -0
  20. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/_cli_rich.py +0 -0
  21. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/_cli_runtime.py +0 -0
  22. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/_cli_summary.py +0 -0
  23. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/_coerce.py +0 -0
  24. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/_html_badges.py +0 -0
  25. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/_html_data_attrs.py +0 -0
  26. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/_html_escape.py +0 -0
  27. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/_html_filters.py +0 -0
  28. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/_html_js.py +0 -0
  29. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/_html_report/__init__.py +0 -0
  30. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/_html_report/_assemble.py +0 -0
  31. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/_html_report/_components.py +0 -0
  32. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/_html_report/_context.py +0 -0
  33. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/_html_report/_glossary.py +0 -0
  34. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/_html_report/_icons.py +0 -0
  35. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/_html_report/_sections/__init__.py +0 -0
  36. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/_html_report/_sections/_clones.py +0 -0
  37. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/_html_report/_sections/_coupling.py +0 -0
  38. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/_html_report/_sections/_dead_code.py +0 -0
  39. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/_html_report/_sections/_dependencies.py +0 -0
  40. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/_html_report/_sections/_overview.py +0 -0
  41. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/_html_report/_sections/_structural.py +0 -0
  42. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/_html_report/_sections/_suggestions.py +0 -0
  43. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/_html_report/_tables.py +0 -0
  44. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/_html_report/_tabs.py +0 -0
  45. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/_html_snippets.py +0 -0
  46. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/_schema_validation.py +0 -0
  47. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/baseline.py +0 -0
  48. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/blockhash.py +0 -0
  49. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/blocks.py +0 -0
  50. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/cache.py +0 -0
  51. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/cfg.py +0 -0
  52. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/cfg_model.py +0 -0
  53. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/cli.py +0 -0
  54. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/contracts.py +0 -0
  55. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/domain/__init__.py +0 -0
  56. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/domain/findings.py +0 -0
  57. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/domain/quality.py +0 -0
  58. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/domain/source_scope.py +0 -0
  59. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/errors.py +0 -0
  60. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/extractor.py +0 -0
  61. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/fingerprint.py +0 -0
  62. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/grouping.py +0 -0
  63. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/html_report.py +0 -0
  64. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/meta_markers.py +0 -0
  65. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/metrics/__init__.py +0 -0
  66. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/metrics/cohesion.py +0 -0
  67. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/metrics/complexity.py +0 -0
  68. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/metrics/coupling.py +0 -0
  69. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/metrics/dead_code.py +0 -0
  70. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/metrics/dependencies.py +0 -0
  71. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/metrics/health.py +0 -0
  72. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/metrics_baseline.py +0 -0
  73. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/models.py +0 -0
  74. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/normalize.py +0 -0
  75. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/paths.py +0 -0
  76. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/pipeline.py +0 -0
  77. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/py.typed +0 -0
  78. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/report/__init__.py +0 -0
  79. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/report/_formatting.py +0 -0
  80. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/report/_source_kinds.py +0 -0
  81. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/report/blocks.py +0 -0
  82. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/report/derived.py +0 -0
  83. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/report/explain.py +0 -0
  84. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/report/explain_contract.py +0 -0
  85. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/report/findings.py +0 -0
  86. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/report/json_contract.py +0 -0
  87. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/report/markdown.py +0 -0
  88. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/report/merge.py +0 -0
  89. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/report/overview.py +0 -0
  90. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/report/sarif.py +0 -0
  91. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/report/segments.py +0 -0
  92. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/report/serialize.py +0 -0
  93. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/report/suggestions.py +0 -0
  94. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/report/types.py +0 -0
  95. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/scanner.py +0 -0
  96. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/structural_findings.py +0 -0
  97. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/suppressions.py +0 -0
  98. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/templates.py +0 -0
  99. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone/ui_messages.py +0 -0
  100. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone.egg-info/SOURCES.txt +0 -0
  101. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone.egg-info/dependency_links.txt +0 -0
  102. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone.egg-info/entry_points.txt +0 -0
  103. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/codeclone.egg-info/top_level.txt +0 -0
  104. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/setup.cfg +0 -0
  105. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/tests/test_architecture.py +0 -0
  106. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/tests/test_baseline.py +0 -0
  107. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/tests/test_blockhash.py +0 -0
  108. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/tests/test_blocks.py +0 -0
  109. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/tests/test_cache.py +0 -0
  110. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/tests/test_cfg.py +0 -0
  111. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/tests/test_cfg_model.py +0 -0
  112. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/tests/test_cli_config.py +0 -0
  113. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/tests/test_cli_inprocess.py +0 -0
  114. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/tests/test_cli_main_guard.py +0 -0
  115. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/tests/test_cli_main_guard_runpy.py +0 -0
  116. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/tests/test_cli_smoke.py +0 -0
  117. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/tests/test_cli_unit.py +0 -0
  118. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/tests/test_coerce.py +0 -0
  119. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/tests/test_core_branch_coverage.py +0 -0
  120. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/tests/test_detector_golden.py +0 -0
  121. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/tests/test_extractor.py +0 -0
  122. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/tests/test_fingerprint.py +0 -0
  123. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/tests/test_golden_v2.py +0 -0
  124. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/tests/test_html_report_helpers.py +0 -0
  125. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/tests/test_init.py +0 -0
  126. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/tests/test_metrics_baseline.py +0 -0
  127. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/tests/test_metrics_modules.py +0 -0
  128. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/tests/test_normalize.py +0 -0
  129. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/tests/test_pipeline_metrics.py +0 -0
  130. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/tests/test_pipeline_process.py +0 -0
  131. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/tests/test_python_syntax_compat.py +0 -0
  132. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/tests/test_report_branch_invariants.py +0 -0
  133. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/tests/test_report_explain.py +0 -0
  134. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/tests/test_report_source_kinds.py +0 -0
  135. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/tests/test_report_suggestions.py +0 -0
  136. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/tests/test_scanner_extra.py +0 -0
  137. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/tests/test_security.py +0 -0
  138. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/tests/test_segments.py +0 -0
  139. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/tests/test_structural_findings.py +0 -0
  140. {codeclone-2.0.0b1 → codeclone-2.0.0b2}/tests/test_suppressions.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codeclone
3
- Version: 2.0.0b1
3
+ Version: 2.0.0b2
4
4
  Summary: Structural code quality analysis for Python
5
5
  Author-email: Den Rozhnovskiy <pytelemonbot@mail.ru>
6
6
  Maintainer-email: Den Rozhnovskiy <pytelemonbot@mail.ru>
@@ -36,7 +36,7 @@ Requires-Dist: pytest-cov>=7.1.0; extra == "dev"
36
36
  Requires-Dist: build>=1.4.1; extra == "dev"
37
37
  Requires-Dist: twine>=5.0.0; extra == "dev"
38
38
  Requires-Dist: mypy>=1.19.1; extra == "dev"
39
- Requires-Dist: ruff>=0.15.7; extra == "dev"
39
+ Requires-Dist: ruff>=0.15.8; extra == "dev"
40
40
  Requires-Dist: pre-commit>=4.5.1; extra == "dev"
41
41
  Dynamic: license-file
42
42
 
@@ -239,7 +239,7 @@ Dynamic/runtime false positives are resolved via explicit inline suppressions, n
239
239
  {
240
240
  "report_schema_version": "2.1",
241
241
  "meta": {
242
- "codeclone_version": "2.0.0b1",
242
+ "codeclone_version": "2.0.0b2",
243
243
  "project_name": "...",
244
244
  "scan_root": ".",
245
245
  "report_mode": "full",
@@ -197,7 +197,7 @@ Dynamic/runtime false positives are resolved via explicit inline suppressions, n
197
197
  {
198
198
  "report_schema_version": "2.1",
199
199
  "meta": {
200
- "codeclone_version": "2.0.0b1",
200
+ "codeclone_version": "2.0.0b2",
201
201
  "project_name": "...",
202
202
  "scan_root": ".",
203
203
  "report_mode": "full",
@@ -139,15 +139,18 @@ _LAYOUT = """\
139
139
  box-shadow:var(--shadow-sm)}
140
140
  .topbar-inner{display:flex;align-items:center;justify-content:space-between;
141
141
  height:72px;padding:0 var(--sp-6);max-width:var(--container-max);margin:0 auto}
142
- .brand{display:flex;align-items:center;gap:var(--sp-3)}
142
+ .brand{display:flex;align-items:center;gap:var(--sp-3);min-width:0;flex:1}
143
143
  .brand-logo{flex-shrink:0}
144
- .brand-text{display:flex;flex-direction:column;gap:2px}
145
- .brand h1{font-size:1.15rem;font-weight:700;color:var(--text-primary);line-height:1.3}
146
- .brand-meta{font-size:.78rem;color:var(--text-muted)}
147
- .brand-project{font-weight:500;color:var(--text-secondary)}
144
+ .brand-text{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1}
145
+ .brand h1{display:flex;flex-wrap:wrap;align-items:baseline;gap:var(--sp-1);font-size:1.15rem;
146
+ font-weight:700;color:var(--text-primary);line-height:1.3;min-width:0}
147
+ .brand-meta{font-size:.78rem;color:var(--text-muted);overflow-wrap:anywhere}
148
+ .brand-project{display:inline-flex;flex-wrap:wrap;align-items:baseline;gap:4px;
149
+ font-weight:500;color:var(--text-secondary);min-width:0}
148
150
  .brand-project-name{font-family:var(--font-mono);font-size:.85em;font-weight:500;padding:1px 5px;
149
- border-radius:var(--radius-sm);background:var(--bg-overlay);color:var(--accent-primary)}
150
- .topbar-actions{display:flex;align-items:center;gap:var(--sp-2)}
151
+ border-radius:var(--radius-sm);background:var(--bg-overlay);color:var(--accent-primary);
152
+ max-width:100%;overflow-wrap:anywhere}
153
+ .topbar-actions{display:flex;align-items:center;gap:var(--sp-2);flex-shrink:0;flex-wrap:wrap}
151
154
 
152
155
  /* Theme toggle */
153
156
  .theme-toggle{display:inline-flex;align-items:center;gap:var(--sp-1);
@@ -284,9 +287,15 @@ _INSIGHT = """\
284
287
  # ---------------------------------------------------------------------------
285
288
 
286
289
  _TABLES = """\
287
- .table-wrap{overflow-x:auto;border:1px solid var(--border);border-radius:var(--radius-lg);
288
- margin-bottom:var(--sp-4)}
289
- .table{width:100%;border-collapse:collapse;font-size:.82rem;font-family:var(--font-mono)}
290
+ .table-wrap{display:block;inline-size:100%;max-inline-size:100%;min-inline-size:0;overflow-x:auto;
291
+ overflow-y:hidden;border:1px solid var(--border);border-radius:var(--radius-lg);margin-bottom:var(--sp-4);
292
+ background:
293
+ linear-gradient(to right,var(--bg-surface) 30%,transparent) left center / 40px 100% no-repeat local,
294
+ linear-gradient(to left,var(--bg-surface) 30%,transparent) right center / 40px 100% no-repeat local,
295
+ linear-gradient(to right,rgba(0,0,0,.15),transparent) left center / 14px 100% no-repeat scroll,
296
+ linear-gradient(to left,rgba(0,0,0,.15),transparent) right center / 14px 100% no-repeat scroll}
297
+ .table{inline-size:max-content;min-inline-size:100%;border-collapse:collapse;font-size:.82rem;
298
+ font-family:var(--font-mono)}
290
299
  .table th{position:sticky;top:0;z-index:2;padding:var(--sp-2) var(--sp-3);text-align:left;font-family:var(--font-sans);
291
300
  font-weight:600;font-size:.75rem;text-transform:uppercase;letter-spacing:.05em;
292
301
  color:var(--text-muted);background:var(--bg-overlay);border-bottom:1px solid var(--border);
@@ -300,10 +309,11 @@ _TABLES = """\
300
309
  .table tr:last-child td{border-bottom:none}
301
310
  .table tr:hover td{background:var(--bg-raised)}
302
311
  .table .col-name{font-weight:500;color:var(--text-primary)}
303
- .table .col-file{color:var(--text-muted);max-width:240px;overflow:hidden;
312
+ .table .col-file,.table .col-path{color:var(--text-muted);max-width:240px;overflow:hidden;
304
313
  text-overflow:ellipsis;white-space:nowrap}
305
- .table .col-number{font-variant-numeric:tabular-nums;text-align:right;white-space:nowrap}
306
- .table .col-risk{white-space:nowrap}
314
+ .table .col-number,.table .col-num{font-variant-numeric:tabular-nums;text-align:right;white-space:nowrap}
315
+ .table .col-risk,.table .col-badge,.table .col-cat{white-space:nowrap}
316
+ .table .col-steps{max-width:120px;word-break:break-word}
307
317
  .table .col-wide{max-width:320px;word-break:break-all}
308
318
  .table-empty{padding:var(--sp-8);text-align:center;color:var(--text-muted);font-size:.9rem}
309
319
  """
@@ -478,6 +488,12 @@ _OVERVIEW = """\
478
488
  }
479
489
  @media(max-width:520px){
480
490
  .overview-kpi-cards{grid-template-columns:1fr}
491
+ .overview-kpi-cards .meta-item{grid-template-rows:auto auto auto;align-content:start;
492
+ min-height:0}
493
+ .overview-kpi-cards .meta-item .meta-label{min-height:0}
494
+ .overview-kpi-cards .meta-item .meta-value{padding-top:0}
495
+ .overview-kpi-cards .kpi-detail{align-self:start}
496
+ .overview-kpi-cards .kpi-micro{max-width:100%;white-space:normal;overflow-wrap:anywhere}
481
497
  }
482
498
 
483
499
  /* Health gauge */
@@ -510,7 +526,7 @@ _OVERVIEW = """\
510
526
  background:var(--bg-alt)}
511
527
 
512
528
  /* Badge modal */
513
- .badge-modal{max-width:540px;width:90vw;max-height:85vh}
529
+ .badge-modal{max-width:680px;width:92vw;max-height:85vh}
514
530
  .badge-modal .modal-head{display:flex;align-items:center;justify-content:space-between;
515
531
  padding:var(--sp-3) var(--sp-4);border-bottom:1px solid var(--border)}
516
532
  .badge-modal .modal-head h2{font-size:1rem;font-weight:700;margin:0}
@@ -916,12 +932,19 @@ _META_PANEL = """\
916
932
  /* Provenance summary badges */
917
933
  .prov-summary{display:flex;flex-wrap:wrap;align-items:center;gap:6px;
918
934
  padding:var(--sp-2) var(--sp-4);border-top:1px solid var(--border)}
919
- .prov-badge{font-size:.65rem;font-weight:600;padding:2px 8px;
920
- border-radius:10px;white-space:nowrap;display:inline-flex;align-items:center;gap:3px}
921
- .prov-badge.green{background:var(--success-muted);color:var(--success)}
922
- .prov-badge.red{background:var(--error-muted);color:var(--error)}
923
- .prov-badge.amber{background:var(--warning-muted);color:var(--warning)}
924
- .prov-badge.neutral{background:var(--bg-overlay);color:var(--text-muted)}
935
+ .prov-badge{display:inline-flex;align-items:center;gap:4px;font-size:.66rem;
936
+ padding:2px 8px;border-radius:var(--radius-sm);background:var(--bg-raised);
937
+ white-space:nowrap;line-height:1.3;border:1px solid color-mix(in srgb,var(--border) 55%,transparent)}
938
+ .prov-badge-val{font-weight:600;font-variant-numeric:tabular-nums}
939
+ .prov-badge-lbl{font-weight:400;color:var(--text-muted);text-transform:lowercase}
940
+ .prov-badge--green{background:var(--success-muted);border-color:color-mix(in srgb,var(--success) 20%,transparent)}
941
+ .prov-badge--green .prov-badge-val{color:var(--success)}
942
+ .prov-badge--red{background:var(--error-muted);border-color:color-mix(in srgb,var(--error) 20%,transparent)}
943
+ .prov-badge--red .prov-badge-val{color:var(--error)}
944
+ .prov-badge--amber{background:var(--warning-muted);border-color:color-mix(in srgb,var(--warning) 20%,transparent)}
945
+ .prov-badge--amber .prov-badge-val{color:var(--warning)}
946
+ .prov-badge--neutral{background:var(--bg-overlay);border-color:color-mix(in srgb,var(--border) 75%,transparent)}
947
+ .prov-badge--neutral .prov-badge-val{color:var(--text-secondary)}
925
948
  .prov-explain{font-size:.62rem;color:var(--text-muted);margin-left:auto;font-style:italic}
926
949
  """
927
950
 
@@ -1033,12 +1056,39 @@ _UTILITY = """\
1033
1056
  .overview-row-spread{margin-left:0;width:100%}
1034
1057
  .suggestion-head{flex-direction:column;align-items:flex-start}
1035
1058
  .suggestion-facts{grid-template-columns:1fr}
1059
+ .sf-head{flex-direction:column;align-items:flex-start}
1060
+ .sf-meta{width:100%}
1036
1061
  .container{padding:0 var(--sp-3)}
1037
- .main-tabs{padding:0 var(--sp-3)}
1062
+ .topbar{position:static}
1063
+ .topbar-inner{height:auto;padding:var(--sp-2) var(--sp-3);flex-direction:row;
1064
+ align-items:center;gap:var(--sp-2)}
1065
+ .brand{flex:1;min-width:0;align-items:center;gap:var(--sp-2)}
1066
+ .brand-logo{width:24px;height:24px}
1067
+ .brand-text{gap:0}
1068
+ .brand h1{font-size:.85rem;line-height:1.25;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
1069
+ .brand-project-name{font-size:.78em;padding:0 3px}
1070
+ .brand-meta{display:none}
1071
+ .topbar-actions{flex-shrink:0;gap:var(--sp-1)}
1072
+ .topbar-actions .btn-prov{font-size:0;gap:0;width:32px;height:32px;
1073
+ padding:0;align-items:center;justify-content:center}
1074
+ .topbar-actions .btn-prov .prov-dot{width:10px;height:10px}
1075
+ .theme-toggle{font-size:0;gap:0;width:32px;height:32px;
1076
+ padding:0;align-items:center;justify-content:center}
1077
+ .theme-toggle svg{width:16px;height:16px}
1078
+ .main-tabs-wrap{position:sticky;top:0;z-index:90;padding:var(--sp-2) 0 0}
1079
+ .main-tabs{padding:var(--sp-1);gap:2px;
1080
+ background:
1081
+ linear-gradient(to right,var(--bg-surface) 30%,transparent) left center / 28px 100% no-repeat local,
1082
+ linear-gradient(to left,var(--bg-surface) 30%,transparent) right center / 28px 100% no-repeat local,
1083
+ linear-gradient(to right,rgba(0,0,0,.12),transparent) left center / 10px 100% no-repeat scroll,
1084
+ linear-gradient(to left,rgba(0,0,0,.12),transparent) right center / 10px 100% no-repeat scroll,
1085
+ var(--bg-surface)}
1086
+ .main-tab{flex:none;padding:var(--sp-1) var(--sp-2);font-size:.78rem}
1038
1087
  }
1039
1088
  @media(max-width:480px){
1040
1089
  .overview-kpi-grid{grid-template-columns:1fr}
1041
1090
  .search-box input[type="text"]{width:140px}
1091
+ .brand-logo{width:28px;height:28px}
1042
1092
  }
1043
1093
 
1044
1094
  /* Print */
@@ -333,36 +333,41 @@ def render_meta_panel(ctx: ReportContext) -> str:
333
333
  _section_html(st, rows) for st, rows in meta_sections if rows
334
334
  )
335
335
 
336
- def _prov_badge(label: str, color: str) -> str:
337
- return f'<span class="prov-badge {color}">{_escape_html(label)}</span>'
336
+ def _prov_badge(label: str, value: str, color: str) -> str:
337
+ return (
338
+ f'<span class="prov-badge prov-badge--{color}">'
339
+ f'<span class="prov-badge-val">{_escape_html(value)}</span>'
340
+ f'<span class="prov-badge-lbl">{_escape_html(label)}</span>'
341
+ "</span>"
342
+ )
338
343
 
339
344
  badges: list[str] = []
340
345
  if _bl_verified is True:
341
- badges.append(_prov_badge("Baseline verified", "green"))
346
+ badges.append(_prov_badge("Baseline", "verified", "green"))
342
347
  elif _bl_loaded is True and _bl_verified is not True:
343
- badges.append(_prov_badge("Baseline untrusted", "red"))
348
+ badges.append(_prov_badge("Baseline", "untrusted", "red"))
344
349
  elif _bl_loaded is False or _bl_loaded is None:
345
- badges.append(_prov_badge("Baseline missing", "amber"))
350
+ badges.append(_prov_badge("Baseline", "missing", "amber"))
346
351
  if ctx.report_schema_version:
347
- badges.append(_prov_badge(f"Schema {ctx.report_schema_version}", "neutral"))
352
+ badges.append(_prov_badge("Schema", str(ctx.report_schema_version), "neutral"))
348
353
  if _bl_fp_ver is not None:
349
- badges.append(_prov_badge(f"Fingerprint {_bl_fp_ver}", "neutral"))
354
+ badges.append(_prov_badge("Fingerprint", str(_bl_fp_ver), "neutral"))
350
355
  gen_name = str(_bl_gen_name or "")
351
356
  if gen_name and gen_name != "codeclone":
352
- badges.append(_prov_badge(f"Generator mismatch: {gen_name}", "red"))
357
+ badges.append(_prov_badge("Generator mismatch", gen_name, "red"))
353
358
  if _cache_used is True:
354
- badges.append(_prov_badge("Cache hit", "green"))
359
+ badges.append(_prov_badge("Cache", "hit", "green"))
355
360
  elif _cache_used is False:
356
- badges.append(_prov_badge("Cache miss", "amber"))
361
+ badges.append(_prov_badge("Cache", "miss", "amber"))
357
362
  else:
358
- badges.append(_prov_badge("Cache N/A", "neutral"))
363
+ badges.append(_prov_badge("Cache", "N/A", "neutral"))
359
364
  analysis_mode = str(_meta_pick(meta.get("analysis_mode")) or "")
360
365
  if analysis_mode:
361
- badges.append(_prov_badge(f"Mode: {analysis_mode}", "neutral"))
366
+ badges.append(_prov_badge("Mode", analysis_mode, "neutral"))
362
367
  if _mbl_verified is True:
363
- badges.append(_prov_badge("Metrics baseline verified", "green"))
368
+ badges.append(_prov_badge("Metrics baseline", "verified", "green"))
364
369
  elif _mbl_loaded is True and _mbl_verified is not True:
365
- badges.append(_prov_badge("Metrics baseline untrusted", "red"))
370
+ badges.append(_prov_badge("Metrics baseline", "untrusted", "red"))
366
371
 
367
372
  prov_summary = (
368
373
  f'<div class="prov-summary">{"".join(badges)}'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codeclone
3
- Version: 2.0.0b1
3
+ Version: 2.0.0b2
4
4
  Summary: Structural code quality analysis for Python
5
5
  Author-email: Den Rozhnovskiy <pytelemonbot@mail.ru>
6
6
  Maintainer-email: Den Rozhnovskiy <pytelemonbot@mail.ru>
@@ -36,7 +36,7 @@ Requires-Dist: pytest-cov>=7.1.0; extra == "dev"
36
36
  Requires-Dist: build>=1.4.1; extra == "dev"
37
37
  Requires-Dist: twine>=5.0.0; extra == "dev"
38
38
  Requires-Dist: mypy>=1.19.1; extra == "dev"
39
- Requires-Dist: ruff>=0.15.7; extra == "dev"
39
+ Requires-Dist: ruff>=0.15.8; extra == "dev"
40
40
  Requires-Dist: pre-commit>=4.5.1; extra == "dev"
41
41
  Dynamic: license-file
42
42
 
@@ -239,7 +239,7 @@ Dynamic/runtime false positives are resolved via explicit inline suppressions, n
239
239
  {
240
240
  "report_schema_version": "2.1",
241
241
  "meta": {
242
- "codeclone_version": "2.0.0b1",
242
+ "codeclone_version": "2.0.0b2",
243
243
  "project_name": "...",
244
244
  "scan_root": ".",
245
245
  "report_mode": "full",
@@ -10,5 +10,5 @@ pytest-cov>=7.1.0
10
10
  build>=1.4.1
11
11
  twine>=5.0.0
12
12
  mypy>=1.19.1
13
- ruff>=0.15.7
13
+ ruff>=0.15.8
14
14
  pre-commit>=4.5.1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "codeclone"
7
- version = "2.0.0b1"
7
+ version = "2.0.0b2"
8
8
  description = "Structural code quality analysis for Python"
9
9
  readme = { file = "README.md", content-type = "text/markdown" }
10
10
  license = "MIT"
@@ -67,7 +67,7 @@ dev = [
67
67
  "build>=1.4.1",
68
68
  "twine>=5.0.0",
69
69
  "mypy>=1.19.1",
70
- "ruff>=0.15.7",
70
+ "ruff>=0.15.8",
71
71
  "pre-commit>=4.5.1",
72
72
  ]
73
73
 
@@ -746,6 +746,54 @@ def test_html_report_topbar_actions_present() -> None:
746
746
  assert 'id="help-modal"' not in html
747
747
 
748
748
 
749
+ def test_html_report_mobile_topbar_reflows_brand_block() -> None:
750
+ html = build_html_report(func_groups={}, block_groups={}, segment_groups={})
751
+ _assert_html_contains(
752
+ html,
753
+ "@media(max-width:768px){",
754
+ ".topbar{position:static}",
755
+ ".topbar-inner{height:auto;",
756
+ ".brand-meta{display:none}",
757
+ ".main-tabs-wrap{position:sticky;top:0;",
758
+ ".main-tab{flex:none;",
759
+ )
760
+
761
+
762
+ def test_html_report_narrow_kpi_cards_keep_badges_inside_card() -> None:
763
+ html = build_html_report(func_groups={}, block_groups={}, segment_groups={})
764
+ _assert_html_contains(
765
+ html,
766
+ "@media(max-width:520px){",
767
+ (
768
+ ".overview-kpi-cards .meta-item{grid-template-rows:auto auto auto;"
769
+ "align-content:start;"
770
+ ),
771
+ ".overview-kpi-cards .kpi-detail{align-self:start}",
772
+ (
773
+ ".overview-kpi-cards .kpi-micro{max-width:100%;white-space:normal;"
774
+ "overflow-wrap:anywhere}"
775
+ ),
776
+ )
777
+
778
+
779
+ def test_html_report_table_css_matches_rendered_column_classes() -> None:
780
+ html = build_html_report(func_groups={}, block_groups={}, segment_groups={})
781
+ _assert_html_contains(
782
+ html,
783
+ ".table-wrap{display:block;inline-size:100%;max-inline-size:100%;min-inline-size:0;overflow-x:auto;",
784
+ ".table{inline-size:max-content;min-inline-size:100%;border-collapse:collapse;",
785
+ (
786
+ ".table .col-file,.table .col-path{color:var(--text-muted);"
787
+ "max-width:240px;overflow:hidden;"
788
+ ),
789
+ (
790
+ ".table .col-number,.table .col-num{font-variant-numeric:"
791
+ "tabular-nums;text-align:right;white-space:nowrap}"
792
+ ),
793
+ ".table .col-risk,.table .col-badge,.table .col-cat{white-space:nowrap}",
794
+ )
795
+
796
+
749
797
  def test_html_report_footer_links_present() -> None:
750
798
  html = build_html_report(func_groups={}, block_groups={}, segment_groups={})
751
799
  assert f'href="{REPOSITORY_URL}"' in html
@@ -809,6 +857,31 @@ def test_html_report_includes_provenance_metadata(
809
857
  assert "deterministic render" not in html
810
858
 
811
859
 
860
+ def test_html_report_provenance_summary_uses_card_like_badges(
861
+ report_meta_factory: Callable[..., dict[str, object]],
862
+ ) -> None:
863
+ html = build_html_report(
864
+ func_groups={},
865
+ block_groups={},
866
+ segment_groups={},
867
+ report_meta=report_meta_factory(
868
+ baseline_schema_version=2,
869
+ baseline_fingerprint_version=1,
870
+ ),
871
+ )
872
+ _assert_html_contains(
873
+ html,
874
+ 'class="prov-badge prov-badge--green"',
875
+ 'class="prov-badge prov-badge--neutral"',
876
+ '<span class="prov-badge-val">verified</span>',
877
+ '<span class="prov-badge-lbl">Baseline</span>',
878
+ '<span class="prov-badge-val">2.1</span>',
879
+ '<span class="prov-badge-lbl">Schema</span>',
880
+ '<span class="prov-badge-val">1</span>',
881
+ '<span class="prov-badge-lbl">Fingerprint</span>',
882
+ )
883
+
884
+
812
885
  def test_html_report_escapes_meta_and_title(
813
886
  tmp_path: Path,
814
887
  report_meta_factory: Callable[..., dict[str, object]],
@@ -1712,10 +1785,17 @@ def test_html_report_provenance_badges_cover_mismatch_and_untrusted_metrics() ->
1712
1785
  "baseline_fingerprint_version": "1",
1713
1786
  },
1714
1787
  )
1715
- assert "Baseline missing" in html
1716
- assert "Generator mismatch: other-generator" in html
1717
- assert "Metrics baseline untrusted" in html
1718
- assert "Cache N/A" in html
1788
+ _assert_html_contains(
1789
+ html,
1790
+ '<span class="prov-badge-val">missing</span>',
1791
+ '<span class="prov-badge-lbl">Baseline</span>',
1792
+ '<span class="prov-badge-val">other-generator</span>',
1793
+ '<span class="prov-badge-lbl">Generator mismatch</span>',
1794
+ '<span class="prov-badge-val">untrusted</span>',
1795
+ '<span class="prov-badge-lbl">Metrics baseline</span>',
1796
+ '<span class="prov-badge-val">N/A</span>',
1797
+ '<span class="prov-badge-lbl">Cache</span>',
1798
+ )
1719
1799
 
1720
1800
 
1721
1801
  def test_html_report_provenance_handles_non_boolean_baseline_loaded() -> None:
@@ -1729,8 +1809,12 @@ def test_html_report_provenance_handles_non_boolean_baseline_loaded() -> None:
1729
1809
  "report_schema_version": "2.0",
1730
1810
  },
1731
1811
  )
1732
- assert "Schema 2.0" in html
1733
- assert "Baseline missing" not in html
1812
+ _assert_html_contains(
1813
+ html,
1814
+ '<span class="prov-badge-val">2.0</span>',
1815
+ '<span class="prov-badge-lbl">Schema</span>',
1816
+ )
1817
+ assert '<span class="prov-badge-lbl">Baseline</span>' not in html
1734
1818
 
1735
1819
 
1736
1820
  def test_html_report_dependency_hubs_deterministic_tie_order() -> None:
@@ -491,7 +491,7 @@ def test_report_sarif_uses_representative_and_related_locations() -> None:
491
491
  }
492
492
  sarif_payload = json.loads(
493
493
  to_sarif_report(
494
- meta={"codeclone_version": "2.0.0b1", "scan_root": "/repo"},
494
+ meta={"codeclone_version": "2.0.0b2", "scan_root": "/repo"},
495
495
  func_groups=groups,
496
496
  block_groups={},
497
497
  segment_groups={},
@@ -427,7 +427,7 @@ def _rich_report_document() -> dict[str, object]:
427
427
  ),
428
428
  )
429
429
  meta = {
430
- "codeclone_version": "2.0.0b1",
430
+ "codeclone_version": "2.0.0b2",
431
431
  "project_name": "codeclone",
432
432
  "scan_root": "/repo/codeclone",
433
433
  "python_version": "3.13.11",
@@ -1251,7 +1251,7 @@ def test_render_sarif_report_document_without_srcroot_keeps_relative_payload() -
1251
1251
  payload = {
1252
1252
  "report_schema_version": "2.1",
1253
1253
  "meta": {
1254
- "codeclone_version": "2.0.0b1",
1254
+ "codeclone_version": "2.0.0b2",
1255
1255
  "analysis_mode": "ci",
1256
1256
  "report_mode": "full",
1257
1257
  "runtime": {},
File without changes
File without changes