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>
106 lines
5.8 KiB
Markdown
106 lines
5.8 KiB
Markdown
# 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:
|
||
|
||
```json
|
||
{
|
||
"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
|
||
1. Extract with ast-grep: `enum_declaration`, `type_alias_declaration` (keep string-literal
|
||
unions), `as const` arrays/objects. Record each definition's **member set** + location.
|
||
2. Cluster (cheap model, small N): group by member-set overlap — exact, subset/superset,
|
||
high Jaccard.
|
||
3. Judge each cluster. Typical outcomes: identical union+const in the same package →
|
||
consolidate (idiomatic TS: one `as const` array as source of truth, derive the union via
|
||
`typeof 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 that `cpd` misses (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)
|
||
1. ast-grep extracts object literals; record each one's **top-level key set**.
|
||
2. Keep only shapes with ≥3 keys repeated **≥3×** that do **NOT** match an existing
|
||
`interface`/`type` member set. (An inline object matching an existing interface is normal
|
||
usage — TS structurally type-checks it — **not** a smell. Do not report those.)
|
||
3. Judge: is this a real DTO worth naming, or incidental? Recommend an interface + reuse.
|
||
|
||
## F2 — Reinventing-the-wheel
|
||
1. 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).
|
||
2. For a target (a changed span, or scanning the repo): find existing functions with high
|
||
name/parameter/return similarity to the candidate.
|
||
3. 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.
|