agentleFS
piousppiousp/llm-skills/skills/refactor-identification/SKILL.md

refactor-identification

Skill2 starsChanged 6 days agoMITView on GitHub

Finds refactor candidates in the current branch diff (Java, Scala, JS, TS): missing abstractions, weak encapsulation, poor data types, flags or enums that model variants. Every finding needs file:line evidence. Use for "refactor candidates". [DO NOT] use for renames, applying fixes, or whole-repo scans.

The file

We split this skill into the rules it gives an agent

8 rules. About 0 are common, 7 are distinctive, and 1 is very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈7

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 1

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Use this skill for a deep, quantified dive on exactly 4 structural categories, when the question is not "does this diff pass review" but …

    line 9
  2. Distinctive:

    Do not use it as a substitute for a broader code-review checklist pass: that pass already covers part of A1 superficially (its …

    line 11
  3. Distinctive:

    If the caller's prompt already hands you a diff (a path, a commit range, or diff content), use exactly that.

    line 24
  4. Very specific:

    Anchoring rule: every finding needs at least one file:line inside a changed hunk, or on a symbol the branch changed.

    line 29
  5. Distinctive:

    Do not expand further than one hop.

    line 35
  6. Distinctive:

    If adjudicating a candidate needs a fact beyond the 1-hop radius or outside this repo (including a fact only documentation claims), do not …

    line 45
  7. Distinctive:

    Every candidate must pass through every row below before it becomes a finding.

    line 133
  8. Distinctive:

    Never restate a gof/fp table row here.

    line 277

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Refactor Identification: Branch-Scoped Structural Candidates

Use this skill for a deep, quantified dive on exactly 4 structural categories, when the question is not "does this diff pass review" but "is there a structural refactor worth investing in here". Do not use it as a substitute for a broader code-review checklist pass: that pass already covers part of A1 superficially (its "Abstractions" section and the SRP/OCP portion of "Structural Code Smells") as part of a broad, fast merge gate; reach for this skill only when that surface-level pass isn't enough. See Boundaries below for the exact split. Identify candidates only; never execute a refactor and never explain how to apply a pattern once one is chosen. Route the "how" to pablo-code-philosophy's references/gof/ (pattern catalog) and references/fp/ (FP mechanics); route the cut-off criterion (when NOT to bother) to that same skill.

Scope law: the branch's work only

This skill never scans a whole repository. It only looks at what the current branch changed.

  1. Derive the diff (the same first step as any diff-scoped review):
    • If the caller's prompt already hands you a diff (a path, a commit range, or diff content), use exactly that. Do not go looking for a different diff and do not ask anything.
    • Otherwise, derive it yourself: git diff (unstaged), git diff --cached (staged), git diff main...HEAD (full branch). Ask for the parent branch only if invoked standalone and it isn't obvious.
  2. Anchoring rule: every finding needs at least one file:line inside a changed hunk, or on a symbol the branch changed. A finding with no such anchor does not exist for this skill.
  3. 1-hop context rule: you may read (a) the full contents of every changed file, (b) files that directly define or use a symbol the branch changed (a code-navigation/indexing tool if the repo has one, otherwise grep), and (c) the public signatures (not bodies) of types the changed lines call, solely to detect an existing helper being re-implemented. Do not expand further than one hop.
  4. Occurrence-counting exception: once a candidate is anchored inside the branch's diff, a repo-wide grep is allowed solely to count how many total occurrences exist, because the DRY thresholds below (2 for both structural and business duplication) count all occurrences, not just the ones inside the diff. This exception never originates a new candidate; it only measures one already anchored in step 2. A zero-hit grep outside the diff establishes local absence only, never runtime unreachability (see rule 5).
  5. Evidence ceiling rule: code/config read within the 1-hop radius, and tests that exercise it, are what settle a candidate. Documentation, wikis, comments, and domain knowledge may only motivate a hypothesis; [NEVER] let them decide a gate row or a finding by themselves. If adjudicating a candidate needs a fact beyond the 1-hop radius or outside this repo (including a fact only documentation claims), do not infer or derive it: name the exact lookup that would resolve it (repo, symbol/path, what to check) and mark the candidate N9/Unresolved. The radius never widens; the verdict is suspended.

The four categories

IDCategoryOne-line definitionThreshold anchor
A1Missing or misplaced abstractionsSRP/OCP violated, the same structural/business logic duplicated, inheritance used for reuse rather than variation, or mixed-phase functionsDuplication ≥2, structural or business (owner's review rule: extract at 2 copies unless very expensive; overrides pablo-code-philosophy DRY Three Strikes here)
A2Weak encapsulationMutable state exposed, invariants unprotected or checked by callers instead of the object, mixed query/mutation, hidden ambient dependencies, or read-through delegate chains1 occurrence anchored in the diff, unless noted otherwise
A3Poor data typesPrimitives/data clumps standing in for a domain type; null or exceptions used for expected control flow; redundant derived fields; mutable references used as valuesVaries per smell, see the A3 table below
A4Flag/enum-modeled variantsA discriminator (boolean/enum/string) drives dispatch that a sealed ADT + pattern matching would express directlyVaries per smell, see the A4 table below

A1: Missing or misplaced abstractions

A class or function carries more than one reason to change, the same shape of logic is copy-pasted with only the types or literals differing, it inherits for reuse rather than variation, or it mixes separable phases in one body.

2-copy override of DRY: pablo-code-philosophy DRY.md (Three Strikes) waits for 3 copies. In this lens the owner's review rule wins: duplication is a candidate at 2 copies, structural or business, unless the extraction is very expensive (N3 and P3 still apply). DRY.md itself is unchanged.

SmellDetection cue (language-neutral)Evidence to recordThreshold
Structural duplication (same algorithm, different type)Two methods/functions identical modulo types: same pipeline shape (stream().filter().collect / map/fold chains), same control flow, only types/field-accessors differ. At least one copy in a changed hunk; count the rest repo-wide.Both file:line spans + "identical modulo type: <T1> vs <T2>"≥2 occurrences (DRY structural rule, abstract at 2)
Inline re-implementation of an existing helperChanged lines re-implement what an existing named helper, predicate or constant already provides (found via the 1-hop public-signature read, Scope law 3c).Changed file:line + the existing helper's file:line1 (the existing helper is the other copy)
Business-rule duplicationSame domain decision (same predicate over the same domain fields) encoded in multiple places; literals/field names match even if syntax differs.All file:line sites + the rule in one sentence≥2 occurrences, ≥1 touched by the branch; N3 still applies; an extraction that crosses a module boundary or public contract is P3
SRP violation in a touched classDiff adds methods/fields serving a second axis of change: new imports/collaborators from an unrelated domain; methods that share no fields with the rest of the class. Does not count: an injected collaborator with 0 in-repo readers is dead code (code-review-checklist Abstractions), not an axis.Class file:line + list of the ≥2 axes of change + the added members' lines≥2 distinct reasons-to-change, ≥1 introduced/extended by the branch
OCP violation (dispatch modified, not extended)The branch ADDS a case/branch to an existing switch/if-else chain selecting behavior, instead of extending via a new implementation.Chain file:line, the added branch's hunk lines, chain length after changeChain length ≥3 after the change
Misplaced logic (feature envy)Touched method calls ≥3 getters/fields of one foreign type and ≤1 of its own class; the computation belongs on the foreign type.Method file:line + count "N foreign accessor calls vs M own"≥3 foreign accessor calls (calibratable)
Inheritance used for reuse, not variation§A subclass shares fields/methods of its parent with no behavioral variation: it overrides a method only to leave it empty/pass-through, or never overrides anything: composition would carry the same state without the "is-a" claim. Does not count: a subclass required by a framework/language (extends Exception, a framework base class), a sealed hierarchy (that's A4), or a base class holding only fields and getters.Subclass file:line + the parent member it reuses/leaves empty1 occurrence, ≥1 touched by the branch
Mixed-phase function (no Split Phase)A touched method does two genuinely separable jobs end to end in one body (e.g. parse/validate raw input, then compute a business result from it), with no named structure between the phases; phase-1 locals feed phase-2 logic across a long body.Method file:line + the two phase line-ranges, named in one sentence each≥2 independently nameable phases

§ Inheritance-for-reuse is narrower than the deep-inheritance smells code-review-checklist deliberately excludes (Call super, Circle-ellipse, Yo-yo, Poltergeist): those are style-level nitpicks this codebase rarely has; this row only fires when composition would carry the same behavior without an "is-a" claim, operationalizing pablo-code-philosophy's composition-over- inheritance rule (SOLID.md, the L section). Refactor direction: Replace Subclass/Superclass with Delegate, Remove Subclass; never an open subclass hierarchy as the "fix".

A2: Weak encapsulation

State that should be private and self-protecting is instead exposed, mutable, or trusted to be validated by every caller, or a method mixes query with mutation, reads ambient state, or reaches through a delegate chain.

SmellDetection cue (language-neutral)Evidence to recordThreshold
Public mutable fieldJava: non-final public/package-private field. Scala: var in a public class/case class or trait member.file:line of the declaration1
Mutable internals escapingGetter returning an internal mutable collection/array without defensive copy or immutable wrapper (return this.list; where field is ArrayList; Scala: exposing mutable.Buffer/Map fields).Getter file:line + field file:line + "escapes: <type>"1
Invariant bypassed by mutatorConstructor/factory validates a condition but a setter/mutator on the same field doesn't (or copy in Scala bypasses a smart-constructor check).Validation file:line + unguarded mutator file:line1
Invariant enforced by callers (check-then-act)≥2 call sites perform the same precondition check before calling the same method; the check belongs inside the callee.Each caller file:line + callee file:line≥2 call sites, ≥1 touched by the branch
Mutation through reached-into internalsTouched code does a.getB().getC().setX(...) / a.b.c = x; mutating another object's internals through a chain.file:line of the chain + depthChain depth ≥2 with terminal mutation
Query/mutator mixed (CQS violation)A touched method returns a value AND mutates a field of this or a mutable argument passed in, in the same call (not a local accumulator that dies with the stack frame). Does not count: idiomatic query+mutate operations (pop, poll, getAndIncrement, compareAndSet, a Map.put returning the previous value) or a builder/fluent setter returning this.Method file:line + what it mutates vs what it returns1 occurrence anchored in the diff
Hidden dependency on ambient stateA touched method reads a static/global/singleton/system clock/env variable directly instead of receiving it as a parameter, so it can't be exercised in isolation (pablo-code-philosophy FP.md, "Scientific code"). Does not count: a static logger, or a static final immutable constant.Method file:line + the ambient source read1 occurrence anchored in the diff
Read-through delegate chainTouched code reads (no mutation) through a chain of ≥3 accessors (a.getB().getC().getD()) to reach a value that belongs closer to a, repeated at ≥2 sites.Each chain file:line + depthDepth ≥3, ≥2 occurrences; gate N3 before proposing a new middle-man wrapper

A3: Poor data types

Domain concepts are carried by bare primitives or null, expected outcomes are signaled by throwing instead of by the return type, a field stores a value it could derive instead, or a mutable value is aliased where it should be copied.

SmellDetection cue (language-neutral)Evidence to recordThreshold
Primitive obsessionString/int/long/double carrying domain meaning (id, ISO code, currency, email, status) crossing method signatures; or the same primitive format-validated in multiple places. Does not count: an opaque identifier (id, key, token) the branch only passes, stores or compares without adding parsing, validation or formatting; never suggest an id wrapper type for it.Each signature file:line + the domain concept nameCrosses ≥3 signatures in scope, OR validated in ≥2 places (calibratable)
Stringly-typed stateField compared against string literals to branch ("Active".equals(status) / status == "Active"); or a set of ≥2 string constants declared as the allowed values of one field.Each comparison file:line + the literal set observed≥2 distinct literal comparisons on the same field; or 1 declaration in the diff of ≥2 string constants that are the allowed values of one field
null as domain absenceJava: method returns null for an expected "not found"/"missing" outcome; callers null-check. Scala: any null, Option.get, .getOrElse(null). JS/TS: return of null/undefined for an expected "not found", callers guarding with ?./??/!x; TS: also optional ? members and non-null assertionsProducer file:line + each null-checking caller file:lineJava: ≥2 null-checks on the same value; Scala: 1; JS/TS: ≥2 guards, or 1 for a ! assertion
Exceptions as control flowthrow for an expected domain outcome (validation failed, not found) with a caller that catches to branch on it.throw file:line + catching caller file:line1 (throw+catch pair present)
Data clumpThe same group of ≥3 parameters traveling together through multiple signatures (same names/types in the same order).Each signature file:line + the clump membersGroup of ≥3 params in ≥2 touched signatures (an interface method, its implementations and overloads count as one signature)
Redundant derived fieldA field stores a value already derivable from another field the same object holds, at the point of use; the invariant that makes the derivation total is visible in the same file.Field file:line + the field it duplicates1 occurrence, derivation invariant visible locally; else N9
Shared mutable reference used as a valueAn object with no identity meaning (an amount, a range, a coordinate) is passed/stored by mutable reference and aliased across ≥2 owners, so mutating one silently affects the other.Alias site file:line + mutation site file:line1 occurrence of aliasing + mutation on the same reference

A4: Flag/enum-modeled variants

A discriminator drives behavior that varies by case, in a shape that a sealed ADT + pattern matching would express more directly and exhaustively.

SmellDetection cue (language-neutral)Evidence to recordThreshold
Duplicated dispatch over the same enum/flag≥2 switch/match/if-else chains over the same enum/int-flag/string discriminator, where variants carry different data or behavior → sealed ADT candidate. Count extra dispatch sites repo-wide once anchored.Each dispatch file:line + the discriminator + variant count≥2 dispatch sites (structural-dup-at-2 rule)
Type-check cascade on unsealed hierarchyJava: instanceof chains. Scala: isInstanceOf/case x: T matches on a non-sealed trait/class. JS/TS: typeof/instanceof/Array.isArray cascades, or a union with no discriminant and no never checkCascade file:line + branch count + "hierarchy not sealed: <root>"≥2 branches
Mode field checked across methodsA status/mode field checked at the top of several methods of a touched class (State candidate, direction only).Field file:line + each checking method file:line≥3 methods check the same field
Nullable-fields-as-variantsClass where certain fields are only meaningful for some values of a discriminator (null-guards conditioned on it; comments like "only set when...").Class file:line + discriminator + the conditional fields' lines1 class with ≥2 discriminator-dependent fields

When NOT to report (gate)

Every candidate must pass through every row below before it becomes a finding. Check every row against every candidate: at most one row applies (the most specific match, never stacked as rhetorical reinforcement), and a candidate with no matching row passes to Findings. A candidate matching N9 moves to Unresolved or Filtered out as the N9 row says; a candidate matching any other row moves to Filtered out with that row's ID. No candidate is silently dropped; record the row checked in the finding's gate note.

"Filtered out" describes why this lens doesn't surface the item as a structural refactor candidate in this branch. [NEVER] treat it, or cite a row ID, as a claim that the change is wrong, risky, unsafe, or should be rejected.

Rows can be logically incompatible: in particular N8 (rename/formatting/trivial, behavior-preserving by definition) cannot co-apply with any argument that the candidate changes behavior, and N1 (only one variant exists) cannot co-apply with N2 (different business rules exist). Citing incompatible rows together means the candidate is misclassified; stop and re-derive.

IDSituationGoverning ruleAction
N1Only one variant/implementation exists today and the branch adds no concrete second one (A1/A4 only; never filters an A3 closed value set)YAGNI, no imaginary flexibilityFiltered out
N2The similar blocks encode different business rules that change for different reasons (accidental duplication)†DRY vs SRP → SRP winsFiltered out
N3The abstraction removing the smell would be harder to read than the duplication/conditional it replaces‡KISS > DRY, KISS > SOLIDFiltered out
N4No evidence line falls inside a changed hunk or on a symbol the branch changedScope law, branch work onlyFiltered out
N5Flag/mode has exactly 2 simple states with trivial behavior differenceState pattern only earns its cost past thisFiltered out (A4)
N6Single failure point already handled inline by the immediate callerEither/Option is ceremony hereFiltered out (A3)
N7Primitive/clump stays local: crosses no signature boundary and is validated in <2 placesKISS, a domain type adds ceremonyFiltered out (A3)
N8The "finding" is really a rename, formatting, or trivial extract-method in disguise; extracting a helper that removes a duplicate copy is never N8Skill boundary, non-structuralFiltered out
N9The candidate's disposition depends on a fact outside 1-hop context or outside this repo (reachability, another service's behavior, runtime data layout)Scope law, cannot be adjudicated hereUnresolved only if the answer could make it P1/P2; otherwise, including a candidate rooted in another repo, one Filtered out line naming the repo or fact
N10Every evidence line lives in test, spec, fixture, or test-helper code (including the main sources of a dedicated integration-test repo/module)Test duplication is often deliberate clarity; test-structure quality is out of scope for this family (see pablo-test-review Scope law)Filtered out

† N2 check: compare what each predicate/block accepts (its extension), not the polarity of its call sites. If one's accepted set is a superset/subset of the other's, they are not different rules and N2 does not apply.

‡ N3 check: "harder to read" explicitly includes hiding an effect. If the abstraction that removes the duplication would fold a mutation, a metric, a log, or any other side effect that each duplicated site currently performs in plain sight into a single method's body, N3 applies: a few duplicated, explicit lines beat one abstraction with hidden effects (KISS > DRY). Name this check in the gate note of every A1 structural-duplication candidate whose refactor direction merges call sites that mutate state.

Priority assignment

Assign strictly by this table; no judgment outside it.

PriorityMechanical rule (all conditions checkable)
P1The smell's root-cause line (the declaration, definition, or dispatch site actually being flagged, not merely a call site or check site referencing it) is ADDED or MODIFIED by the branch (the branch introduces or worsens the smell itself) AND the fix stays within files the branch already changed → do it in this branch
P2The smell's root-cause line pre-exists untouched; the branch only adds a new occurrence, case, or call site that references it (e.g. a second null-check on an existing nullable producer); the fix stays within changed files + 1-hop context → candidate for this branch or an immediate follow-up
P3Anchored in changed code, but the fix exceeds 1-hop context, touches a public contract, or crosses module boundaries → follow-up ticket material, never inline

Tie-break: if a candidate has multiple evidence lines spanning both a pre-existing root cause and branch-added occurrences, priority follows the root cause, not the occurrence count: P2, not P1. Exception: a copy the branch ADDS next to an untouched one is P1 when the fix stays within files the branch changed (references/examples.md A1); an in-place retype of a pre-existing line stays P2.

Step-by-step process

1. Derive the scope. If the caller's prompt hands you a diff (path, range, or content), use
   exactly that; do not ask. Otherwise: `git diff` (unstaged), `git diff --cached` (staged),
   `git diff main...HEAD` (full branch); ask for the parent branch only if invoked standalone
   and it isn't obvious.
   Verify: you can list every changed file with its hunks.
2. Build the scope table: one row per changed file, with its path, language
   (Java/Scala/JavaScript/TypeScript), and changed line ranges. A file in any other language
   gets language `out of language scope` and yields no candidates.
   Verify: row count == changed-file count.
3. Expand context, 1 hop max: for each changed symbol you may read (a) its full containing
   file, (b) files that directly define or use it (a code-navigation/indexing tool if the repo
   has one, else grep), (c) public signatures (not bodies) of types the changed lines call
   (Scope law 3c). Never expand further.
   Verify: every context file read maps to a named changed symbol.
4. Detection pass A1 over changed hunks + context, using the A1 table and the language cues
   in references/. Record raw candidates: category, smell row, file:line list, measure.
5. Detection pass A2, same procedure.
6. Detection pass A3, same procedure.
7. Detection pass A4, same procedure.
8. Count occurrences for threshold-based candidates: grep repo-wide for the anchored
   pattern; update each candidate's measure. Drop candidates below threshold.
   Verify: every surviving candidate's measure meets its row's threshold.
8b. Merge candidates that share a root-cause line into one RF (one root cause = one fix;
    definition: `pablo-code-review` `references/merge-rules.md` § Root cause); name the
    merged ones in its Evidence line as "also: A<n> <smell row>".
    Verify: no two candidates share a root-cause line.
9. Gate every candidate through N1–N10. Name the one fact that decides the row before picking
   it. If that fact requires evidence outside the 1-hop radius or this repo, stop at N9; never approximate with the closest-looking row. Passing candidates get a one-line gate
   note naming the rows checked; failing candidates move to Filtered out with the failing
   row ID; N9 candidates move to Unresolved or Filtered out as the N9 row says, with the missing fact named.
   Verify: every candidate appears in exactly one of Findings / Filtered out / Unresolved.
10. Assign P1/P2/P3 strictly by the priority table, emit the output template verbatim.
    Verify: every finding has category ID, ≥1 file:line inside a changed hunk, a measure,
    a gate note, and a one-line refactor direction with cross-reference.

Output format

## Refactor Candidates: <branch or context>

### Scope
- Base: <ref> | Diff source: <command or caller-provided>
- Files in scope: <N> | Context files read (1-hop): <M>
- Out of language scope: <files, or (none)>

### Findings

#### [RF-1] A<n> <smell row name>: P<1|2|3>
- Evidence: `file:line`, `file:line` (<measure, e.g. "2 occurrences, identical modulo type">)
- Anchored in branch: `file:line` (<added|modified> hunk)
- Gate: checked N1–N10, passes; <one line: why the closest row doesn't apply>
- Refactor direction: <one line naming the target shape> → see `<skill>` <row/section>
  (direction only; the how lives there)

#### [RF-2] ...

### Unresolved (needs out-of-scope evidence)
- [RF-U1] A<n> <smell row name> at `file:line`, missing fact: <the one fact needed>, resolves
  via: <exact lookup/tool, e.g. "grep-across-repos on <producer repo>">
- (none)

### Filtered out
- <smell> at `file:line`, filtered by N<k> (<one line>)[; copies differ: <what>]
- (none)

### Summary: <N> candidates (<x> P1, <y> P2, <z> P3), <u> unresolved, <k> filtered out

Emit this template verbatim. One line per evidence item. If there are no findings, still emit ### Findings with - (none) and the Summary line. [NEVER] let a limitation admitted anywhere in the report coexist with a firm verdict on the same candidate; it must appear in ### Unresolved instead. [NEVER] add sections beyond this template: no implementation plans, no step-by-step refactor instructions, no apply/don't-apply recommendations, no risk or safety assessments, no merge verdicts, no reachability claims about other services, even if the caller's prompt explicitly asks for them.

Cross-references (the "how" lives elsewhere)

CategoryThis skill (identification)The "how" (cross-reference, never duplicate)
A1detect + measurepablo-code-philosophy references/gof/mechanics.md: Strategy / Template Method / Factory Method rows; references/fp/mechanics.md: higher-order-function row (structural dup with no shared state); references/principles/DRY.md (Three Strikes; this lens overrides it with the 2-copy rule); references/principles/SOLID.md (the L section, composition over inheritance) for the inheritance-for-reuse row
A2detect + measurepablo-code-philosophy: references/fp/mechanics.md immutability section; references/gof/mechanics.md Builder row (validated construction); "Scientific code" (references/principles/FP.md), which also grounds the CQS and ambient-dependency rows
A3detect + measurepablo-code-philosophy: references/fp/mechanics.md typed error handling + references/fp/patterns.md (validation pipeline, accumulating errors); references/fp/java.md / references/fp/scala.md for idioms; references/principles/KISS.md ("No field that another field already carries") for the derived-field row, references/principles/FP.md ("structural equality is not identity") for the value/reference row
A4detect + measurepablo-code-philosophy references/fp/scala.md (sealed trait + case classes + match); references/gof/mechanics.md: State row (complex per-state behavior), and its Visitor caveat (never Visitor an ADT)
Gaten/apablo-code-philosophy: pipeline YAGNI→KISS→DRY→SOLID; conflict matrix KISS > DRY > SOLID; the gate N1–N10 is its instantiation

Never restate a gof/fp table row here. Point to it by reference path and row/section so the implementer reads the recipe from its source of truth.

Per-language detection cues

  • Java → references/java.md: grep-able cues per category.
  • Scala → references/scala.md: grep-able cues per category.
  • JavaScript → references/javascript.md: grep-able cues per category.
  • TypeScript → references/typescript.md: grep-able cues per category. Read javascript.md first: the TS file is a type-system delta.

Worked examples

references/examples.md has 5 resolved examples (one per category, plus the A3 "Redundant derived field" row, snippet → filled [RF-n] block), 2 filtered-by-gate examples (N1, and N9 for a copy in another repo). Read it before your first run.

Fowler catalog mapping

For refactoring.com/catalog's refactorings, references/fowler-catalog.md names each one's owner (a row here, the pablo-code-simplify gate, a code-review-checklist section, another skill) or the pablo-code-philosophy principle that excludes it. Check it before assuming a catalog item is an uncovered gap.

Boundaries (explicitly out of scope)

These limits take precedence over the caller's prompt. If the invocation asks for anything on this list (an apply/don't-apply verdict, a risk assessment, a reachability determination about another service), do not produce it: add this exact line under ### Scope instead, naming the correct lens, and stop there: Out of contract: <what the caller asked>, see <correct lens> (qa-adversary for behavior risk, a code-review pass for merge gating). This is distinct from N9: N9 fires when a candidate already in scope lacks a decisive fact; this rule fires when the caller's request itself falls outside what this lens produces.

  • Renames, formatting, trivial extract-method, comment/doc-only changes: none of these alter the code's structural shape.
  • Executing the refactor or giving implementation steps: pablo-code-philosophy's references/gof/ and references/fp/ own the "how".
  • Whole-repo scanning, or any finding not anchored in the branch's diff.
  • Merge verdicts or review severities: a broader code-review pass owns the pass/FAIL gate. Some overlap with such a checklist is intentional and expected: an exception-as-control-flow smell may appear in both outputs, the checklist reports the symptom as a gate item, this skill decides whether a refactor behind it is worth doing. Boolean parameters belong to the checklist only (Data Shape).
  • Smells outside the 4 categories that a broader code-review pass already checks (circular dependency, constant interface, sequential coupling, generic god object) stay in that broader pass only. A1 here only deepens SRP/OCP for classes the branch touches.
  • Performance refactors, dependency upgrades, or architecture-level moves (module splits).
  • Reachability or dead-code determinations that depend on another service's behavior or on a runtime data layout outside this repo: route those to N9/Unresolved, never to a verdict.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.