Skip to content

Package codemods: formal migration system for user package source - #1049

Merged
kody-bot merged 14 commits into
mainfrom
cursor/package-codemods-86c8
Jul 30, 2026
Merged

kody-bot merged 14 commits into
mainfrom
cursor/package-codemods-86c8

Conversation

@kentcdodds

@kentcdodds kentcdodds commented Jul 30, 2026 •

Copy link
Copy Markdown
Owner

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

  • Codemod contract + registry (packages/worker/src/package-codemods/): versioned codemods (NNNN-kebab-name) exposing pure, deterministic, idempotent detect(files) / transform(files) over a package's published file tree. Unconfident cases emit needsManual findings instead of guessing.
  • Engine (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 via syncArtifactSourceSnapshot with a codemod(<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's afterCommit, with snapshot ownership validation; source items are marked reverted so re-reverts are no-ops.
  • Safety rails: unpublished packages skipped; packages whose repo HEAD differs from published_commit are skipped and reported (never overwritten); per-package failure isolation; paged, resumable, scope-validated runs; ledger text columns bounded per the backup-restore safety policy.
  • Run ledger (migration 0111): package_codemod_runs + package_codemod_run_items — audit trail, resumability, and revert targets. Registered for account export/deletion coverage.
  • Subscription events: host-dispatched package.codemod.applied / package.codemod.reverted fan out (best-effort, deferred via waitUntil) to the owning user's subscribed packages, mirroring run.error.recorded.
  • Admin UI at /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.
  • MCP capabilities: self-scoped 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-logged admin_package_codemod_* fleet capabilities.
  • Proving codemod 0001-ambient-storage-to-package-storage: AST-based rewrite of the deprecated ambient storage import from kody:runtime to packageStorage() 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 flagged needsManual instead of transformed (ambient storage binds different buckets on those surfaces — repointing live data is not automatable). Post-rewrite AST verification ensures no free storage identifiers remain; parse failures flag needsManual.
  • Docs: 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-model branch is expected to register its dynamic-import/invokeChecked migration as a codemod on top of this system instead of a manual migration.

Review and verification

  • npm run validate fully green locally (format, lint, typecheck, node + workers unit, MCP E2E, Playwright E2E, backup build, migrations, primitives).
  • An independent review pass ran on the full diff; all critical and important findings were addressed (module-scope binding hazard, storage bucket repointing, KV snapshot namespacing/TTL/deletion coverage, ledger text bounds, drift re-checks, paging correctness, audit logging, run-scope validation). One deliberate skip: self-scoped apply does not reuse the publish entitlement guard (it is coupled to projection deltas and userEmail); step limits bound the blast radius instead.
  • Manual demo against a seeded local dev server:

admin_codemods_dry_run_click_to_history_demo.mp4

/admin/codemods with a completed run and details view

System recap — adds a new primitive (high risk)

Mode: recap · Base: main @ 60badbaf · Head: 395db239

Classification: adds — introduces the package-codemods primitive (registered in primitives.yaml); other touched primitives are composed or minimally extended.

Primitives touched

Primitive Group Impact
package-codemods assistant adds — engine, registry, ledger, revert snapshots, subscription events
d1-app-db storage extends — migration 0111 adds package_codemod_runs / _run_items
saved-packages assistant composes — new package_codemod_* capabilities call the engine
app-ui surfaces composes — new /admin/codemods page + JSON routes
rbac auth composes — existing admin role gates + audit-logged admin capabilities
account-export assistant extends — ledger tables registered for export/deletion coverage

System 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:#fff
Loading

Before / after

Before: fleet package-source migrations were one-off hardcoded paths
        (username-scope rewrite) or manual; no dry-run, ledger, or revert.
After:  registered codemods run as scan | dry-run | apply | revert with
        publish-check gates, drift re-checks, a D1 run ledger, TTL'd KV
        revert snapshots, admin UI, and user/admin MCP capabilities.

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.

Open in Web Open in Cursor 

Summary by CodeRabbit

  • New Features

    • Added package codemod support with scan, dry-run, apply, and revert modes.
    • Added an admin Codemods interface with filtering, live results, run history, and revert actions.
    • Added self-service and administrative automation capabilities for package migrations.
    • Added execution tracking, pagination, safety checks, drift handling, and revert snapshots.
    • Added subscription events for successful codemod applications and reversions.
  • Documentation

    • Added authoring, architecture, operations, and subscription documentation for package codemods.
  • Tests

    • Added coverage for codemod transformations, execution workflows, permissions, persistence, paging, and reverts.

cursoragent and others added 5 commits July 30, 2026 11:49
…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>
@coderabbitai

coderabbitai Bot commented Jul 30, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@cursor[bot], you've reached your PR review limit, so we couldn't start this review.

Next review available in: 2 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d85afce3-56bb-4b73-8a51-37bdb1ed34ec

📥 Commits

Reviewing files that changed from the base of the PR and between 395db23 and 88a35e9.

📒 Files selected for processing (15)
  • .github/workflows/backfill-mcp-agent-sessions.yml
  • docs/contributing/package-codemods.md
  • packages/worker/client/routes/admin-codemods.tsx
  • packages/worker/src/account/data-targets.node.test.ts
  • packages/worker/src/account/data-targets.ts
  • packages/worker/src/app/account-deletion.node.test.ts
  • packages/worker/src/app/account-deletion.ts
  • packages/worker/src/app/handlers/admin-codemods.node.test.ts
  • packages/worker/src/app/handlers/admin-codemods.ts
  • packages/worker/src/app/loader-data.ts
  • packages/worker/src/mcp/capabilities/packages/package-codemod-shared.ts
  • packages/worker/src/package-codemods/engine.node.test.ts
  • packages/worker/src/package-codemods/engine.ts
  • packages/worker/src/package-codemods/ledger.node.test.ts
  • packages/worker/src/package-codemods/ledger.ts
📝 Walkthrough

Walkthrough

The 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.

Changes

Package codemod platform

Layer / File(s) Summary
Codemod contracts and ledger
packages/worker/src/package-codemods/types.ts, packages/worker/src/package-codemods/ledger.ts, packages/worker/migrations/*, packages/worker/src/package-codemods/registry.ts
Defines codemod interfaces, registry lookup, bounded D1 ledger records, pagination, and migration tables.
Codemod engine and subscription events
packages/worker/src/package-codemods/*, docs/contributing/package-codemods.md
Implements the ambient-storage transform, run modes, safety gates, snapshots, revert processing, and applied/reverted subscription dispatch.
MCP capability surfaces
packages/worker/src/mcp/capabilities/{packages,admin}/*
Registers caller-scoped and admin fleet capabilities for listing, scanning, dry-running, applying, and reverting codemods.
Admin HTTP and UI interface
packages/worker/src/app/handlers/admin-codemods.ts, packages/worker/client/routes/admin-codemods.tsx, packages/worker/src/app/routes.ts
Adds authenticated admin endpoints and a paged UI for running codemods, viewing results and history, and initiating reverts.
Documentation and data governance
docs/contributing/*, docs/guides/package-subscriptions.md, packages/worker/src/account/data-targets.ts
Documents codemod authoring, rollout, revert behavior, subscription envelopes, and account cleanup/export handling for ledger data.

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
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 1.03% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: a formal package codemod migration system for user package source.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cursor/package-codemods-86c8

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

- 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>
@kody-bot
kody-bot marked this pull request as ready for review July 30, 2026 12:44
@github-actions

github-actions Bot commented Jul 30, 2026 •

Copy link
Copy Markdown
Contributor

🔎 Preview deployed: https://kody-pr-1049.kody-a99.workers.dev

Worker: kody-pr-1049
D1: kody-pr-1049-db
KV: kody-pr-1049-oauth-kv

Mocks:

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (18)
packages/worker/src/package-codemods/ledger.node.test.ts (1)

1-142: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Consider adding coverage for the ledger's byte-truncation helpers.

ledger.ts exports packageCodemodLedgerTextBounds specifically for testing truncation math, but this suite doesn't exercise it. Worth adding cases for boundTextColumn, boundJsonStringArray, and boundFindingsJson (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 value

Drop the unused storageImportStarts parameter.

It is never read and is explicitly discarded via void storageImportStarts; the caller only builds importStarts (Lines 521-525) to satisfy the signature.

♻️ Proposed cleanup
-function hasStorageBindingSite(
-	parsed: AstNode,
-	storageImportStarts: Set<number>,
-) {
+function hasStorageBindingSite(parsed: AstNode) {
 	let found = false
 		if (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 win

Re-derive the scan predicates from #worker/repo/checks.ts instead of re-declaring them.

scannableModuleFilePattern and isTypeDeclarationFilePath mirror the internals of collectAmbientStorageImportFiles (packages/worker/src/repo/checks.ts:963-976). detect/transform combine 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 tradeoff

Consider flattening the revert item branches.

The success path is nested nine levels deep through sequential if/else guards (missing snapshot → ownership → saved package → afterCommit → drift → publish). Extracting a revertPriorItem() 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 win

Normalize snapshot keys and implement glob filtering before running checks.

createSnapshotFilesWorkspace passes keys directly into runRepoChecks, and that workflow normalizes global results before readFile, so leading / keys become unreadable. glob() also ignores pattern, 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 filter glob() 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 win

Split 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 win

Add cases for the remaining manual-gate branches.

Uncovered branches in 0001-ambient-storage-to-package-storage.ts that all block a rewrite: missing package.json and unparseable package.json (Lines 395-412), two kody:runtime import declarations and export { storage } from 'kody:runtime' (Lines 480-508), an ambient import with no member use (Line 541), and optional-chained storage?.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 value

Reuse createEngineDb() and the static ./ledger.ts import 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 createPackageCodemodRun and insertPackageCodemodRunItem added to the top-level ./ledger.ts import.

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 Env defeats the narrowed Pick<Env, …> contract.

The signature promises only APP_DB and BUNDLE_ARTIFACTS_KV, but the cast lets loadPackageManifestBySourceId (and invokePackageSubscription at Line 184) read any binding, so a missing binding surfaces as a runtime undefined instead 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 win

Retryable infrastructure responses are detected and then only logged — no retry, backoff, or dead-letter.

readPreExecutionPackageInvocationInfrastructureCode is used to convert transient infra responses into a throw, but the sole consumer is the console.warn loop; dispatchPackageCodemodSubscriptionEvent then 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 win

Duplicated cursor/limit field definitions across step and revert schemas.

packageCodemodStepInputSchema and packageCodemodRevertInputSchema repeat identical runId/cursor/limit blocks verbatim. Extracting a shared packageCodemodPagingInputSchema and 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 win

Six near-identical defineDomainCapability wrappers 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 into createPackageCodemodStepCapability({ 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 using runFleetPackageCodemodStep and auditAdminCapabilityInvocation.
  • 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 value

Lint gate flags beforeEach.

epic-web(prefer-dispose-in-tests) wants the mock reset expressed as disposable setup in each test body rather than beforeEach.

🤖 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 win

Coverage gap: revert with a runId but no revertOfRunId.

This asserts the happy rejection path only. Add a case posting { mode: 'revert', scope: 'fleet', runId: 'run-1' } — with the current parseRunBody condition in admin-codemods.ts it 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 value

Hardcoded paths duplicate the routes contract.

/admin/codemods.json, /admin/codemods/run.json, and the pathname compared in isAdminCodemodsPath are string literals while routes.adminCodemods* already exists in packages/worker/src/app/routes.ts (and this client tree imports routes elsewhere). 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 win

Confirm 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 (fleet vs 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 value

Consider narrowing mode/status to unions instead of string.

AdminCodemodRunListItem.mode and AdminCodemodRunItemListItem.status are string while run.status uses 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 in admin-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 win

No audit event on failure, and every engine error becomes a 400.

Success is audited but the catch returns getErrorMessage(error) with a 400 and writes nothing to the ledger-adjacent audit trail. For a destructive fleet operation a failed/partial apply is 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

📥 Commits

Reviewing files that changed from the base of the PR and between c54a268 and 395db23.

📒 Files selected for processing (42)
  • docs/contributing/adding-capabilities.md
  • docs/contributing/architecture/primitives.yaml
  • docs/contributing/index.md
  • docs/contributing/package-codemods.md
  • docs/guides/package-subscriptions.md
  • packages/worker/client/lazy-route.tsx
  • packages/worker/client/routes/account-management-components.tsx
  • packages/worker/client/routes/admin-area.ts
  • packages/worker/client/routes/admin-codemods.tsx
  • packages/worker/client/routes/index.tsx
  • packages/worker/migrations/0111-package-codemod-ledger.sql
  • packages/worker/src/account/data-targets.ts
  • packages/worker/src/app/document-head.ts
  • packages/worker/src/app/handlers/admin-codemods.node.test.ts
  • packages/worker/src/app/handlers/admin-codemods.ts
  • packages/worker/src/app/loader-data.ts
  • packages/worker/src/app/router.ts
  • packages/worker/src/app/routes.ts
  • packages/worker/src/mcp/capabilities/admin/admin-package-codemod-apply.ts
  • packages/worker/src/mcp/capabilities/admin/admin-package-codemod-capabilities.node.test.ts
  • packages/worker/src/mcp/capabilities/admin/admin-package-codemod-dry-run.ts
  • packages/worker/src/mcp/capabilities/admin/admin-package-codemod-revert.ts
  • packages/worker/src/mcp/capabilities/admin/admin-package-codemod-scan.ts
  • packages/worker/src/mcp/capabilities/admin/domain.ts
  • packages/worker/src/mcp/capabilities/packages/domain.ts
  • packages/worker/src/mcp/capabilities/packages/package-codemod-apply.ts
  • packages/worker/src/mcp/capabilities/packages/package-codemod-capabilities.node.test.ts
  • packages/worker/src/mcp/capabilities/packages/package-codemod-dry-run.ts
  • packages/worker/src/mcp/capabilities/packages/package-codemod-list.ts
  • packages/worker/src/mcp/capabilities/packages/package-codemod-revert.ts
  • packages/worker/src/mcp/capabilities/packages/package-codemod-scan.ts
  • packages/worker/src/mcp/capabilities/packages/package-codemod-shared.ts
  • packages/worker/src/package-codemods/codemods/0001-ambient-storage-to-package-storage.node.test.ts
  • packages/worker/src/package-codemods/codemods/0001-ambient-storage-to-package-storage.ts
  • packages/worker/src/package-codemods/engine.node.test.ts
  • packages/worker/src/package-codemods/engine.ts
  • packages/worker/src/package-codemods/ledger.node.test.ts
  • packages/worker/src/package-codemods/ledger.ts
  • packages/worker/src/package-codemods/registry.ts
  • packages/worker/src/package-codemods/subscription-events.ts
  • packages/worker/src/package-codemods/types.ts
  • tools/migration-ledger.json

Comment thread packages/worker/client/routes/admin-codemods.tsx
Comment thread packages/worker/src/account/data-targets.ts
Comment thread packages/worker/src/app/handlers/admin-codemods.ts Outdated
Comment thread packages/worker/src/app/handlers/admin-codemods.ts
Comment on lines +267 to +297
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
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ 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.

Comment thread packages/worker/src/package-codemods/engine.ts Outdated
Comment thread packages/worker/src/package-codemods/ledger.ts
- 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>
Comment thread packages/worker/src/package-codemods/engine.ts
Comment thread packages/worker/src/package-codemods/engine.ts
Comment thread packages/worker/src/package-codemods/engine.ts
cursoragent and others added 3 commits July 30, 2026 13:21
…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>
Comment thread packages/worker/src/package-codemods/engine.ts
Comment thread packages/worker/src/mcp/capabilities/packages/package-codemod-shared.ts Outdated
…ate MCP limit docs

Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Comment thread packages/worker/src/package-codemods/engine.ts
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Comment thread packages/worker/src/package-codemods/engine.ts
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
error: getErrorMessage(error),
})
}
await persistItem(input.env, result, input.run.id)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a530659. Configure here.

Comment thread packages/worker/src/package-codemods/engine.ts
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Fix All in Cursor

❌ 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,
},
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JSON filter deletion corrupts ids

Medium Severity

Account deletion rewrites package_codemod_runs.filters_json via LIKE '%"&lt;userId&gt;"%' 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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 88a35e9. Configure here.

@kody-bot
kody-bot merged commit d93cc2e into main Jul 30, 2026
10 checks passed
@kody-bot
kody-bot deleted the cursor/package-codemods-86c8 branch July 30, 2026 14:01
kentcdodds added a commit that referenced this pull request Jul 30, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants