From 5d225760faf904e30f7f45c5f707846eaa4506ca Mon Sep 17 00:00:00 2001 From: Wolfvin Date: Mon, 13 Jul 2026 07:13:05 +0700 Subject: [PATCH] feat(audit): restore css-deep as audit --check css (closes #251) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cssdeep_engine.py (unused CSS vars, orphan keyframes, specificity wars, duplicate props, unused media queries, z-index abuse) is fully functional — 154 real findings verified on a live workspace — but was orphaned in the #195 consolidation: its command entry point was deleted, the engine kept. Same situation as export-snapshot (#218). CSS is in CodeLens's stated scope (react/css/html), so the loss meant falling back to manual grep for "is this CSS var still used?". Restored as `audit --check css` — a sub-check under the audit umbrella alongside dead-code/complexity/smell/perf-hint (all code-quality analyses), NOT a new top-level command. Command count stays exactly 12 (verified via --command-count) — compatible with the #195 12-umbrella consolidation, not a reversal of it. New scripts/commands/css_deep.py is a thin wrapper (no engine logic duplicated, mirrors export_snapshot.py). --severity and --category passthrough to the engine verified. Design doc at docs/design/0251-restore-css-deep.md. --- docs/agent-usage-guide.md | 1 + docs/design/0251-restore-css-deep.md | 77 ++++++++++++++++++++++++++ scripts/commands/audit.py | 11 ++++ scripts/commands/css_deep.py | 42 +++++++++++++++ tests/test_command_registry.py | 12 ++--- tests/test_css_deep_command.py | 81 ++++++++++++++++++++++++++++ 6 files changed, 218 insertions(+), 6 deletions(-) create mode 100644 docs/design/0251-restore-css-deep.md create mode 100644 scripts/commands/css_deep.py create mode 100644 tests/test_css_deep_command.py diff --git a/docs/agent-usage-guide.md b/docs/agent-usage-guide.md index bda1b7c..1e1d4e6 100644 --- a/docs/agent-usage-guide.md +++ b/docs/agent-usage-guide.md @@ -42,6 +42,7 @@ codelens audit --check dead-code # different order, also | "Any circular imports?" | `deps . --check circular` | | | "Any secrets/vulnerable deps/injection risk?" | `security . --check secrets\|vuln-scan\|taint\|regex-audit` | **Taint is Python/JS/TS/TSX only** — see Known Gaps, no Rust coverage. | | "10-second repo orientation" | `context . --check orient` (or bare `context .`, it's the default) | Framework detection, dev commands, entry points. | +| "Is this CSS var / keyframe still used? specificity/z-index problems?" | `audit . --check css` | Unused CSS vars, orphan keyframes, specificity wars, duplicate props, unused media queries, z-index abuse. `--severity`/`--category` filters. Restored issue #251 (engine was orphaned since #195). | | "Prioritized health snapshot" | `summary .` | Aggregates dead-code/smell/taint/vuln-scan; use `--lite` for an agent-sized payload. | --- diff --git a/docs/design/0251-restore-css-deep.md b/docs/design/0251-restore-css-deep.md new file mode 100644 index 0000000..8b3dec0 --- /dev/null +++ b/docs/design/0251-restore-css-deep.md @@ -0,0 +1,77 @@ +# Design Doc: Restore css-deep as `audit --check css` + +> **Status:** Accepted +> **Date:** 2026-07-13 +> **Author:** Claude (direct implementation, no worker — user directive) +> **Related issues:** #251 +> **Related PRs:** (this PR) + +--- + +## Problem + +`cssdeep_engine.py` (deep CSS analysis: unused CSS variables, orphan +keyframes, specificity wars, duplicate properties, unused media queries, +z-index abuse) is fully functional — verified 154 real findings on the +Coretax `smart-tax-assistance` workspace — but **orphaned**: its CLI entry +point (the old standalone `css-deep` command) was deleted in the #195 +umbrella consolidation, and `analyze_css_deep()` is now reachable from no +command, MCP tool, or `--check` sub-mode. This is the same situation as +`export-snapshot` (issue #218, restored): a working engine with a dead +entry point. + +CSS is in CodeLens's stated language scope (react/css/html). Losing deep +CSS analysis means falling back to manual grep for questions like "is this +CSS variable still used?". + +## Goal + +`codelens audit --check css` runs `analyze_css_deep()` and +returns findings in the standard umbrella `{s, st, r}` shape, with +`--severity` and `--category` passthrough. + +## Changes + +### New Files +- `scripts/commands/css_deep.py` — thin wrapper over + `cssdeep_engine.analyze_css_deep()`, mirroring `export_snapshot.py`'s + structure. No engine logic duplicated. + +### Modified Files +- `scripts/commands/audit.py` — registered `css` in `_CHECKS`, added the + namespace branch (severity + category passthrough), updated epilog. +- `tests/test_command_registry.py` — added `css_deep` to the + implementation-module allowlist (it's imported by the audit umbrella, + not self-registering — same as `export_snapshot`). +- `docs/agent-usage-guide.md` — css-deep documented as available again. + +### Not Changed +- `cssdeep_engine.py` — the engine already works and needed no changes. + +## Why a sub-check, not a restored top-level command + +The #195 consolidation reduced 78 commands to 12 umbrellas. Re-adding +`css-deep` as a **top-level** command would violate that (command count +would go to 13). As a `--check css` sub-mode under `audit` — alongside +dead-code / complexity / smell / perf-hint, all code-quality analyses — it +fits the umbrella taxonomy and keeps the command count at exactly 12 +(verified via `--command-count`). This is compatible with the #195 +philosophy (fewer top-level commands, richer sub-checks), not a reversal +of it. + +## Testing + +`tests/test_css_deep_command.py`: wrapper delegation, severity/category +passthrough, audit umbrella dispatch of `css`, and severity reaching the +engine through the synthetic namespace. Verified end-to-end on the real +workspace: 154 findings, `--severity high` → 3, `--category z_index_abuse` +→ 1. + +## Alternatives Considered + +- **Leave it dropped.** Rejected — the engine works, CSS is in scope, and + the loss forces manual grep for CSS-variable/keyframe usage. Same + reasoning that restored `export-snapshot` (#218). +- **Restore as a top-level `css-deep` command.** Rejected — violates the + 12-umbrella consolidation. Sub-check placement recovers the capability + without growing the command surface. diff --git a/scripts/commands/audit.py b/scripts/commands/audit.py index 6b904a3..1ad623e 100644 --- a/scripts/commands/audit.py +++ b/scripts/commands/audit.py @@ -59,6 +59,10 @@ "module": "commands.side_effect", "help": "Pure vs impure function analysis", }, + "css": { + "module": "commands.css_deep", + "help": "Deep CSS analysis: unused vars, orphan keyframes, specificity wars, z-index abuse (issue #251)", + }, } ALL_CHECKS = list(_CHECKS.keys()) @@ -75,11 +79,14 @@ def add_args(parser): " staleness Per-file staleness detection\n" " perf-hint Performance anti-patterns\n" " side-effect Pure vs impure function analysis\n" + " css Deep CSS analysis: unused vars, orphan keyframes,\n" + " specificity wars, z-index abuse (issue #251)\n" "\n" "Examples:\n" " codelens audit . # all checks\n" " codelens audit . --check dead-code # only dead-code\n" " codelens audit . --check complexity,smell # pick subset\n" + " codelens audit . --check css # deep CSS analysis\n" ) parser.add_argument("workspace", nargs="?", default=None, help="Path to workspace root (auto-detected if omitted)") @@ -173,6 +180,10 @@ def _build_namespace(base_args, check_name: str) -> argparse.Namespace: ns.name = getattr(base_args, "name", None) ns.file = getattr(base_args, "file", None) ns.max_files = getattr(base_args, "max_files", None) or 3000 + elif check_name == "css": + # cssdeep_engine accepts severity (high|medium|low) + single category + ns.severity = getattr(base_args, "severity", None) + ns.category = getattr(base_args, "category", None) return ns diff --git a/scripts/commands/css_deep.py b/scripts/commands/css_deep.py new file mode 100644 index 0000000..5e34385 --- /dev/null +++ b/scripts/commands/css_deep.py @@ -0,0 +1,42 @@ +# @WHO: scripts/commands/css_deep.py +# @WHAT: Deep CSS analysis command — thin wrapper over cssdeep_engine (issue #251) +# @PART: commands +# @ENTRY: execute() +"""css-deep command — deep CSS analysis (issue #251 restoration). + +Wraps ``cssdeep_engine.analyze_css_deep()`` — detects unused CSS variables, +orphan keyframes, specificity wars, duplicate properties, unused media +queries, and z-index abuse. + +The engine was never deleted, but its CLI entry point (the old standalone +``css-deep`` command) was dropped in the #195 umbrella consolidation, +leaving the working engine orphaned (same situation as ``export-snapshot``, +issue #218). This restores access as ``audit --check css`` — a sub-check +under the audit umbrella, NOT a new top-level command, so the 12-umbrella +consolidation is preserved (command count stays 12). +""" + +from cssdeep_engine import analyze_css_deep +from commands import register_command + + +def add_args(parser): + parser.add_argument("workspace", nargs="?", default=None, + help="Path to workspace root (auto-detected if omitted)") + parser.add_argument("--severity", choices=["high", "medium", "low"], default=None, + help="Filter by severity level") + parser.add_argument("--category", default=None, + help="Filter to one category: unused_vars, orphan_keyframes, " + "specificity_wars, duplicate_props, unused_media, z_index_abuse") + + +def execute(args, workspace): + return analyze_css_deep( + workspace, + severity=getattr(args, "severity", None), + category=getattr(args, "category", None), + ) + +# Issue #251: registered as the `css` sub-check of the `audit` umbrella +# (see commands/audit.py), NOT as a standalone command — keeps command +# count at 12. This module is imported by audit.py, not self-registering. diff --git a/tests/test_command_registry.py b/tests/test_command_registry.py index 8729d54..92d6ced 100644 --- a/tests/test_command_registry.py +++ b/tests/test_command_registry.py @@ -40,12 +40,12 @@ def test_every_command_module_registers(): # but the umbrella commands import them for --check sub-analyses. _DEPRECATED_ALIAS_MODULES = { "affected", "arch_metrics", "architecture", "binary_scan", - "circular", "complexity", "dashboard", "dataflow", "dead_code", - "dependents", "diff", "env_check", "export_snapshot", "git_status", - "graph_schema", "import_snapshot", "init", "lsp_status", "orient", - "outline", "ownership", "perf_hint", "query_graph", "regex_audit", - "secrets", "side_effect", "smell", "staleness", "taint", "trace", - "vuln_scan", + "circular", "complexity", "css_deep", "dashboard", "dataflow", + "dead_code", "dependents", "diff", "env_check", "export_snapshot", + "git_status", "graph_schema", "import_snapshot", "init", "lsp_status", + "orient", "outline", "ownership", "perf_hint", "query_graph", + "regex_audit", "secrets", "side_effect", "smell", "staleness", + "taint", "trace", "vuln_scan", } _UTILITY_MODULES |= _DEPRECATED_ALIAS_MODULES missing = [] diff --git a/tests/test_css_deep_command.py b/tests/test_css_deep_command.py new file mode 100644 index 0000000..5b48ae6 --- /dev/null +++ b/tests/test_css_deep_command.py @@ -0,0 +1,81 @@ +# @WHO: tests/test_css_deep_command.py +# @WHAT: Tests for restored css-deep as audit --check css (issue #251) +# @PART: tests +"""Tests for the css sub-check of the audit umbrella (issue #251). + +cssdeep_engine was orphaned in the #195 consolidation (its command entry +point was deleted, the engine kept). This restores access as +`audit --check css` — a sub-check, NOT a new top-level command. These +tests verify the wrapper delegates correctly, the audit umbrella dispatches +`css`, and passthrough filters (severity/category) reach the engine. +""" + +import argparse +import os +import sys +from unittest import mock + +import pytest + +SCRIPT_DIR = os.path.join( + os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "scripts" +) +if SCRIPT_DIR not in sys.path: + sys.path.insert(0, SCRIPT_DIR) + +from commands import css_deep # noqa: E402 +from commands import audit # noqa: E402 + + +class TestCssDeepCommand: + def test_execute_delegates_to_engine(self): + args = argparse.Namespace(workspace=".", severity=None, category=None) + with mock.patch( + "commands.css_deep.analyze_css_deep", + return_value={"status": "ok", "stats": {"total_issues": 3}}, + ) as mock_engine: + result = css_deep.execute(args, ".") + mock_engine.assert_called_once_with(".", severity=None, category=None) + assert result["status"] == "ok" + + def test_severity_and_category_passthrough(self): + args = argparse.Namespace(workspace=".", severity="high", category="z_index_abuse") + with mock.patch( + "commands.css_deep.analyze_css_deep", + return_value={"status": "ok"}, + ) as mock_engine: + css_deep.execute(args, ".") + mock_engine.assert_called_once_with(".", severity="high", category="z_index_abuse") + + +class TestAuditDispatchesCss: + def test_css_is_registered_check(self): + assert "css" in audit.ALL_CHECKS + + def test_audit_check_css_routes_to_engine(self): + base = argparse.Namespace( + workspace=".", check="css", severity=None, category=None, + ) + with mock.patch( + "commands.css_deep.analyze_css_deep", + return_value={"status": "ok", "stats": {"total_issues": 7}}, + ): + result = audit.execute(base, ".") + # umbrella envelope + assert result["s"] == "ok" + assert result["st"]["checks_run"] == 1 + assert result["r"][0]["_check"] == "css" + assert result["r"][0]["stats"]["total_issues"] == 7 + + def test_audit_check_css_severity_reaches_engine(self): + base = argparse.Namespace( + workspace=".", check="css", severity="high", category=None, + ) + with mock.patch( + "commands.css_deep.analyze_css_deep", + return_value={"status": "ok"}, + ) as mock_engine: + audit.execute(base, ".") + # audit builds a synthetic namespace; the engine must receive the filter + _, kwargs = mock_engine.call_args + assert kwargs.get("severity") == "high"