---
name: fix-jscpd
description: Decision framework for fixing jscpd (copy-paste detector) errors. Use when asked to fix jscpd issues, copy-paste errors, clones or COPYPASTE lint failures.
user-invocable: false
---

# Fixing jscpd / Copy-Paste Issues

When jscpd reports a clone, decide between **factorization** and **ignore comments** based on these criteria.

## Decision Criteria

### Factorize when
- The duplicate logic is semantically identical and would not diverge (same intent, same data shape).
- Extracting it produces a clean, named helper with a clear single responsibility.
- The helper is small (under ~15 lines) and self-contained.
- The duplication is within the same file or a closely related module.

Examples in this project: `buildPersonaCols(n)` in profiles-extract.ts, `formatLimitLine(limit)` in limits.ts.

### Ignore with `/* jscpd:ignore-start */` / `/* jscpd:ignore-end */` when
- The clones are in separate, unrelated command files (each command is a standalone unit).
- The structure is the same but the data shapes or intent diverge (e.g., different result fields in different audit commands).
- The duplication is unavoidable boilerplate (standard flags: `agent`, `debug`, `websocket`, `skipauth`).
- Extracting would add indirection with no real benefit (e.g., `buildInitialMarkdownLines` in different DocBuilder subclasses).

## Ignore Comment Placement

Place `/* jscpd:ignore-start */` on the line **before** the first duplicate line, and `/* jscpd:ignore-end */` on the line **after** the last duplicate line. Keep the comments on their own lines, indented to match the surrounding code.

```typescript
/* jscpd:ignore-start */
for (const catcher of catchers) {
  const results = await catchMatches(catcher, file, fileText, this);
  this.matchResults.push(...results);
}
/* jscpd:ignore-end */
```

## Factorization Pattern

Add a private (or private static) helper method to the enclosing class, or a module-level function if the logic is pure and stateless.

```typescript
private static formatLimitLine(limit: any): string {
  return `• ${limit.name}: *${limit.percentUsed}%* used (${limit.used}/${limit.max})`;
}
```

Then replace the duplicate `.map(...)` blocks with `.map(MyClass.formatLimitLine)`.

## Standard Flag Boilerplate

The `agent`, `debug`, `websocket`, and `skipauth` flags appear in every command. These are always intentional duplicates - wrap them with jscpd:ignore rather than abstracting.
