TL;DR: Guard the visitor so it skips components that are already wrapped. Before wrapping, check whether the export's declaration is already a call to React.memo (or a bare memo import) and return the node unchanged if so. Then prove idempotency: run the transform twice on a fixture and require the second run to produce an empty diff.

The problem as reported:
```text
the agent's jscodeshift transform double-wrapped every component in React.memo on the second run - the transform wasnt idempotent and the agent couldnt run it twice
```

1. Open the transform and find the visitor that wraps components (usually on ExportDefaultDeclaration or FunctionDeclaration).
2. Add a guard function and call it before wrapping:
```js
function alreadyWrapped(decl) {
  if (decl.type !== 'CallExpression') return false;
  var c = decl.callee;
  if (c.type === 'MemberExpression' &&
      c.object.name === 'React' && c.property.name === 'memo') return true;
  if (c.type === 'Identifier' && c.name === 'memo') return true;
  return false;
}

root.find(j.ExportDefaultDeclaration).forEach(function(p) {
  var decl = p.node.declaration;
  if (alreadyWrapped(decl)) return;
  p.node.declaration = j.callExpression(
    j.memberExpression(j.identifier('React'), j.identifier('memo')),
    [decl]
  );
});
```
Expected: the transform reads cleanly and the guard covers both React.memo and bare memo call styles.
3. Run it twice on a sample file, then check the diff after the second run. Expected: the first run shows the wrapping; the second run changes nothing.
4. Add the double-run check to CI for every transform in the repo. Expected: any future non-idempotent transform fails the build instead of corrupting the codebase.

## Use this when
- A codemod produces different output when run twice.
- Components end up wrapped in memo(memo(...)) or similar doubled constructs.
- You need a transform to be safely re-runnable mid-migration.

## Not for this skill when
- The transform is a one-shot script you'll delete after the migration.
- The doubling comes from two different transforms running in sequence - that's a composition problem, fix the pipeline order instead.

## Variant phrasings
- jscodeshift transform not idempotent
- codemod runs twice different result
- React.memo applied twice by codemod
- transform matches its own output

## Why it happens
The matcher's predicate describes the pre-transform shape ("a default-exported function"), but the transform's output (a default-exported call expression wrapping that function) still satisfies a loose predicate, so every run wraps again.

## Edge cases
- memo imported under an alias: match on the import binding, not the call name.
- Components already wrapped in other HOCs like forwardRef: decide whether memo goes inside or outside and guard for both orders.
- Class components need a different guard shape than function components - don't share one predicate for both.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_V7S5R0CzsdTDwnhhl3JUeA
