Skip to content

Commit d45d4aa

Browse files
committed
feat(core): optimize MCP payloads, harden MCP safety semantics, and streamline report/html internals
1 parent fbbfa56 commit d45d4aa

31 files changed

+1786
-491
lines changed

CHANGELOG.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@ sync SPDX headers.
1717
remediation, granular checks, gate preview, PR summary, and session review markers.
1818
- Bounded run retention (`--history-limit`), `--allow-remote` guard, `cache_policy=refresh` rejected to preserve
1919
read-only semantics.
20-
- Agent-optimised payloads: slim inventory counts in summaries, `base_uri` envelope with relative locations,
21-
single-dimension `health` in `check_*`, three-tier `detail_level` on finding cards, and `metrics` / `metrics_detail`
22-
split — all without changing canonical report schema until the later `2.2` report-threshold update below.
23-
- `cache.effective_freshness` marker and `get_production_triage` / `codeclone://latest/triage` for compact
24-
production-first overview.
20+
- Agent-optimised payloads: short MCP run/finding ids, slim summary inventory, compact summary/default finding cards,
21+
single-dimension `health` in `check_*`, bounded `metrics_detail`, and compact changed-files / compare-runs responses
22+
— all without changing the canonical report contract.
23+
- `cache.freshness` marker and `get_production_triage` / `codeclone://latest/triage` for compact production-first
24+
overview.
2525
- Honest run comparison: `compare_runs` reports `mixed` / `incomparable` instead of misleading single verdicts;
2626
`clones_only` runs surface `health: unavailable` instead of zeroed placeholders.
27+
- Safety hardening: MCP analysis now requires an absolute repository root and rejects relative roots like `.`, so the
28+
server cannot silently analyze the wrong directory when its cwd differs from the client workspace.
2729
- Fix hotlist key resolution for `production_hotspots` and `test_fixture_hotspots`.
2830
- Bump cache schema to `2.3` (stale metric entries rebuilt, not reused).
2931

