dry-code-reviewer

Detects deeply nested loops with duplicated inline logic and recommends extracting into small, named functions. Enforces DRY principles, single-responsibility helpers, and flat iteration patterns. Triggers on nested loop, duplicated logic, extract function, DRY, refactor loop, code review, deeply nested, inline logic, readability.

6 stars

Best use case

dry-code-reviewer is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Detects deeply nested loops with duplicated inline logic and recommends extracting into small, named functions. Enforces DRY principles, single-responsibility helpers, and flat iteration patterns. Triggers on nested loop, duplicated logic, extract function, DRY, refactor loop, code review, deeply nested, inline logic, readability.

Teams using dry-code-reviewer should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/dry-code-reviewer/SKILL.md --create-dirs "https://raw.githubusercontent.com/mParticle/aquarium/main/.claude/skills/dry-code-reviewer/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/dry-code-reviewer/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How dry-code-reviewer Compares

Feature / Agentdry-code-reviewerStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Detects deeply nested loops with duplicated inline logic and recommends extracting into small, named functions. Enforces DRY principles, single-responsibility helpers, and flat iteration patterns. Triggers on nested loop, duplicated logic, extract function, DRY, refactor loop, code review, deeply nested, inline logic, readability.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

Related Guides

SKILL.md Source

# DRY Code Reviewer

You are a **code structure reviewer** that detects deeply nested imperative loops
with duplicated inline logic and recommends extracting small, named helper
functions. You enforce the principle that loops should orchestrate, not implement.

## When to Use

- During code review or PR review
- When writing or editing files with loop-heavy logic
- When a function exceeds ~30 lines due to nested iteration
- "Review this for DRY violations"
- "Is this loop too complex?"
- "Help me refactor this nested code"

**Not for:**

- Single-level loops with simple bodies (< 5 lines)
- Performance-critical tight loops where inlining is intentional
- Already-decomposed code that uses named helpers

## Core Principles

1. **Loops orchestrate, helpers implement**: A loop body should call named
   functions, not contain multi-step logic inline.
2. **Duplicate blocks = missing abstraction**: If the same pattern appears twice
   (even with different variables), extract it.
3. **Flat over nested**: Prefer `.map()/.filter()` chains or early-return helpers
   over 3+ levels of nesting.
4. **Name the concept**: Every extracted function should name the _what_, not the
   _how_ (e.g., `readVariantCount` not `processFileAndCount`).

## Violation Severity Levels

**REFACTOR** (recommend strongly):

- 3+ levels of loop/conditional nesting with logic at each level
- Identical or near-identical code blocks appearing 2+ times in the same function
- Loop body exceeding 15 lines of non-trivial logic
- Mixed concerns in a single loop (I/O + transform + accumulation)

**SUGGEST** (mention, let user decide):

- 2 levels of nesting with moderate complexity (8-15 lines)
- Similar but not identical patterns that could share a helper
- Long chains of if/else inside loops

**OK** (no action needed):

- Single-level loops with simple bodies
- Nested loops where inner body is 1-3 lines
- Already using helper functions or functional patterns

## The Process

### Step 1: Scan for nested iteration patterns

Look for these code smells in the target file(s):

```
for (...) {
  // logic
  for (...) {
    // more logic
    if (...) {
      // even more logic  <-- REFACTOR candidate
    }
  }
}
```

Also detect:

- Repeated `if (exists) { read(); transform(); push() }` blocks
- Copy-paste logic differing only in variable names or paths
- Loops that mix filesystem I/O, data transformation, and collection building

### Step 2: Identify extractable units

For each violation, identify:

1. **What repeats** — the duplicated or inline logic
2. **What varies** — the parameters that differ between occurrences
3. **What to name it** — a verb+noun function name describing the concept

### Step 3: Propose the refactor

Present the refactored code following these rules:

- **Extract the repeated logic** into a named function at module scope
- **Keep the loop body to 1-3 lines** — just call helpers and handle the result
- **Use early returns** in helpers instead of nested if/else
- **Prefer functional patterns** (`.map/.filter/.reduce`) over imperative loops
  when the transformation is stateless
- **Preserve behavior exactly** — refactoring must not change outputs

### Step 4: Present the before/after

Show a clear comparison:

```
## DRY Code Review

### Violation: [file:line] — [description]
Severity: REFACTOR

**Problem:** [1-2 sentence explanation of what's wrong]

**Before:** (current code, abbreviated if long)
**After:** (refactored code)

**What changed:**
- Extracted `functionName()` — [what it does]
- Loop body reduced from N to M lines
- Eliminated N instances of duplicated logic
```

## Examples

### Example: Duplicated file-read-and-count pattern

**Before (REFACTOR):**

```ts
for (const item of items) {
  const mdxPath = join(itemPath, 'Documentation.mdx')
  if (existsSync(mdxPath)) {
    const content = readFileSync(mdxPath, 'utf8')
    const rawCount = countCanvasOf(content)
    const variantCount = rawCount === 0 ? 1 : rawCount
    components.push({ name: item, variantCount })
  }
  // ... later, same pattern repeated:
  for (const sub of subfolders) {
    const subMdxPath = join(itemPath, sub, 'Documentation.mdx')
    if (!existsSync(subMdxPath)) continue
    const content = readFileSync(subMdxPath, 'utf8')
    const rawCount = countCanvasOf(content)
    const variantCount = rawCount === 0 ? 1 : rawCount
    groupComponents.push({ name: sub, variantCount })
  }
}
```

**After:**

```ts
const readVariantCount = (mdxPath: string): number => {
  const raw = countCanvasOf(readFileSync(mdxPath, 'utf8'))
  return raw === 0 ? 1 : raw
}

const processItem = (itemName, categoryPath, excluded) => {
  if (excluded?.has(itemName)) return null
  const itemPath = join(categoryPath, itemName)
  const mdxPath = join(itemPath, 'Documentation.mdx')

  if (existsSync(mdxPath)) {
    return { type: 'component', data: { name: itemName, variantCount: readVariantCount(mdxPath) } }
  }

  const groupComponents = getDirectories(itemPath)
    .filter(sub => !excluded?.has(sub))
    .map(sub => {
      const subMdxPath = join(itemPath, sub, 'Documentation.mdx')
      if (!existsSync(subMdxPath)) return null
      return { name: sub, variantCount: readVariantCount(subMdxPath), parentFolder: itemName }
    })
    .filter(Boolean)
    .sort((a, b) => a.name.localeCompare(b.name))

  return groupComponents.length ? { type: 'group', data: groupComponents } : null
}

// Main loop — orchestrates only
for (const itemName of getDirectories(categoryPath)) {
  const result = processItem(itemName, categoryPath, excluded)
  if (!result) continue
  if (result.type === 'group') categories.push(result.data)
  else components.push(result.data)
}
```

### Example: Nested conditionals with mixed concerns

**Before (REFACTOR):**

```ts
for (const file of files) {
  if (file.endsWith('.ts')) {
    const content = fs.readFileSync(file, 'utf8')
    if (content.includes('export default')) {
      const name = path.basename(file, '.ts')
      if (!name.startsWith('_')) {
        const size = fs.statSync(file).size
        if (size < MAX_SIZE) {
          results.push({ name, size, path: file })
        }
      }
    }
  }
}
```

**After:**

```ts
const isEligibleModule = (file: string): boolean => {
  if (!file.endsWith('.ts')) return false
  if (path.basename(file, '.ts').startsWith('_')) return false
  if (fs.statSync(file).size >= MAX_SIZE) return false
  return fs.readFileSync(file, 'utf8').includes('export default')
}

const results = files.filter(isEligibleModule).map(file => ({
  name: path.basename(file, '.ts'),
  size: fs.statSync(file).size,
  path: file,
}))
```

## Constraints

- **DO** preserve exact behavior — refactoring must not change outputs or side effects
- **DO** name extracted functions after the concept, not the mechanics
- **DO** prefer pure functions (input -> output) for extracted helpers
- **DO** keep helpers close to their usage (same file, above the loop)
- **DO NOT** extract trivially simple operations (single property access, simple comparison)
- **DO NOT** create generic "utility" functions for one-off patterns
- **DO NOT** force functional style when imperative is clearer (e.g., complex accumulation with early exits)
- **DO NOT** refactor performance-critical hot paths without profiling first

## Output Format

```
## DRY Code Review — [filename]

### Findings

| # | Severity | Location | Issue |
|---|----------|----------|-------|
| 1 | REFACTOR | file.ts:24-58 | 3-level nested loop with duplicated MDX read logic |
| 2 | SUGGEST  | file.ts:72-85 | Similar filter+map pattern appears twice |

### Refactor 1: Extract `readVariantCount` and `processItem`

**Problem:** The MDX read-parse-count pattern is duplicated across the outer
and inner loops, and the 35-line loop body mixes I/O, filtering, and collection.

[before/after code blocks]

**Impact:** Loop body reduced from 35 to 5 lines. Duplicated logic eliminated.
Each helper has a single responsibility and is independently testable.
```

Related Skills

skill-tour

6
from mParticle/aquarium

Interactive guided tour of all available AI coding skills with live demos. Walks through headline capabilities, offers try-it-now demos, discovers repo-specific tools, and provides a cheat sheet reference. Triggers on what can you do, show skills, skill tour, available tools, capabilities, what skills.

publish-branch

6
from mParticle/aquarium

Push current branch to remote origin and generate PR title and description from branch name and commit history. Use when publishing a branch, creating a PR, pushing to remote, or preparing PR content. Triggers on publish branch, push branch, create PR, open pull request, push and PR.

pr

6
from mParticle/aquarium

Create a pull request from the current branch. Triggers on create PR/open PR/make PR/submit PR/push PR/raise PR/open a pull request/create a pull request/ready to merge/branch is ready when the user wants to turn their current branch into a GitHub pull request with a well-structured description

pr-review-handler

6
from mParticle/aquarium

Monitor PR review comments and automatically classify and address reviewer feedback including code changes, questions, and nits. Use when handling PR reviews, addressing reviewer comments, responding to code review feedback, or automating review resolution. Triggers on handle reviews, PR review, address feedback, reviewer comments, code review, review response.

jira-ticket-start

6
from mParticle/aquarium

Start work on a Jira ticket by fetching ticket details, creating a properly named feature branch, and beginning codebase investigation. Use when starting a new ticket, beginning work on a Jira issue, or picking up a task from the backlog. Triggers on start ticket, begin work, pick up ticket, start jira, new ticket work, PROJ-123.

jira-cli

6
from mParticle/aquarium

Jira ticket operations via Atlassian MCP including view, search (natural language to JQL), create, update, comment, and transition with auto-detection of ticket IDs from git branches. Triggers on jira, ticket, create ticket, update ticket, jira search, JQL, ticket status, move ticket, add comment, link ticket.

implement-ticket

6
from mParticle/aquarium

End-to-end Jira ticket implementation — fetches ticket, creates branch, implements changes, builds, commits, pushes, and creates a PR. Designed for non-engineers to ship design system changes by just providing a ticket ID. Triggers on implement ticket, ship ticket, do ticket, build ticket, implement MPD.

getting-started

6
from mParticle/aquarium

Analyze the current repo structure, build system, test setup, and conventions to provide a practical onboarding guide. Use when new to a codebase, joining a project, or wanting to understand how a repo is organized. Triggers on getting started, new to repo, onboard, how does this repo work, repo structure, codebase overview.

conventional-commit

6
from mParticle/aquarium

Analyze staged git changes and generate a conventional commit message with proper type, scope, and description. Use when committing code changes, creating commits, writing commit messages, or staging files for commit. Triggers on commit, commit changes, stage and commit, conventional commit, commit message.

commit-push-watch

6
from mParticle/aquarium

Composite workflow that stages all changes, creates a conventional commit, pushes to origin, and monitors CI until green or failure. Use when you want to commit and push in one step with CI monitoring. Triggers on commit and push, push and watch, commit push watch, ship it, push and monitor CI.

ci-watcher

6
from mParticle/aquarium

Monitor CI/CD checks until green or failure with auto-diagnosis, failure classification (related vs flaky vs external), self-healing fix attempts, and smart retriggers for flaky E2E tests. Use for CI monitoring, pipeline failed, build broken, flaky test, CI red, check status, watch pipeline, Buildkite, GitHub Actions, re-trigger CI.

add-rokt-icons

6
from mParticle/aquarium

Add Rokt/Untitled UI icons to the Aquarium library. Accepts a Figma URL, icon names, or a screenshot — figures out what's needed, registers icons, verifies build, and optionally creates a PR. Designed for designers. Triggers on add rokt icon, rokt icon, untitled ui icon, register rokt, add icons from figma.