Skip to content

feat(codemod): SDK upgrade codemods (Codemod-OSS / JSSG) for 3.1.0#447

Draft
zama-cremaud wants to merge 10 commits into
prereleasefrom
feat/sdk-codemod-cli
Draft

feat(codemod): SDK upgrade codemods (Codemod-OSS / JSSG) for 3.1.0#447
zama-cremaud wants to merge 10 commits into
prereleasefrom
feat/sdk-codemod-cli

Conversation

@zama-cremaud

@zama-cremaud zama-cremaud commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

What this is

SDK-upgrade codemods for the breaking changes in @zama-fhe/sdk / @zama-fhe/react-sdk 3.1.0, on the Codemod OSS engine — a codemods/sdk-upgrade-v3-1-0 workspace package. It applies the mechanical subset deterministically; the non-mechanical tail is left to an opt-in ai step + manual review.

  • ast-grep YAML rules (rules/*.yml) for symbol/type renames + config-key changes.
  • JSSG transforms (scripts/*.ts) for structural/context-sensitive rewrites (import-aware createZamaConfig→createConfig, config-object→positional, token-field any-shape, UseZamaConfig removal).
  • Keyed per breaking release (sdk-upgrade-v3-1-0, O(N)); distributed via the Codemod registry (npx codemod @zama-fhe/sdk-upgrade-v3-1-0 -t ./src). No SDK repo / Claude Code dependency for consumers.

Authorship: the codemod engine + transforms are @ghermet's (commits preserved). The commits on top (tests, idempotency, a fix, CI) are the test/hardening pass.

Added on top (this pass)

  • Rule test coveragecodemod jssg test only ran the JSSG scripts/; the 5 rules/*.yml had fixtures but no runner. Added test-rules.mjs (same tests/<rule>/ convention + idempotency), wired into test.sh.
  • Fix surfaced by ituse-delegation-status only matched a single-property object ({ tokenAddress: $V }), so realistic useDelegationStatus({ tokenAddress, delegateAddress }) calls slipped through. Rewrote it as a scoped relational inside rule (any object shape / key order; other call sites untouched).
  • JSSG idempotency — an idempotent fixture case per transform (migrated output must round-trip).
  • End-to-endtest-e2e.mjs runs the whole chain in workflow order on a multi-file fixture, asserting convergence + chain-level idempotency.
  • CI — the package tests ran nowhere (pnpm test:coverage is vitest-only); added .github/workflows/codemod.yml.

All green: 4 JSSG transforms (+ idempotent cases) + 5 rules + e2e convergence/idempotency.

Open items for review

  • .ts type-position gap (known limitation): language: tsx rules under-cover plain .ts files — the engine renames import specifiers but misses type-position occurrences (function f(h: Handle) keeps Handle). Works in .tsx. Fix = language: typescript rule variants (or map .ts→tsx in engine config). Documented in the package README; e2e fixtures are .tsx for this reason.
  • ai node gating: the README says it's off-by-default via --param ai=true, but workflow.yaml has no node condition — it's a no-op only because no LLM is configured. Consider explicit gating.
  • Release type: commits use feat(codemod) / fix(codemod). Confirm semantic-release does not cut an SDK release for the codemods/ workspace (scope/path), or adjust types.
  • GA: re-derive against the final 3.1.0 api-report when it ships.

🤖 Generated with Claude Code

ghermet and others added 8 commits June 19, 2026 07:35
…ds/ workspace)

A Codemod (codemod.com) workflow package upgrades @zama-fhe/sdk /
@zama-fhe/react-sdk consumer code, in the dedicated root codemods/ workspace.
`pnpm codemod -t <path>` runs it.

Packages follow a one-per-breaking-release convention (see codemods/README.md):
each is keyed to the SDK release that introduced the breaks, not a from->to
couple. First package: codemods/codemods/3.1.0 (@zama-fhe/sdk-upgrade-3.1.0),
assuming a 3.0.x floor.

- deterministic by default: 7 renames as native ast-grep steps (existing rule
  files) + 2 structural rewrites as JSSG transforms (scripts/*.ts). JSSG
  (ast-grep node API + imperative commitEdits) covers the ordered/structural
  cases plain ast-grep can't, so jscodeshift isn't our own dep.
- optional AI tail: an `ai` workflow step, gated behind `--param ai=true` and
  run only after the deterministic steps, applies the non-mechanical 3.1.0
  changes (useUserDecrypt/useEncrypt reshapes, removed EIP-712/keypair hooks,
  the ZamaSDK capability refactor). Off by default so the standard run + tests
  stay deterministic; configured via env (LLM_API_KEY / LLM_PROVIDER / LLM_MODEL).
- the codemod CLI is a devDependency; it pulls jscodeshift transitively, whose
  @babel/core -> semver@6.3.1 is pinned to the attested 7.x line via a pnpm
  override (pnpm-workspace.yaml), keeping no-downgrade intact.
- the codemods/ workspace is outside the main pnpm workspace globs (no effect on
  the main install); its tests run in the `scripts` vitest project
  (codemods/**/*.test.mjs) against the fixtures (apply + oxfmt == output).
- scope api-report-derived: released stable 2.5.0/3.0.0/3.0.1 are API-identical,
  so 3.1.0 is the first release with breaking changes; provisional (from the
  v3.0.1 -> v3.1.0-alpha.14 diff).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…hape-aware

Testing the workflow on examples/react-wagmi surfaced two bugs in the
ast-grep rules, now ported to import-/AST-aware JSSG scripts:

- createZamaConfig->createConfig was a bare textual match: it renamed the
  local alias in `import { createConfig as createZamaConfig }`, producing the
  self-collision `createConfig as createConfig` (and clashing with wagmi's own
  createConfig). Now only rewrites the actual `createZamaConfig` export from a
  @zama-fhe module, leaving local aliases of the new export untouched.

