Skip to content

Commit 26538bd

Browse files
committed
refactor(report): materialize overview insights, streamline cache/extractor internals, and sync docs
1 parent df0b00d commit 26538bd

27 files changed

Lines changed: 1717 additions & 391 deletions

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@ Agents must preserve these semantics:
236236
- **2** — baseline gating failure (untrusted/missing baseline when CI requires trusted baseline; invalid output
237237
extension, etc.)
238238
- **3** — analysis gating failure (e.g., `--fail-threshold` exceeded or new clones in `--ci` as designed)
239+
- **5** — internal error (unexpected exception escaped top-level CLI handling)
239240

240241
If you introduce a new exit reason, document it and add tests.
241242

README.md

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,8 @@ All report formats are rendered from one canonical JSON report document.
154154
- `--open-html-report` opens the generated HTML report in the default browser and requires `--html`.
155155
- `--timestamped-report-paths` appends a UTC timestamp to default report filenames for bare report flags such as
156156
`--html` or `--json`. Explicit report paths are not rewritten.
157-
Structural findings include:
157+
158+
Structural findings include:
158159

159160
- `duplicated_branches`
160161
- `clone_guard_exit_divergence`
@@ -241,8 +242,18 @@ Dynamic/runtime false positives are resolved via explicit inline suppressions, n
241242
},
242243
"derived": {
243244
"suggestions": [],
244-
"overview": {},
245-
"hotlists": {}
245+
"overview": {
246+
"families": {},
247+
"top_risks": [],
248+
"source_scope_breakdown": {},
249+
"health_snapshot": {}
250+
},
251+
"hotlists": {
252+
"most_actionable_ids": [],
253+
"highest_spread_ids": [],
254+
"production_hotspot_ids": [],
255+
"test_fixture_hotspot_ids": []
256+
}
246257
},
247258
"integrity": {
248259
"canonicalization": {
@@ -290,7 +301,7 @@ Architecture: [`docs/architecture.md`](docs/architecture.md) · CFG semantics: [
290301
| Docker benchmark contract | [`docs/book/18-benchmarking.md`](docs/book/18-benchmarking.md) |
291302
| Determinism | [`docs/book/12-determinism.md`](docs/book/12-determinism.md) |
292303

293-
## * Benchmarking
304+
## * Benchmarking
294305

295306
<details>
296307
<summary>Reproducible Docker Benchmark</summary>

codeclone/_html_badges.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ def _stat_card(
129129
detail: str = "",
130130
tip: str = "",
131131
tone: str = "",
132+
value_tone: str = "",
132133
css_class: str = "meta-item",
133134
glossary_tip_fn: Callable[[str], str] | None = None,
134135
delta_new: int | None = None,
@@ -139,6 +140,10 @@ def _stat_card(
139140
``.meta-label`` / ``.meta-value`` so every stat card shares the
140141
exact same design code.
141142
143+
*value_tone* — semantic color for the main value:
144+
``"good"`` → green (metric is clean), ``"bad"`` → red (metric has issues),
145+
``""`` → default text-primary.
146+
142147
*delta_new* — if provided and > 0, renders a ``+N new`` badge below
143148
the detail line. For "bad" metrics (complexity, coupling, etc.)
144149
positive delta means regression → red; zero means no change → hidden.
@@ -151,18 +156,19 @@ def _stat_card(
151156

152157
detail_html = ""
153158
if detail:
154-
detail_html = f'<div class="kpi-detail">{_escape_html(detail)}</div>'
159+
detail_html = f'<div class="kpi-detail">{detail}</div>'
155160

156161
delta_html = ""
157162
if delta_new is not None and delta_new > 0:
158163
delta_html = f'<div class="kpi-delta kpi-delta--bad">+{delta_new} new</div>'
159164

160165
tone_cls = f" dep-stat-{tone}" if tone else ""
166+
value_cls = f" meta-value--{value_tone}" if value_tone else ""
161167

162168
return (
163169
f'<div class="{_escape_attr(css_class)}{tone_cls}">'
164170
f'<div class="meta-label">{_escape_html(label)}{tip_html}</div>'
165-
f'<div class="meta-value">{_escape_html(str(value))}</div>'
171+
f'<div class="meta-value{value_cls}">{_escape_html(str(value))}</div>'
166172
f"{detail_html}"
167173
f"{delta_html}"
168174
"</div>"

codeclone/_html_css.py

Lines changed: 72 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@
113113
_RESET = """\
114114
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
115115
html{-webkit-text-size-adjust:100%;text-size-adjust:100%;-webkit-font-smoothing:antialiased;
116-
-moz-osx-font-smoothing:grayscale;scroll-behavior:smooth}
116+
-moz-osx-font-smoothing:grayscale;scroll-behavior:smooth;scrollbar-gutter:stable}
117117
body{font-family:var(--font-sans);font-size:14px;line-height:1.6;color:var(--text-primary);
118118
background:var(--bg-body);overflow-x:hidden}
119119
code,pre,kbd{font-family:var(--font-mono);font-size:13px}
@@ -363,6 +363,7 @@
363363
/* Group body */
364364
.group-body{border-top:1px solid var(--border);display:none}
365365
.group-body.expanded{display:block}
366+
.group-body.items.expanded{display:grid}
366367
.group-compare-note{padding:var(--sp-2) var(--sp-4);font-size:.8rem;color:var(--text-muted);
367368
background:var(--bg-raised);border-bottom:1px solid var(--border);font-style:italic}
368369
@@ -382,14 +383,18 @@
382383
# ---------------------------------------------------------------------------
383384

384385
_ITEMS = """\
385-
.item{border-bottom:1px solid var(--border);padding:0}
386-
.item:last-child{border-bottom:none}
386+
.items{grid-template-columns:repeat(2,1fr);gap:0}
387+
.items .item{border-right:1px solid var(--border);border-bottom:1px solid var(--border)}
388+
.items .item:nth-child(2n){border-right:none}
389+
.items .item:nth-last-child(-n+2){border-bottom:none}
390+
.items .item:last-child{border-bottom:none}
391+
.item{padding:0;min-width:0;overflow:hidden}
387392
.item-header{display:flex;align-items:center;justify-content:space-between;
388-
padding:var(--sp-2) var(--sp-4);background:var(--bg-raised);gap:var(--sp-3)}
389-
.item-title{font-weight:500;font-size:.85rem;color:var(--text-primary);font-family:var(--font-mono);
393+
padding:var(--sp-2) var(--sp-3);background:var(--bg-raised);gap:var(--sp-2)}
394+
.item-title{font-weight:500;font-size:.8rem;color:var(--text-primary);font-family:var(--font-mono);
390395
white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0;flex:1}
391-
.item-loc{font-size:.8rem;color:var(--text-muted);font-family:var(--font-mono);white-space:nowrap;flex-shrink:0}
392-
.item-compare-meta{padding:var(--sp-1) var(--sp-4);font-size:.75rem;color:var(--text-muted);
396+
.item-loc{font-size:.72rem;color:var(--text-muted);font-family:var(--font-mono);white-space:nowrap;flex-shrink:0}
397+
.item-compare-meta{padding:var(--sp-1) var(--sp-3);font-size:.72rem;color:var(--text-muted);
393398
background:var(--bg-body);border-bottom:1px solid var(--border)}
394399
"""
395400

@@ -398,15 +403,6 @@
398403
# ---------------------------------------------------------------------------
399404

400405
_CODE = """\
401-
.code-block{overflow-x:auto;font-size:12px;line-height:1.7;background:var(--bg-body);
402-
padding:var(--sp-2) 0;margin:0}
403-
.code-block pre{margin:0;padding:0}
404-
.code-block .linenos{user-select:none;text-align:right;padding-right:var(--sp-3);
405-
color:var(--text-muted);opacity:.5;min-width:3.5em;display:inline-block;font-size:11px}
406-
.code-block .code-line{padding:0 var(--sp-4) 0 var(--sp-2);white-space:pre;display:block}
407-
.code-block .code-line:hover{background:var(--bg-raised)}
408-
.code-block .code-line.hl{background:var(--accent-muted)}
409-
.code-block .code-line.hl:hover{background:color-mix(in oklch,var(--accent-primary) 20%,transparent)}
410406
/* _html_snippets renders .codebox>.hitline/.line */
411407
.codebox{overflow-x:auto;font-size:12px;line-height:1.7;background:var(--bg-body);padding:var(--sp-2) 0;margin:0}
412408
.codebox pre{margin:0;padding:0}
@@ -462,7 +458,7 @@
462458
.overview-health-card{display:flex;align-items:center;justify-content:center;
463459
min-height:0;padding:var(--sp-3) var(--sp-2)}
464460
.overview-health-inner{display:flex;align-items:center;justify-content:center;width:100%;height:100%}
465-
.health-ring{position:relative;width:110px;height:110px}
461+
.health-ring{position:relative;width:130px;height:130px}
466462
.health-ring svg{width:100%;height:100%;transform:rotate(-90deg)}
467463
.health-ring-bg{fill:none;stroke:var(--border);stroke-width:6}
468464
.health-ring-fg{fill:none;stroke-width:6;stroke-linecap:round;
@@ -479,24 +475,35 @@
479475
/* KPI stat card */
480476
.meta-item{padding:var(--sp-3) var(--sp-4);background:var(--bg-surface);border:1px solid var(--border);
481477
border-radius:var(--radius-lg);display:flex;flex-direction:column;gap:var(--sp-1);
482-
transition:border-color var(--dur-fast) var(--ease)}
478+
transition:border-color var(--dur-fast) var(--ease);min-width:0}
483479
.meta-item:hover{border-color:var(--border-strong)}
484480
.meta-item .meta-label{font-size:.75rem;font-weight:500;color:var(--text-muted);
485481
display:flex;align-items:center;gap:var(--sp-1)}
486482
.meta-item .meta-value{font-size:1.25rem;font-weight:700;color:var(--text-primary);
487-
font-variant-numeric:tabular-nums}
488-
.kpi-detail{font-size:.75rem;color:var(--text-muted);margin-top:var(--sp-1)}
489-
.kpi-delta{font-size:.72rem;font-weight:600;margin-top:var(--sp-1)}
483+
font-variant-numeric:tabular-nums;line-height:1.2}
484+
.meta-item .meta-value--good{color:var(--success)}
485+
.meta-item .meta-value--bad{color:var(--error)}
486+
.meta-item .meta-value--warn{color:var(--warning)}
487+
.kpi-detail{display:flex;flex-wrap:wrap;gap:4px;margin-top:4px}
488+
.kpi-micro{display:inline-flex;align-items:center;gap:3px;font-size:.65rem;
489+
padding:1px 6px;border-radius:var(--radius-sm);background:var(--bg-raised);
490+
white-space:nowrap;line-height:1.4}
491+
.kpi-micro-val{font-weight:500;font-variant-numeric:tabular-nums;color:var(--text-muted)}
492+
.kpi-micro-lbl{font-weight:400;color:var(--text-muted);text-transform:lowercase}
493+
.kpi-delta{font-size:.72rem;font-weight:600;margin-top:2px}
490494
.kpi-delta--good{color:var(--success)}
491495
.kpi-delta--bad{color:var(--error)}
492496
.kpi-delta--neutral{color:var(--text-muted)}
493-
.kpi-help{display:inline-flex;align-items:center;justify-content:center;width:14px;height:14px;
494-
font-size:.65rem;font-weight:700;border-radius:50%;background:var(--bg-overlay);
495-
color:var(--text-muted);cursor:help;position:relative}
496-
.kpi-help:hover::after{content:attr(data-tip);position:absolute;bottom:calc(100% + 6px);left:50%;
497+
.kpi-help{display:inline-flex;align-items:center;justify-content:center;width:15px;height:15px;
498+
font-size:.6rem;font-weight:600;border-radius:50%;background:none;
499+
color:var(--text-muted);cursor:help;position:relative;border:1.5px solid var(--border);
500+
opacity:.5;transition:opacity var(--dur-fast) var(--ease)}
501+
.kpi-help:hover{opacity:1}
502+
.kpi-help:hover::after{content:attr(data-tip);position:absolute;top:calc(100% + 6px);left:50%;
497503
transform:translateX(-50%);background:var(--bg-overlay);color:var(--text-primary);
498504
padding:var(--sp-2) var(--sp-3);border-radius:var(--radius-md);font-size:.75rem;font-weight:400;
499-
white-space:nowrap;box-shadow:var(--shadow-md);z-index:50;pointer-events:none;
505+
white-space:normal;width:max-content;max-width:240px;line-height:1.4;
506+
box-shadow:var(--shadow-md);z-index:100;pointer-events:none;
500507
border:1px solid var(--border)}
501508
502509
/* Tone variants */
@@ -511,32 +518,48 @@
511518
.overview-cluster-empty{display:flex;flex-direction:column;align-items:center;gap:var(--sp-2);
512519
padding:var(--sp-5);text-align:center;color:var(--text-muted);font-size:.85rem}
513520
.empty-icon{color:var(--success);opacity:.35;width:32px;height:32px;flex-shrink:0}
514-
.overview-list{display:flex;flex-direction:column;gap:var(--sp-2)}
521+
.overview-list{display:grid;grid-template-columns:repeat(2,1fr);gap:var(--sp-2)}
515522
516523
/* Overview rows */
517-
.overview-row{display:grid;grid-template-columns:1fr auto;gap:var(--sp-4);
524+
.overview-row{display:flex;flex-direction:column;gap:var(--sp-1);
518525
padding:var(--sp-3) var(--sp-4);background:var(--bg-surface);border:1px solid var(--border);
519526
border-radius:var(--radius-lg);transition:border-color var(--dur-fast) var(--ease)}
520527
.overview-row:hover{border-color:var(--border-strong)}
521-
.overview-row-main{min-width:0}
522-
.overview-row-title{font-weight:600;font-size:.9rem;color:var(--text-primary);margin-bottom:var(--sp-1)}
528+
.overview-row[data-severity="critical"]{border-left:3px solid var(--error)}
529+
.overview-row[data-severity="warning"]{border-left:3px solid var(--warning)}
530+
.overview-row[data-severity="info"]{border-left:3px solid var(--info)}
531+
.overview-row-head{display:flex;align-items:center;gap:var(--sp-2);flex-wrap:wrap}
532+
.overview-row-spread{font-size:.72rem;font-family:var(--font-mono);color:var(--text-muted);
533+
margin-left:auto;white-space:nowrap}
534+
.overview-row-title{font-weight:600;font-size:.85rem;color:var(--text-primary)}
523535
.overview-row-summary{font-size:.8rem;color:var(--text-secondary);line-height:1.5}
524-
.overview-row-side{text-align:right;display:flex;flex-direction:column;gap:var(--sp-1);flex-shrink:0}
525-
.overview-row-context{font-size:.72rem;color:var(--text-muted)}
526-
.overview-row-meta{font-size:.75rem;color:var(--text-muted);font-family:var(--font-mono)}
527536
528537
/* Summary grid */
529-
.overview-summary-grid{display:grid;gap:var(--sp-2);margin-bottom:var(--sp-3)}
538+
.overview-summary-grid{display:grid;gap:var(--sp-3);margin-bottom:var(--sp-3)}
530539
.overview-summary-grid--2col{grid-template-columns:repeat(auto-fit,minmax(280px,1fr))}
531540
.overview-summary-item{background:var(--bg-surface);border:1px solid var(--border);
532-
border-radius:var(--radius-lg);padding:var(--sp-3) var(--sp-4)}
533-
.overview-summary-label{font-size:.75rem;font-weight:600;text-transform:uppercase;
534-
letter-spacing:.05em;color:var(--text-muted);margin-bottom:var(--sp-2)}
535-
.overview-summary-list{display:flex;flex-direction:column;gap:var(--sp-1)}
536-
.overview-summary-list li{font-size:.85rem;color:var(--text-secondary);
537-
padding-left:var(--sp-3);position:relative}
541+
border-radius:var(--radius-lg);padding:var(--sp-4)}
542+
.overview-summary-label{display:flex;align-items:center;gap:var(--sp-2);
543+
font-size:.72rem;font-weight:700;text-transform:uppercase;
544+
letter-spacing:.06em;color:var(--text-muted);margin-bottom:var(--sp-3);
545+
padding-bottom:var(--sp-2);border-bottom:1px solid var(--border)}
546+
.summary-icon{flex-shrink:0;opacity:.6}
547+
.summary-icon--risk{color:var(--warning)}
548+
.summary-icon--info{color:var(--accent-primary)}
549+
.overview-summary-list{display:flex;flex-direction:column;gap:var(--sp-2)}
550+
.overview-summary-list li{font-size:.82rem;color:var(--text-secondary);
551+
padding-left:var(--sp-3);position:relative;line-height:1.5}
538552
.overview-summary-list li::before{content:"\\2022";position:absolute;left:0;color:var(--text-muted)}
539553
.overview-summary-value{font-size:.85rem;color:var(--text-muted)}
554+
/* Source breakdown bars */
555+
.breakdown-list{display:flex;flex-direction:column;gap:var(--sp-2)}
556+
.breakdown-row{display:grid;grid-template-columns:6.5rem 2rem 1fr;align-items:center;gap:var(--sp-2)}
557+
.breakdown-row .source-kind-badge{justify-content:center;min-width:0;width:100%;text-align:center}
558+
.breakdown-count{font-size:.8rem;font-weight:600;font-variant-numeric:tabular-nums;
559+
color:var(--text-primary);text-align:right}
560+
.breakdown-bar-track{height:6px;border-radius:3px;background:var(--bg-raised);overflow:hidden}
561+
.breakdown-bar-fill{display:block;height:100%;border-radius:3px;
562+
background:var(--accent-primary);transition:width .6s var(--ease)}
540563
"""
541564

542565
# ---------------------------------------------------------------------------
@@ -546,9 +569,9 @@
546569
_DEPENDENCIES = """\
547570
.dep-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));
548571
gap:var(--sp-3);margin-bottom:var(--sp-4)}
549-
.dep-graph-wrap{overflow-x:auto;margin-bottom:var(--sp-4);border:1px solid var(--border);
572+
.dep-graph-wrap{overflow:hidden;margin-bottom:var(--sp-4);border:1px solid var(--border);
550573
border-radius:var(--radius-lg);background:var(--bg-surface);padding:var(--sp-4)}
551-
.dep-graph-svg{min-width:100%;width:max-content;height:auto;min-height:280px}
574+
.dep-graph-svg{width:100%;height:auto;max-height:520px}
552575
.dep-graph-svg text{fill:var(--text-secondary);font-family:var(--font-mono)}
553576
.dep-node{transition:fill-opacity var(--dur-fast) var(--ease)}
554577
.dep-edge{transition:stroke-opacity var(--dur-fast) var(--ease)}
@@ -591,9 +614,9 @@
591614
.novelty-tabs{display:flex;gap:var(--sp-2)}
592615
.novelty-tab{transition:all var(--dur-fast) var(--ease)}
593616
.novelty-tab.active{background:var(--accent-primary);color:white;border-color:var(--accent-primary)}
594-
.novelty-tab[data-novelty-state="good"]{color:var(--success);border-color:var(--success)}
617+
.novelty-tab[data-novelty-state="good"]{color:var(--success);border-color:var(--success);background:var(--success-muted)}
595618
.novelty-tab[data-novelty-state="good"].active{background:var(--success);color:white;border-color:var(--success)}
596-
.novelty-tab[data-novelty-state="bad"]{color:var(--error);border-color:var(--error)}
619+
.novelty-tab[data-novelty-state="bad"]{color:var(--error);border-color:var(--error);background:var(--error-muted)}
597620
.novelty-tab[data-novelty-state="bad"].active{background:var(--error);color:white;border-color:var(--error)}
598621
.novelty-count{font-size:.72rem;font-weight:600;background:rgba(255,255,255,.15);padding:0 var(--sp-1);
599622
border-radius:var(--radius-sm);margin-left:var(--sp-1)}
@@ -912,8 +935,11 @@
912935
.overview-kpi-grid{grid-template-columns:repeat(2,1fr)}
913936
.toolbar{flex-direction:column;align-items:stretch}
914937
.toolbar-left,.toolbar-right{justify-content:flex-start}
915-
.overview-row{grid-template-columns:1fr}
916-
.overview-row-side{text-align:left}
938+
.overview-list{grid-template-columns:1fr}
939+
.items{grid-template-columns:1fr}
940+
.items .item{border-right:none}
941+
.overview-row-head{flex-wrap:wrap}
942+
.overview-row-spread{margin-left:0;width:100%}
917943
.suggestion-head{flex-direction:column;align-items:flex-start}
918944
.suggestion-facts{grid-template-columns:1fr}
919945
.container{padding:0 var(--sp-3)}

codeclone/_html_js.py

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -400,10 +400,8 @@
400400
(function initHelpModal(){
401401
const dlg=$('#help-modal');
402402
if(!dlg)return;
403-
const openers=$$('[data-help-open]');
404403
const closeBtn=dlg.querySelector('[data-help-close]');
405404
const open=()=>dlg.showModal();
406-
openers.forEach(btn=>btn.addEventListener('click',open));
407405
if(closeBtn)closeBtn.addEventListener('click',()=>dlg.close());
408406
dlg.addEventListener('click',e=>{if(e.target===dlg)dlg.close()});
409407
document.addEventListener('keydown',e=>{
@@ -625,22 +623,7 @@
625623
}
626624
"""
627625

628-
_LAZY_HIGHLIGHT = """\
629-
(function initLazyHighlight(){
630-
if(typeof IntersectionObserver==='undefined')return;
631-
const blocks=$$('.code-block[data-lang]');
632-
if(!blocks.length)return;
633-
const obs=new IntersectionObserver((entries)=>{
634-
entries.forEach(entry=>{
635-
if(entry.isIntersecting){
636-
entry.target.classList.add('visible');
637-
obs.unobserve(entry.target);
638-
}
639-
});
640-
},{rootMargin:'200px'});
641-
blocks.forEach(b=>obs.observe(b));
642-
})();
643-
"""
626+
_LAZY_HIGHLIGHT = ""
644627

645628

646629
# ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)