# AI Antipattern Detection Criteria

## Assumption Verification

AI often makes assumptions. Verify them.

| Check | Question |
|-------|----------|
| Requirements | Does the implementation match what was actually requested? |
| Context | Does it follow the existing codebase conventions? |
| Domain | Are business rules correctly understood? |
| Edge Cases | Did the AI consider realistic edge cases? |

Red flags:
- Implementation appears to answer a different question
- Uses patterns not found elsewhere in the codebase
- Overly generic solution for a specific problem

## Plausible-but-Wrong Detection

AI generates code that looks correct but is wrong.

| Pattern | Example |
|---------|---------|
| Syntactically correct but semantically wrong | Validation that checks format but misses business rules |
| Hallucinated APIs | Calling methods that don't exist in the library version being used |
| Stale patterns | Using deprecated approaches from training data |
| Over-engineering | Adding unnecessary abstraction layers for the task |
| Under-engineering | Missing error handling for realistic scenarios |
| Forgotten wiring | Mechanism is implemented but not passed from entry points |

Verification approach:
1. Can this code actually compile/run?
2. Do the imported modules/functions exist?
3. Is the API used correctly for this library version?
4. If new parameters/fields were added, are they actually passed from callers?
   - AI often implements correctly within individual files but forgets cross-file wiring
   - Grep to check if `options.xxx ?? fallback` always uses the fallback

## Copy-Paste Pattern Detection

AI often repeats the same patterns, including mistakes.

| Check | Action |
|-------|--------|
| Repeated dangerous patterns | Same vulnerability in multiple places |
| Inconsistent implementation | Same logic implemented differently across files |
| Boilerplate explosion | Unnecessary repetition that could be abstracted |

## Redundant Conditional Branch Detection

AI tends to generate if/else blocks that call the same function with only argument differences.

| Pattern | Example | Verdict |
|---------|---------|---------|
| Branch differs only in argument presence | `if (x) f(a, b, c) else f(a, b)` | REJECT |
| Branch differs only in options | `if (x) f(a, {opt: x}) else f(a)` | REJECT |
| Redundant else without using return value | `if (x) { f(a, x); return; } f(a);` | REJECT |

```typescript
// REJECT - both branches call the same function, differing only in the 3rd argument
if (options.format !== undefined) {
  await processFile(input, output, { format: options.format });
} else {
  await processFile(input, output);
}

// OK - extract the conditional into a variable, then make a single call
const formatOpt = options.format !== undefined ? { format: options.format } : undefined;
await processFile(input, output, formatOpt);
```

Verification approach:
1. Find if/else blocks calling the same function
2. If the only difference is optional argument presence, unify with ternary or spread syntax
3. If branches have different preprocessing, store results in a variable and make a single call

## Callback + External Variable Capture Abuse

AI tends to implement data retrieval via callbacks and external variable capture when return values would suffice.

| Pattern | Example | Verdict |
|---------|---------|---------|
| Assign to external variable in callback | `let result; await f(x => { result = x })` | REJECT |
| Get value via event handler | `emitter.on('data', d => { captured = d })` to synchronously get value | REJECT |
| Build state across multiple callbacks | `forEach(item => { externalMap.set(...) })` to construct result | REJECT |

```typescript
// REJECT - Capturing external variable via callback
let selectedMode: string | undefined;
await promptUser(choices, (choice) => {
  selectedMode = choice;
});
return selectedMode;

// OK - Receive via return value
const selectedMode = await promptUser(choices);
return selectedMode;
```

Verification approach:
1. Find places where callback functions assign to variables in the outer scope
2. Check if the value can be returned as a function return value
3. If possible, flag for rewriting to the return-value pattern

## Inappropriate Response to Review Findings

AI sometimes "addresses" review findings by adding tests or documentation that "verify the finding" instead of actually fixing the code.

| Pattern | Example | Verdict |
|---------|---------|---------|
| Adding tests instead of fixing | "Remove unnecessary comments" → adds tests verifying comment presence | REJECT |
| Adding docs instead of fixing | "DRY violation" → adds documentation explaining duplication is intentional | REJECT |
| Changing unrelated files | Security finding → performs unrelated refactoring | REJECT |

Verification approach:
1. Check if the fix diff includes changes to the finding's target file and target lines
2. If the fix consists only of new file additions, check whether those files "fix" the issue or merely "verify" it
3. If tests are added as part of the fix, verify they test "correct behavior after the fix" (not "the finding itself")

## Context Fitness Assessment

Does the code fit this specific project?

| Aspect | Verification |
|--------|-------------|
| Naming conventions | Matches existing codebase style |
| Error handling style | Consistent with project patterns |
| Logging approach | Uses project's logging conventions |
| Test style | Matches existing test patterns |

Questions to ask:
- Would a developer familiar with this codebase write it this way?
- Does it feel like it belongs here?
- Are there unexplained deviations from project conventions?

## Scope Creep Detection

AI tends to over-deliver. Check for unnecessary additions.

| Check | Problem |
|-------|---------|
| Extra features | Functionality not requested |
| Premature abstraction | Interfaces/abstractions for single implementations |
| Over-configuration | Making things configurable that don't need to be |
| Gold-plating | "Nice-to-have" additions not asked for |
| Extra changes disguised as related work | Cleanup, renames, or moves justified only because they are near the edited code |
| Incidental observable contract changes | Changing values observed by users or tests without being asked |
| Unnecessary legacy support | Adding mapping/normalization logic for old values without explicit instruction |

The best code is the minimum code that solves the problem.

### Extra Changes Disguised as Related Work

AI often justifies unnecessary cleanup as related work because it is "in the touched file", "near the same responsibility", or "more idiomatic". Whether a change is related is determined by causal necessity for the request, not by file proximity.

| Pattern | Verdict |
|---------|---------|
| Renaming, moving, or responsibility changes justified only because the file was touched | REJECT |
| Changing Props type names, return shapes, or public function names without direct implementation need | REJECT |
| Deleting existing comments after assuming they are merely explanatory | REJECT |
| Removing or weakening tested existing behavior just to make tests pass | REJECT |
| Mixing framework-style improvements without explicit request | REJECT |
| Adding parameters or updating call sites required to wire the new feature | OK |
| Deleting old implementation that is genuinely no longer used | OK |

Verification approach:
1. Obtain the entire cumulative diff from the task's starting point (the base) and list renames, moves, deletions, responsibility changes, and test expectation changes (do not look only at the most recent fix; unrelated changes buried in earlier iterations do not appear in the latest fix report)
2. For each change, ask whether the request fails without it
3. Do not accept "more readable", "more idiomatic", or "cleanup while here" as necessity
4. Revert changes whose necessity cannot be explained before completing the task

### Incidental Observable Contract Changes

AI often changes existing contracts under the banner of "improvement", "standardization", or "clarity" even when the task does not require it. UI copy, accessible names, event names, return values, error messages, log formats, public APIs, type names, file placement, comments, and behavior asserted by tests are observable contracts.

| Pattern | Verdict |
|---------|---------|
| Contract change unrelated to the request | REJECT |
| Tests are updated only to follow the new contract | REJECT |
| New contract required by new functionality | OK |
| Missing information is added while preserving the existing contract | OK |
| Reason, impact scope, and migration path for the contract change are explicit | OK |

Verification approach:
1. Inspect changed strings, attributes, event names, return values, error messages, and log formats in the diff
2. Check whether each one is directly required by the task
3. If test expectations merely follow implementation changes, check whether the original contract can be preserved
4. If the contract change is necessary, verify that reason and impact scope are explained

Legacy support criteria:
- Unless explicitly instructed to "support legacy values" or "maintain backward compatibility", legacy support is unnecessary
- Do not add `.transform()` normalization, `LEGACY_*_MAP` mappings, or `@deprecated` type definitions
- Support only new values and keep it simple

### Over-Abstracting with Function Objects

AI often turns a small number of concrete branches into config arrays, function objects, and generic loops to make the code look "extensible". The problem is not Strategy itself; the problem is hiding differences in data without naming the concept. A Strategy is useful when it names a domain concept and makes the replacement boundary explicit.

| Pattern | Example | Verdict |
|---------|---------|---------|
| Single-use operation config array | Processing `[{ kind, fields, removedFields }]` in a loop | REJECT |
| Deletions, side effects, or exception cases are hidden in config objects | Readers must inspect config values to find destructive behavior | REJECT |
| Function object introduced when each branch is only 1-3 lines | `handlers[type]()` adds indirection only | REJECT |
| Strategy represents a domain concept and clarifies the implementation boundary | `TaxPolicy`, `PaymentMethod`, `RetryStrategy` | OK |
| Many branches share the same shape and are expected to grow | Consider a handler map | OK |

Verification approach:
1. Grep usage sites for added arrays, Maps, Strategies, or function objects
2. If used in only one place, check whether explicit branching would be clearer
3. Check whether side effects, deleted fields, or compatibility behavior are hidden in config objects
4. Prefer `when` / `switch` when branch names sufficiently express domain meaning
5. Allow Strategy when naming the concept improves understanding

## Premature Caching Strategy Introduction

AI tends to proactively introduce caching mechanisms to "improve" performance. Do not add caching strategies until explicitly requested.

| Pattern | Example | Verdict |
|---------|---------|---------|
| Adding cache layer to data fetching | stale-while-revalidate config, in-memory cache, Redis introduction | REJECT |
| Saving cache locally | Storing API responses in local storage or files for reuse | REJECT |
| Memoization without measurement | Excessive memoization without identifying bottlenecks | REJECT |
| Custom cache invalidation implementation | Adding TTL, cache key management, purge mechanisms | REJECT |

Decision criteria: Is there an explicit request or measurement result indicating "caching is needed"?
- YES → OK to implement
- NO → Do not implement. Naive data fetching is sufficient

## Dead Code Detection

AI adds new code but often forgets to remove code that is no longer needed.