- The query-hook tokenAddress->address rules used exact single-property,
  single-argument patterns, so real call sites silently slipped through:
  multi-property configs ({ tokenAddress, account }), a trailing options
  argument, and shorthand ({ tokenAddress }). The JSSG port rewrites the
  first-argument object on the AST and handles all shapes + drop-wrapper.

Also hardened both this and the existing config-object->positional script to
skip a leading comment node (tree-sitter marks comments as named children),
which was silently skipping useShield/useUnshield/useResumeUnshield calls that
document the wrapper==token invariant above their config.

Removes the two superseded ast-grep rule files; extends the field-rename
fixture (multi-prop, second arg, shorthand, leading comment) and adds a
createConfig-alias-preserved fixture. Workflow fixture suite: 10/10 pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per the Codemod package-structure docs (https://docs.codemod.com/package-structure),
a codemod is a single flat package directory, and the `name` field must match
`/^[a-z0-9-_/]+$/` — dots are explicitly disallowed. The previous
`@zama-fhe/sdk-upgrade-3.1.0` name was therefore invalid (it only slipped past
`workflow validate`, which checks workflow.yaml, not codemod.yaml).

- move codemods/codemods/3.1.0 -> codemods/sdk-upgrade-v3-1-0 (flat)
- codemod.yaml + package.json name -> @zama-fhe/sdk-upgrade-v3-1-0 (3.1.0 -> v3-1-0)
- pnpm-workspace.yaml glob -> codemods/* ; root `codemod` script + lockfile key updated
- tests/workflow.test.mjs: REPO_ROOT recomputed for the shallower (flat) layout
- READMEs: flat-slug layout, dot-free naming note, and corrected ast-grep/JSSG
  split (createZamaConfig + query-hook token-field are JSSG now)

Workflow validates and the fixture suite passes (10/10) from the new location.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… harness

Replace the bespoke vitest workflow harness with the official JSSG testing
approach (https://docs.codemod.com/jssg/testing): per-script tests/<name>/
fixture dirs (input.tsx/expected.tsx), and a test.sh that discovers scripts/*.ts
and runs `codemod jssg test --strictness ast` for each (no hand-maintained list).

Drops the vitest integration test and the codemods glob from vitest.config.ts.
The ast-grep rule fixtures keep their input/expected pairs but are not yet wired
to a runner (noted in the README).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Follow https://docs.codemod.com/jssg/advanced: replace the hand-built edit
literals ({startPos,endPos,insertedText}) with node.replace()/Edit, and export a
getSelector per script so JSSG pre-filters files (skips any that don't mention
the target hook/import/type). Each selector is a superset of what the transform
edits, so this is behavior-preserving — `pnpm test` (codemod jssg test) is green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ulti-prop calls

The `codemod jssg test` harness only ran the JSSG scripts/, so the rules/*.yml
fixtures were never executed. Add test-rules.mjs (same tests/<rule>/ convention,
plus an idempotency check) and wire it into test.sh.

Running it surfaced that use-delegation-status only matched a single-property
object (`{ tokenAddress: $V }`); realistic calls — useDelegationStatus is
essentially always called as `{ tokenAddress, delegateAddress }` — slipped
through unchanged. Rewrite the rule to scope a pair/shorthand rewrite inside the
hook call (relational `inside`), so any object shape and key order is handled
while other call sites' tokenAddress is left untouched. Strengthen the fixture
to cover those shapes.

Exempt codemods/** from no-console (dev tooling, like scripts/**) so the runner lints clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Idempotency: add an `idempotent` fixture case to each JSSG transform (its own
  migrated output, which must round-trip unchanged) so the README's idempotency
  claim is enforced by the native harness.
- End-to-end: test-e2e.mjs applies the whole chain in workflow.yaml order to a
  multi-file tests/_e2e fixture, asserts convergence to the expected tree, then
  re-applies and asserts a no-op — catching step-ordering / cross-transform
  regressions the per-transform tests can't.
- README: document the rule + e2e runners, the partner run flow, and two known
  limitations (tsx-language rules under-cover type positions in plain .ts files;
  the `ai` node isn't formally gated by the param).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The codemod package's tests (JSSG harness + ast-grep rule runner + e2e chain) ran
nowhere in CI — `pnpm test:coverage` (vitest) doesn't cover them. Add a
path-filtered workflow that runs `pnpm --filter @zama-fhe/sdk-upgrade-v3-1-0 test`.
No SDK build needed; the transforms are syntactic (ast-grep / JSSG on fixtures).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@cla-bot cla-bot Bot added the cla-signed label Jun 19, 2026
@github-actions

Copy link
Copy Markdown

Public API Changes

✅ No public API changes detected.

@github-actions

github-actions Bot commented Jun 19, 2026

Copy link
Copy Markdown

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 92.18% (🎯 80%) 3116 / 3380
🔵 Statements 92.26% 3210 / 3479
🔵 Functions 92.44% (🎯 80%) 1040 / 1125
🔵 Branches 84.73% (🎯 80%) 1199 / 1415
File CoverageNo changed files found.
Generated in workflow #2702 for commit e18e9bb by the Vitest Coverage Report Action

zama-cremaud and others added 2 commits June 19, 2026 16:56
Copied from lint.yml (which is called by other workflows); nothing calls the
codemod workflow, so the trigger is dead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
oxfmt-format the e2e expected fixtures + README (CI format:check flagged them).
The codemods don't format their output, so the e2e runner now formats its result
with oxfmt before comparing to the (repo-formatted) expected tree — an exact match
that also mirrors the real "run the codemod, then your formatter" flow.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants