The skill no longer modifies code. It emits exactly two artifacts under simplify/: findings.json (machine-actionable ledger) + a ranked report. Every finding now REQUIRES title/action/verify (locked in ledger.schema.json) so each is independently executable by the calling agent. Removed: apply/report modes, Track A autofix execution, baseline gate, verify-each-change loop, the include-tests switch (tests excluded by default). Reframed SKILL.md, README, PATTERNS (deterministic/judgment), DETECTION, OPTIMIZATION (judge-only model tiering), PORTABILITY, SVELTE, CONSOLIDATION accordingly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5.8 KiB
Pass B — consolidation & anti-pattern analysis
Pass B finds cross-file, AI-typical problems that per-node rules (Pass A) can't: duplication, failure to reuse, reinvented utilities. It is report-only (these changes risk coupling — never autofix) and every candidate is judged by a model with AHA guardrails before it's reported, because mechanical similarity has false positives.
Pipeline (each detector): extract (0 tokens) → cluster (tool or cheap model) → JUDGE
(mandatory) → consolidation report. Tools are the bundled, non-intrusive binaries
(bin/manifest.json); nothing is written into the target project.
The judge contract (shared by all Pass B detectors)
For each candidate cluster, give the model the real code spans and ask for exactly:
{
"same_concept": true,
"verdict": "consolidate | partial | keep-separate",
"confidence": 0.0,
"why": "cite concrete evidence from the code",
"risk": "what coupling/regression consolidating could cause",
"recommendation": "the concrete change, or why to leave it"
}
Only report findings with verdict != keep-separate AND confidence >= 0.6. For
keep-separate, record the reason in the report so re-runs don't re-flag it.
When you write the finding into findings.json, the judge's recommendation becomes the
required action (a concrete, executable instruction) and you must add a verify
(the config build/test/typecheck command) and a one-line title — these three fields
are mandatory for every finding (see assets/ledger.schema.json) so the acting agent can
execute it without re-running this skill.
AHA guardrails (put these in the judge prompt — they prevent harmful merges)
- Rule of three: 2 occurrences usually isn't worth abstracting; prefer ≥3.
- "The wrong abstraction is worse than duplication" (Sandi Metz). If unifying would
need extra params/conditionals to satisfy diverging callers →
keep-separate. - Respect boundaries: do NOT merge across layers / bounded contexts — e.g. a DB enum vs an API/wire enum vs a UI label set; one adapter vs another adapter; per-service copies in a microservice split. These look identical but are decoupled on purpose.
- Preserve intentional subsets: a smaller set that's a named category (e.g.
COORDINATOR_ROLES⊂AgentRole) is not duplication — recommend deriving it from the canonical source, not merging it away. - Drift is a signal, not always a bug: when near-identical definitions have diverged (one has an extra member), flag the divergence — it may be a latent bug (a value produced but unrepresentable elsewhere).
Model tiering (see OPTIMIZATION.md): cluster + judge on a cheap model; escalate to a stronger model only on low confidence. Fan out one judge per cluster where the runtime has sub-agents; else judge sequentially.
F1 — Consolidation core
Duplicate enums / labels / unions
- Extract with ast-grep:
enum_declaration,type_alias_declaration(keep string-literal unions),as constarrays/objects. Record each definition's member set + location. - Cluster (cheap model, small N): group by member-set overlap — exact, subset/superset, high Jaccard.
- Judge each cluster. Typical outcomes: identical union+const in the same package →
consolidate (idiomatic TS: one
as constarray as source of truth, derive the union viatypeof arr[number]); same-named types in different packages that drifted → consolidate + reconcile; intentional subset → derive, don't merge.
Near-duplicate functions
Two complementary signals:
- Exact / copy-paste →
cpd(bundled):cpd <paths> --min-tokens 50 --reporters json --output <tmp> --silent. Token-based (Type-1/2). 0 model tokens. - Renamed / structural ("almost the same") → ast-grep extracts function nodes; normalize
each to a skeleton (strip comments; replace identifiers→
V, strings/numbers→L; drop whitespace); group by identical skeleton (ignore trivial: skeleton < ~120 chars). This catches Type-2/3 thatcpdmisses (proven on software-house: 7 clusters). Then judge each cluster:unify(extract shared helper / parametrize the literal that differs) |partial(share a stateless core, keep thin wrappers) |keep-separate(the "not quite" is essential — different data source, retry strategy, adapter, etc.).
Untyped repeated object shapes (→ DTO)
- ast-grep extracts object literals; record each one's top-level key set.
- Keep only shapes with ≥3 keys repeated ≥3× that do NOT match an existing
interface/typemember set. (An inline object matching an existing interface is normal usage — TS structurally type-checks it — not a smell. Do not report those.) - Judge: is this a real DTO worth naming, or incidental? Recommend an interface + reuse.
F2 — Reinventing-the-wheel
- ast-grep builds a lightweight signature index across the repo:
{name, params, returns, file, line}for every function/method (no extra tool; universal-ctags is GPL → excluded). - For a target (a changed span, or scanning the repo): find existing functions with high name/parameter/return similarity to the candidate.
- Judge: does the new code re-implement an existing utility? If yes, recommend importing the existing one. Guardrail: confirm the existing function's contract actually fits (don't force reuse across incompatible interfaces/perf constraints).
Output
Write simplify/consolidation-report.md: clusters grouped by detector, each with the
verdict, why, risk, and recommendation; a separate "kept separate (and why)" section; and a
"latent drift/bugs" callout. Add a ledger finding per reported cluster with
kind: "consolidation" (see assets/ledger.schema.json). Pass B never edits files,
regardless of the run mode.