Skip to content

Commit 01745d7

Browse files
committed
feat: truncation banner + total_discovered; robust cross-OS Setup (pipx/venv)
1 parent 9152838 commit 01745d7

15 files changed

Lines changed: 116 additions & 29 deletions

documentation.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,8 @@ Command:
448448
ecocode profile-repo --root . --ext .py --runs 3 --json
449449
```
450450

451+
Ignored directories (`node_modules`, `.git`, `.venv`, `dist`, `build`, `target`, …) are pruned during discovery, so scans stay fast even in large repositories. Discovery is capped by `--max-files` (default 50 on the CLI, 200 in the VS Code extension). When more matching files exist than the cap, the JSON reports `total_discovered` (all matching files) alongside `total_files` (the scanned subset), and the human output prints a "scanned X of Y files" note — totals then cover the scanned subset only. Raise `--max-files` (or `ecocode.maxFiles`) to scan more.
452+
451453
Example JSON excerpt:
452454

453455
```json

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "ecocode-cli"
7-
version = "0.2.1"
7+
version = "0.2.2"
88
description = "EcoCode — energy and performance profiler for source code (CLI core for the EcoCode VS Code extension)"
99
readme = "README.md"
1010
requires-python = ">=3.10"

src/ecocode/commands/profile_repo.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ def handle(args: argparse.Namespace) -> int:
173173
"collector": args.collector,
174174
"runs": args.runs,
175175
"total_files": result.total_files,
176+
"total_discovered": result.total_discovered,
176177
"total_cpu_seconds": result.total_cpu_seconds,
177178
"total_memory_mb": result.total_memory_mb,
178179
"total_energy_wh": result.total_energy_wh,
@@ -230,6 +231,11 @@ def handle(args: argparse.Namespace) -> int:
230231
print("EcoCode repository profile")
231232
print(f"Root: {result.root}")
232233
print(f"Files profiled: {result.total_files}")
234+
if result.total_discovered > result.total_files:
235+
print(
236+
f"Note: scanned {result.total_files} of {result.total_discovered} files "
237+
f"(--max-files limit). Totals cover the scanned subset only."
238+
)
233239
print(f"Total CPU time (s): {result.total_cpu_seconds}")
234240
print(f"Total memory peak (MB): {result.total_memory_mb}")
235241
print(f"Total estimated Wh: {result.total_energy_wh}")

src/ecocode/core/repository_profiler.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,12 @@ class RepoProfileResult:
6060
total_energy_wh: float
6161
average_sustainability_score: float
6262
results: list[ProfileResult]
63+
total_discovered: int = 0
6364

6465

65-
def discover_profile_targets(
66+
def _discover_all_targets(
6667
root: Path,
6768
extensions: set[str],
68-
max_files: int,
6969
include_globs: list[str] | None = None,
7070
exclude_globs: list[str] | None = None,
7171
) -> list[Path]:
@@ -95,7 +95,17 @@ def discover_profile_targets(
9595
candidates.append(path)
9696

9797
candidates.sort()
98-
return candidates[:max_files]
98+
return candidates
99+
100+
101+
def discover_profile_targets(
102+
root: Path,
103+
extensions: set[str],
104+
max_files: int,
105+
include_globs: list[str] | None = None,
106+
exclude_globs: list[str] | None = None,
107+
) -> list[Path]:
108+
return _discover_all_targets(root, extensions, include_globs, exclude_globs)[:max_files]
99109

100110

101111
def _profile_target_resilient(
@@ -146,13 +156,14 @@ def profile_repository(
146156
raise FileNotFoundError(f"Repository root not found: {root}")
147157

148158
profile_extensions = extensions or DEFAULT_SCRIPT_EXTENSIONS
149-
targets = discover_profile_targets(
159+
all_targets = _discover_all_targets(
150160
root,
151161
profile_extensions,
152-
max_files,
153162
include_globs=include_globs,
154163
exclude_globs=exclude_globs,
155164
)
165+
total_discovered = len(all_targets)
166+
targets = all_targets[:max_files]
156167

157168
results = [
158169
_profile_target_resilient(
@@ -186,4 +197,5 @@ def profile_repository(
186197
total_energy_wh=total_energy_wh,
187198
average_sustainability_score=average_sustainability_score,
188199
results=results,
200+
total_discovered=total_discovered,
189201
)

src/ecocode/core/schemas.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ class SchemaValidationError(ValueError):
250250
"collector": {"type": "string", "enum": ["placeholder", "runtime", "static"]},
251251
"runs": {"type": "integer", "minimum": 1},
252252
"total_files": {"type": "integer", "minimum": 0},
253+
"total_discovered": {"type": "integer", "minimum": 0},
253254
"total_cpu_seconds": {"type": "number"},
254255
"total_memory_mb": {"type": "number"},
255256
"total_energy_wh": {"type": "number"},

tests/test_output_contracts.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"collector",
3939
"runs",
4040
"total_files",
41+
"total_discovered",
4142
"total_cpu_seconds",
4243
"total_memory_mb",
4344
"total_energy_wh",

tests/test_static_and_multilang.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,3 +100,13 @@ def test_discovery_prunes_ignored_directories(tmp_path: Path) -> None:
100100
names = {path.name for path in targets}
101101

102102
assert names == {"app.py"}
103+
104+
105+
def test_repo_reports_total_discovered_when_truncated(tmp_path: Path) -> None:
106+
for index in range(5):
107+
(tmp_path / f"f{index}.py").write_text("print('x')\n", encoding="utf-8")
108+
109+
result = profile_repository(tmp_path, extensions={".py"}, max_files=2)
110+
111+
assert result.total_files == 2
112+
assert result.total_discovered == 5

vscode-extension/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ Repeated runs expose variability (coefficient of variation) so you can trust the
3838

3939
The extension is a UI on top of the **EcoCode CLI** (a Python package). Install the CLI once — either:
4040

41-
- **One click:** run **EcoCode: Setup CLI In Workspace** (or click *Setup CLI* when prompted). It installs the CLI into a dedicated virtual environment for you (no PEP 668 issues).
41+
- **One click:** run **EcoCode: Setup CLI In Workspace** (or click *Setup CLI* when prompted). It uses **pipx** when available, otherwise a dedicated virtual environment PEP 668-safe on Linux/macOS/Windows, and the extension auto-detects either install.
4242
- **Manually with pipx:**
4343

4444
```bash
@@ -93,7 +93,7 @@ Workspace scans use the `static` collector by default: a source-based estimate t
9393
- `ecocode.diagnosticsEnabled`: show inline optimization suggestions while editing (default `true`).
9494
- `ecocode.timeoutSeconds`: max seconds per CLI invocation (default `120`).
9595
- `ecocode.installSource`: pip install source used by *Setup CLI* (PyPI name, git URL, or local path).
96-
- `ecocode.maxFiles`: max number of scanned files.
96+
- `ecocode.maxFiles`: max number of files scanned per workspace scan (default 200). Large repos are capped at this number; when the limit is reached the dashboard shows a "showing X of Y files" banner with a one-click **Increase limit** button. Raise it to scan more.
9797
- `ecocode.runs`: repeated runs for stability.
9898
- `ecocode.extensions`: optional extension filters.
9999
- `ecocode.includeGlobs`: optional include globs.

vscode-extension/media/dashboard.css

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,3 +257,14 @@ th {
257257
opacity: 0.85;
258258
margin-top: 2px;
259259
}
260+
261+
#truncationBanner {
262+
border-left: 3px solid #bb8009;
263+
background: rgba(187, 128, 9, 0.12);
264+
font-size: 0.85rem;
265+
}
266+
267+
#truncationBanner button {
268+
margin-left: 8px;
269+
cursor: pointer;
270+
}

vscode-extension/media/dashboard.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const el = {
1111
showCurrentFile: byId("showCurrentFile"),
1212
showSuggestions: byId("showSuggestions"),
1313
errorBox: byId("errorBox"),
14+
truncationBanner: byId("truncationBanner"),
1415
summarySection: byId("summarySection"),
1516
stabilitySection: byId("stabilitySection"),
1617
filesSection: byId("filesSection"),
@@ -62,6 +63,24 @@ function renderMetrics(report) {
6263
`;
6364
}
6465

66+
function renderTruncation(report) {
67+
const discovered = report && report.total_discovered;
68+
if (!report || typeof discovered !== "number" || discovered <= report.total_files) {
69+
el.truncationBanner.classList.add("hidden");
70+
el.truncationBanner.innerHTML = "";
71+
return;
72+
}
73+
el.truncationBanner.classList.remove("hidden");
74+
el.truncationBanner.innerHTML =
75+
`<strong>Partial scan:</strong> showing <strong>${report.total_files}</strong> of <strong>${discovered}</strong> matching files (file limit reached). ` +
76+
`Totals below cover the scanned subset only. ` +
77+
`<button id="increaseLimitBtn">Increase limit</button>`;
78+
const button = byId("increaseLimitBtn");
79+
if (button) {
80+
button.addEventListener("click", () => vscode.postMessage({ type: "increaseMaxFiles" }));
81+
}
82+
}
83+
6584
function renderStability(report) {
6685
if (!report || !report.stability) {
6786
el.stabilitySection.innerHTML = "<h2>Stability</h2><p>No repeated-run stability data yet.</p>";
@@ -205,6 +224,7 @@ function render(state) {
205224
el.errorBox.classList.toggle("error", !!state.lastError);
206225
el.errorBox.textContent = state.lastError || "";
207226

227+
renderTruncation(state.workspaceReport);
208228
renderMetrics(state.workspaceReport);
209229
renderStability(state.workspaceReport);
210230
const topLimit = Number.isFinite(state.showTopFiles) ? state.showTopFiles : 15;

0 commit comments

Comments
 (0)