← Public packages
@kentcdodds/codemod-runner
Run codemods over your own saved packages: scan, dry-run with real publish checks and diffs, apply, and revert.
AGENTS.md
134 lines · 4.1 KB · Markdown@kentcdodds/codemod-runner — agent notes
Human setup and intent live in README.md. This file is for
agents: imports, smoke/dry-run snippets, and edge cases. No user secrets.
Do not disable live webhooks or jobs.
Secrets
None.
Import paths
| Export | Import |
|---|---|
| scan | kody:@kentcdodds/codemod-runner/scan |
| dry-run | kody:@kentcdodds/codemod-runner/dry-run |
| apply | kody:@kentcdodds/codemod-runner/apply |
| revert | kody:@kentcdodds/codemod-runner/revert |
| runs | kody:@kentcdodds/codemod-runner/runs |
| contract | kody:@kentcdodds/codemod-runner/contract |
Prefer static kody:@kentcdodds/codemod-runner/... imports from execute.
Do not lead with packages.invoke.
Smoke / dryRun
Contract docs (no side effects):
import contract from 'kody:@kentcdodds/codemod-runner/contract'
export default async function main() {
return contract()
// => { exportName: 'codemod', input: '...', limits: {...} }
}Ledger list (read-only):
import runs from 'kody:@kentcdodds/codemod-runner/runs'
export default async function main() {
return await runs()
// => { runs: [...] }
}Canary dry-run (publishes nothing; needs a real codemod you own):
import dryRun from 'kody:@kentcdodds/codemod-runner/dry-run'
export default async function main() {
return await dryRun({
codemod: {
kodyId: 'my-codemod',
specifier: 'kody:@your-username/my-codemod',
},
packageIds: ['one-kody-id'],
limit: 1,
})
// page with returned runId + cursor until nextCursor is null
}Scan (read-only detect):
import scan from 'kody:@kentcdodds/codemod-runner/scan'
export default async function main() {
return await scan({
codemod: {
kodyId: 'my-codemod',
specifier: 'kody:@your-username/my-codemod',
},
packageIds: ['one-kody-id'],
limit: 1,
})
}Apply / revert only after a completed dry-run; keep runId for revert:
import apply from 'kody:@kentcdodds/codemod-runner/apply'
import revert from 'kody:@kentcdodds/codemod-runner/revert'
export default async function main() {
const result = await apply({
codemod: {
kodyId: 'my-codemod',
specifier: 'kody:@your-username/my-codemod',
},
packageIds: ['one-kody-id'],
limit: 1,
})
// later:
// return await revert({ revertOfRunId: result.runId })
return result
}Edge cases
- Every export is paged: keep calling with
runId+cursoruntilnextCursoris null. Interactive MCPexecutetimes out ~90s — do not try to finish a whole fleet in one call. - Drive large sweeps from
workflows.createwith anidempotencyKeyon the outer create so client timeouts recover without double-starting. - Canary first:
packageIds: ['one-kody-id']. Keeplimitsmall (scan default 1 / max 2; dry-run/apply default 1 / max 2). - Explicit
specifiermust name the samekodyIdunder its actual owner — the runner never guesses the codemod owner from the caller. - Optional
detectPattern(JS regex): zerorepo_searchhits → clean without reading the tree. applyrefuses until a dry-run for the same codemod completed. Transformed trees must pass real publish checks; non-idempotent transforms fail before write.- Drift: unpublished HEAD vs published commit → skip; revert skips packages republished after apply. Runner excludes itself and the codemod package.
- Statuses:
detected,clean,dry_run_ok,checks_failed,needs_manual,skipped_drift,skipped_unpublished,applied,reverted,failed. Runs are single-pass — start a new run to retry. - UTF-8 text only; empty files invisible; paths with whitespace cannot be deleted by a transform. Fix pre-existing check failures before apply.
- When the runner is wrong for a simple string rewrite and sweeps keep timing
out: batched
repo_searchdiscovery +packageGetGitRemoteclone/edit/push- one-at-a-time
packagePublishExternalPush. Polldispatchedpublishes viaworkflowRunList; readstatic_dependents.stale— Kody does not republish dependents.
- one-at-a-time
- Substring migrations: confirm must-keep holdouts with the user; put classification in the codemod; re-scan after apply.