Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
18a96b6
feat: complete spec 2.0.0 architecture and UX updates
orenlab Mar 7, 2026
40b73f4
feat(cli iu): improvements to the cli UI, adding pre-commit hooks, up…
orenlab Mar 8, 2026
e151ede
chore(docs): update logo size
orenlab Mar 8, 2026
20cbaad
feat(core): UI improvements, updating report schemas; adding new form…
orenlab Mar 11, 2026
ad5067f
feat(core): Comprehensive benchmark for codeclone has been added, the…
orenlab Mar 11, 2026
94daa3c
fix(tests): stabilize unreadable-source CLI assertion across runners …
orenlab Mar 11, 2026
623f6d0
fix(ci): isolate unreadable-source CLI tests with per-test cache path…
orenlab Mar 11, 2026
c6ecb5f
fix(tests): deflake unreadable-source CLI check by asserting JSON sou…
orenlab Mar 11, 2026
7f7eeb7
chore(docs): update README.md and update package deps
orenlab Mar 12, 2026
4772c73
fix(perf): harden scanner root filtering and optimize report snippet/…
orenlab Mar 14, 2026
6b6ed54
docs(changelog): add 1.4.4 backport section under 2.0.0b1
orenlab Mar 14, 2026
93d4419
feat(core): ship clone_guard_exit_divergence + clone_cohort_drift, op…
orenlab Mar 15, 2026
3d8e372
fix(detect): treat module-level PEP 562 hooks (__getattr__/__dir__) a…
orenlab Mar 17, 2026
c585462
feat(detect): add declaration-scoped # noqa: codeclone[dead-code] sup…
orenlab Mar 17, 2026
986fe5f
fix(report): propagate dead-code noqa suppressions end-to-end (detect…
orenlab Mar 17, 2026
97a3a22
chore(docs): update docs
orenlab Mar 18, 2026
11f84f1
refactor(domain): centralize report/pipeline taxonomies and unify coe…
orenlab Mar 18, 2026
3117533
refactor(domain): centralize finding taxonomies across sarif/structur…
orenlab Mar 18, 2026
11e1730
refactor(report): modularize HTML rendering, finalize codeclone suppr…
orenlab Mar 22, 2026
2fa06fd
fix(suppressions): bind multiline inline ignores to decorated declara…
orenlab Mar 22, 2026
50901f5
fix(suppressions): bind multiline inline ignores to decorated declara…
orenlab Mar 22, 2026
633c890
refactor(html): refresh report layouts, findings cards, and metric de…
orenlab Mar 22, 2026
9a287be
feat(cli): add browser-open HTML reports and timestamped default repo…
orenlab Mar 22, 2026
df0b00d
test(cli): make HTML open warning assertion robust across wrapped con…
orenlab Mar 22, 2026
26538bd
refactor(report): materialize overview insights, streamline cache/ext…
orenlab Mar 23, 2026
3be4937
feat(core): improve report UX, strengthen cache/report contracts, and…
orenlab Mar 25, 2026
f7ebd78
chore(docs): update AGENTS.md
orenlab Mar 25, 2026
1aab58c
perf(core): add lazy suppression/report fast-paths and streamline ext…
orenlab Mar 25, 2026
4332374
chore(release): finalize beta packaging metadata, PyPI README links, …
orenlab Mar 25, 2026
091f9ad
Merge origin/main into feat/2.0.0
orenlab Mar 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
.git
.cache
.venv
.pytest_cache
.mypy_cache
.ruff_cache
.idea
__pycache__/
*.pyc
*.pyo
*.pyd
.coverage
build/
dist/
*.egg-info/
.uv-cache
docs
codeclone.egg-info
.pre-commit-config.yaml
uv.lock
4 changes: 2 additions & 2 deletions .github/actions/codeclone/action.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: CodeClone
description: >
AST-based Python code clone detector focused on architectural duplication
and CI-friendly baseline enforcement.
Structural code quality analysis for Python with
CI-friendly baseline enforcement.

author: OrenLab

Expand Down
227 changes: 227 additions & 0 deletions .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
name: benchmark
run-name: benchmark • ${{ github.event_name }} • ${{ github.ref_name }}

on:
push:
branches: [ "feat/2.0.0" ]
pull_request:
branches: [ "feat/2.0.0" ]
workflow_dispatch:
inputs:
profile:
description: Benchmark profile
required: true
default: smoke
type: choice
options:
- smoke
- extended

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
benchmark:
name: >-
bench • ${{ matrix.label }}
runs-on: ${{ matrix.os }}
timeout-minutes: ${{ matrix.timeout_minutes }}

strategy:
fail-fast: false
matrix:
include:
# default profile for push / PR
- profile: smoke
label: linux-smoke
os: ubuntu-latest
runs: 12
warmups: 3
cpus: "1.0"
memory: "2g"
timeout_minutes: 45

# extended profile for manual runs
- profile: extended
label: linux-extended
os: ubuntu-latest
runs: 16
warmups: 4
cpus: "1.0"
memory: "2g"
timeout_minutes: 50

- profile: extended
label: macos-extended
os: macos-latest
runs: 12
warmups: 3
cpus: ""
memory: ""
timeout_minutes: 60

steps:
- name: Resolve run profile gate
shell: bash
run: |
enabled=0
if [ "${{ github.event_name }}" != "workflow_dispatch" ]; then
if [ "${{ matrix.profile }}" = "smoke" ]; then
enabled=1
fi
else
if [ "${{ matrix.profile }}" = "${{ inputs.profile }}" ]; then
enabled=1
fi
fi
echo "BENCH_ENABLED=$enabled" >> "$GITHUB_ENV"

- name: Checkout
if: env.BENCH_ENABLED == '1'
uses: actions/checkout@v6.0.2

- name: Set up Python (macOS local benchmark)
if: env.BENCH_ENABLED == '1' && runner.os == 'macOS'
uses: actions/setup-python@v6.2.0
with:
python-version: "3.13"
allow-prereleases: true

- name: Set up uv (macOS local benchmark)
if: env.BENCH_ENABLED == '1' && runner.os == 'macOS'
uses: astral-sh/setup-uv@v5
with:
enable-cache: true

- name: Install dependencies (macOS local benchmark)
if: env.BENCH_ENABLED == '1' && runner.os == 'macOS'
run: uv sync --all-extras --dev

- name: Set benchmark output path
if: env.BENCH_ENABLED == '1'
shell: bash
run: |
mkdir -p .cache/benchmarks
echo "BENCH_JSON=.cache/benchmarks/codeclone-benchmark-${{ matrix.label }}.json" >> "$GITHUB_ENV"

- name: Build and run Docker benchmark (Linux)
if: env.BENCH_ENABLED == '1' && runner.os == 'Linux'
env:
RUNS: ${{ matrix.runs }}
WARMUPS: ${{ matrix.warmups }}
CPUS: ${{ matrix.cpus }}
MEMORY: ${{ matrix.memory }}
run: |
./benchmarks/run_docker_benchmark.sh
cp .cache/benchmarks/codeclone-benchmark.json "$BENCH_JSON"

- name: Run local benchmark (macOS)
if: env.BENCH_ENABLED == '1' && runner.os == 'macOS'
run: |
uv run python benchmarks/run_benchmark.py \
--target . \
--runs "${{ matrix.runs }}" \
--warmups "${{ matrix.warmups }}" \
--tmp-dir "/tmp/codeclone-bench-${{ matrix.label }}" \
--output "$BENCH_JSON"

- name: Print benchmark summary
if: env.BENCH_ENABLED == '1'
shell: bash
run: |
python - <<'PY'
import json
import os
from pathlib import Path

report_path = Path(os.environ["BENCH_JSON"])
if not report_path.exists():
print(f"benchmark report not found: {report_path}")
raise SystemExit(1)

payload = json.loads(report_path.read_text(encoding="utf-8"))
scenarios = payload.get("scenarios", [])
comparisons = payload.get("comparisons", {})

print("CodeClone benchmark summary")
print(f"label={os.environ.get('RUNNER_OS','unknown').lower()} / {os.environ.get('GITHUB_JOB','benchmark')}")
for scenario in scenarios:
name = str(scenario.get("name", "unknown"))
stats = scenario.get("stats_seconds", {})
median = float(stats.get("median", 0.0))
p95 = float(stats.get("p95", 0.0))
stdev = float(stats.get("stdev", 0.0))
digest = str(scenario.get("digest", ""))
print(
f"- {name:16s} median={median:.4f}s "
f"p95={p95:.4f}s stdev={stdev:.4f}s digest={digest}"
)

if comparisons:
print("ratios:")
for key, value in sorted(comparisons.items()):
print(f"- {key}={float(value):.3f}x")

summary_file = os.environ.get("GITHUB_STEP_SUMMARY")
if not summary_file:
raise SystemExit(0)

lines = [
f"## CodeClone benchmark — {os.environ.get('RUNNER_OS', 'unknown')} / ${{ matrix.label }}",
"",
f"- Tool: `{payload['tool']['name']} {payload['tool']['version']}`",
f"- Target: `{payload['config']['target']}`",
f"- Runs: `{payload['config']['runs']}`",
f"- Warmups: `{payload['config']['warmups']}`",
f"- Generated: `{payload['generated_at_utc']}`",
"",
"### Scenarios",
"",
"| Scenario | Median (s) | p95 (s) | Stdev (s) | Deterministic | Digest |",
"|---|---:|---:|---:|:---:|---|",
]

for scenario in scenarios:
stats = scenario.get("stats_seconds", {})
lines.append(
"| "
f"{scenario.get('name', '')} | "
f"{float(stats.get('median', 0.0)):.4f} | "
f"{float(stats.get('p95', 0.0)):.4f} | "
f"{float(stats.get('stdev', 0.0)):.4f} | "
f"{'yes' if bool(scenario.get('deterministic')) else 'no'} | "
f"{scenario.get('digest', '')} |"
)

if comparisons:
lines.extend(
[
"",
"### Ratios",
"",
"| Metric | Value |",
"|---|---:|",
]
)
for key, value in sorted(comparisons.items()):
lines.append(f"| {key} | {float(value):.3f}x |")

with Path(summary_file).open("a", encoding="utf-8") as fh:
fh.write("\n".join(lines) + "\n")
PY

- name: Skip non-selected profile
if: env.BENCH_ENABLED != '1'
run: echo "Skipping matrix profile '${{ matrix.profile }}' for event '${{ github.event_name }}'"

- name: Upload benchmark artifact
if: env.BENCH_ENABLED == '1'
uses: actions/upload-artifact@v4
with:
name: codeclone-benchmark-${{ matrix.label }}
path: ${{ env.BENCH_JSON }}
if-no-files-found: error
75 changes: 75 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
name: docs
run-name: docs • ${{ github.event_name }} • ${{ github.ref_name }}

on:
push:
branches: [ "main" ]
pull_request:
workflow_dispatch:

permissions:
contents: read

concurrency:
group: docs-${{ github.ref }}
cancel-in-progress: true

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6.0.2

- name: Set up Python
uses: actions/setup-python@v6.2.0
with:
python-version: "3.13"
allow-prereleases: true

- name: Set up uv
uses: astral-sh/setup-uv@v5
with:
enable-cache: true

- name: Install project dependencies
run: uv sync --dev

- name: Configure GitHub Pages
uses: actions/configure-pages@v5

- name: Build docs site
run: uv run --with mkdocs --with mkdocs-material mkdocs build --strict

- name: Generate sample report artifacts
run: uv run python scripts/build_docs_example_report.py --output-dir site/examples/report/live

- name: Upload docs artifact
if: ${{ github.event_name != 'push' || github.ref != 'refs/heads/main' }}
uses: actions/upload-artifact@v7
with:
name: codeclone-docs-site
path: site
if-no-files-found: error

- name: Upload GitHub Pages artifact
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
uses: actions/upload-pages-artifact@v4
with:
path: site

deploy:
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
runs-on: ubuntu-latest
needs: build
permissions:
contents: read
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
7 changes: 5 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ __pycache__/
.coverage
.coverage.*
htmlcov/
site/

# Tool caches
.cache/
Expand All @@ -33,5 +34,7 @@ htmlcov/

# Logs
*.log

.claude
/.claude/
/docs/SPEC-2.0.0.md
/.uv-cache/
/package-lock.json
Loading
Loading