codeclone/_html_report/_assemble.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ def _tab_badge(count: int) -> str:
163163
tab_icon = section_icon_html(
164164
tab_icon_keys.get(tab_id, ""),
165165
class_name="main-tab-icon",
166-
size=14,
166+
size=15,
167167
)
168168
tab_buttons.append(
169169
f'<button class="main-tab" role="tab" data-tab="{tab_id}" '

codeclone/_html_report/_components.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,11 @@
1111
from collections.abc import Mapping
1212
from typing import Literal
1313

14-
from .. import _coerce
14+
from .._coerce import as_int as _as_int
1515
from .._html_badges import _source_kind_badge_html
1616
from .._html_escape import _escape_attr, _escape_html
1717
from ._icons import section_icon_html
1818

19-
_as_int = _coerce.as_int
20-
_as_mapping = _coerce.as_mapping
21-
2219
Tone = Literal["ok", "warn", "risk", "info"]
2320

2421
_EMPTY_ICON = (

codeclone/_html_report/_context.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from dataclasses import dataclass
1313
from typing import TYPE_CHECKING
1414

15-
from .. import _coerce
15+
from .._coerce import as_mapping as _as_mapping
1616
from ..contracts import REPORT_SCHEMA_VERSION
1717
from ..report.overview import build_report_overview, materialize_report_overview
1818

@@ -26,9 +26,6 @@
2626
Suggestion,
2727
)
2828

29-
_as_mapping = _coerce.as_mapping
30-
_as_sequence = _coerce.as_sequence
31-
3229

3330
@dataclass(frozen=True, slots=True)
3431
class ReportContext:

codeclone/_html_report/_icons.py

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -94,16 +94,16 @@ def _svg_with_class(size: int, sw: str, body: str, *, class_name: str = "") -> s
9494

9595
_SECTION_ICON_BODIES: dict[str, tuple[str, str]] = {
9696
"overview": (
97-
"2",
98-
'<rect x="3" y="4" width="8" height="7" rx="1.5"/>'
99-
'<rect x="13" y="4" width="8" height="7" rx="1.5"/>'
100-
'<rect x="3" y="13" width="8" height="7" rx="1.5"/>'
101-
'<rect x="13" y="13" width="8" height="7" rx="1.5"/>',
97+
"1.8",
98+
'<rect x="3" y="3" width="7.5" height="7.5" rx="1.5"/>'
99+
'<rect x="13.5" y="3" width="7.5" height="7.5" rx="1.5"/>'
100+
'<rect x="3" y="13.5" width="7.5" height="7.5" rx="1.5"/>'
101+
'<rect x="13.5" y="13.5" width="7.5" height="7.5" rx="1.5"/>',
102102
),
103103
"clones": (
104104
"2",
105-
'<rect x="9" y="9" width="10" height="10" rx="2"/>'
106-
'<rect x="5" y="5" width="10" height="10" rx="2"/>',
105+
'<rect x="9" y="9" width="11" height="11" rx="2"/>'
106+
'<rect x="4" y="4" width="11" height="11" rx="2"/>',
107107
),
108108
"quality": (
109109
"2",
@@ -113,9 +113,9 @@ def _svg_with_class(size: int, sw: str, body: str, *, class_name: str = "") -> s
113113
),
114114
"dependencies": (
115115
"2",
116-
'<circle cx="6" cy="6" r="2"/><circle cx="18" cy="6" r="2"/>'
117-
'<circle cx="12" cy="18" r="2"/><path d="M8 7.5l2.5 6.5"/>'
118-
'<path d="M16 7.5l-2.5 6.5"/>',
116+
'<circle cx="6" cy="6" r="2.5"/><circle cx="18" cy="6" r="2.5"/>'
117+
'<circle cx="12" cy="18" r="2.5"/><path d="M8 7.8l2.7 6.4"/>'
118+
'<path d="M16 7.8l-2.7 6.4"/>',
119119
),
120120
"dead-code": (
121121
"2",
@@ -124,14 +124,14 @@ def _svg_with_class(size: int, sw: str, body: str, *, class_name: str = "") -> s
124124
),
125125
"suggestions": (
126126
"2",
127-
'<path d="M12 3l1.8 4.7L18.5 9.5l-4.7 1.8L12 16l-1.8-4.7L5.5 9.5l4.7-1.8Z"/>'
128-
'<path d="M19 16l.8 2.2L22 19l-2.2.8L19 22l-.8-2.2L16 19l2.2-.8Z"/>',
127+
'<path d="M11.5 3l1.9 5 5 1.9-5 1.9L11.5 17l-1.9-5-5-1.9 5-1.9Z"/>'
128+
'<path d="M19.5 16l.8 2 2 .8-2 .8-.8 2-.8-2-2-.8 2-.8Z"/>',
129129
),
130130
"structural-findings": (
131131
"2",
132-
'<circle cx="6" cy="6" r="2"/><circle cx="18" cy="18" r="2"/>'
133-
'<circle cx="18" cy="6" r="2"/><path d="M8 6h8"/>'
134-
'<path d="M6 8v8a2 2 0 0 0 2 2h8"/>',
132+
'<circle cx="6" cy="6" r="2.5"/><circle cx="18" cy="18" r="2.5"/>'
133+
'<circle cx="18" cy="6" r="2.5"/><path d="M8.5 6h7"/>'
134+
'<path d="M6 8.5v7a2 2 0 0 0 2 2h7"/>',
135135
),
136136
"top-risks": (
137137
"2",
@@ -164,8 +164,8 @@ def _svg_with_class(size: int, sw: str, body: str, *, class_name: str = "") -> s
164164
),
165165
"clone-groups": (
166166
"2",
167-
'<rect x="9" y="9" width="10" height="10" rx="2"/>'
168-
'<rect x="5" y="5" width="10" height="10" rx="2"/>',
167+
'<rect x="9" y="9" width="11" height="11" rx="2"/>'
168+
'<rect x="4" y="4" width="11" height="11" rx="2"/>',
169169
),
170170
"low-cohesion": (
171171
"2",

codeclone/blockhash.py

Lines changed: 0 additions & 32 deletions
This file was deleted.

codeclone/blocks.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88

99
from typing import TYPE_CHECKING
1010

11-
from .blockhash import stmt_hashes
1211
from .fingerprint import sha1
1312
from .models import BlockUnit, SegmentUnit
13+
from .normalize import stmt_hashes
1414

1515
if TYPE_CHECKING:
1616
import ast

codeclone/extractor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
from typing import TYPE_CHECKING, Literal, NamedTuple
1919

2020
from . import qualnames as _qualnames
21-
from .blockhash import stmt_hashes
2221
from .blocks import extract_blocks, extract_segments
2322
from .cfg import CFGBuilder
2423
from .errors import ParseError
@@ -46,6 +45,7 @@
4645
AstNormalizer,
4746
NormalizationConfig,
4847
normalized_ast_dump_from_list,
48+
stmt_hashes,
4949
)
5050
from .paths import is_test_filepath
5151
from .structural_findings import scan_function_structure

0 commit comments

Comments
 (0)