Skip to content

Commit 286520a

Browse files
authored
Prepare CodeClone 2.0.1 stability release (#30)
* chore(docs): update pipeline overview diagram * chore(release): prepare 2.0.1 package metadata * fix(vscode): align hotspot counts with report semantics * fix(mcp): derive baseline help from contract version * chore(docs): alignment of documentation to the current codebase * chore(ci): updating the base image to the current tag * chore(ci): updating versions of action * feat(cli): add dead-code migration note * fix(cli): refresh banner subtitle * chore(release): update 2.0.1 changelog date
1 parent 7c90e1b commit 286520a

48 files changed

Lines changed: 1493 additions & 331 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/actions/codeclone/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ source under test. Remote consumers still install from PyPI.
3838
For strict reproducibility, pin the full release tag:
3939
4040
```yaml
41-
- uses: orenlab/codeclone/.github/actions/codeclone@v2.0.0
41+
- uses: orenlab/codeclone/.github/actions/codeclone@v2.0.1
4242
```
4343
4444
For long-lived workflows, `@v2` follows the latest compatible 2.x action
@@ -80,7 +80,7 @@ jobs:
8080
| Input | Default | Purpose |
8181
|-------------------------|---------------------------------|-------------------------------------------------------------------------------------------------------------------|
8282
| `python-version` | `3.14` | Python version used to run the action |
83-
| `package-version` | `2.0.0` | CodeClone version from PyPI for remote installs; ignored when the action runs from the checked-out CodeClone repo |
83+
| `package-version` | `2.0.1` | CodeClone version from PyPI for remote installs; ignored when the action runs from the checked-out CodeClone repo |
8484
| `path` | `.` | Project root to analyze |
8585
| `json-path` | `.cache/codeclone/report.json` | JSON report output path |
8686
| `sarif` | `true` | Generate SARIF and try to upload it |
@@ -145,7 +145,7 @@ Notes:
145145
## Install policy
146146

147147
Released action tags pin the PyPI package version in action metadata. For
148-
example, `@v2.0.0` installs `codeclone==2.0.0` unless you override
148+
example, `@v2.0.1` installs `codeclone==2.0.1` unless you override
149149
`package-version`.
150150

151151
Explicit prerelease or smoke-test override:

.github/actions/codeclone/_action_impl.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from typing import Literal
2626

2727
COMMENT_MARKER = "<!-- codeclone-report -->"
28-
DEFAULT_CODECLONE_PACKAGE_VERSION = "2.0.1b1"
28+
DEFAULT_CODECLONE_PACKAGE_VERSION = "2.0.1"
2929

3030

3131
@dataclass(frozen=True, slots=True)

.github/actions/codeclone/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ inputs:
1818
package-version:
1919
description: "CodeClone version from PyPI for remote installs (ignored when the action runs from the checked-out CodeClone repo)"
2020
required: false
21-
default: "2.0.1b1"
21+
default: "2.0.1"
2222

2323
path:
2424
description: "Project root"

.github/workflows/benchmark.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ jobs:
219219

220220
- name: Upload benchmark artifact
221221
if: env.BENCH_ENABLED == '1'
222-
uses: actions/upload-artifact@v4
222+
uses: actions/upload-artifact@v7
223223
with:
224224
name: codeclone-benchmark-${{ matrix.label }}
225225
path: ${{ env.BENCH_JSON }}

.github/workflows/publish.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ jobs:
6767
run: uv run --with twine twine check dist/*
6868

6969
- name: Upload distributions
70-
uses: actions/upload-artifact@v4
70+
uses: actions/upload-artifact@v7
7171
with:
7272
name: python-package-distributions
7373
path: dist/
@@ -83,7 +83,7 @@ jobs:
8383
id-token: write
8484
steps:
8585
- name: Download distributions
86-
uses: actions/download-artifact@v5
86+
uses: actions/download-artifact@v8
8787
with:
8888
name: python-package-distributions
8989
path: dist/
@@ -107,7 +107,7 @@ jobs:
107107
id-token: write
108108
steps:
109109
- name: Download distributions
110-
uses: actions/download-artifact@v5
110+
uses: actions/download-artifact@v8
111111
with:
112112
name: python-package-distributions
113113
path: dist/

CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
# Changelog
22

3-
## [Unreleased]
3+
## [2.0.1] - 2026-05-14
4+
5+
`2.0.1` is a focused stability release for dead-code precision and cache/report
6+
contract parity after the 2.0 line.
47

58
### Dead code
69

@@ -13,6 +16,9 @@
1316
- Treat `typing.Protocol` and `typing_extensions.Protocol` declarations, including
1417
generic `Protocol[T]`, as type-only contracts so structural interfaces do not produce
1518
false-positive dead-code findings.
19+
- Show a one-time interactive CLI migration note for projects upgrading from
20+
the 2.0.0 line when the refined reachability model may reduce dead-code
21+
findings.
1622
- Bump cache schema to `2.7` and report schema to `2.11` to carry reachability facts
1723
for cold/warm parity and report explainability.
1824

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ Live sample report: [orenlab.github.io/codeclone/examples/report/](https://orenl
5151
profile, dead code, health score, and overloaded-module profiling
5252
- **Adoption & API** — type/docstring annotation coverage, public API surface inventory and baseline diff
5353
- **Coverage Join** — fuse external Cobertura XML into the current run to surface coverage hotspots and scope gaps
54+
- **Security Surfaces** — report-only inventory of security-relevant capability boundaries without vulnerability claims
5455

5556
**Surfaces & integrations**
5657

@@ -318,7 +319,7 @@ Top-level keys: `report_schema_version`, `meta`, `inventory`, `findings`, `metri
318319
{
319320
"report_schema_version": "2.11",
320321
"meta": {
321-
"codeclone_version": "2.0.0",
322+
"codeclone_version": "2.0.1",
322323
"project_name": "...",
323324
"scan_root": ".",
324325
"...": "..."

benchmarks/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# syntax=docker/dockerfile:1.7
22

3-
FROM python:3.14.3-slim-bookworm
3+
FROM python:3.14.5-slim-bookworm
44

55
ENV PYTHONDONTWRITEBYTECODE=1 \
66
PYTHONUNBUFFERED=1 \

codeclone/surfaces/cli/tips.py

Lines changed: 126 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,24 @@
1212
from pathlib import Path
1313
from typing import TextIO
1414

15+
from packaging.version import InvalidVersion, Version
16+
1517
from ... import ui_messages as ui
1618
from ...utils.json_io import read_json_object, write_json_document_atomically
1719
from .attrs import bool_attr
1820
from .types import PrinterLike
1921

2022
_VSCODE_EXTENSION_TIP_KEY = "vscode_extension"
23+
_DEAD_CODE_REACHABILITY_MIGRATION_TIP_KEY = (
24+
"dead_code_reachability_2_0_1_migration_shown"
25+
)
2126
_TIPS_SCHEMA_VERSION = 1
2227
_VSCODE_EXTENSION_URL = (
2328
"https://marketplace.visualstudio.com/items?itemName=orenlab.codeclone"
2429
)
30+
_DEAD_CODE_REACHABILITY_BASELINE_MIN = Version("2.0.0b1")
31+
_DEAD_CODE_REACHABILITY_BASELINE_MAX = Version("2.0.0")
32+
_DEAD_CODE_REACHABILITY_CURRENT_MIN = Version("2.0.1")
2533
_CI_ENV_KEYS: tuple[str, ...] = (
2634
"CI",
2735
"GITHUB_ACTIONS",
@@ -91,6 +99,16 @@ def _tip_last_shown_version(state: Mapping[str, object], *, tip_key: str) -> str
9199
return ""
92100

93101

102+
def _tip_was_shown(state: Mapping[str, object], *, tip_key: str) -> bool:
103+
tips = state.get("tips")
104+
if not isinstance(tips, dict):
105+
return False
106+
entry = tips.get(tip_key)
107+
if not isinstance(entry, dict):
108+
return False
109+
return entry.get("shown") is True
110+
111+
94112
def _remember_tip_version(
95113
*,
96114
path: Path,
@@ -113,6 +131,60 @@ def _remember_tip_version(
113131
)
114132

115133

134+
def _remember_tip_shown(
135+
*,
136+
path: Path,
137+
state: Mapping[str, object],
138+
tip_key: str,
139+
) -> None:
140+
tips = state.get("tips")
141+
updated_tips = dict(tips) if isinstance(tips, dict) else {}
142+
updated_tips[tip_key] = {"shown": True}
143+
write_json_document_atomically(
144+
path,
145+
{
146+
"schema_version": _TIPS_SCHEMA_VERSION,
147+
"tips": updated_tips,
148+
},
149+
sort_keys=True,
150+
indent=True,
151+
trailing_newline=True,
152+
)
153+
154+
155+
def _tip_context_allowed(
156+
*,
157+
args: object,
158+
environ: Mapping[str, str],
159+
stream: TextIO,
160+
) -> bool:
161+
if bool_attr(args, "quiet") or bool_attr(args, "ci"):
162+
return False
163+
if _is_ci_environment(environ):
164+
return False
165+
return _stream_is_tty(stream)
166+
167+
168+
def _dead_code_reachability_migration_applies(
169+
*,
170+
baseline_generator_version: str | None,
171+
codeclone_version: str,
172+
) -> bool:
173+
if not baseline_generator_version:
174+
return False
175+
try:
176+
baseline_version = Version(baseline_generator_version)
177+
current_version = Version(codeclone_version)
178+
except InvalidVersion:
179+
return False
180+
return (
181+
_DEAD_CODE_REACHABILITY_BASELINE_MIN
182+
<= baseline_version
183+
<= _DEAD_CODE_REACHABILITY_BASELINE_MAX
184+
and current_version >= _DEAD_CODE_REACHABILITY_CURRENT_MIN
185+
)
186+
187+
116188
def maybe_print_vscode_extension_tip(
117189
*,
118190
args: object,
@@ -124,11 +196,11 @@ def maybe_print_vscode_extension_tip(
124196
) -> bool:
125197
effective_environ = os.environ if environ is None else environ
126198
effective_stream = sys.stdout if stream is None else stream
127-
if bool_attr(args, "quiet") or bool_attr(args, "ci"):
128-
return False
129-
if _is_ci_environment(effective_environ):
130-
return False
131-
if not _stream_is_tty(effective_stream):
199+
if not _tip_context_allowed(
200+
args=args,
201+
environ=effective_environ,
202+
stream=effective_stream,
203+
):
132204
return False
133205
if not _is_vscode_environment(effective_environ):
134206
return False
@@ -154,6 +226,55 @@ def maybe_print_vscode_extension_tip(
154226
return True
155227

156228

229+
def maybe_print_dead_code_reachability_migration_note(
230+
*,
231+
args: object,
232+
console: PrinterLike,
233+
codeclone_version: str,
234+
cache_path: Path,
235+
baseline_generator_version: str | None,
236+
baseline_trusted_for_diff: bool,
237+
environ: Mapping[str, str] | None = None,
238+
stream: TextIO | None = None,
239+
) -> bool:
240+
if not baseline_trusted_for_diff:
241+
return False
242+
if not _dead_code_reachability_migration_applies(
243+
baseline_generator_version=baseline_generator_version,
244+
codeclone_version=codeclone_version,
245+
):
246+
return False
247+
248+
effective_environ = os.environ if environ is None else environ
249+
effective_stream = sys.stdout if stream is None else stream
250+
if not _tip_context_allowed(
251+
args=args,
252+
environ=effective_environ,
253+
stream=effective_stream,
254+
):
255+
return False
256+
257+
state_path = _tips_state_path(cache_path)
258+
state = _load_tips_state(state_path)
259+
if _tip_was_shown(
260+
state,
261+
tip_key=_DEAD_CODE_REACHABILITY_MIGRATION_TIP_KEY,
262+
):
263+
return False
264+
265+
console.print(ui.fmt_dead_code_reachability_migration_note())
266+
try:
267+
_remember_tip_shown(
268+
path=state_path,
269+
state=state,
270+
tip_key=_DEAD_CODE_REACHABILITY_MIGRATION_TIP_KEY,
271+
)
272+
except OSError:
273+
return True
274+
return True
275+
276+
157277
__all__ = [
278+
"maybe_print_dead_code_reachability_migration_note",
158279
"maybe_print_vscode_extension_tip",
159280
]

codeclone/surfaces/cli/workflow.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,9 @@
114114
resolve_changed_clone_gate = cli_post_run.resolve_changed_clone_gate
115115
warn_new_clones_without_fail = cli_post_run.warn_new_clones_without_fail
116116
maybe_print_vscode_extension_tip = cli_tips.maybe_print_vscode_extension_tip
117+
maybe_print_dead_code_reachability_migration_note = (
118+
cli_tips.maybe_print_dead_code_reachability_migration_note
119+
)
117120

118121
_report_path_origins = cli_reports_output._report_path_origins
119122
_resolve_output_paths = cli_reports_output._resolve_output_paths
@@ -522,6 +525,14 @@ def _main_impl() -> None:
522525
notice_new_clones_count=notice_new_clones_count,
523526
console=_console(),
524527
)
528+
maybe_print_dead_code_reachability_migration_note(
529+
args=args,
530+
console=_console(),
531+
codeclone_version=__version__,
532+
cache_path=cache_path,
533+
baseline_generator_version=baseline_state.baseline.generator_version,
534+
baseline_trusted_for_diff=baseline_state.trusted_for_diff,
535+
)
525536
maybe_print_vscode_extension_tip(
526537
args=args,
527538
console=_console(),

0 commit comments

Comments
 (0)