Package codemods: formal migration system for user package source - #1049
Conversation
…odemod Adds a formal codemod system for migrating user package source when the platform package API changes: a pure detect/transform contract, an engine with scan/dry-run/apply/revert modes (drift + unpublished skips, no-new-failures publish gate, mechanical idempotency check, KV revert snapshots), a D1 run ledger, host-dispatched package.codemod.applied / package.codemod.reverted subscription events, and the first codemod (ambient storage import -> packageStorage()). Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Admin-only page + JSON routes to run codemods fleet-wide: paged run loop with live status counts, per-package results, run history, and revert on completed apply runs. Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Self-scoped package_codemod_* capabilities in the packages domain (own packages only) and admin-gated, audit-logged fleet admin_package_codemod_* capabilities in the admin domain. Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Contributor doc for authoring/running codemods with the rollout doctrine, docs map entry, package.codemod.* subscription topics, and admin capability list update. Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
|
Warning Review limit reached
Next review available in: 2 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (15)
📝 WalkthroughWalkthroughThe PR introduces versioned package codemods with scan, dry-run, apply, and revert modes. It adds the execution engine, D1 ledger, KV snapshots, subscription events, admin UI/API, MCP capabilities, the first storage migration codemod, tests, and supporting documentation. ChangesPackage codemod platform
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant Admin
participant AdminCodemodsAPI
participant runPackageCodemodStep
participant D1Ledger
participant KV
participant PackageSubscriptions
Admin->>AdminCodemodsAPI: POST codemod step
AdminCodemodsAPI->>runPackageCodemodStep: execute mode and page
runPackageCodemodStep->>D1Ledger: create or resume run
runPackageCodemodStep->>KV: save or load revert snapshot
runPackageCodemodStep->>D1Ledger: persist item status
runPackageCodemodStep->>PackageSubscriptions: dispatch applied or reverted event
AdminCodemodsAPI-->>Admin: return items and next cursor
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
- 0001 rewrites storage call sites to packageStorage() (no module-scope binding), AST-verifies no free storage identifiers remain, gates packages with app/service/job/subscription surfaces as needs_manual (storage bucket-identity hazard), and treats parse failures as needs_manual - revert snapshots are user-namespaced KV keys with 90-day TTL, recorded on ledger items (revert_snapshot_key); revert validates snapshot ownership - drift re-checked immediately before every publish; revert drift-checks against the apply item's afterCommit; failed publishes keep their itemId and snapshot key - ledger text columns bounded per backup-restore safety policy; run resume validates scope; user paging cursor comparison fixed; fleet paging bounds scanned pages and always advances; subscription fan-out cached per user and deferred via waitUntil; dry-run/apply/revert limits lowered to 5/10 - new-failure identity normalizes positions; fleet runs self-revertible for the caller's own items; re-revert marks source items reverted - /admin/codemods registered in the lazy-route admin area; ledger tables registered for account export/deletion; admin run steps audit-logged; apply/revert require explicit scope; run loop gains stop control, step ceiling, and stuck-cursor guard; docs reconciled Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
|
🔎 Preview deployed: https://kody-pr-1049.kody-a99.workers.dev Worker: Mocks:
|
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (18)
packages/worker/src/package-codemods/ledger.node.test.ts (1)
1-142: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winConsider adding coverage for the ledger's byte-truncation helpers.
ledger.tsexportspackageCodemodLedgerTextBoundsspecifically for testing truncation math, but this suite doesn't exercise it. Worth adding cases forboundTextColumn,boundJsonStringArray, andboundFindingsJson(oversized arrays/text, empty arrays, at-the-boundary sizes) given they guard D1 restore-safety limits — unless already covered elsewhere.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/worker/src/package-codemods/ledger.node.test.ts` around lines 1 - 142, Extend the ledger test suite to cover the exported packageCodemodLedgerTextBounds helpers: boundTextColumn, boundJsonStringArray, and boundFindingsJson. Add cases for oversized values, empty arrays, and inputs exactly at the configured byte limits, asserting truncation and preservation behavior according to the D1 restore-safety bounds; avoid duplicating coverage if equivalent tests already exist elsewhere.packages/worker/src/package-codemods/codemods/0001-ambient-storage-to-package-storage.ts (2)
254-290: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDrop the unused
storageImportStartsparameter.It is never read and is explicitly discarded via
void storageImportStarts; the caller only buildsimportStarts(Lines 521-525) to satisfy the signature.♻️ Proposed cleanup
-function hasStorageBindingSite( - parsed: AstNode, - storageImportStarts: Set<number>, -) { +function hasStorageBindingSite(parsed: AstNode) { let found = falseif (parent?.type === 'AssignmentPattern' && parent.left === node) { found = true } }) - void storageImportStarts return found }And at the call site:
- const importStarts = new Set<number>( - typeof target.declaration.start === 'number' - ? [target.declaration.start] - : [], - ) - if (hasStorageBindingSite(parsed, importStarts)) { + if (hasStorageBindingSite(parsed)) {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/worker/src/package-codemods/codemods/0001-ambient-storage-to-package-storage.ts` around lines 254 - 290, Remove the unused storageImportStarts parameter from hasStorageBindingSite and delete the void storageImportStarts statement. Update its caller to invoke the function without importStarts, and remove the now-unnecessary importStarts construction used only for this signature.
33-33: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRe-derive the scan predicates from
#worker/repo/checks.tsinstead of re-declaring them.
scannableModuleFilePatternandisTypeDeclarationFilePathmirror the internals ofcollectAmbientStorageImportFiles(packages/worker/src/repo/checks.ts:963-976).detect/transformcombine both sources, so any future change to the canonical pattern silently desynchronizes candidate selection here. Prefer exporting and reusing the shared predicates.Also applies to: 88-92
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/worker/src/package-codemods/codemods/0001-ambient-storage-to-package-storage.ts` at line 33, Update the codemod’s scan logic to reuse the canonical predicates from collectAmbientStorageImportFiles in checks.ts, exporting those predicates there if needed and removing the local scannableModuleFilePattern and isTypeDeclarationFilePath declarations. Ensure detect and transform use the shared predicates so candidate selection stays synchronized with the repository checks.packages/worker/src/package-codemods/engine.ts (2)
855-1019: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoffConsider flattening the revert item branches.
The success path is nested nine levels deep through sequential
if/elseguards (missing snapshot → ownership → saved package → afterCommit → drift → publish). Extracting arevertPriorItem()helper that returns early on each failure would make the control flow reviewable without changing behavior.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/worker/src/package-codemods/engine.ts` around lines 855 - 1019, The revert-item success path is excessively nested across sequential validation branches. Extract the logic from the surrounding revert flow into a revertPriorItem helper, returning immediately for missing snapshots, ownership mismatches, missing saved packages, absent afterCommit, detected drift, and publish failures while preserving each existing result and the successful projection refresh, event dispatch, and run-item update behavior.
123-135: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winNormalize snapshot keys and implement glob filtering before running checks.
createSnapshotFilesWorkspacepasses keys directly intorunRepoChecks, and that workflow normalizes global results beforereadFile, so leading/keys become unreadable.glob()also ignorespattern, so it can feed unrelated snapshot files into checks that depend on file-type globs. This shouldn’t block the gate here, but it makes the dry-run/apply workspace a lossy stand-in. Keep the map normalized and filterglob()output before returning matches.♻️ Suggested tightening
function createSnapshotFilesWorkspace(files: Record<string, string>) { + const normalized = new Map( + Object.entries(files).map(([path, contents]) => [ + normalizeRepoWorkspacePath(path), + contents, + ]), + ) return { async readFile(path: string) { - return files[normalizeRepoWorkspacePath(path)] ?? null + return normalized.get(normalizeRepoWorkspacePath(path)) ?? null }, - async glob(_pattern: string) { - return Object.keys(files).map((path) => ({ - path, - type: 'file' as const, - })) - }, + async glob(pattern: string) { + return [...normalized.keys()] + .filter((path) => matchesWorkspaceGlob(path, pattern)) + .map(( path, type: 'file' as const })) + }, } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/worker/src/package-codemods/engine.ts` around lines 123 - 135, Update createSnapshotFilesWorkspace to normalize all snapshot keys when constructing the workspace map, ensuring readFile can resolve paths after runRepoChecks normalization. Implement glob pattern filtering in its glob method so it returns only matching normalized snapshot files while preserving the existing file entry shape.packages/worker/src/package-codemods/codemods/0001-ambient-storage-to-package-storage.node.test.ts (2)
35-189: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winSplit the nine scenarios into separate
test()cases.A single assertion failure anywhere aborts the remaining scenarios, so a regression in the parse-failure or clean-package path is masked by an earlier failure. Each fixture block (plain, mixed, aliased, value-pass, app gate, comment/string traps, unparseable, clean) is already self-contained.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/worker/src/package-codemods/codemods/0001-ambient-storage-to-package-storage.node.test.ts` around lines 35 - 189, Split the combined test around ambientStorageToPackageStorageCodemod into separate test() cases for each self-contained fixture scenario: plain, mixed, aliased, value-pass, app gate, comment trap, string trap, unparseable, and clean. Preserve each scenario’s existing assertions and setup so failures identify the specific regression without preventing later cases from running.
111-125: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd cases for the remaining manual-gate branches.
Uncovered branches in
0001-ambient-storage-to-package-storage.tsthat all block a rewrite: missingpackage.jsonand unparseablepackage.json(Lines 395-412), twokody:runtimeimport declarations andexport { storage } from 'kody:runtime'(Lines 480-508), an ambient import with no member use (Line 541), and optional-chainedstorage?.get(). These are the safety gates protecting user source, so they deserve direct coverage.Also applies to: 157-178
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/worker/src/package-codemods/codemods/0001-ambient-storage-to-package-storage.node.test.ts` around lines 111 - 125, Add direct test cases in the ambientStorageToPackageStorageCodemod suite for every remaining rewrite-blocking branch: missing or unparseable package.json, duplicate kody:runtime imports, export { storage } from kody:runtime, unused ambient imports, and optional-chained storage?.get() usage. For each case, assert the transform is unchanged, records the expected manual-gate result, and preserves the original source files.packages/worker/src/package-codemods/engine.node.test.ts (1)
96-108: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueReuse
createEngineDb()and the static./ledger.tsimport in the bounds test.Lines 815-825 duplicate the sqlite + migration bootstrap already in
createEngineDb(), and Lines 826-827 dynamically import./ledger.ts, which is already statically imported at the top of the file.♻️ Proposed cleanup
- const sqlite = new DatabaseSync(':memory:') - sqlite.exec( - readFileSync( - new URL( - '../../migrations/0111-package-codemod-ledger.sql', - import.meta.url, - ), - 'utf8', - ), - ) - const db = createD1FromSqlite(sqlite) - const { createPackageCodemodRun, insertPackageCodemodRunItem } = - await import('./ledger.ts') + const db = createEngineDb()with
createPackageCodemodRunandinsertPackageCodemodRunItemadded to the top-level./ledger.tsimport.Also applies to: 815-827
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/worker/src/package-codemods/engine.node.test.ts` around lines 96 - 108, Update the bounds test around the relevant ledger setup to call the existing createEngineDb() helper instead of duplicating SQLite and migration initialization, and reuse the top-level static ./ledger.ts import by adding createPackageCodemodRun and insertPackageCodemodRunItem to it. Remove the dynamic import and redundant bootstrap code while preserving the test behavior.packages/worker/src/package-codemods/subscription-events.ts (2)
104-121: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
input.env as Envdefeats the narrowedPick<Env, …>contract.The signature promises only
APP_DBandBUNDLE_ARTIFACTS_KV, but the cast letsloadPackageManifestBySourceId(andinvokePackageSubscriptionat Line 184) read any binding, so a missing binding surfaces as a runtimeundefinedinstead of a type error. Either widen the parameter to the bindings actually required transitively, or narrow the callees' env types.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/worker/src/package-codemods/subscription-events.ts` around lines 104 - 121, Update the environment typing around the subscription codemod flow so the narrowed input contract cannot be bypassed by the `input.env as Env` cast. Ensure both `loadPackageManifestBySourceId` and `invokePackageSubscription` receive an environment type covering every binding they actually require, either by widening the relevant parameter type or narrowing those callees’ environment contracts, while preserving compile-time errors for missing bindings.
197-216: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winRetryable infrastructure responses are detected and then only logged — no retry, backoff, or dead-letter.
readPreExecutionPackageInvocationInfrastructureCodeis used to convert transient infra responses into a throw, but the sole consumer is theconsole.warnloop;dispatchPackageCodemodSubscriptionEventthen swallows everything and returns[]. Callers of a codemod apply/revert get no signal that a subscriber was never notified. Consider at least one bounded retry with backoff for the retryable class, or recording the failure alongside the ledger item so it can be replayed.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/worker/src/package-codemods/subscription-events.ts` around lines 197 - 216, The retryable infrastructure error thrown in the subscription invocation flow is only logged and then swallowed, so codemod callers receive no failure signal. Update dispatchPackageCodemodSubscriptionEvent and its settled-result handling to provide bounded retry with backoff for errors identified by readPreExecutionPackageInvocationInfrastructureCode, or persist those failures with the ledger item for replay; ensure unrecoverable failures are propagated rather than returning an empty success result.packages/worker/src/mcp/capabilities/packages/package-codemod-shared.ts (1)
70-122: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicated
cursor/limitfield definitions across step and revert schemas.
packageCodemodStepInputSchemaandpackageCodemodRevertInputSchemarepeat identicalrunId/cursor/limitblocks verbatim. Extracting a sharedpackageCodemodPagingInputSchemaand spreading/merging it into both would reduce drift risk if paging semantics change later.♻️ Suggested extraction
+const packageCodemodPagingFields = { + runId: z.string().min(1).optional().describe('Existing run id to continue; required with cursor when paging.'), + cursor: z.string().min(1).optional().describe('Opaque pagination cursor from a previous nextCursor value.'), + limit: z.number().int().min(1).max(50).optional().describe('Max packages to process in this step (default 20, max 50).'), +} + export const packageCodemodStepInputSchema = z.object({ codemodId: z.string().min(1).describe('Registered package codemod id from package_codemod_list.'), packageIds: z.array(z.string().min(1)).optional().describe('Optional saved package ids to limit this step.'), - runId: ..., - cursor: ..., - limit: ..., + ...packageCodemodPagingFields, })🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/worker/src/mcp/capabilities/packages/package-codemod-shared.ts` around lines 70 - 122, Extract the duplicated runId, cursor, and limit definitions into a shared packageCodemodPagingInputSchema, then merge that schema into both packageCodemodStepInputSchema and packageCodemodRevertInputSchema. Preserve the existing validation rules and descriptions while leaving each schema’s unique fields unchanged.packages/worker/src/mcp/capabilities/packages/package-codemod-scan.ts (1)
1-41: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winSix near-identical
defineDomainCapabilitywrappers differ only by mode/description/flags. Consider extracting a small factory to avoid drift between variants as this surface grows (e.g., a future revert-like capability, or a flag change applied inconsistently).
packages/worker/src/mcp/capabilities/packages/package-codemod-scan.ts#L1-L41: extract intocreatePackageCodemodStepCapability({ mode: 'scan', ... }).packages/worker/src/mcp/capabilities/packages/package-codemod-dry-run.ts#L1-L41: same factory,mode: 'dry-run'.packages/worker/src/mcp/capabilities/packages/package-codemod-apply.ts#L1-L41: same factory,mode: 'apply',destructive: true.packages/worker/src/mcp/capabilities/admin/admin-package-codemod-scan.ts#L1-L54: analogous fleet-scoped factory variant usingrunFleetPackageCodemodStepandauditAdminCapabilityInvocation.packages/worker/src/mcp/capabilities/admin/admin-package-codemod-dry-run.ts#L1-L55: same fleet factory,mode: 'dry-run'.packages/worker/src/mcp/capabilities/admin/admin-package-codemod-apply.ts#L1-L56: same fleet factory,mode: 'apply',destructive: true.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/worker/src/mcp/capabilities/packages/package-codemod-scan.ts` around lines 1 - 41, Extract the duplicated capability construction into a shared createPackageCodemodStepCapability factory, preserving the existing package capability metadata and handler while parameterizing mode, description, and destructive flags. Apply it to packages/worker/src/mcp/capabilities/packages/package-codemod-scan.ts lines 1-41, package-codemod-dry-run.ts lines 1-41, and package-codemod-apply.ts lines 1-41; the apply variant must retain destructive: true. Create the analogous fleet-scoped factory using runFleetPackageCodemodStep and auditAdminCapabilityInvocation, then apply it to packages/worker/src/mcp/capabilities/admin/admin-package-codemod-scan.ts lines 1-54, admin-package-codemod-dry-run.ts lines 1-55, and admin-package-codemod-apply.ts lines 1-56, retaining destructive: true for apply.packages/worker/src/app/handlers/admin-codemods.node.test.ts (2)
92-95: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueLint gate flags
beforeEach.
epic-web(prefer-dispose-in-tests)wants the mock reset expressed as disposable setup in each test body rather thanbeforeEach.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/worker/src/app/handlers/admin-codemods.node.test.ts` around lines 92 - 95, Replace the beforeEach setup in the admin codemod tests with disposable mock-reset setup inside each test body, removing the beforeEach hook. Preserve both vi.clearAllMocks() and logAuditEventSpy.mockClear() behavior for every test while satisfying the prefer-dispose-in-tests lint rule.Source: Linters/SAST tools
384-396: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winCoverage gap: revert with a
runIdbut norevertOfRunId.This asserts the happy rejection path only. Add a case posting
{ mode: 'revert', scope: 'fleet', runId: 'run-1' }— with the currentparseRunBodycondition inadmin-codemods.tsit is accepted and reaches the engine.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/worker/src/app/handlers/admin-codemods.node.test.ts` around lines 384 - 396, The admin codemod tests lack coverage for revert requests that include runId without revertOfRunId. Extend the tests around the existing missingRevert case to submit mode “revert”, scope “fleet”, and runId “run-1”, then assert the same 400 validation response and verify mockModule.runPackageCodemodStep is not called.packages/worker/client/routes/admin-codemods.tsx (2)
53-54: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueHardcoded paths duplicate the
routescontract.
/admin/codemods.json,/admin/codemods/run.json, and the pathname compared inisAdminCodemodsPathare string literals whileroutes.adminCodemods*already exists inpackages/worker/src/app/routes.ts(and this client tree importsrouteselsewhere). Deriving them keeps a future path rename from silently breaking this page.Also applies to: 64-66
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/worker/client/routes/admin-codemods.tsx` around lines 53 - 54, Replace the hardcoded admin codemod API paths and the pathname literal used by isAdminCodemodsPath with the corresponding routes.adminCodemods* contract values. Reuse the existing routes import pattern in this client tree, including the run endpoint, so all path comparisons and requests derive from the centralized route definitions.
730-780: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConfirm label hides the blast radius.
"Confirm apply" / "Confirm revert" reads the same whether the run is filtered to one package or the entire fleet. Including the effective scope (
fleetvs the parsed filter counts) in the confirm label or a sibling warning would make an accidental unfiltered fleet apply much harder.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/worker/client/routes/admin-codemods.tsx` around lines 730 - 780, Update the apply and revert confirmation UI in the selectedMode action buttons to show the effective run scope, distinguishing fleet-wide execution from the parsed package/filter counts. Use the existing scope or filter-count state used by runPagedCodemod, and include it in the confirmation label or an adjacent warning while preserving the current confirmation behavior.packages/worker/src/app/loader-data.ts (1)
218-259: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider narrowing
mode/statusto unions instead ofstring.
AdminCodemodRunListItem.modeandAdminCodemodRunItemListItem.statusarestringwhilerun.statususes a union. Reusing the engine/ledger literal unions ('scan' | 'dry-run' | 'apply' | 'revert') would let the UI's mode-dependent logic (e.g.run.mode === 'apply'revert gating inadmin-codemods.tsx) be type-checked.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/worker/src/app/loader-data.ts` around lines 218 - 259, Narrow AdminCodemodRunListItem.mode to the established engine/ledger mode union ('scan' | 'dry-run' | 'apply' | 'revert') and AdminCodemodRunItemListItem.status to its appropriate literal union, reusing existing type symbols where available. Keep the existing run.status union and ensure the loader data types support type-checking mode-dependent UI logic such as apply-mode revert gating.packages/worker/src/app/handlers/admin-codemods.ts (1)
151-189: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winNo audit event on failure, and every engine error becomes a 400.
Success is audited but the
catchreturnsgetErrorMessage(error)with a 400 and writes nothing to the ledger-adjacent audit trail. For a destructive fleet operation a failed/partialapplyis exactly what an operator needs recorded; infrastructure faults (D1/KV) also get misreported as client errors.♻️ Suggested shape
} catch (error) { + void logAuditEvent({ + category: 'admin', + action: 'package_codemod_run_step', + result: 'failure', + email: actor.email, + ip: getRequestIp(request) ?? undefined, + path: url.pathname, + reason: [ + `codemod_id=${parsed.codemodId}`, + `mode=${parsed.mode}`, + `scope=${formatScopeForAudit(parsed.scope)}`, + `error=${getErrorMessage(error)}`, + ].join(';'), + }) return jsonResponse({ ok: false, error: getErrorMessage(error) }, 400) }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/worker/src/app/handlers/admin-codemods.ts` around lines 151 - 189, Update the error path around runPackageCodemodStep to record a package_codemod_run_step audit event with result failure, including the actor, request IP/path, codemod ID, mode, scope, and error details. Return infrastructure or engine failures with an appropriate server-error status instead of always mapping them to 400, while preserving 400 for genuine client/input errors using the existing error classification utilities.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/worker/client/routes/admin-codemods.tsx`:
- Around line 475-491: Update handleRunSubmit so apply/revert submissions set
the confirmation hint and update the UI before returning, rather than silently
doing nothing; preserve the existing codemod validation and runPagedCodemod
behavior for other modes.
In `@packages/worker/src/account/data-targets.ts`:
- Around line 122-141: Update the package_codemod_runs cleanup flow around the
existing replace_user_column targets to redact deleted users’ identifiers from
filters_json, including documented userIds values, while preserving package
identifiers and retained ledger rows. Implement the JSON-aware removal or
enforce the ledger contract that filters contain no user-identifying values, and
add account deletion/export coverage verifying no deleted-user identifiers
remain.
In `@packages/worker/src/app/handlers/admin-codemods.ts`:
- Around line 277-279: Update the revert validation in
packages/worker/src/app/handlers/admin-codemods.ts:277-279 to reject whenever
revertOfRunId is missing, regardless of runId. Add a regression case in
packages/worker/src/app/handlers/admin-codemods.node.test.ts:384-396 posting
revert mode with only runId, asserting a 400 response and that
runPackageCodemodStep is not called.
- Around line 377-379: Update parseFilters and its early-return logic around
userIds/packageIds to distinguish omitted filter keys from supplied keys that
parse to zero usable IDs. For apply/revert flows, reject filters containing an
explicitly supplied empty or blank-only userIds or packageIds instead of
treating them as no filters and widening execution to the whole fleet; preserve
existing behavior for truly omitted keys.
In `@packages/worker/src/package-codemods/engine.ts`:
- Around line 267-297: Extend the existing validation in the runId branch of the
package codemod run flow to compare normalized input.filters and
initiatedByUserId with the values stored on existing, rejecting mismatches
before returning the run. Parse or normalize the persisted filtersJson using the
same representation as input.filters, and preserve the existing codemod, mode,
scope, and revertOfRunId checks.
- Around line 820-848: Bound the revert paging loop around
listPackageCodemodRunItems with the existing maxFleetPagesPerStep limit,
matching the forward path. Count fetched pages rather than matched items, and
when the ceiling is reached return the current cursor as nextCursor without
marking the run completed, so the caller can resume scanning.
In `@packages/worker/src/package-codemods/ledger.ts`:
- Around line 243-256: The ledger read methods getPackageCodemodRunById,
listPackageCodemodRunItems, and getPackageCodemodRunItemById must accept an
optional userId and apply it in their SQL WHERE clauses when provided. Update
the self-scoped request paths to pass the requesting user’s ID, while preserving
unscoped behavior for callers without a userId.
---
Nitpick comments:
In `@packages/worker/client/routes/admin-codemods.tsx`:
- Around line 53-54: Replace the hardcoded admin codemod API paths and the
pathname literal used by isAdminCodemodsPath with the corresponding
routes.adminCodemods* contract values. Reuse the existing routes import pattern
in this client tree, including the run endpoint, so all path comparisons and
requests derive from the centralized route definitions.
- Around line 730-780: Update the apply and revert confirmation UI in the
selectedMode action buttons to show the effective run scope, distinguishing
fleet-wide execution from the parsed package/filter counts. Use the existing
scope or filter-count state used by runPagedCodemod, and include it in the
confirmation label or an adjacent warning while preserving the current
confirmation behavior.
In `@packages/worker/src/app/handlers/admin-codemods.node.test.ts`:
- Around line 92-95: Replace the beforeEach setup in the admin codemod tests
with disposable mock-reset setup inside each test body, removing the beforeEach
hook. Preserve both vi.clearAllMocks() and logAuditEventSpy.mockClear() behavior
for every test while satisfying the prefer-dispose-in-tests lint rule.
- Around line 384-396: The admin codemod tests lack coverage for revert requests
that include runId without revertOfRunId. Extend the tests around the existing
missingRevert case to submit mode “revert”, scope “fleet”, and runId “run-1”,
then assert the same 400 validation response and verify
mockModule.runPackageCodemodStep is not called.
In `@packages/worker/src/app/handlers/admin-codemods.ts`:
- Around line 151-189: Update the error path around runPackageCodemodStep to
record a package_codemod_run_step audit event with result failure, including the
actor, request IP/path, codemod ID, mode, scope, and error details. Return
infrastructure or engine failures with an appropriate server-error status
instead of always mapping them to 400, while preserving 400 for genuine
client/input errors using the existing error classification utilities.
In `@packages/worker/src/app/loader-data.ts`:
- Around line 218-259: Narrow AdminCodemodRunListItem.mode to the established
engine/ledger mode union ('scan' | 'dry-run' | 'apply' | 'revert') and
AdminCodemodRunItemListItem.status to its appropriate literal union, reusing
existing type symbols where available. Keep the existing run.status union and
ensure the loader data types support type-checking mode-dependent UI logic such
as apply-mode revert gating.
In `@packages/worker/src/mcp/capabilities/packages/package-codemod-scan.ts`:
- Around line 1-41: Extract the duplicated capability construction into a shared
createPackageCodemodStepCapability factory, preserving the existing package
capability metadata and handler while parameterizing mode, description, and
destructive flags. Apply it to
packages/worker/src/mcp/capabilities/packages/package-codemod-scan.ts lines
1-41, package-codemod-dry-run.ts lines 1-41, and package-codemod-apply.ts lines
1-41; the apply variant must retain destructive: true. Create the analogous
fleet-scoped factory using runFleetPackageCodemodStep and
auditAdminCapabilityInvocation, then apply it to
packages/worker/src/mcp/capabilities/admin/admin-package-codemod-scan.ts lines
1-54, admin-package-codemod-dry-run.ts lines 1-55, and
admin-package-codemod-apply.ts lines 1-56, retaining destructive: true for
apply.
In `@packages/worker/src/mcp/capabilities/packages/package-codemod-shared.ts`:
- Around line 70-122: Extract the duplicated runId, cursor, and limit
definitions into a shared packageCodemodPagingInputSchema, then merge that
schema into both packageCodemodStepInputSchema and
packageCodemodRevertInputSchema. Preserve the existing validation rules and
descriptions while leaving each schema’s unique fields unchanged.
In
`@packages/worker/src/package-codemods/codemods/0001-ambient-storage-to-package-storage.node.test.ts`:
- Around line 35-189: Split the combined test around
ambientStorageToPackageStorageCodemod into separate test() cases for each
self-contained fixture scenario: plain, mixed, aliased, value-pass, app gate,
comment trap, string trap, unparseable, and clean. Preserve each scenario’s
existing assertions and setup so failures identify the specific regression
without preventing later cases from running.
- Around line 111-125: Add direct test cases in the
ambientStorageToPackageStorageCodemod suite for every remaining rewrite-blocking
branch: missing or unparseable package.json, duplicate kody:runtime imports,
export { storage } from kody:runtime, unused ambient imports, and
optional-chained storage?.get() usage. For each case, assert the transform is
unchanged, records the expected manual-gate result, and preserves the original
source files.
In
`@packages/worker/src/package-codemods/codemods/0001-ambient-storage-to-package-storage.ts`:
- Around line 254-290: Remove the unused storageImportStarts parameter from
hasStorageBindingSite and delete the void storageImportStarts statement. Update
its caller to invoke the function without importStarts, and remove the
now-unnecessary importStarts construction used only for this signature.
- Line 33: Update the codemod’s scan logic to reuse the canonical predicates
from collectAmbientStorageImportFiles in checks.ts, exporting those predicates
there if needed and removing the local scannableModuleFilePattern and
isTypeDeclarationFilePath declarations. Ensure detect and transform use the
shared predicates so candidate selection stays synchronized with the repository
checks.
In `@packages/worker/src/package-codemods/engine.node.test.ts`:
- Around line 96-108: Update the bounds test around the relevant ledger setup to
call the existing createEngineDb() helper instead of duplicating SQLite and
migration initialization, and reuse the top-level static ./ledger.ts import by
adding createPackageCodemodRun and insertPackageCodemodRunItem to it. Remove the
dynamic import and redundant bootstrap code while preserving the test behavior.
In `@packages/worker/src/package-codemods/engine.ts`:
- Around line 855-1019: The revert-item success path is excessively nested
across sequential validation branches. Extract the logic from the surrounding
revert flow into a revertPriorItem helper, returning immediately for missing
snapshots, ownership mismatches, missing saved packages, absent afterCommit,
detected drift, and publish failures while preserving each existing result and
the successful projection refresh, event dispatch, and run-item update behavior.
- Around line 123-135: Update createSnapshotFilesWorkspace to normalize all
snapshot keys when constructing the workspace map, ensuring readFile can resolve
paths after runRepoChecks normalization. Implement glob pattern filtering in its
glob method so it returns only matching normalized snapshot files while
preserving the existing file entry shape.
In `@packages/worker/src/package-codemods/ledger.node.test.ts`:
- Around line 1-142: Extend the ledger test suite to cover the exported
packageCodemodLedgerTextBounds helpers: boundTextColumn, boundJsonStringArray,
and boundFindingsJson. Add cases for oversized values, empty arrays, and inputs
exactly at the configured byte limits, asserting truncation and preservation
behavior according to the D1 restore-safety bounds; avoid duplicating coverage
if equivalent tests already exist elsewhere.
In `@packages/worker/src/package-codemods/subscription-events.ts`:
- Around line 104-121: Update the environment typing around the subscription
codemod flow so the narrowed input contract cannot be bypassed by the `input.env
as Env` cast. Ensure both `loadPackageManifestBySourceId` and
`invokePackageSubscription` receive an environment type covering every binding
they actually require, either by widening the relevant parameter type or
narrowing those callees’ environment contracts, while preserving compile-time
errors for missing bindings.
- Around line 197-216: The retryable infrastructure error thrown in the
subscription invocation flow is only logged and then swallowed, so codemod
callers receive no failure signal. Update
dispatchPackageCodemodSubscriptionEvent and its settled-result handling to
provide bounded retry with backoff for errors identified by
readPreExecutionPackageInvocationInfrastructureCode, or persist those failures
with the ledger item for replay; ensure unrecoverable failures are propagated
rather than returning an empty success result.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 34ae5180-f0ac-4bc8-9168-12cdbb0e5fb7
📒 Files selected for processing (42)
docs/contributing/adding-capabilities.mddocs/contributing/architecture/primitives.yamldocs/contributing/index.mddocs/contributing/package-codemods.mddocs/guides/package-subscriptions.mdpackages/worker/client/lazy-route.tsxpackages/worker/client/routes/account-management-components.tsxpackages/worker/client/routes/admin-area.tspackages/worker/client/routes/admin-codemods.tsxpackages/worker/client/routes/index.tsxpackages/worker/migrations/0111-package-codemod-ledger.sqlpackages/worker/src/account/data-targets.tspackages/worker/src/app/document-head.tspackages/worker/src/app/handlers/admin-codemods.node.test.tspackages/worker/src/app/handlers/admin-codemods.tspackages/worker/src/app/loader-data.tspackages/worker/src/app/router.tspackages/worker/src/app/routes.tspackages/worker/src/mcp/capabilities/admin/admin-package-codemod-apply.tspackages/worker/src/mcp/capabilities/admin/admin-package-codemod-capabilities.node.test.tspackages/worker/src/mcp/capabilities/admin/admin-package-codemod-dry-run.tspackages/worker/src/mcp/capabilities/admin/admin-package-codemod-revert.tspackages/worker/src/mcp/capabilities/admin/admin-package-codemod-scan.tspackages/worker/src/mcp/capabilities/admin/domain.tspackages/worker/src/mcp/capabilities/packages/domain.tspackages/worker/src/mcp/capabilities/packages/package-codemod-apply.tspackages/worker/src/mcp/capabilities/packages/package-codemod-capabilities.node.test.tspackages/worker/src/mcp/capabilities/packages/package-codemod-dry-run.tspackages/worker/src/mcp/capabilities/packages/package-codemod-list.tspackages/worker/src/mcp/capabilities/packages/package-codemod-revert.tspackages/worker/src/mcp/capabilities/packages/package-codemod-scan.tspackages/worker/src/mcp/capabilities/packages/package-codemod-shared.tspackages/worker/src/package-codemods/codemods/0001-ambient-storage-to-package-storage.node.test.tspackages/worker/src/package-codemods/codemods/0001-ambient-storage-to-package-storage.tspackages/worker/src/package-codemods/engine.node.test.tspackages/worker/src/package-codemods/engine.tspackages/worker/src/package-codemods/ledger.node.test.tspackages/worker/src/package-codemods/ledger.tspackages/worker/src/package-codemods/registry.tspackages/worker/src/package-codemods/subscription-events.tspackages/worker/src/package-codemods/types.tstools/migration-ledger.json
| if (input.runId) { | ||
| const existing = await getPackageCodemodRunById( | ||
| input.env.APP_DB, | ||
| input.runId, | ||
| ) | ||
| if (!existing) { | ||
| throw new Error(`Package codemod run "${input.runId}" was not found.`) | ||
| } | ||
| if (existing.codemodId !== input.codemodId) { | ||
| throw new Error( | ||
| `Package codemod run "${input.runId}" belongs to codemod "${existing.codemodId}", not "${input.codemodId}".`, | ||
| ) | ||
| } | ||
| if (existing.mode !== input.mode) { | ||
| throw new Error( | ||
| `Package codemod run "${input.runId}" is mode "${existing.mode}", not "${input.mode}".`, | ||
| ) | ||
| } | ||
| if (existing.scopeUserId !== scopeUserId) { | ||
| throw new Error( | ||
| `Package codemod run "${input.runId}" scope does not match the requested scope.`, | ||
| ) | ||
| } | ||
| const requestedRevertOf = input.revertOfRunId ?? null | ||
| if ((existing.revertOfRunId ?? null) !== requestedRevertOf) { | ||
| throw new Error( | ||
| `Package codemod run "${input.runId}" revertOfRunId does not match the requested value.`, | ||
| ) | ||
| } | ||
| return existing | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
Resume validates codemod/mode/scope/revertOf but not filters, so the ledger row can misdescribe the work performed.
A resumed step may pass arbitrary filters while filtersJson keeps the values recorded at creation. Since this table is the audit ledger for apply/revert, subsequent steps of the same run can process packages the recorded filters exclude. Consider comparing the normalized filters (and initiatedByUserId) against the stored run and rejecting mismatches.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/worker/src/package-codemods/engine.ts` around lines 267 - 297,
Extend the existing validation in the runId branch of the package codemod run
flow to compare normalized input.filters and initiatedByUserId with the values
stored on existing, rejecting mismatches before returning the run. Parse or
normalize the persisted filtersJson using the same representation as
input.filters, and preserve the existing codemod, mode, scope, and revertOfRunId
checks.
- admin revert requires revertOfRunId even when resuming with runId - supplied-but-empty filter arrays are rejected instead of widening destructive runs to the fleet - run resume validates normalized filters against the recorded run - revert paging is page-bounded like the forward fleet path, and user-scoped reverts filter items in SQL - ledger reads accept an optional userId scope for self-scoped paths - account deletion redacts deleted user ids inside filters_json - apply/revert Enter-key submit shows a confirmation hint instead of silently doing nothing Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
…ds-86c8 Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
…t:check) Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
…rt filters
- account deletion prefix-deletes package-codemod-revert:{userId}: keys in
BUNDLE_ARTIFACTS_KV
- revert applies the run's userIds/packageIds filters so canary reverts
work as expected
- docs: state that partial transforms are applied with needsManual
findings riding along
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
…ate MCP limit docs Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
| error: getErrorMessage(error), | ||
| }) | ||
| } | ||
| await persistItem(input.env, result, input.run.id) |
There was a problem hiding this comment.
Revert updates source before ledger
Medium Severity
On a successful revert, the engine marks the original apply ledger row reverted and republishes the package before inserting the new revert-run item. If persistItem fails afterward, the package is restored and the apply row no longer shows applied, but the revert run has no matching success row—retries against that apply run skip the package.
Reviewed by Cursor Bugbot for commit a530659. Configure here.
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
There are 2 total unresolved issues (including 1 from previous review).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 88a35e9. Configure here.
| search: quotedUserId, | ||
| replacement: quotedReplacement, | ||
| }, | ||
| } |
There was a problem hiding this comment.
JSON filter deletion corrupts ids
Medium Severity
Account deletion rewrites package_codemod_runs.filters_json via LIKE '%"<userId>"%' and REPLACE on the quoted id string. When another stored id contains that substring (e.g. deleting user-1 while user-10 is listed), the row matches and REPLACE mangles the longer id instead of leaving it unchanged.
Reviewed by Cursor Bugbot for commit 88a35e9. Configure here.
…xception (#1058) The privacy policy promised that private packages and their source never appear in any admin endpoint or payload, not even in redacted form. The package-codemod fleet surface (#1049) made that sentence inaccurate: scan/apply results expose package identity, affected file paths, and finding messages, and apply writes reviewed migrations into user source. Qualify the promise honestly on /privacy and docs/use/privacy.md with a new Platform maintenance section (reviewed in-repo transforms, dry-run gating, audit log, revert snapshots, codemod commits, applied/reverted events, no source contents in results), add the user-facing note to docs/use/packages.md, and make no-source-in-findings an explicit codemod authoring invariant in docs/contributing/package-codemods.md. Co-authored-by: Cursor Agent <cursoragent@cursor.com>


Adds a formal, safe way to migrate all user packages when the platform package API changes — the fleet-rewrite counterpart to D1 migrations, formalizing the pattern previously hardcoded in the username-scope rewrite.
What this adds
packages/worker/src/package-codemods/): versioned codemods (NNNN-kebab-name) exposing pure, deterministic, idempotentdetect(files)/transform(files)over a package's published file tree. Unconfident cases emitneedsManualfindings instead of guessing.runPackageCodemodStep) with four modes:scan— detect only (default 20 / max 50 packages per step).dry-run— transform in memory and run the full publish check suite (runRepoChecks) on both the original and transformed trees; no-new-failures gate (pre-existing check failures don't block, new ones do, with position-normalized failure identity); mechanical idempotency verification (double transform). Default 5 / max 10 per step.apply— same gates, then snapshots the original tree to user-namespaced KV (package-codemod-revert:{userId}:{itemId}, 90-day TTL, key recorded on the ledger item), re-checks drift immediately before publishing, republishes viasyncArtifactSourceSnapshotwith acodemod(<id>): …commit, refreshes the projection, and fires the subscription event.revert— republishes the KV-stored pre-codemod tree for a prior run's applied items, drift-checked against the apply item'safterCommit, with snapshot ownership validation; source items are marked reverted so re-reverts are no-ops.published_commitare skipped and reported (never overwritten); per-package failure isolation; paged, resumable, scope-validated runs; ledger text columns bounded per the backup-restore safety policy.0111):package_codemod_runs+package_codemod_run_items— audit trail, resumability, and revert targets. Registered for account export/deletion coverage.package.codemod.applied/package.codemod.revertedfan out (best-effort, deferred viawaitUntil) to the owning user's subscribed packages, mirroringrun.error.recorded./admin/codemods: fleet scan/dry-run/apply/revert with filters, live paged progress with stop control and stuck-cursor guard, per-package results, run history, and revert. Apply/revert require explicit scope; every run step is audit-logged.package_codemod_*in the packages domain (own packages only — any user can migrate their own packages, and self-revert their own items from a fleet run), and admin-gated, audit-loggedadmin_package_codemod_*fleet capabilities.0001-ambient-storage-to-package-storage: AST-based rewrite of the deprecated ambientstorageimport fromkody:runtimetopackageStorage()call-site rewrites (storage.get(…)→packageStorage().get(…), never a module-scope binding, which would freeze per-run state into reused isolates). Packages declaring apps/services/jobs/subscriptions/webhooks/retrievers are flaggedneedsManualinstead of transformed (ambient storage binds different buckets on those surfaces — repointing live data is not automatable). Post-rewrite AST verification ensures no freestorageidentifiers remain; parse failures flagneedsManual.docs/contributing/package-codemods.md(authoring guide + staged rollout doctrine), subscription topic docs, primitives map entry.Rollout doctrine (documented)
scan → fleet dry-run → canary apply via filters → fleet apply → land the enforcement lint for new publishes. The
static-first-package-modelbranch is expected to register its dynamic-import/invokeCheckedmigration as a codemod on top of this system instead of a manual migration.Review and verification
npm run validatefully green locally (format, lint, typecheck, node + workers unit, MCP E2E, Playwright E2E, backup build, migrations, primitives).userEmail); step limits bound the blast radius instead.System recap — adds a new primitive (high risk)
Mode: recap · Base:
main@60badbaf· Head:395db239Classification: adds — introduces the
package-codemodsprimitive (registered inprimitives.yaml); other touched primitives are composed or minimally extended.Primitives touched
package-codemodsd1-app-db0111addspackage_codemod_runs/_run_itemssaved-packagespackage_codemod_*capabilities call the engineapp-ui/admin/codemodspage + JSON routesrbacaccount-exportSystem map
Codemod runs flow from the admin UI and MCP capabilities into the new engine, which reads published package source, writes the D1 ledger and KV revert snapshots, republishes transformed trees, and fans out subscription events.
Legend: green = composes (wiring only) · amber = extended by this PR · red = new primitive · gray = context (unchanged, included only when an edge crosses it).
flowchart LR appUi["app-ui<br/>Browser app (Remix 3)"]:::touched rbac["rbac<br/>Role-based access control"]:::touched savedPackages["saved-packages<br/>Saved packages"]:::touched packageCodemods["package-codemods<br/>Package codemods"]:::added d1AppDb["d1-app-db<br/>D1 app database"]:::extended accountExport["account-export<br/>Account data export"]:::extended bundleKv["bundle-artifacts-kv<br/>Bundle artifacts KV"]:::untouched artifactsRepos["artifacts-repos<br/>Artifacts git repos"]:::untouched packageRuntime["package-runtime<br/>Package runtime"]:::untouched appUi -->|"/admin/codemods run.json paged steps (audit-logged)"| packageCodemods appUi -->|"requireUserWithRole('admin')"| rbac savedPackages -->|"package_codemod_* (self) + admin_package_codemod_* (audit-logged)"| packageCodemods packageCodemods -->|"package_codemod_runs / _run_items (0111)"| d1AppDb packageCodemods -->|"revert snapshots package-codemod-revert:{userId}:{itemId} (90d TTL)"| bundleKv packageCodemods -->|"republish codemod(id) commits via syncArtifactSourceSnapshot"| artifactsRepos packageCodemods -->|"package.codemod.applied/reverted subscription fan-out"| packageRuntime accountExport -->|"deletion + export coverage of ledger tables"| d1AppDb classDef touched fill:#1a7f37,color:#fff classDef extended fill:#9a6700,color:#fff classDef added fill:#cf222e,color:#fff classDef untouched fill:#57606a,color:#fffBefore / after
New tables:
package_codemod_runs,package_codemod_run_items(0111). New routes:GET /admin/codemods(.json),POST /admin/codemods/run.json. New capabilities:package_codemod_{list,scan,dry_run,apply,revert},admin_package_codemod_{scan,dry_run,apply,revert}. New subscription topics:package.codemod.applied,package.codemod.reverted.Invariants
Per-user isolation: self-scoped capabilities hard-code
scope = { kind: 'user', userId: caller }(no userId input); self-revert validates run ownership (fleet runs revert only the caller's own items); run resume validates scope; revert snapshots are user-namespaced and covered by account deletion. Fleet-wide scope exists only behind admin role gates with audit-logged invocations.Summary by CodeRabbit
New Features
Documentation
Tests