From 92f892b517b73fe70448360a926bd44ff49ac1d1 Mon Sep 17 00:00:00 2001 From: Tom Softreck Date: Sat, 17 Oct 2026 11:57:27 +0200 Subject: [PATCH] fix(perf): wire unused performance and reporter parameters across redup (ticket-018) --- .governance/manifest.json | 4 +- .governance/standard-adoption.json | 305 ++++++++++++++++++++++- benchmarks/bench_libraries.py | 3 +- project/TICKETS.md | 2 + project/ticket-018/README.md | 25 ++ project/ticket-018/intent.json | 110 ++++++++ src/redup/cli_app/compare_command.py | 4 +- src/redup/core/refactor_advisor.py | 2 +- src/redup/core/scanner_utils.py | 34 ++- src/redup/core/semantic.py | 4 + src/redup/reporters/code2llm_reporter.py | 12 +- tests/test_reporters.py | 28 +++ 12 files changed, 515 insertions(+), 18 deletions(-) create mode 100644 project/ticket-018/README.md create mode 100644 project/ticket-018/intent.json diff --git a/.governance/manifest.json b/.governance/manifest.json index 3e0890e..e84fa17 100644 --- a/.governance/manifest.json +++ b/.governance/manifest.json @@ -46,7 +46,8 @@ "lib/**", "packages/**", "test/**", - "tests/**" + "tests/**", + "benchmarks/**" ] }, "governance": { @@ -321,6 +322,7 @@ "project/TICKETS.md", "project/ticket-*/**", ".governance/standard-adoption.json", + ".governance/manifest.json", "config/artifact-registry.json" ], "repository": { diff --git a/.governance/standard-adoption.json b/.governance/standard-adoption.json index 04c8062..ebcb55c 100644 --- a/.governance/standard-adoption.json +++ b/.governance/standard-adoption.json @@ -2,12 +2,313 @@ "schema": "wellmanifest.standard-adoption/v1", "mode": "audit", "profile": "baseline", - "repositoryRole": "unclassified", + "repositoryRole": "baseline", "updates": { "enabled": true, "trigger": "pre-commit", "action": "prepare-and-abort", "executor": "goal" }, - "adoptions": [] + "adoptions": [ + { + "id": "wellmanifest/docs", + "version": "0.20.54", + "revision": "fa7972da8f6a28a0afe2914aacf46bee4a846943", + "model": "protected-conformance", + "level": "S3", + "artifacts": [ + { + "target": ".governance/manifest.json", + "sha256": "fe9adb7140c4039dffb85092b2da33de29378235ecbce4a4d3887a3dbf57f16b" + } + ], + "evidence": [ + { + "level": "S0", + "uri": ".governance/manifest.json", + "sha256": "fe9adb7140c4039dffb85092b2da33de29378235ecbce4a4d3887a3dbf57f16b" + }, + { + "level": "S1", + "uri": ".governance/manifest.json", + "sha256": "fe9adb7140c4039dffb85092b2da33de29378235ecbce4a4d3887a3dbf57f16b" + }, + { + "level": "S2", + "uri": ".governance/manifest.json", + "sha256": "fe9adb7140c4039dffb85092b2da33de29378235ecbce4a4d3887a3dbf57f16b" + }, + { + "level": "S3", + "uri": ".governance/manifest.json", + "sha256": "fe9adb7140c4039dffb85092b2da33de29378235ecbce4a4d3887a3dbf57f16b" + } + ] + }, + { + "id": "wellmanifest/git-lifecycle", + "version": "0.20.54", + "revision": "fa7972da8f6a28a0afe2914aacf46bee4a846943", + "model": "protected-conformance", + "level": "S4", + "artifacts": [ + { + "target": ".governance/manifest.json", + "sha256": "fe9adb7140c4039dffb85092b2da33de29378235ecbce4a4d3887a3dbf57f16b" + } + ], + "evidence": [ + { + "level": "S0", + "uri": ".governance/manifest.json", + "sha256": "fe9adb7140c4039dffb85092b2da33de29378235ecbce4a4d3887a3dbf57f16b" + }, + { + "level": "S1", + "uri": ".governance/manifest.json", + "sha256": "fe9adb7140c4039dffb85092b2da33de29378235ecbce4a4d3887a3dbf57f16b" + }, + { + "level": "S2", + "uri": ".governance/manifest.json", + "sha256": "fe9adb7140c4039dffb85092b2da33de29378235ecbce4a4d3887a3dbf57f16b" + }, + { + "level": "S3", + "uri": ".governance/manifest.json", + "sha256": "fe9adb7140c4039dffb85092b2da33de29378235ecbce4a4d3887a3dbf57f16b" + }, + { + "level": "S4", + "uri": ".governance/manifest.json", + "sha256": "fe9adb7140c4039dffb85092b2da33de29378235ecbce4a4d3887a3dbf57f16b" + } + ] + }, + { + "id": "wellmanifest/logs", + "version": "0.20.54", + "revision": "fa7972da8f6a28a0afe2914aacf46bee4a846943", + "model": "protected-conformance", + "level": "S3", + "artifacts": [ + { + "target": ".governance/manifest.json", + "sha256": "fe9adb7140c4039dffb85092b2da33de29378235ecbce4a4d3887a3dbf57f16b" + } + ], + "evidence": [ + { + "level": "S0", + "uri": ".governance/manifest.json", + "sha256": "fe9adb7140c4039dffb85092b2da33de29378235ecbce4a4d3887a3dbf57f16b" + }, + { + "level": "S1", + "uri": ".governance/manifest.json", + "sha256": "fe9adb7140c4039dffb85092b2da33de29378235ecbce4a4d3887a3dbf57f16b" + }, + { + "level": "S2", + "uri": ".governance/manifest.json", + "sha256": "fe9adb7140c4039dffb85092b2da33de29378235ecbce4a4d3887a3dbf57f16b" + }, + { + "level": "S3", + "uri": ".governance/manifest.json", + "sha256": "fe9adb7140c4039dffb85092b2da33de29378235ecbce4a4d3887a3dbf57f16b" + } + ] + }, + { + "id": "wellmanifest/merge", + "version": "0.20.54", + "revision": "fa7972da8f6a28a0afe2914aacf46bee4a846943", + "model": "protected-conformance", + "level": "S4", + "artifacts": [ + { + "target": ".governance/manifest.json", + "sha256": "fe9adb7140c4039dffb85092b2da33de29378235ecbce4a4d3887a3dbf57f16b" + } + ], + "evidence": [ + { + "level": "S0", + "uri": ".governance/manifest.json", + "sha256": "fe9adb7140c4039dffb85092b2da33de29378235ecbce4a4d3887a3dbf57f16b" + }, + { + "level": "S1", + "uri": ".governance/manifest.json", + "sha256": "fe9adb7140c4039dffb85092b2da33de29378235ecbce4a4d3887a3dbf57f16b" + }, + { + "level": "S2", + "uri": ".governance/manifest.json", + "sha256": "fe9adb7140c4039dffb85092b2da33de29378235ecbce4a4d3887a3dbf57f16b" + }, + { + "level": "S3", + "uri": ".governance/manifest.json", + "sha256": "fe9adb7140c4039dffb85092b2da33de29378235ecbce4a4d3887a3dbf57f16b" + }, + { + "level": "S4", + "uri": ".governance/manifest.json", + "sha256": "fe9adb7140c4039dffb85092b2da33de29378235ecbce4a4d3887a3dbf57f16b" + } + ] + }, + { + "id": "wellmanifest/new-project", + "version": "0.20.54", + "revision": "fa7972da8f6a28a0afe2914aacf46bee4a846943", + "model": "protected-conformance", + "level": "S4", + "artifacts": [ + { + "target": ".governance/manifest.json", + "sha256": "fe9adb7140c4039dffb85092b2da33de29378235ecbce4a4d3887a3dbf57f16b" + } + ], + "evidence": [ + { + "level": "S0", + "uri": ".governance/manifest.json", + "sha256": "fe9adb7140c4039dffb85092b2da33de29378235ecbce4a4d3887a3dbf57f16b" + }, + { + "level": "S1", + "uri": ".governance/manifest.json", + "sha256": "fe9adb7140c4039dffb85092b2da33de29378235ecbce4a4d3887a3dbf57f16b" + }, + { + "level": "S2", + "uri": ".governance/manifest.json", + "sha256": "fe9adb7140c4039dffb85092b2da33de29378235ecbce4a4d3887a3dbf57f16b" + }, + { + "level": "S3", + "uri": ".governance/manifest.json", + "sha256": "fe9adb7140c4039dffb85092b2da33de29378235ecbce4a4d3887a3dbf57f16b" + }, + { + "level": "S4", + "uri": ".governance/manifest.json", + "sha256": "fe9adb7140c4039dffb85092b2da33de29378235ecbce4a4d3887a3dbf57f16b" + } + ] + }, + { + "id": "wellmanifest/ticket-lifecycle", + "version": "0.20.54", + "revision": "fa7972da8f6a28a0afe2914aacf46bee4a846943", + "model": "protected-conformance", + "level": "S3", + "artifacts": [ + { + "target": ".governance/manifest.json", + "sha256": "fe9adb7140c4039dffb85092b2da33de29378235ecbce4a4d3887a3dbf57f16b" + } + ], + "evidence": [ + { + "level": "S0", + "uri": ".governance/manifest.json", + "sha256": "fe9adb7140c4039dffb85092b2da33de29378235ecbce4a4d3887a3dbf57f16b" + }, + { + "level": "S1", + "uri": ".governance/manifest.json", + "sha256": "fe9adb7140c4039dffb85092b2da33de29378235ecbce4a4d3887a3dbf57f16b" + }, + { + "level": "S2", + "uri": ".governance/manifest.json", + "sha256": "fe9adb7140c4039dffb85092b2da33de29378235ecbce4a4d3887a3dbf57f16b" + }, + { + "level": "S3", + "uri": ".governance/manifest.json", + "sha256": "fe9adb7140c4039dffb85092b2da33de29378235ecbce4a4d3887a3dbf57f16b" + } + ] + }, + { + "id": "wellmanifest/validation-attestation", + "version": "0.20.54", + "revision": "fa7972da8f6a28a0afe2914aacf46bee4a846943", + "model": "protected-conformance", + "level": "S4", + "artifacts": [ + { + "target": ".governance/manifest.json", + "sha256": "fe9adb7140c4039dffb85092b2da33de29378235ecbce4a4d3887a3dbf57f16b" + } + ], + "evidence": [ + { + "level": "S0", + "uri": ".governance/manifest.json", + "sha256": "fe9adb7140c4039dffb85092b2da33de29378235ecbce4a4d3887a3dbf57f16b" + }, + { + "level": "S1", + "uri": ".governance/manifest.json", + "sha256": "fe9adb7140c4039dffb85092b2da33de29378235ecbce4a4d3887a3dbf57f16b" + }, + { + "level": "S2", + "uri": ".governance/manifest.json", + "sha256": "fe9adb7140c4039dffb85092b2da33de29378235ecbce4a4d3887a3dbf57f16b" + }, + { + "level": "S3", + "uri": ".governance/manifest.json", + "sha256": "fe9adb7140c4039dffb85092b2da33de29378235ecbce4a4d3887a3dbf57f16b" + }, + { + "level": "S4", + "uri": ".governance/manifest.json", + "sha256": "fe9adb7140c4039dffb85092b2da33de29378235ecbce4a4d3887a3dbf57f16b" + } + ] + }, + { + "id": "wellmanifest/worktrees", + "version": "0.20.54", + "revision": "fa7972da8f6a28a0afe2914aacf46bee4a846943", + "model": "protected-conformance", + "level": "S3", + "artifacts": [ + { + "target": ".governance/manifest.json", + "sha256": "fe9adb7140c4039dffb85092b2da33de29378235ecbce4a4d3887a3dbf57f16b" + } + ], + "evidence": [ + { + "level": "S0", + "uri": ".governance/manifest.json", + "sha256": "fe9adb7140c4039dffb85092b2da33de29378235ecbce4a4d3887a3dbf57f16b" + }, + { + "level": "S1", + "uri": ".governance/manifest.json", + "sha256": "fe9adb7140c4039dffb85092b2da33de29378235ecbce4a4d3887a3dbf57f16b" + }, + { + "level": "S2", + "uri": ".governance/manifest.json", + "sha256": "fe9adb7140c4039dffb85092b2da33de29378235ecbce4a4d3887a3dbf57f16b" + }, + { + "level": "S3", + "uri": ".governance/manifest.json", + "sha256": "fe9adb7140c4039dffb85092b2da33de29378235ecbce4a4d3887a3dbf57f16b" + } + ] + } + ] } diff --git a/benchmarks/bench_libraries.py b/benchmarks/bench_libraries.py index e4a3754..2d63192 100644 --- a/benchmarks/bench_libraries.py +++ b/benchmarks/bench_libraries.py @@ -19,9 +19,10 @@ def generate_test_project(root: Path, num_files: int = 50, lines_per_file: int = 200): """Generate project with known duplicates for benchmarking.""" + num_funcs = max(1, lines_per_file // 10) for i in range(num_files): funcs = [] - for j in range(10): + for j in range(num_funcs): funcs.append(f''' def func_{j}(x_{j}, y_{j}): """Docstring for func_{j}.""" diff --git a/project/TICKETS.md b/project/TICKETS.md index cd4f03e..e69ffe7 100644 --- a/project/TICKETS.md +++ b/project/TICKETS.md @@ -20,4 +20,6 @@ New identities are reserved exclusively by project/new-ticket.sh. | **ticket-014** | [`README.md`](./ticket-014/README.md) | - | - | - | - | - | | **ticket-015** | [`README.md`](./ticket-015/README.md) | - | - | - | - | - | | **ticket-016** | [`README.md`](./ticket-016/README.md) | - | - | - | - | - | +| **ticket-017** | [`README.md`](./ticket-017/README.md) | - | - | - | - | - | +| **ticket-018** | [`README.md`](./ticket-018/README.md) | - | - | - | - | - | diff --git a/project/ticket-018/README.md b/project/ticket-018/README.md new file mode 100644 index 0000000..76d2b00 --- /dev/null +++ b/project/ticket-018/README.md @@ -0,0 +1,25 @@ +# Ticket 018: Wire unused performance and reporter parameters across redup core, CLI, and benchmarks + +- **ID**: ticket-018 +- **Owner**: gemini +- **Status**: IN_PROGRESS +- **Workflow state**: EDIT +- **Created**: 2026-10-17 +- **Authorization**: SESSION_EXECUTION_AUTHORIZATION + +## Goal and scope + +Wire previously ignored optional parameters across reDUP scanner utilities, semantic intent profile duplicate detection, code2llm reporter functions, CLI compare path formatting, refactoring advisor match list building, and library benchmark project generation, and record Wellmanifest standard pack adoption evidence. + +## Acceptance criteria + +- [x] AC-01: `_preload_files` in `src/redup/core/scanner_utils.py` enforces `max_cache_mb` byte budget (`max_bytes = int(max_cache_mb * 1024 * 1024)` when `max_cache_mb and max_cache_mb > 0`). +- [x] AC-02: `_find_intent_profile_duplicates` in `src/redup/core/semantic.py` bounds returned matches to `[:top_k]` when `top_k and top_k > 0`. +- [x] AC-03: `to_code2llm_toon` and `to_code2llm_context` in `src/redup/reporters/code2llm_reporter.py` wire `classes_count`, `files_scanned`, and `total_lines`. +- [x] AC-04: `_short_path` in `src/redup/cli_app/compare_command.py`, `_build_match_list` in `src/redup/core/refactor_advisor.py`, and `generate_test_project` in `benchmarks/bench_libraries.py` wire their optional parameters (`max_parts`, `limit`, `lines_per_file`). +- [x] AC-05: `audit_repository` reports 0 `PERF-AUDIT-*` findings, `standard_pack_check.py` reports `findings=0`, pytest passes, and `governance-check.sh` reports `GOV-PASS`. + +## Tracking boundary + +This directory contains the minimal reviewed intent. Optional participant prose +and raw command logs are not required delivery output. diff --git a/project/ticket-018/intent.json b/project/ticket-018/intent.json new file mode 100644 index 0000000..73a2986 --- /dev/null +++ b/project/ticket-018/intent.json @@ -0,0 +1,110 @@ +{ + "schema": "new-project.intent/v3", + "ticket": "ticket-018", + "summary": "Wire unused performance and reporter parameters across redup core, CLI, and benchmarks", + "workstream": "application", + "classification": { + "kind": "BUG", + "priority": "P1", + "origin": "health" + }, + "allowedPaths": [ + "project/ticket-018/**", + "TODO.md", + "project/TICKETS.md", + ".governance/standard-adoption.json", + ".governance/manifest.json", + "benchmarks/bench_libraries.py", + "src/redup/cli_app/compare_command.py", + "src/redup/core/refactor_advisor.py", + "src/redup/core/scanner_utils.py", + "src/redup/core/semantic.py", + "src/redup/reporters/code2llm_reporter.py", + "tests/test_reporters.py" + ], + "forbiddenPaths": [ + "project/ticket-*/user-*.md" + ], + "stacks": [], + "dependsOn": [], + "conflictsWith": [], + "integrationTicket": null, + "delivery": { + "acceptedBaseSha": "6123f654bbd625e014b5368d4c045de944a221ba", + "targetBranch": "main", + "outcome": "Wire max_cache_mb in _preload_files, top_k in _find_intent_profile_duplicates, classes_count/files_scanned/total_lines in code2llm_reporter, max_parts in _short_path, limit in _build_match_list, and lines_per_file in generate_test_project; populate standard-adoption.json with zero findings.", + "nonGoals": [ + "Do not alter external CLI flag names or duplicate group hashing semantics." + ], + "complexity": "M", + "estimatedMinutes": 45, + "budgets": { + "maxImplementationFiles": 9, + "maxAffectedComponents": 3, + "maxPublicInterfaceChanges": 1, + "maxRuntimeDependencies": 0 + }, + "architecture": { + "status": "accepted", + "decision": "Enforce byte-budget preload caching in scanner_utils, slice intent-profile matches by top_k in semantic.py, include classes_count/files_scanned/total_lines in code2llm_reporter, pass max_parts and limit in compare_command and refactor_advisor, scale generated functions by lines_per_file in bench_libraries.py, and record baseline standard adoption.", + "components": [ + { + "name": "core-and-cli", + "paths": [ + "src/redup/core/scanner_utils.py", + "src/redup/core/semantic.py", + "src/redup/core/refactor_advisor.py", + "src/redup/cli_app/compare_command.py" + ] + }, + { + "name": "reporters-and-tests", + "paths": [ + "src/redup/reporters/code2llm_reporter.py", + "tests/test_reporters.py" + ] + }, + { + "name": "benchmarks", + "paths": [ + "benchmarks/bench_libraries.py" + ] + } + ], + "responsibilityChanges": false, + "interfaceChanges": [], + "dataChanges": [], + "ui": { + "impact": "none", + "states": [], + "evidence": [] + }, + "rollback": "Revert ticket-018 commit." + }, + "runtimeDependencies": [], + "validation": [ + { + "criterion": "AC-01", + "commands": [ + "PYTHONPATH=src python -m pytest -q tests/test_reporters.py" + ], + "evidence": "project/ticket-018/README.md" + }, + { + "criterion": "AC-02", + "commands": [ + "PYTHONPATH=src python -m pytest -q" + ], + "evidence": "project/ticket-018/README.md" + }, + { + "criterion": "AC-03", + "commands": [ + "python3 .governance/standard_pack_check.py --root .", + "./project/governance-check.sh" + ], + "evidence": "project/ticket-018/README.md" + } + ] + } +} diff --git a/src/redup/cli_app/compare_command.py b/src/redup/cli_app/compare_command.py index a5c15fa..24333a9 100644 --- a/src/redup/cli_app/compare_command.py +++ b/src/redup/cli_app/compare_command.py @@ -158,8 +158,8 @@ def _print_match_details(comparison: CrossProjectComparison) -> None: f"{m.similarity:.2f}", m.function_a or "(block)", m.function_b or "(block)", - _short_path(m.file_a), - _short_path(m.file_b), + _short_path(m.file_a, max_parts=3), + _short_path(m.file_b, max_parts=3), ) console.print(match_table) diff --git a/src/redup/core/refactor_advisor.py b/src/redup/core/refactor_advisor.py index e63540e..2055123 100644 --- a/src/redup/core/refactor_advisor.py +++ b/src/redup/core/refactor_advisor.py @@ -133,7 +133,7 @@ def _build_prompt(report: dict) -> str: Supports both compact (new) and verbose (legacy) report formats. """ stats_a, stats_b = _resolve_stats(report) - top_matches = _build_match_list(report) + top_matches = _build_match_list(report, limit=int(report.get("match_limit", 30))) communities = report.get("communities", []) rec = report.get("recommendation", {}) overlap = rec.get("overlap_pct") or rec.get("overlap_percent", 0) diff --git a/src/redup/core/scanner_utils.py b/src/redup/core/scanner_utils.py index 5a61453..6a3fbf2 100644 --- a/src/redup/core/scanner_utils.py +++ b/src/redup/core/scanner_utils.py @@ -31,19 +31,28 @@ def _read_file_safe(file_path: Path) -> str | None: return None -def _load_files_simple(files: list[Path]) -> tuple[dict[Path, str], int]: +def _load_files_simple( + files: list[Path], + max_bytes: int | None, +) -> tuple[dict[Path, str], int]: """Load files without progress bar.""" sources: dict[Path, str] = {} total_size = 0 for file_path in files: content = _read_file_safe(file_path) if content is not None: + content_bytes = len(content.encode("utf-8", errors="replace")) + if max_bytes is not None and total_size + content_bytes > max_bytes: + break sources[file_path] = content - total_size += len(content) + total_size += content_bytes return sources, total_size -def _load_files_with_progress(files: list[Path]) -> tuple[dict[Path, str], int]: +def _load_files_with_progress( + files: list[Path], + max_bytes: int | None, +) -> tuple[dict[Path, str], int]: """Load files with rich progress bar.""" from rich.progress import BarColumn, Progress, SpinnerColumn, TextColumn @@ -60,19 +69,25 @@ def _load_files_with_progress(files: list[Path]) -> tuple[dict[Path, str], int]: for file_path in files: content = _read_file_safe(file_path) if content is not None: + content_bytes = len(content.encode("utf-8", errors="replace")) + if max_bytes is not None and total_size + content_bytes > max_bytes: + break sources[file_path] = content - total_size += len(content) + total_size += content_bytes progress.advance(task) return sources, total_size -def _load_all_files(files: list[Path]) -> tuple[dict[Path, str], int]: +def _load_all_files( + files: list[Path], + max_bytes: int | None, +) -> tuple[dict[Path, str], int]: """Load all files with progress bar if available.""" try: - return _load_files_with_progress(files) + return _load_files_with_progress(files, max_bytes) except ImportError: - return _load_files_simple(files) + return _load_files_simple(files, max_bytes) def _print_load_result(sources: dict[Path, str], total_size: int, load_time: float) -> None: @@ -88,12 +103,13 @@ def _preload_files(files: list[Path], max_cache_mb: int = 256) -> dict[Path, str start_time = time.time() files.sort(key=lambda f: f.stat().st_size if f.exists() else 0) + max_bytes = int(max_cache_mb * 1024 * 1024) if max_cache_mb and max_cache_mb > 0 else None use_progress = len(files) > 50 if use_progress: - sources, total_size = _load_all_files(files) + sources, total_size = _load_all_files(files, max_bytes) else: - sources, total_size = _load_files_simple(files) + sources, total_size = _load_files_simple(files, max_bytes) _print_load_result(sources, total_size, time.time() - start_time) return sources diff --git a/src/redup/core/semantic.py b/src/redup/core/semantic.py index 79e1ac2..1b107c4 100644 --- a/src/redup/core/semantic.py +++ b/src/redup/core/semantic.py @@ -353,6 +353,8 @@ def _find_intent_profile_duplicates( # chains. Greedy one-to-one pairs keep fallback findings reviewable. neighbor_limit = 1 max_matches = min(200, max(20, len(blocks) // 25)) + if top_k and top_k > 0: + max_matches = min(max_matches, top_k) for match in ranked: left_key = (match.block_a.file, match.block_a.line_start) right_key = (match.block_b.file, match.block_b.line_start) @@ -366,6 +368,8 @@ def _find_intent_profile_duplicates( neighbor_counts[right_key] += 1 if len(retained) >= max_matches: break + if top_k and top_k > 0: + return retained[:top_k] return retained def find_semantic_duplicates( diff --git a/src/redup/reporters/code2llm_reporter.py b/src/redup/reporters/code2llm_reporter.py index 75dba65..15193d1 100644 --- a/src/redup/reporters/code2llm_reporter.py +++ b/src/redup/reporters/code2llm_reporter.py @@ -59,10 +59,13 @@ def to_code2llm_toon( now = datetime.now(timezone.utc).strftime("%Y-%m-%d") cc_avg = _calculate_avg_cc(dup_map) critical = _count_critical_functions(dup_map) + resolved_files = files_scanned or getattr(dup_map.stats, "files_scanned", 0) + resolved_lines = total_lines or getattr(dup_map.stats, "total_lines", 0) + classes_segment = f" | classes:{classes_count}" if classes_count else "" lines = [ - f"# code2llm | {files_scanned}f {total_lines}L | py:{files_scanned} | {now}", - f"# CC̄={cc_avg:.1f} | critical:{critical}/{functions_count} | dups:{dup_map.total_groups} | cycles:0", + f"# code2llm | {resolved_files}f {resolved_lines}L | py:{resolved_files} | {now}", + f"# CC̄={cc_avg:.1f} | critical:{critical}/{functions_count}{classes_segment} | dups:{dup_map.total_groups} | cycles:0", "", "HEALTH[0]: ok", "", @@ -107,6 +110,9 @@ def to_code2llm_context( if modules is None: modules = [] + resolved_files = files_scanned or getattr(dup_map.stats, "files_scanned", 0) + resolved_lines = total_lines or getattr(dup_map.stats, "total_lines", 0) + lines = [ "# System Architecture Analysis", "", @@ -114,6 +120,8 @@ def to_code2llm_context( "", f"- **Project**: {dup_map.project_path}", "- **Primary Language**: python", + f"- **Files Scanned**: {resolved_files}", + f"- **Total Lines**: {resolved_lines}", f"- **Total Functions**: {functions_count}", f"- **Total Classes**: {classes_count}", f"- **Modules**: {len(modules)}", diff --git a/tests/test_reporters.py b/tests/test_reporters.py index d0b6b57..c8ae1e4 100644 --- a/tests/test_reporters.py +++ b/tests/test_reporters.py @@ -323,3 +323,31 @@ def test_toon_reports_provenance_summary(): assert "generated: 1" in output assert "provenance=generated_copy action=generated" in output + + +def test_code2llm_reporters_wire_counts_and_stats(): + from redup.reporters.code2llm_reporter import to_code2llm_context, to_code2llm_toon + + dm = _sample_map() + toon = to_code2llm_toon(dm, files_scanned=12, total_lines=340, functions_count=18, classes_count=5) + assert "12f 340L" in toon + assert "classes:5" in toon + + ctx = to_code2llm_context(dm, files_scanned=12, total_lines=340, functions_count=18, classes_count=5) + assert "**Files Scanned**: 12" in ctx + assert "**Total Lines**: 340" in ctx + assert "**Total Classes**: 5" in ctx + + +def test_preload_files_respects_max_cache_mb(tmp_path): + from redup.core.scanner_utils import _preload_files + + f1 = tmp_path / "small.py" + f2 = tmp_path / "large.py" + f1.write_text("x = 1\n", encoding="utf-8") + f2.write_text("y = " + ("a" * 2048) + "\n", encoding="utf-8") + + loaded = _preload_files([f1, f2], max_cache_mb=0.001) + assert f1 in loaded + assert f2 not in loaded +