| Pattern | Example |
|---------|---------|
| Unused functions/methods | Old implementations remaining after refactoring |
| Unused variables/constants | Definitions no longer needed after condition changes |
| Unreachable code | Processing remaining after early returns, always-true/false conditions |
| Logically unreachable defensive code | Branches that never execute due to caller constraints |
| Unused imports/dependencies | Import statements or package dependencies for removed features |
| Orphaned exports/public APIs | Re-exports or index registrations remaining after implementation is removed |
| Unused interfaces/type definitions | Old types remaining after implementation changes |
| Disabled code | Code left commented out |

Logical dead code detection:

AI tends to add "just in case" defensive code, but when considering caller constraints, it may be unreachable. Code that is syntactically reachable but logically unreachable due to call chain preconditions should be removed.

```typescript
// REJECT - callers always require interactive input
// This function is never called from non-interactive environments
function displayResult(data: ResultData): void {
  const isInteractive = process.stdin.isTTY === true;
  // isInteractive is always true (callers assume TTY)
  const output = isInteractive ? formatRich(data) : formatPlain(data);  // else branch is unreachable
}

// OK - understands caller constraints and removes unnecessary branching
function displayResult(data: ResultData): void {
  // Only called from interactive menus, so TTY is always present
  console.log(formatRich(data));
}
```

Verification approach:
1. When finding defensive branches, grep to check all callers of the function
2. If all callers already satisfy the condition, the defense is unnecessary
3. Grep to confirm no references to changed/deleted code remain
4. Verify that public module (index files, etc.) export lists match actual implementations
5. Check that no old code remains corresponding to newly added code

## Fallback/Default Argument Overuse Detection

AI overuses fallbacks and default arguments to hide uncertainty.

| Pattern | Example | Verdict |
|---------|---------|---------|
| Fallback on required data | `user?.id ?? 'unknown'` | REJECT |
| Default argument overuse | `function f(x = 'default')` where all callers omit it | REJECT |
| Nullish coalescing with no input path | `options?.cwd ?? process.cwd()` with no way to pass from above | REJECT |
| try-catch returning empty | `catch { return ''; }` | REJECT |
| Multi-level fallback | `a ?? b ?? c ?? d` | REJECT |
| Silent ignore in conditionals | `if (!x) return;` silently skipping what should be an error | REJECT |

Verification approach:
1. Grep the diff for `??`, `||`, `= defaultValue`, `catch`
2. For each fallback/default argument:
   - Is it required data? -> REJECT
   - Do all callers omit it? -> REJECT
   - Is there a path to pass the value from above? -> If not, REJECT
3. REJECT if any fallback/default argument exists without justification

## Unused Code Detection

AI tends to generate unnecessary code for "future extensibility", "symmetry", or "just in case". Code not currently called from anywhere should be removed.

| Verdict | Criteria |
|---------|----------|
| REJECT | Public functions/methods not called from anywhere currently |
| REJECT | Setters/getters created "for symmetry" but not used |
| REJECT | Interfaces or options prepared for future extension |
| REJECT | Exported but no usage found via grep |
| OK | Implicitly called by framework (lifecycle hooks, etc.) |

Verification approach:
1. Grep to confirm no references to changed/deleted code remain
2. Verify that public module (index files, etc.) export lists match actual implementations
3. Check that no old code remains corresponding to newly added code

## Unnecessary Backward Compatibility Code Detection

AI tends to leave unnecessary code "for backward compatibility". Don't miss this.

Code to remove:

| Pattern | Example | Verdict |
|---------|---------|---------|
| deprecated + no usage | `@deprecated` annotation with no one using it | Remove immediately |
| Both old and new APIs exist | Old function remains alongside new function | Remove old, unless both have active usage sites |
| Completed migration wrapper | Wrapper created for compatibility but migration is complete | Remove |
| Comment says "remove later" | `// TODO: remove after migration` left abandoned | Remove now |
| Excessive proxy/adapter usage | Complexity added solely for backward compatibility | Replace simply |

Code to keep:

| Pattern | Example | Verdict |
|---------|---------|---------|
| Externally published API | npm package exports | Consider carefully |
| Config file compatibility | Can read old format config | Maintain until major version |
| During data migration | In the middle of DB schema migration | Maintain until complete |

Decision criteria:
1. Are there usage sites? -> Verify with grep/search. Remove if none
2. Do both old and new have usage sites? -> If both are currently in use, this may be intentional coexistence rather than backward compatibility. Check callers
3. Is it externally published? -> Can remove immediately if internal only
4. Is migration complete? -> Remove if complete

When AI says "for backward compatibility", be skeptical. Verify if it's truly necessary.

## Decision Traceability Review

Verify that the Coder's decision log is valid.

| Check | Question |
|-------|----------|
| Decision is documented | Are non-obvious choices explained? |
| Rationale is sound | Does the reasoning make sense? |
| Alternatives considered | Were other approaches evaluated? |
| Assumptions explicit | Are assumptions explicit and reasonable? |
