Claude Skill

migrate-ops

Framework and language migration patterns - version upgrades, breaking changes, dependency audit, safe rollback. Use for: migrate, migration, upgrade, version bump, breaking changes, deprecation, dependency audit, npm audit, pip-audit, codemod, jscodeshift, rector, rollback, semv

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download 0xdarkmatter-claude-mods-skills_migrate-ops-3dfaf0b.zip · 36 KB
Part of 0xdarkmatter/claude-mods — 94 skills

Install

skills CLI npx skills add https://github.com/0xDarkMatter/claude-mods/tree/main/skills/migrate-ops
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install 0xdarkmatter-claude-mods@llmmart
Git git clone https://github.com/0xDarkMatter/claude-mods.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole 0xdarkmatter/claude-mods collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Migrate Operations

Comprehensive migration skill covering framework upgrades, language version bumps, dependency auditing, breaking change detection, codemods, and rollback strategies.

Ecosystem facts verified as of 2026-07-05.

Migration Strategy Decision Tree

What kind of migration are you performing?
│
├─ Small library update (patch/minor version)
│  └─ In-place upgrade
│     Update dependency, run tests, deploy
│
├─ Major framework version (React 18→19, Vue 2→3, Laravel 12→13)
│  │
│  ├─ Codebase < 50k LOC, good test coverage (>70%)
│  │  └─ Big Bang Migration
│  │     Upgrade everything at once in a feature branch
│  │     Pros: clean cutover, no dual-version complexity
│  │     Cons: high risk, long branch life, merge conflicts
│  │
│  ├─ Codebase > 50k LOC, partial test coverage
│  │  └─ Incremental Migration
│  │     Upgrade module by module, use compatibility layers
│  │     Pros: lower risk per step, continuous delivery
│  │     Cons: dual-version code, longer total duration
│  │
│  ├─ Monolith → microservice or complete architecture shift
│  │  └─ Strangler Fig Pattern
│  │     Route new features to new system, migrate old features gradually
│  │     Pros: zero-downtime, reversible, production-validated
│  │     Cons: routing complexity, data sync challenges
│  │
│  └─ High-risk data pipeline or financial system
│     └─ Parallel Run
│        Run old and new systems simultaneously, compare outputs
│        Pros: highest confidence, catch subtle differences
│        Cons: double infrastructure cost, comparison logic
│
└─ Language version upgrade (Python 3.12→3.14, Node 22→26)
   └─ In-place upgrade with CI matrix
      Test against both old and new versions in CI
      Drop old version support once all tests pass

Framework Upgrade Decision Tree

Which framework are you upgrading?
│
├─ React 18 → 19
│  ├─ Check: Remove forwardRef wrappers (ref is now a regular prop)
│  ├─ Check: Replace <Context.Provider> with <Context>
│  ├─ Check: Adopt useActionState / useFormStatus for forms
│  ├─ Check: Replace manual memoization if using React Compiler
│  ├─ Codemod: npx codemod@latest react/19/migration-recipe
│  └─ Load: ./references/framework-upgrades.md
│
├─ Next.js Pages Router → App Router
│  ├─ Check: Move pages/ to app/ with new file conventions
│  ├─ Check: Replace getServerSideProps/getStaticProps with async components
│  ├─ Check: Convert _app.tsx and _document.tsx to layout.tsx
│  ├─ Check: Update data fetching to use fetch() with caching options
│  ├─ Codemod: npx @next/codemod@latest
│  └─ Load: ./references/framework-upgrades.md
│
├─ Vue 2 → 3
│  ├─ Check: Replace Options API with Composition API (optional but recommended)
│  ├─ Check: Replace Vuex with Pinia
│  ├─ Check: Replace event bus with mitt or provide/inject
│  ├─ Check: Update v-model syntax (modelValue prop)
│  ├─ Tool: Migration build (@vue/compat) for incremental migration
│  └─ Load: ./references/framework-upgrades.md
│
├─ Laravel 12 → 13
│  ├─ Check: PHP 8.3 is now the minimum (8.5 supported)
│  ├─ Check: Cache/Redis key prefixes now use hyphenated suffixes
│  ├─ Check: Adopt native PHP attributes (models, jobs, controllers) — optional
│  ├─ Check: Queue routing by class via Queue::route(...) — optional
│  ├─ Tool: laravel shift (automated upgrade service)
│  └─ Load: ./references/framework-upgrades.md (covers 10→11 in depth; 12→13 is near zero-break)
│
├─ Angular (any major version)
│  ├─ Check: Run ng update for guided migration
│  ├─ Check: Review Angular Update Guide (update.angular.io)
│  ├─ Tool: ng update @angular/core @angular/cli
│  └─ Load: ./references/framework-upgrades.md
│
└─ Django (any major version)
   ├─ Check: Run python -Wd manage.py test for deprecation warnings
   ├─ Check: Review Django release notes for removals
   ├─ Tool: django-upgrade (automatic fixer)
   └─ Load: ./references/framework-upgrades.md

Dependency Audit Workflow

Ecosystem?
│
├─ JavaScript / Node.js
│  ├─ npm audit / npm audit fix
│  ├─ npx audit-ci --moderate (CI integration)
│  └─ Socket.dev for supply chain analysis
│
├─ Python
│  ├─ pip-audit
│  ├─ safety check
│  └─ pip-audit --fix (auto-update vulnerable packages)
│
├─ Rust
│  ├─ cargo audit
│  └─ cargo deny check advisories
│
├─ Go
│  ├─ govulncheck ./...
│  └─ go list -m -u all (list available updates)
│
├─ PHP
│  ├─ composer audit
│  └─ composer outdated --direct
│
└─ Multi-ecosystem
   └─ Trivy, Snyk, or Dependabot across all

Pre-Migration Checklist

[ ] Test coverage measured and documented (target: >70% for critical paths)
[ ] CI pipeline green on current version
[ ] All dependencies up to date (or pinned with rationale)
[ ] Database backup taken (if applicable)
[ ] Git state clean — migration branch created from latest main
[ ] Rollback plan documented and tested
[ ] Breaking change list reviewed from upstream changelog
[ ] Team notified of migration window
[ ] Feature flags in place for gradual rollout (if applicable)
[ ] Monitoring and alerting configured for regression detection
[ ] Performance baseline captured (response times, memory, CPU)
[ ] Lock file committed (package-lock.json, yarn.lock, Cargo.lock, etc.)

Breaking Change Detection Patterns

How do you detect breaking changes?
│
├─ Semver Analysis
│  ├─ Major version bump → breaking changes guaranteed
│  ├─ Check CHANGELOG.md or BREAKING_CHANGES.md in repo
│  └─ npm: npx npm-check-updates --target major
│
├─ Changelog Parsing
│  ├─ Search for: "BREAKING", "removed", "deprecated", "renamed"
│  ├─ GitHub: compare releases page between versions
│  └─ Read migration guide if one exists
│
├─ Compiler / Runtime Warnings
│  ├─ Enable all deprecation warnings before upgrading
│  ├─ Python: python -Wd (turn deprecation warnings to errors)
│  ├─ Node: node --throw-deprecation
│  └─ TypeScript: strict mode catches type-level breaks
│
├─ Codemods (automated detection + fix)
│  ├─ jscodeshift — JavaScript/TypeScript AST transforms
│  ├─ ast-grep — language-agnostic structural search/replace
│  ├─ rector — PHP automated refactoring
│  ├─ gofmt / gofumpt — Go formatting changes
│  └─ 2to3 — Python 2 to 3 (legacy)
│
└─ Type Checking
   ├─ TypeScript: tsc --noEmit catches API shape changes
   ├─ Python: mypy / pyright after upgrade
   └─ Go: go vet ./... after upgrade

Codemod Quick Reference

Ecosystem Tool Command Use Case
JS/TS jscodeshift npx jscodeshift -t transform.ts src/ Custom AST transforms
JS/TS ast-grep sg --pattern 'old($$$)' --rewrite 'new($$$)' Structural find/replace
React react-codemod npx codemod@latest react/19/migration-recipe React version upgrades
Next.js next-codemod npx @next/codemod@latest Next.js version upgrades
Vue vue-codemod npx @vue/codemod src/ Vue 2 to 3 transforms
PHP Rector vendor/bin/rector process src PHP version + framework upgrades
Python pyupgrade pyupgrade --py314-plus *.py Python version syntax upgrades
Python django-upgrade django-upgrade --target-version 5.0 *.py Django version upgrades
Go gofmt gofmt -w . Go formatting updates
Go gofix go fix ./... Go API changes
Rust cargo fix cargo fix --edition Rust edition migration
Multi ast-grep sg scan --rule rules.yml Any language with custom rules

Rollback Strategy Decision Tree

Migration failed or caused issues — how to roll back?
│
├─ Code-only change, no data migration
│  ├─ Small number of commits
│  │  └─ Git Revert
│  │     git revert --no-commit HEAD~N..HEAD && git commit
│  │     Pros: clean history, safe for shared branches
│  │     Cons: merge conflicts if code has diverged
│  │
│  └─ Entire feature branch
│     └─ Revert merge commit
│        git revert -m 1 <merge-commit-sha>
│
├─ Feature flag controlled
│  └─ Toggle flag off
│     Instant rollback, no deployment needed
│     Keep old code path until new path is proven
│
├─ Database schema changed
│  ├─ Reversible migration exists
│  │  └─ Run down migration
│  │     rails db:rollback / php artisan migrate:rollback / alembic downgrade
│  │
│  └─ Irreversible migration (dropped column, changed type)
│     └─ Restore from backup + replay write-ahead log
│        This is why you take backups BEFORE migration
│
└─ Infrastructure / deployment
   ├─ Blue-Green deployment
   │  └─ Switch traffic back to blue (old) environment
   │
   ├─ Canary deployment
   │  └─ Route 100% traffic back to stable version
   │
   └─ Container orchestration (K8s)
      └─ kubectl rollout undo deployment/app

Common Gotchas

Gotcha Why It Happens Prevention
Upgrading multiple major versions at once Each major version may have sequential breaking changes that compound Upgrade one major version at a time, verify, then proceed
Lock file not committed before migration Cannot reproduce pre-migration dependency state Always commit lock files; take a snapshot branch before starting
Running codemods without committing first Cannot diff what the codemod changed vs your manual changes Commit clean state, run codemod, commit codemod changes separately
Ignoring deprecation warnings in current version Deprecated APIs are removed in next major version Fix all deprecation warnings BEFORE upgrading
Testing only happy paths after migration Edge cases and error paths are most likely to break Run full test suite plus manual exploratory testing
Not checking transitive dependencies A direct dep upgrade may pull in incompatible transitive deps Use npm ls, pip show, cargo tree to inspect dependency tree
Assuming codemods catch everything Codemods handle common patterns, not all patterns Review codemod output manually; check for skipped files
Skipping the migration guide Framework authors document known pitfalls and workarounds Read the official migration guide end-to-end before starting
Migrating in a long-lived branch Main branch diverges, causing painful merge conflicts Use feature flags for incremental migration on main
Not updating CI to test both versions CI passes on old version but new version has failures Add matrix testing for both versions during transition
Database migration without backup Irreversible schema changes with no recovery path Always backup before migration; test rollback procedure
Forgetting to update Docker/CI base images Code upgraded but runtime is still old version Update Dockerfile FROM, CI config, and deployment manifests

Reference Files

File Contents Lines
references/framework-upgrades.md React 18→19, Next.js Pages→App Router, Vue 2→3, Laravel 10→13, Angular, Django upgrade paths ~700
references/language-upgrades.md Python 3.9→3.14, Node 18→26, TypeScript 4→6, Go 1.20→1.26, Rust 2021→2024, PHP 8.1→8.5 ~650
references/dependency-management.md Audit tools, update strategies, lock files, monorepo deps, supply chain security ~550

Staleness verifier

This skill hardcodes specific framework/language target versions (React 19, Laravel 13, Python 3.14, Node 26, TypeScript 6, Go 1.26, Rust 2024, PHP 8.5). scripts/check-migrate-facts.py guards them against silent drift:

# Structural (PR CI, no network): every catalogued target version still appears
# where it is recorded (description vs body), and the currency note carries a year.
python scripts/check-migrate-facts.py --offline        # exit 0 consistent, 10 drift

# Live (freshness job, never blocks a PR): each target is resolved against
# endoflife.date (python/nodejs/laravel/php/go) and npm (react/typescript).
python scripts/check-migrate-facts.py --live            # exit 10 a target lags latest, 7 unreachable

The canonical target-version list lives in assets/migrate-facts.json; when you change a recommended target, update it to match or --offline fails CI. A --live drift means the skill is naming an older target than the ecosystem's current stable — review, don't auto-rewrite.

See Also

Skill When to Combine
testing-ops Ensuring test coverage before migration, writing regression tests after
debug-ops Diagnosing failures introduced by migration, bisecting breaking commits
git-ops Branch strategy for migration, git bisect to find breaking change
refactor-ops Code transformations that often accompany version upgrades
ci-cd-ops Updating CI pipelines to test against new versions, matrix builds
container-orchestration Updating base images, Dockerfile changes for new runtime versions
security-ops Vulnerability remediation that triggers dependency upgrades
Files (claude-mods)
  • assets
    • .gitkeep 0 B · in bundle
    • migrate-facts.json 2.5 KB
      {
        "_comment": "Canonical fast-moving facts the migrate-ops skill encodes: the framework/language target versions it hardcodes in its description and body. scripts/check-migrate-facts.py asserts each claim still appears where recorded + a dated currency note (--offline), and resolves the current versions via endoflife.date + npm to flag lag (--live). Edit deliberately: a change here is a skill-content decision, not housekeeping.",
        "schema": "claude-mods.migrate-ops.facts/v1",
        "as_of": "2026-07-05",
        "claims": [
          {
            "label": "React",
            "version": "19",
            "where": ["description", "body"],
            "pattern": "react[^\\n]*\\b19\\b",
            "live": {"source": "npm", "product": "react", "compare": "major", "documented": "19"}
          },
          {
            "label": "Laravel",
            "version": "13",
            "where": ["description", "body"],
            "pattern": "laravel[^\\n]*\\b13\\b",
            "live": {"source": "endoflife", "product": "laravel", "compare": "major", "documented": "13"}
          },
          {
            "label": "Python",
            "version": "3.14",
            "where": ["description", "body"],
            "pattern": "python[^\\n]*3\\.14\\b",
            "live": {"source": "endoflife", "product": "python", "compare": "line", "documented": "3.14"}
          },
          {
            "label": "Node",
            "version": "26",
            "where": ["description", "body"],
            "pattern": "node[^\\n]*\\b26\\b",
            "live": {"source": "endoflife", "product": "nodejs", "compare": "major", "documented": "26"}
          },
          {
            "label": "TypeScript",
            "version": "6",
            "where": ["description", "body"],
            "pattern": "typescript[^\\n]*\\b6\\b",
            "live": {"source": "npm", "product": "typescript", "compare": "major", "documented": "6"}
          },
          {
            "label": "Go",
            "version": "1.26",
            "where": ["description", "body"],
            "pattern": "go[^\\n]*1\\.26\\b",
            "live": {"source": "endoflife", "product": "go", "compare": "line", "documented": "1.26"}
          },
          {
            "label": "Rust",
            "version": "2024",
            "where": ["description", "body"],
            "pattern": "rust[^\\n]*2024\\b",
            "live": null,
            "_comment": "Rust 2024 is an EDITION (reached stable with Rust 1.85), not a semver major tracked on endoflife.date's product list. Still the newest edition as of 2026-07 (next expected 2027). Live-checked nowhere; offline-only."
          },
          {
            "label": "PHP",
            "version": "8.5",
            "where": ["description", "body"],
            "pattern": "php[^\\n]*8\\.5\\b",
            "live": {"source": "endoflife", "product": "php", "compare": "line", "documented": "8.5"}
          }
        ]
      }
      
  • references
    • dependency-management.md 16 KB
      # Dependency Management
      
      Comprehensive guide to auditing, updating, and securing dependencies across ecosystems.
      
      ---
      
      ## Audit Tools by Ecosystem
      
      ### JavaScript / Node.js
      
      ```bash
      # Built-in npm audit
      npm audit                      # Show vulnerabilities
      npm audit fix                  # Auto-fix where possible
      npm audit fix --force          # Fix with major version bumps (risky)
      npm audit --json               # JSON output for CI parsing
      
      # npm audit in CI (fail on moderate+)
      npx audit-ci --moderate        # Fail CI on moderate or higher
      
      # Socket.dev (supply chain analysis)
      # Detects: typosquatting, install scripts, obfuscated code
      npx socket:npm info <package>
      
      # Check for outdated packages
      npm outdated                   # Show outdated direct deps
      npx npm-check-updates          # Interactive update tool
      npx npm-check-updates -u       # Update package.json
      
      # Yarn
      yarn audit
      yarn upgrade-interactive
      
      # pnpm
      pnpm audit
      pnpm update --interactive
      ```
      
      ### Python
      
      ```bash
      # pip-audit (recommended)
      pip install pip-audit
      pip-audit                      # Scan installed packages
      pip-audit -r requirements.txt  # Scan requirements file
      pip-audit --fix                # Auto-update vulnerable packages
      pip-audit -f json              # JSON output for CI
      
      # Safety (alternative)
      pip install safety
      safety check                   # Scan installed packages
      safety check -r requirements.txt
      
      # Check outdated
      pip list --outdated
      
      # uv (fast alternative)
      uv pip list --outdated
      uv pip audit                   # If available in your uv version
      ```
      
      ### Rust
      
      ```bash
      # cargo-audit
      cargo install cargo-audit
      cargo audit                    # Check for known vulnerabilities
      cargo audit fix                # Auto-apply fixes (where possible)
      
      # cargo-deny (comprehensive policy enforcement)
      cargo install cargo-deny
      cargo deny check advisories    # Security advisories
      cargo deny check bans          # Banned crate checks
      cargo deny check licenses      # License compliance
      cargo deny check sources       # Source restrictions
      
      # Check outdated
      cargo outdated                 # Requires cargo-outdated
      cargo update --dry-run         # Show what would update
      ```
      
      ### Go
      
      ```bash
      # govulncheck (official Go vulnerability checker)
      go install golang.org/x/vuln/cmd/govulncheck@latest
      govulncheck ./...              # Scan project
      govulncheck -mode binary app   # Scan compiled binary
      
      # Check for updates
      go list -m -u all              # List all modules with available updates
      go get -u ./...                # Update all dependencies
      go mod tidy                    # Clean up go.sum
      
      # Nancy (Sonatype vulnerability scanner)
      go list -m -json all | nancy sleuth
      ```
      
      ### PHP
      
      ```bash
      # Composer built-in audit
      composer audit                 # Check for known vulnerabilities
      composer audit --format=json   # JSON output for CI
      
      # Check outdated
      composer outdated              # All outdated
      composer outdated --direct     # Only direct dependencies
      
      # Symfony security checker
      composer require --dev sensiolabs/security-checker
      vendor/bin/security-checker security:check
      ```
      
      ### Ruby
      
      ```bash
      # bundler-audit
      gem install bundler-audit
      bundle audit check             # Scan Gemfile.lock
      bundle audit update            # Update vulnerability database
      
      # Check outdated
      bundle outdated
      ```
      
      ### Multi-Ecosystem
      
      ```bash
      # Trivy (containers, filesystems, git repos)
      trivy fs .                     # Scan current directory
      trivy image myapp:latest       # Scan container image
      trivy repo https://github.com/org/repo
      
      # Snyk
      snyk test                      # Test for vulnerabilities
      snyk monitor                   # Monitor for new vulnerabilities
      
      # OSV-Scanner (Google)
      osv-scanner -r .               # Recursive scan
      osv-scanner --lockfile=package-lock.json
      ```
      
      ---
      
      ## Dependency Update Strategies
      
      ### Automated Update Services
      
      | Service | Ecosystems | Key Features |
      |---------|-----------|-------------|
      | **Dependabot** | npm, pip, Cargo, Go, Composer, Bundler, Docker, GitHub Actions | GitHub-native, grouped updates, auto-merge rules |
      | **Renovate** | 50+ managers | Highly configurable, monorepo support, custom rules, self-hosted option |
      | **Snyk** | npm, pip, Go, Java, .NET, Ruby | Security-focused, fix PRs, runtime monitoring |
      
      ### Dependabot Configuration
      
      ```yaml
      # .github/dependabot.yml
      version: 2
      updates:
        - package-ecosystem: npm
          directory: "/"
          schedule:
            interval: weekly
            day: monday
          open-pull-requests-limit: 10
          groups:
            dev-dependencies:
              dependency-type: development
            minor-and-patch:
              update-types: [minor, patch]
          ignore:
            - dependency-name: "aws-sdk"
              update-types: ["version-update:semver-major"]
      
        - package-ecosystem: docker
          directory: "/"
          schedule:
            interval: weekly
      
        - package-ecosystem: github-actions
          directory: "/"
          schedule:
            interval: weekly
      ```
      
      ### Renovate Configuration
      
      ```json
      {
        "$schema": "https://docs.renovatebot.com/renovate-schema.json",
        "extends": [
          "config:recommended",
          "group:allNonMajor",
          ":automergeMinor",
          ":automergePatch"
        ],
        "packageRules": [
          {
            "matchUpdateTypes": ["major"],
            "automerge": false,
            "labels": ["breaking-change"]
          },
          {
            "matchDepTypes": ["devDependencies"],
            "automerge": true
          }
        ],
        "schedule": ["before 7am on Monday"]
      }
      ```
      
      ### Manual Update Workflow
      
      ```
      When to update manually:
      │
      ├─ Major version bump
      │  1. Read CHANGELOG.md / release notes
      │  2. Check for breaking changes
      │  3. Check if codemods exist
      │  4. Create feature branch
      │  5. Update dependency
      │  6. Run tests
      │  7. Fix breaking changes
      │  8. Run full CI pipeline
      │  9. Review diff carefully
      │  10. Merge when green
      │
      ├─ Security patch (critical)
      │  1. Verify advisory affects your usage
      │  2. Update to patched version
      │  3. Run tests
      │  4. Deploy immediately
      │
      └─ Minor / patch version
         1. Update dependency
         2. Run tests
         3. Spot-check changelog for surprises
         4. Merge
      ```
      
      ---
      
      ## Lock File Management
      
      ### When to Regenerate Lock Files
      
      ```
      Should you regenerate the lock file?
      │
      ├─ Lock file has merge conflicts
      │  └─ YES: Delete lock file, reinstall, commit
      │     npm: rm package-lock.json && npm install
      │     yarn: rm yarn.lock && yarn install
      │     pnpm: rm pnpm-lock.yaml && pnpm install
      │     pip: rm requirements.txt && pip freeze > requirements.txt
      │     cargo: rm Cargo.lock && cargo generate-lockfile
      │
      ├─ Dependency resolution is broken
      │  └─ YES: Delete lock file, clean cache, reinstall
      │     npm: rm -rf node_modules package-lock.json && npm cache clean --force && npm install
      │     pip: pip cache purge && pip install -r requirements.txt
      │
      ├─ Routine update
      │  └─ NO: Use update commands that modify lock file in place
      │     npm update / yarn upgrade / pnpm update
      │     cargo update
      │
      └─ CI builds are inconsistent
         └─ Check: Is lock file committed? If not, commit it.
            Applications: ALWAYS commit lock files
            Libraries: Commit lock files (for CI reproducibility)
      ```
      
      ### Lock File Conflict Resolution
      
      ```bash
      # npm
      git checkout --theirs package-lock.json  # accept incoming
      npm install                              # regenerate properly
      
      # yarn
      git checkout --theirs yarn.lock
      yarn install
      
      # pnpm
      git checkout --theirs pnpm-lock.yaml
      pnpm install
      
      # Cargo
      git checkout --theirs Cargo.lock
      cargo update
      
      # Go
      git checkout --theirs go.sum
      go mod tidy
      
      # Composer
      git checkout --theirs composer.lock
      composer update --lock
      ```
      
      ---
      
      ## Major Version Upgrade Workflow
      
      Detailed workflow for upgrading a dependency by one or more major versions.
      
      ### Step 1: Research
      
      ```bash
      # Read the changelog
      # GitHub: check Releases page
      # npm: npm info <package> changelog
      # Or find CHANGELOG.md / CHANGES.md / HISTORY.md in repo
      
      # Check breaking changes
      # Search for: "BREAKING", "removed", "renamed", "changed"
      # Look for migration guide
      
      # Check your usage of affected APIs
      rg "importedFunction|removedAPI" src/
      ```
      
      ### Step 2: Check Compatibility
      
      ```bash
      # npm: check peer dependency requirements
      npm info <package>@latest peerDependencies
      
      # Check if other deps are compatible
      npm ls <package>  # see who depends on it
      
      # Python: check classifiers and python_requires
      pip show <package> | rg -i "requires"
      
      # Go: check go.mod requirements of dependency
      go mod graph | rg <module>
      ```
      
      ### Step 3: Update
      
      ```bash
      # Create a branch
      git checkout -b upgrade/<package>-v<version>
      
      # npm
      npm install <package>@latest
      
      # pip
      pip install <package>==<version>
      
      # cargo
      cargo update -p <crate> --precise <version>
      
      # go
      go get <module>@v<version>
      go mod tidy
      
      # composer
      composer require <package>:<version>
      ```
      
      ### Step 4: Fix and Test
      
      ```bash
      # Run type checker first (catches API shape changes)
      npx tsc --noEmit        # TypeScript
      mypy .                   # Python
      go vet ./...             # Go
      
      # Run tests
      npm test                 # Node.js
      pytest                   # Python
      cargo test               # Rust
      go test ./...            # Go
      php artisan test         # Laravel
      
      # Run linter
      npm run lint
      ruff check .
      cargo clippy
      golangci-lint run
      ```
      
      ### Step 5: Verify
      
      ```bash
      # Build for production
      npm run build
      cargo build --release
      go build ./...
      
      # Run integration/e2e tests if available
      npm run test:e2e
      pytest tests/integration/
      ```
      
      ---
      
      ## Monorepo Dependency Management
      
      ### npm/pnpm/yarn Workspaces
      
      ```bash
      # List workspace packages
      npm ls --all --workspaces
      
      # Update a dependency across all workspaces
      npm update <package> --workspaces
      
      # Install a dependency in a specific workspace
      npm install <package> --workspace=packages/core
      
      # Check for inconsistent versions across workspaces
      npx syncpack list-mismatches
      
      # Fix inconsistent versions
      npx syncpack fix-mismatches
      ```
      
      ### Shared Version Strategy
      
      ```
      Monorepo version strategy:
      │
      ├─ Single version policy (recommended for most teams)
      │  All packages use the same version of shared dependencies
      │  Enforced with: syncpack, manypkg, or Renovate grouping
      │  Pros: consistent behavior, simpler debugging
      │  Cons: all packages must be compatible with same version
      │
      ├─ Independent versions
      │  Each package manages its own dependency versions
      │  Pros: flexibility, independent upgrade cycles
      │  Cons: version conflicts, larger node_modules, harder debugging
      │
      └─ Hybrid
         Pin shared infrastructure deps (React, TypeScript)
         Allow independent versions for leaf dependencies
      ```
      
      ### Turborepo / Nx Considerations
      
      ```bash
      # Turborepo: ensure dependency changes trigger correct rebuilds
      # turbo.json should include package.json in inputs
      
      # Nx: use nx migrate for framework updates
      npx nx migrate latest
      npx nx migrate --run-migrations
      ```
      
      ---
      
      ## Vendoring vs Lock Files
      
      ### Decision Tree
      
      ```
      Should you vendor dependencies?
      │
      ├─ Deploying to air-gapped environment
      │  └─ YES: Vendor everything
      │
      ├─ Registry availability is critical
      │  └─ YES: Vendor to protect against registry outages
      │
      ├─ Reproducible builds without network access
      │  └─ YES: Vendor dependencies
      │
      ├─ Open source library
      │  └─ NO: Use lock files, vendoring bloats the repo
      │
      ├─ Standard web application
      │  └─ NO: Lock files are sufficient
      │
      └─ Go modules
         └─ CONSIDER: Go vendor is well-supported
            go mod vendor  # creates vendor/ directory
            go build -mod=vendor
      ```
      
      ### Vendoring by Ecosystem
      
      ```bash
      # Go
      go mod vendor
      # Build with: go build -mod=vendor ./...
      
      # Python (pip download)
      pip download -r requirements.txt -d vendor/
      # Install from: pip install --no-index --find-links=vendor/ -r requirements.txt
      
      # Node.js (not common, but possible)
      # Use npm pack to create tarballs
      # Or use pnpm with node_modules layout
      
      # Rust
      # Use cargo-vendor
      cargo vendor
      # Configure .cargo/config.toml to use vendored sources
      ```
      
      ---
      
      ## License Compliance Checking
      
      ### Tools
      
      ```bash
      # Node.js
      npx license-checker --summary
      npx license-checker --failOn "GPL-3.0;AGPL-3.0"
      npx license-checker --production  # only production deps
      
      # Python
      pip install pip-licenses
      pip-licenses --format=table
      pip-licenses --fail-on="GPLv3;AGPL-3.0"
      
      # Rust
      cargo deny check licenses
      
      # Go
      go install github.com/google/go-licenses@latest
      go-licenses check ./...
      go-licenses report ./...
      
      # Multi-ecosystem
      # FOSSA: https://fossa.com
      # Snyk: snyk test --license
      ```
      
      ### License Compatibility Matrix
      
      | Your License | Compatible Dependencies | Incompatible |
      |-------------|------------------------|-------------|
      | MIT | MIT, BSD, ISC, Apache-2.0, Unlicense | - |
      | Apache-2.0 | MIT, BSD, ISC, Apache-2.0, Unlicense | GPL-2.0 (debated) |
      | GPL-3.0 | MIT, BSD, ISC, Apache-2.0, GPL-2.0, LGPL, AGPL | Proprietary |
      | Proprietary | MIT, BSD, ISC, Apache-2.0, Unlicense | GPL, AGPL, LGPL (static) |
      
      ### Cargo Deny License Config
      
      ```toml
      # deny.toml
      [licenses]
      allow = [
          "MIT",
          "Apache-2.0",
          "BSD-2-Clause",
          "BSD-3-Clause",
          "ISC",
          "Unicode-3.0",
      ]
      deny = [
          "AGPL-3.0",
      ]
      confidence-threshold = 0.8
      ```
      
      ---
      
      ## Supply Chain Security
      
      ### npm Provenance
      
      ```bash
      # Verify package provenance (npm 9.5+)
      npm audit signatures
      
      # Publish with provenance (in GitHub Actions)
      npm publish --provenance
      
      # Check a specific package
      npm view <package> --json | jq '.dist.attestations'
      ```
      
      ### Sigstore / cosign
      
      ```bash
      # Verify container image signatures
      cosign verify --key cosign.pub myregistry/myimage:tag
      
      # Sign a container image
      cosign sign --key cosign.key myregistry/myimage:tag
      
      # Verify in CI
      cosign verify --certificate-identity user@example.com \
        --certificate-oidc-issuer https://accounts.google.com \
        myregistry/myimage:tag
      ```
      
      ### cargo-vet (Rust)
      
      ```bash
      # Initialize cargo-vet
      cargo vet init
      
      # Certify a crate after review
      cargo vet certify <crate> <version>
      
      # Import trusted audits from other organizations
      cargo vet trust <organization>
      
      # Check all dependencies are vetted
      cargo vet
      ```
      
      ### Supply Chain Best Practices
      
      ```
      Supply chain security checklist:
      │
      ├─ [ ] Lock files committed and reviewed in PRs
      ├─ [ ] Dependabot or Renovate configured for automated updates
      ├─ [ ] npm audit / pip-audit / cargo audit in CI pipeline
      ├─ [ ] npm audit signatures verified (if using npm)
      ├─ [ ] Avoid running arbitrary install scripts (npm ignore-scripts)
      ├─ [ ] Pin GitHub Actions to SHA, not tag
      │      Bad:  uses: actions/checkout@v4
      │      Good: uses: actions/checkout@b4ffde65f46...
      ├─ [ ] Review new dependencies before adding
      │      Check: download count, maintenance activity, known issues
      ├─ [ ] Use private registry or proxy for sensitive environments
      ├─ [ ] Container images signed and verified
      ├─ [ ] SBOM (Software Bill of Materials) generated for releases
      │      Tools: syft, cyclonedx-cli, npm sbom
      └─ [ ] Socket.dev or similar for install-time behavior analysis
      ```
      
      ### SBOM Generation
      
      ```bash
      # Syft (Anchore)
      syft dir:. -o spdx-json > sbom.json
      syft myimage:tag -o cyclonedx-json > sbom.json
      
      # npm (built-in)
      npm sbom --sbom-format cyclonedx
      
      # CycloneDX
      # Python
      pip install cyclonedx-bom
      cyclonedx-py environment -o sbom.json
      
      # Go
      go install github.com/CycloneDX/cyclonedx-gomod/cmd/cyclonedx-gomod@latest
      cyclonedx-gomod mod -output sbom.json
      
      # Rust
      cargo install cargo-cyclonedx
      cargo cyclonedx --format json
      ```
      
      ---
      
      ## Dependency Update CI Integration
      
      ### GitHub Actions Example
      
      ```yaml
      name: Dependency Audit
      on:
        push:
          branches: [main]
        pull_request:
        schedule:
          - cron: '0 8 * * 1'  # Weekly Monday 8am
      
      jobs:
        audit:
          runs-on: ubuntu-latest
          steps:
            - uses: actions/checkout@v4
      
            - name: npm audit
              run: npm audit --audit-level=moderate
      
            - name: License check
              run: npx license-checker --failOn "GPL-3.0;AGPL-3.0" --production
      
            - name: Check for outdated deps
              run: npm outdated || true  # informational, don't fail
      ```
      
      ### Pre-commit Hook
      
      ```bash
      #!/bin/bash
      # .git/hooks/pre-commit or via pre-commit framework
      
      # Check for new dependencies without lock file update
      if git diff --cached --name-only | rg -q "package\.json"; then
        if ! git diff --cached --name-only | rg -q "package-lock\.json"; then
          echo "ERROR: package.json changed but package-lock.json was not updated"
          echo "Run: npm install"
          exit 1
        fi
      fi
      ```
      
    • framework-upgrades.md 18.3 KB
      # Framework Upgrade Paths
      
      Detailed upgrade procedures for major framework version transitions.
      
      ---
      
      ## React 18 to 19
      
      ### Pre-Upgrade Checklist
      
      ```
      [ ] Running React 18.3.x (last 18.x with deprecation warnings)
      [ ] All deprecation warnings resolved
      [ ] No usage of legacy string refs
      [ ] No usage of legacy context (contextTypes)
      [ ] No usage of defaultProps on function components
      [ ] No usage of propTypes at runtime
      [ ] Test suite passing on 18.3.x
      [ ] TypeScript 5.x or later (for type changes)
      ```
      
      ### Step-by-Step Process
      
      1. **Upgrade to React 18.3.x first** -- this version surfaces deprecation warnings for all APIs removed in 19.
      2. **Fix all deprecation warnings** before proceeding.
      3. **Run the official codemod:**
         ```bash
         npx codemod@latest react/19/migration-recipe --target src/
         ```
      4. **Update package.json:**
         ```bash
         npm install react@19 react-dom@19
         npm install -D @types/react@19 @types/react-dom@19
         ```
      5. **Update react-dom entry point:**
         ```tsx
         // Before (React 18)
         import { createRoot } from 'react-dom/client';
         // After (React 19) -- same API, but check for removed APIs below
         ```
      6. **Run tests and fix remaining issues.**
      
      ### Breaking Changes
      
      | Removed API | Replacement |
      |------------|-------------|
      | `forwardRef` | Pass `ref` as a regular prop |
      | `<Context.Provider>` | Use `<Context>` directly as provider |
      | `defaultProps` on function components | Use JS default parameters |
      | `propTypes` runtime checking | Use TypeScript or Flow |
      | `react-test-renderer` | Use `@testing-library/react` |
      | `ReactDOM.render` | Use `createRoot` (already required in 18) |
      | `ReactDOM.hydrate` | Use `hydrateRoot` (already required in 18) |
      | `unmountComponentAtNode` | Use `root.unmount()` |
      | `ReactDOM.findDOMNode` | Use refs |
      
      ### New APIs to Adopt
      
      ```tsx
      // use() hook -- read promises and context in render
      import { use } from 'react';
      
      function UserProfile({ userPromise }: { userPromise: Promise<User> }) {
        const user = use(userPromise);
        return <h1>{user.name}</h1>;
      }
      
      // useActionState -- form action with state
      import { useActionState } from 'react';
      
      function LoginForm() {
        const [state, formAction, isPending] = useActionState(
          async (prev: State, formData: FormData) => {
            const result = await login(formData);
            return result;
          },
          { error: null }
        );
        return <form action={formAction}>...</form>;
      }
      
      // useOptimistic -- optimistic updates
      import { useOptimistic } from 'react';
      
      function TodoList({ todos }: { todos: Todo[] }) {
        const [optimisticTodos, addOptimistic] = useOptimistic(
          todos,
          (state, newTodo: Todo) => [...state, newTodo]
        );
        // ...
      }
      
      // ref as prop -- no more forwardRef
      function Input({ ref, ...props }: { ref?: React.Ref<HTMLInputElement> }) {
        return <input ref={ref} {...props} />;
      }
      
      // Context as provider
      const ThemeContext = createContext('light');
      // Before: <ThemeContext.Provider value="dark">
      // After:
      <ThemeContext value="dark">
        <App />
      </ThemeContext>
      ```
      
      ### Verification Steps
      
      ```bash
      # Run type checking
      npx tsc --noEmit
      
      # Run tests
      npm test
      
      # Search for removed APIs that codemods may have missed
      rg "forwardRef" src/
      rg "Context\.Provider" src/
      rg "defaultProps" src/ --glob "*.tsx"
      rg "propTypes" src/ --glob "*.tsx"
      rg "findDOMNode" src/
      rg "react-test-renderer" package.json
      ```
      
      ---
      
      ## Next.js Pages Router to App Router
      
      ### Pre-Upgrade Checklist
      
      ```
      [ ] Running latest Next.js 14.x or 15.x
      [ ] Understood Server vs Client Component model
      [ ] Identified pages that need client-side interactivity
      [ ] Reviewed data fetching strategy (no more getServerSideProps/getStaticProps)
      [ ] Identified API routes that need migration
      [ ] Middleware already using edge runtime (if applicable)
      ```
      
      ### Step-by-Step Process
      
      1. **Create `app/` directory** alongside existing `pages/`.
      2. **Create `app/layout.tsx`** (replaces `_app.tsx` and `_document.tsx`):
         ```tsx
         export default function RootLayout({ children }: { children: React.ReactNode }) {
           return (
             <html lang="en">
               <body>{children}</body>
             </html>
           );
         }
         ```
      3. **Migrate pages one at a time** -- both routers work simultaneously.
      4. **Convert data fetching:**
         ```tsx
         // Before (Pages Router)
         export async function getServerSideProps() {
           const data = await fetchData();
           return { props: { data } };
         }
         export default function Page({ data }) { ... }
      
         // After (App Router)
         export default async function Page() {
           const data = await fetchData(); // direct async component
           return <div>{data}</div>;
         }
         ```
      5. **Convert dynamic routes:**
         ```
         pages/posts/[id].tsx  →  app/posts/[id]/page.tsx
         pages/[...slug].tsx   →  app/[...slug]/page.tsx
         ```
      6. **Run the official codemod:**
         ```bash
         npx @next/codemod@latest
         ```
      
      ### File Convention Changes
      
      | Pages Router | App Router | Purpose |
      |-------------|-----------|---------|
      | `pages/index.tsx` | `app/page.tsx` | Home page |
      | `pages/about.tsx` | `app/about/page.tsx` | Static page |
      | `pages/posts/[id].tsx` | `app/posts/[id]/page.tsx` | Dynamic page |
      | `pages/_app.tsx` | `app/layout.tsx` | Root layout |
      | `pages/_document.tsx` | `app/layout.tsx` | HTML document |
      | `pages/_error.tsx` | `app/error.tsx` | Error boundary |
      | `pages/404.tsx` | `app/not-found.tsx` | Not found page |
      | `pages/api/hello.ts` | `app/api/hello/route.ts` | API route |
      | N/A | `app/loading.tsx` | Loading UI (new) |
      | N/A | `app/template.tsx` | Re-mounted layout (new) |
      
      ### Data Fetching Migration
      
      | Pages Router | App Router |
      |-------------|-----------|
      | `getServerSideProps` | `async` Server Component (fetches on every request) |
      | `getStaticProps` | `async` Server Component + `fetch` with `cache: 'force-cache'` |
      | `getStaticPaths` | `generateStaticParams()` |
      | `getInitialProps` | Remove entirely -- use Server Components |
      | `useRouter().query` | `useSearchParams()` (client) or `searchParams` prop (server) |
      
      ### Common Breaking Changes
      
      - `useRouter` from `next/navigation` not `next/router`
      - `pathname` no longer includes query parameters
      - `Link` no longer requires `<a>` child
      - CSS Modules class names may differ
      - `Image` component default behavior changes
      - Metadata API replaces `<Head>` component
      - Route handlers replace API routes (different request/response model)
      
      ### Verification Steps
      
      ```bash
      # Check for Pages Router imports in migrated files
      rg "from 'next/router'" app/
      rg "getServerSideProps|getStaticProps|getInitialProps" app/
      rg "next/head" app/
      
      # Verify all routes work
      npm run build  # catches most issues at build time
      npm run dev    # test interactive behavior
      ```
      
      ---
      
      ## Vue 2 to 3
      
      ### Pre-Upgrade Checklist
      
      ```
      [ ] Identified all breaking syntax changes (v-model, filters, events)
      [ ] Listed third-party Vue 2 plugins that need Vue 3 equivalents
      [ ] Decided on migration approach: migration build (@vue/compat) vs direct
      [ ] Decided on state management: Vuex → Pinia migration
      [ ] Test suite passing on Vue 2
      ```
      
      ### Step-by-Step Process (Using Migration Build)
      
      1. **Upgrade to Vue 2.7** first (backports Composition API, `<script setup>`).
      2. **Start adopting Composition API** in Vue 2.7 where convenient.
      3. **Switch to Vue 3 + @vue/compat:**
         ```bash
         npm install vue@3 @vue/compat
         ```
      4. **Configure compat mode** in bundler (Vite or Webpack):
         ```js
         // vite.config.js
         export default {
           resolve: {
             alias: { vue: '@vue/compat' }
           }
         };
         ```
      5. **Fix compatibility warnings** one category at a time.
      6. **Remove `@vue/compat`** once all warnings are resolved.
      
      ### Breaking Changes
      
      | Vue 2 | Vue 3 | Notes |
      |-------|-------|-------|
      | `v-model` (default) | `v-model` uses `modelValue` prop + `update:modelValue` event | Custom `model` option removed |
      | `v-bind.sync` | `v-model:propName` | `.sync` modifier removed |
      | Filters `{{ value \| filter }}` | Methods or computed | Filters removed entirely |
      | `$on`, `$off`, `$once` | External library (mitt) | Event bus pattern removed |
      | `Vue.component()` global | `app.component()` | Global API restructured |
      | `Vue.use()` | `app.use()` | Plugin installation |
      | `Vue.mixin()` | `app.mixin()` or Composition API | Global mixins |
      | `Vue.filter()` | N/A | Filters removed |
      | `this.$set` / `Vue.set` | Direct assignment | Reactivity system rewritten (Proxy-based) |
      | `this.$delete` / `Vue.delete` | `delete obj.prop` | Proxy handles this |
      | `$listeners` | Merged into `$attrs` | Separate `$listeners` removed |
      | `$children` | Template refs | Direct child access removed |
      | `<transition>` class names | `v-enter-from` / `v-leave-from` | `-active` suffix retained |
      
      ### Vuex to Pinia Migration
      
      ```ts
      // Vuex (old)
      const store = createStore({
        state: { count: 0 },
        mutations: { increment(state) { state.count++; } },
        actions: { asyncIncrement({ commit }) { commit('increment'); } },
        getters: { doubleCount: (state) => state.count * 2 }
      });
      
      // Pinia (new)
      export const useCounterStore = defineStore('counter', () => {
        const count = ref(0);
        const doubleCount = computed(() => count.value * 2);
        function increment() { count.value++; }
        async function asyncIncrement() { increment(); }
        return { count, doubleCount, increment, asyncIncrement };
      });
      ```
      
      ### Available Codemods
      
      ```bash
      # Vue official codemod
      npx @vue/codemod src/
      
      # Specific transforms
      npx @vue/codemod src/ --transform vue-class-component-v8
      npx @vue/codemod src/ --transform new-global-api
      npx @vue/codemod src/ --transform vue-router-v4
      ```
      
      ### Verification Steps
      
      ```bash
      # Search for Vue 2 patterns
      rg "\$on\(|\.sync|Vue\.component|Vue\.use|Vue\.mixin" src/
      rg "this\.\$set|this\.\$delete|this\.\$children" src/
      rg "filters:" src/ --glob "*.vue"
      rg "v-bind\.sync" src/ --glob "*.vue"
      
      # Build and test
      npm run build
      npm test
      ```
      
      ---
      
      ## Laravel 10 to 13
      
      ### Pre-Upgrade Checklist
      
      ```
      [ ] Running PHP 8.2+ (Laravel 11 requires PHP 8.2 minimum)
      [ ] All tests passing on Laravel 10
      [ ] Reviewed Laravel 11 release notes
      [ ] Identified custom service providers that need updates
      [ ] Checked third-party package Laravel 11 compatibility
      ```
      
      ### Step-by-Step Process
      
      1. **Use Laravel Shift** (recommended, paid automated service):
         ```
         https://laravelshift.com
         ```
      2. **Or manual upgrade -- update composer.json:**
         ```json
         {
           "require": {
             "laravel/framework": "^11.0"
           }
         }
         ```
      3. **Run composer update:**
         ```bash
         composer update
         ```
      4. **Apply skeleton changes** (Laravel 11 uses a slimmer skeleton):
         - `bootstrap/app.php` is simplified
         - Many config files removed from `config/` (use defaults)
         - Service providers consolidated
         - Middleware moved to `bootstrap/app.php`
         - `app/Http/Kernel.php` removed
      5. **Fix deprecation warnings and test.**
      
      ### Breaking Changes
      
      | Laravel 10 | Laravel 11 | Notes |
      |-----------|-----------|-------|
      | `app/Http/Kernel.php` | `bootstrap/app.php` | Middleware registration moved |
      | Multiple service providers | Single `AppServiceProvider` | Consolidated providers |
      | Full `config/` directory | Minimal config (publish as needed) | `php artisan config:publish` to restore |
      | Console `Kernel.php` | `routes/console.php` with closures | Schedule defined in `routes/console.php` |
      | Exception handler class | `bootstrap/app.php` withExceptions() | Exception handling consolidated |
      | `$schedule->command()->everyMinute()` | `->everySecond()` now available | Per-second scheduling added |
      | Explicit casts property | `casts()` method on model | Method-based casting |
      
      ### New Features to Adopt
      
      ```php
      // Per-second scheduling
      Schedule::command('check:pulse')->everySecond();
      
      // Dumpable trait
      use Illuminate\Support\Traits\Dumpable;
      
      class MyService {
          use Dumpable;
          // Now supports ->dd() and ->dump() chaining
      }
      
      // Method-based casts
      protected function casts(): array {
          return [
              'options' => AsArrayObject::class,
              'created_at' => 'datetime:Y-m-d',
          ];
      }
      
      // Simplified bootstrap/app.php
      return Application::configure(basePath: dirname(__DIR__))
          ->withRouting(
              web: __DIR__.'/../routes/web.php',
              api: __DIR__.'/../routes/api.php',
          )
          ->withMiddleware(function (Middleware $middleware) {
              $middleware->web(append: [CustomMiddleware::class]);
          })
          ->withExceptions(function (Exceptions $exceptions) {
              $exceptions->report(function (SomeException $e) {
                  // custom reporting
              });
          })
          ->create();
      ```
      
      ### Verification Steps
      
      ```bash
      # Check for removed patterns
      rg "class Kernel extends HttpKernel" app/
      rg "class Handler extends ExceptionHandler" app/
      
      # Run tests
      php artisan test
      
      # Check config
      php artisan config:show
      
      # Verify routes
      php artisan route:list
      ```
      
      ### Continuing to Laravel 12 and 13
      
      Upgrade one major at a time (11 → 12 → 13); both steps are deliberately light.
      
      **Laravel 12** (Feb 2025) — maintenance-focused: minimal breaking changes, dependency
      bumps, and new starter kits (React/Vue/Livewire). Most apps upgrade by updating
      `composer.json` constraints and re-running the suite.
      
      **Laravel 13** (Mar 2026) — stability-first with an AI/DX push:
      - **Requires PHP 8.3+** (supports through PHP 8.5) — upgrade the runtime first
      - Default cache and Redis key prefixes now use hyphenated suffixes — irrelevant if your
        config files already pin these values, breaking if you shared a store across versions
      - New (opt-in): first-party Laravel AI SDK, native PHP attributes across models/jobs/
        controllers/authorization, `Queue::route(...)` class-based queue routing, query-builder
        vector similarity search (pgvector)
      - Laravel Shift remains the automated PR-based upgrade path
      
      ```bash
      # Per major: bump constraint, update, test
      composer require laravel/framework:^13.0 --with-all-dependencies
      php artisan config:show cache   # verify key-prefix expectations
      php artisan test
      ```
      
      ---
      
      ## Angular Version Upgrades
      
      ### Pre-Upgrade Checklist
      
      ```
      [ ] Check Angular Update Guide: https://update.angular.io
      [ ] Running the latest patch of current major version
      [ ] All tests passing
      [ ] No deprecated APIs in use (check ng build warnings)
      [ ] Third-party libraries checked for target version compatibility
      ```
      
      ### Step-by-Step Process
      
      1. **Check the update guide** for your specific version jump:
         ```
         https://update.angular.io/?from=16.0&to=17.0
         ```
      2. **Run ng update** for core packages:
         ```bash
         ng update @angular/core @angular/cli
         ```
      3. **Run ng update** for additional Angular packages:
         ```bash
         ng update @angular/material  # if using Material
         ng update @angular/router    # if needed
         ```
      4. **Review and apply schematics** that ng update runs automatically.
      5. **Fix any remaining issues** and run tests.
      
      ### Recent Major Changes by Version
      
      | Version | Key Changes |
      |---------|-------------|
      | **14** | Standalone components, typed forms, inject() function |
      | **15** | Standalone APIs stable, directive composition, image optimization |
      | **16** | Signals (developer preview), required inputs, esbuild builder |
      | **17** | Signals stable, deferrable views, built-in control flow, esbuild default |
      | **18** | Zoneless change detection (experimental), Material 3, fallback content |
      | **19** | Standalone by default, linked signals, resource API, incremental hydration |
      
      ### Common Pitfalls
      
      - **RxJS version**: Angular often requires specific RxJS versions. Check compatibility.
      - **TypeScript version**: Each Angular major requires a specific TS range.
      - **Zone.js**: Being phased out in favor of signals. Plan accordingly.
      - **Module vs Standalone**: Newer versions push toward standalone components.
      
      ### Verification Steps
      
      ```bash
      ng build --configuration=production
      ng test
      ng e2e
      
      # Check for deprecation warnings in build output
      ng build 2>&1 | rg -i "deprecated|warning"
      ```
      
      ---
      
      ## Django Version Upgrades
      
      ### Pre-Upgrade Checklist
      
      ```
      [ ] Running the latest patch of current major version
      [ ] All deprecation warnings resolved
      [ ] Tests passing with python -Wd (warnings as errors)
      [ ] Third-party packages checked for target version support
      [ ] Database migrations up to date
      ```
      
      ### Step-by-Step Process
      
      1. **Enable deprecation warnings:**
         ```bash
         python -Wd manage.py test
         ```
      2. **Fix all deprecation warnings** on current version.
      3. **Read release notes** for target version:
         ```
         https://docs.djangoproject.com/en/5.0/releases/
         ```
      4. **Update Django:**
         ```bash
         pip install Django==5.0
         ```
      5. **Run django-upgrade codemod:**
         ```bash
         pip install django-upgrade
         django-upgrade --target-version 5.0 $(fd -e py)
         ```
      6. **Run tests and fix issues.**
      
      ### Recent Major Changes by Version
      
      | Version | Key Changes |
      |---------|-------------|
      | **4.0** | Redis cache backend, `scrypt` hasher, template-based form rendering |
      | **4.1** | Async ORM, `async` view support, validation of model constraints |
      | **4.2** | Psycopg 3, `STORAGES` setting, custom file storage |
      | **5.0** | Facet filters in admin, simplified templates, database-computed default |
      | **5.1** | LoginRequiredMiddleware, connection pool for PostgreSQL |
      
      ### Available Codemods
      
      ```bash
      # django-upgrade: automated fixes
      pip install django-upgrade
      django-upgrade --target-version 5.0 **/*.py
      
      # Specific transforms handled:
      # - url() to path() in urlconfs
      # - @admin.register decorator
      # - HttpResponse charset parameter
      # - Deprecated model field arguments
      ```
      
      ### Verification Steps
      
      ```bash
      # Full test suite with warnings
      python -Wd manage.py test
      
      # Check for deprecated imports
      rg "from django.utils.encoding import force_text" .
      rg "from django.conf.urls import url" .
      rg "from django.utils.translation import ugettext" .
      
      # Verify migrations
      python manage.py makemigrations --check
      python manage.py migrate --run-syncdb
      
      # Check system
      python manage.py check --deploy
      ```
      
      ---
      
      ## Cross-Framework Migration Checklist
      
      Regardless of which framework you are upgrading, follow this universal checklist after the migration is complete:
      
      ```
      Post-Migration Verification
      │
      ├─ [ ] All tests pass (unit, integration, e2e)
      ├─ [ ] Build succeeds in production mode
      ├─ [ ] No deprecation warnings in build output
      ├─ [ ] Bundle size compared to pre-migration baseline
      ├─ [ ] Performance benchmarks compared to pre-migration baseline
      ├─ [ ] Error monitoring shows no new error types
      ├─ [ ] All pages/routes load correctly (smoke test)
      ├─ [ ] Forms and user interactions work
      ├─ [ ] Authentication and authorization work
      ├─ [ ] Third-party integrations verified
      ├─ [ ] CI/CD pipeline updated for new version
      ├─ [ ] Docker/deployment images updated
      ├─ [ ] Documentation updated (README, setup guide)
      └─ [ ] Team notified of completed migration
      ```
      
    • language-upgrades.md 25.3 KB
      # Language Version Upgrades
      
      Detailed upgrade paths for major programming language version transitions.
      
      ---
      
      ## Python 3.9 to 3.14
      
      ### Python 3.10 (from 3.9)
      
      **Key Features Gained:**
      - Structural pattern matching (`match`/`case`)
      - Parenthesized context managers
      - Better error messages with precise line indicators
      - `typing.TypeAlias` for explicit type aliases
      - `zip()` gets `strict` parameter
      - `bisect` and `statistics` module improvements
      
      **Breaking Changes:**
      - `distutils` deprecated (use `setuptools` instead)
      - `loop` parameter removed from most `asyncio` high-level APIs
      - `int` has a new `bit_count()` method (name collision risk)
      
      **Migration Commands:**
      ```bash
      # Update pyproject.toml / setup.cfg
      python-requires = ">=3.10"
      
      # Run pyupgrade for syntax modernization
      pip install pyupgrade
      pyupgrade --py310-plus $(fd -e py)
      
      # Check for distutils usage
      rg "from distutils" .
      rg "import distutils" .
      ```
      
      ### Python 3.11 (from 3.10)
      
      **Key Features Gained:**
      - Exception groups and `except*` syntax
      - `tomllib` in standard library (TOML parsing)
      - Task groups in asyncio (`asyncio.TaskGroup`)
      - Fine-grained error locations in tracebacks
      - 10-60% faster CPython (Faster CPython project)
      - `Self` type in `typing` module
      - `StrEnum` class
      
      **Breaking Changes:**
      - `asyncio.coroutine` decorator removed
      - `unittest.TestCase.addModuleCleanup` behavior change
      - `locale.getdefaultlocale()` deprecated
      - `smtpd` module removed (use `aiosmtpd`)
      
      **Migration Commands:**
      ```bash
      pyupgrade --py311-plus $(fd -e py)
      
      # Replace manual TOML parsing
      rg "import toml\b" .        # replace with: import tomllib
      rg "toml\.loads?" .          # replace with: tomllib.loads / tomllib.load
      
      # Check for removed modules
      rg "import smtpd" .
      rg "asyncio\.coroutine" .
      ```
      
      ### Python 3.12 (from 3.11)
      
      **Key Features Gained:**
      - Type parameter syntax (`class Stack[T]:`, `def first[T](l: list[T]) -> T:`)
      - `type` statement for type aliases (`type Vector = list[float]`)
      - F-string improvements (nested quotes, backslashes, comments)
      - Per-interpreter GIL (subinterpreters)
      - `pathlib.Path.walk()` method
      - Improved `asyncio.TaskGroup` semantics
      - Buffer protocol accessible from Python (`__buffer__`)
      
      **Breaking Changes:**
      - `distutils` package removed entirely (was deprecated in 3.10)
      - `imp` module removed (use `importlib`)
      - `locale.getdefaultlocale()` removed
      - `unittest` method aliases removed (`assertEquals` etc.)
      - `asyncio` legacy API removals
      - `pkgutil.find_loader()` / `get_loader()` removed
      - `sqlite3` default adapters and converters no longer registered by default
      - `os.popen()` and `os.spawn*()` deprecated
      - Wstr representation removed from C API
      
      **Migration Commands:**
      ```bash
      pyupgrade --py312-plus $(fd -e py)
      
      # Check for removed modules
      rg "import imp\b" .          # replace with importlib
      rg "from imp " .
      rg "import distutils" .      # must use setuptools
      rg "from distutils" .
      
      # Check for removed unittest aliases
      rg "assertEquals|assertNotEquals|assertRegexpMatches" .
      
      # Adopt new type syntax (optional but recommended)
      # Old: T = TypeVar('T')
      # New: def func[T](x: T) -> T:
      ```
      
      ### Python 3.13 (from 3.12)
      
      **Key Features Gained:**
      - Free-threaded mode (experimental, `--disable-gil` build)
      - Improved interactive interpreter (REPL with colors, multiline editing)
      - `locals()` returns copy with defined semantics
      - Improved error messages (color, suggestions)
      - `dbm.sqlite3` as default dbm backend
      - `argparse` deprecations enforced
      - JIT compiler (experimental, `--enable-experimental-jit` build)
      
      **Breaking Changes:**
      - `aifc`, `audioop`, `cgi`, `cgitb`, `chunk`, `crypt`, `imghdr`, `mailcap`, `msilib`, `nis`, `nntplib`, `ossaudiodev`, `pipes`, `sndhdr`, `spwd`, `sunau`, `telnetlib`, `uu`, `xdrlib` modules removed
      - `pathlib.PurePath.is_relative_to()` and `relative_to()` semantics change
      - `typing.io` and `typing.re` namespaces removed
      - `locale.resetlocale()` removed
      - C API changes affecting extension modules
      
      **Migration Commands:**
      ```bash
      # Check for removed stdlib modules
      rg "import (aifc|audioop|cgi|cgitb|chunk|crypt|imghdr|mailcap|nis|nntplib|ossaudiodev|pipes|sndhdr|spwd|sunau|telnetlib|uu|xdrlib)" .
      
      # For cgi module replacement
      rg "import cgi" .       # replace with: from urllib.parse import parse_qs
      rg "cgi.FieldStorage" . # replace with: manual multipart parsing or framework
      
      # Check for typing namespace changes
      rg "typing\.io\." .
      rg "typing\.re\." .
      
      # Test free-threaded mode (experimental)
      python3.13t script.py  # if built with --disable-gil
      ```
      
      ### Python 3.14 (from 3.13)
      
      **Key Features Gained:**
      - Deferred evaluation of annotations by default (PEP 649/749) — inspect via `annotationlib`; `from __future__ import annotations` is no longer needed
      - Template strings / t-strings (PEP 750)
      - Free-threaded build officially supported (PEP 779 — still a separate build, no longer "experimental")
      - Multiple interpreters in the stdlib (`concurrent.interpreters`, PEP 734)
      - Zstandard compression (`compression.zstd`, PEP 784)
      - Safe external debugger interface (`sys.remote_exec`)
      
      **Breaking Changes:**
      - `asyncio.get_event_loop()` no longer creates a new event loop — use `asyncio.run()` or `get_running_loop()`
      - Long-deprecated `ast` aliases (`ast.Num`, `ast.Str`, `ast.Bytes`, ...) removed — use `ast.Constant`
      - Annotation-introspection code that reads `__annotations__` eagerly may see deferred semantics — migrate to `annotationlib`
      
      **Migration Commands:**
      ```bash
      # Find eager-annotation assumptions
      rg "from __future__ import annotations" .   # now redundant (harmless to keep)
      rg "__annotations__" .                       # candidates for annotationlib
      
      # Find event-loop creation via the removed pattern
      rg "asyncio\.get_event_loop\(\)" .
      
      # Modernize syntax to the new floor
      pyupgrade --py314-plus **/*.py
      ```
      
      ### Python Version Upgrade Summary
      
      | From → To | Key Action | Biggest Risk |
      |-----------|-----------|--------------|
      | 3.9 → 3.10 | Fix `distutils` usage, adopt pattern matching | `asyncio` loop parameter removal |
      | 3.10 → 3.11 | Replace `toml` with `tomllib`, enjoy speed boost | `smtpd` removal |
      | 3.11 → 3.12 | Remove `distutils`/`imp`, adopt type syntax | `distutils` full removal, sqlite3 adapter changes |
      | 3.12 → 3.13 | Remove deprecated stdlib modules | Large number of removed stdlib modules |
      | 3.13 → 3.14 | Adopt deferred annotations + t-strings | `asyncio.get_event_loop()` no longer creates a loop |
      
      ---
      
      ## Node.js 18 to 26
      
      ### Node.js 20 (from 18)
      
      **Key Features Gained:**
      - Permission model (`--experimental-permission`)
      - Stable test runner (`node:test`)
      - `.env` file support (`--env-file=.env`)
      - V8 11.3 (improved performance)
      - `import.meta.resolve()` unflagged
      - Single executable applications (SEA)
      - `URL.canParse()` static method
      - `ArrayBuffer.transfer()` and `resizable` option
      - `WebSocket` client (experimental)
      
      **Breaking Changes:**
      - `url.parse()` may throw on invalid URLs (stricter parsing)
      - `fs.read()` parameter validation stricter
      - Custom ESM loader hooks (`load`, `resolve`) are off-thread
      - `http.IncomingMessage` connected socket timeout default change
      
      **Migration Commands:**
      ```bash
      # Update nvm / fnm
      nvm install 20
      nvm use 20
      
      # Or update Docker
      # FROM node:20-alpine
      
      # Check for url.parse usage (may need URL constructor)
      rg "url\.parse\(" .
      
      # Adopt built-in test runner (optional)
      # Replace: jest/mocha test files
      # With: import { test, describe } from 'node:test';
      
      # Use .env file support
      node --env-file=.env app.js
      ```
      
      ### Node.js 22 (from 20)
      
      **Key Features Gained:**
      - `require()` for ESM modules (experimental `--experimental-require-module`)
      - WebSocket client stable
      - Built-in watch mode stable (`node --watch`)
      - `glob` and `globSync` in `node:fs`
      - V8 12.4 (Maglev compiler, `Array.fromAsync`)
      - `node:sqlite` built-in module (experimental)
      - `--run` flag for package.json scripts
      - Task runner integration
      - `AbortSignal.any()`
      - Stable permission model
      
      **Breaking Changes:**
      - `node:http` stricter header validation
      - `node:buffer` Blob changes
      - Minimum glibc 2.28 on Linux
      - `node:child_process` IPC serialization changes
      - `node:dns` default resolver changes
      
      **Migration Commands:**
      ```bash
      nvm install 22
      nvm use 22
      
      # Or update Docker
      # FROM node:22-alpine
      
      # Check for incompatible native modules
      npm rebuild
      
      # Test ESM/CJS interop if using mixed modules
      node --experimental-require-module app.js
      
      # Adopt built-in features
      # Replace: glob package → node:fs { glob, globSync }
      # Replace: ws package → built-in WebSocket (for client usage)
      # Replace: chokidar/nodemon → node --watch
      ```
      
      ### Node.js 24 (from 22)
      
      **Key Features Gained:**
      - `require()` of ESM modules enabled by default (no flag)
      - V8 13.6 — `Float16Array`, `RegExp.escape`, `Error.isError`, explicit resource management (`await using`)
      - `URLPattern` as a global
      - Permission model stable (flag renamed to `--permission`)
      - npm 11, Undici 7
      
      **Breaking Changes:**
      - Windows native builds drop MSVC (ClangCL toolchain) — affects native-addon build pipelines
      - Assorted deprecated API removals (`url.parse()` further discouraged — use `URL`)
      
      **Migration Commands:**
      ```bash
      nvm install 24
      nvm use 24
      # FROM node:24-alpine
      
      npm rebuild                      # native modules against new ABI
      rg "require\(['\"]\./.*\.mjs" .  # spots that relied on require(esm) flags
      ```
      
      ### Node.js 26 (from 24)
      
      Node 26 is the Current line (April 2026); it enters Active LTS in October 2026. For
      production, upgrade LTS-to-LTS: 22 → 24 now, 24 → 26 once 26 is LTS. The mechanics are
      the same as every even-major bump: rebuild native modules, re-run the test suite on the
      new V8, and update `FROM node:26` images plus CI matrices when you cut over.
      
      ### Node.js Version Upgrade Summary
      
      | From → To | Key Action | Biggest Risk |
      |-----------|-----------|--------------|
      | 18 → 20 | Rebuild native modules, test URL parsing | Stricter URL validation, loader hooks off-thread |
      | 20 → 22 | Rebuild native modules, check glibc version | Native module compatibility, header validation |
      | 22 → 24 | Adopt require(esm), permission model | Native-addon toolchain change on Windows |
      | 24 → 26 | LTS-to-LTS bump when 26 enters LTS (Oct 2026) | Riding the Current line before LTS |
      
      ---
      
      ## TypeScript 4.x to 6.0
      
      ### TypeScript 5.0 (from 4.9)
      
      **Key Features Gained:**
      - ECMAScript decorators (stage 3 standard)
      - `const` type parameters
      - `--moduleResolution bundler`
      - `extends` on multiple config files
      - All `enum`s become union `enum`s
      - `--verbatimModuleSyntax` (replaces `isolatedModules`)
      - Speed and size improvements (TS migrated to modules internally)
      - `satisfies` operator (introduced in 4.9, now mature)
      
      **Breaking Changes:**
      - `--target ES3` removed
      - `--out` removed (use `--outFile`)
      - `--noImplicitUseStrict` removed
      - `--suppressExcessPropertyErrors` removed
      - `--suppressImplicitAnyIndexErrors` removed
      - `--prepend` in project references removed
      - Runtime behavior of decorators changed (now ECMAScript standard)
      - `--moduleResolution node` renamed to `node10`
      - `--module` value changes
      
      **Migration Commands:**
      ```bash
      npm install -D typescript@5
      
      # Check for removed compiler options in tsconfig.json
      rg '"target":\s*"ES3"' tsconfig.json
      rg '"out":' tsconfig.json
      rg '"suppressExcessPropertyErrors"' tsconfig.json
      
      # If using legacy decorators, keep experimentalDecorators flag
      # If adopting new decorators, remove experimentalDecorators
      
      # Adopt bundler module resolution
      # tsconfig.json: "moduleResolution": "bundler"
      ```
      
      ### TypeScript 5.1-5.7 Highlights
      
      | Version | Key Feature |
      |---------|-------------|
      | **5.1** | Easier implicit return for `undefined`, unrelated getter/setter types |
      | **5.2** | `using` declarations (explicit resource management), decorator metadata |
      | **5.3** | `import` attribute support, `resolution-mode` in all module modes |
      | **5.4** | `NoInfer<T>` utility type, `Object.groupBy` / `Map.groupBy` types |
      | **5.5** | Inferred type predicates, regex syntax checking, `isolatedDeclarations` |
      | **5.6** | Iterator helper methods, `--noUncheckedSideEffectImports` |
      | **5.7** | `--rewriteRelativeImportExtensions`, `--target es2024` |
      
      ### TypeScript 6.0 (from 5.x)
      
      The last release on the JavaScript-based compiler — 6.0 modernises defaults as the
      bridge to the native (Go) compiler in TypeScript 7.
      
      **Key Features Gained:**
      - `es2025` target/lib with types for Temporal, `Map.getOrInsert`, `RegExp.escape`
      - Better inference for `this`-less functions; `#/` subpath-import support
      - `--stableTypeOrdering` flag to ease 6.0 → 7.0 migration diffing
      - Large-monorepo `tsc --watch` rebuilds significantly faster
      
      **Breaking Changes:**
      - `strict: true` is now the **default** — configs that never set it surface new errors
      - Defaults changed: `module: esnext`, `target: es2025`
      - Removed: `moduleResolution: classic`; `module: amd/umd/system/none`; minimum `target` is ES2015 (`es5` deprecated)
      - `esModuleInterop` / `allowSyntheticDefaultImports` can no longer be set to `false`
      - Namespace-with-class merging requires explicit `export`
      
      **Migration Commands:**
      ```bash
      npm install -D typescript@6
      
      # Find options 6.0 removed or hard-wires
      rg '"moduleResolution":\s*"classic"' tsconfig.json
      rg '"module":\s*"(amd|umd|system|none)"' tsconfig.json
      rg '"esModuleInterop":\s*false' tsconfig.json
      rg '"target":\s*"es5"' -i tsconfig.json
      
      # Surface the strict-by-default delta before committing to it
      npx tsc --noEmit
      ```
      
      ### Migration Strategy
      
      ```
      TypeScript version upgrade approach:
      │
      ├─ Minor version (x.y → x.z)
      │  └─ Generally safe, just update and fix new errors
      │     npm install -D typescript@latest
      │     npx tsc --noEmit
      │
      ├─ Major version (4.x → 5.x)
      │  ├─ 1. Update tsconfig.json (remove deleted options)
      │  ├─ 2. Install typescript@5
      │  ├─ 3. Run tsc --noEmit, fix errors
      │  ├─ 4. Decide on decorator strategy (legacy vs ECMAScript)
      │  └─ 5. Consider adopting moduleResolution: "bundler"
      │
      └─ Major version (5.x → 6.0)
         ├─ 1. Set strict: true explicitly and fix errors BEFORE upgrading
         ├─ 2. Replace removed module/moduleResolution options
         ├─ 3. Install typescript@6, run tsc --noEmit
         └─ 4. Pin the target you actually ship (defaults moved to es2025)
      ```
      
      ---
      
      ## Go 1.20 to 1.26
      
      ### Go 1.21 (from 1.20)
      
      **Key Features Gained:**
      - `log/slog` structured logging (standard library)
      - `slices` and `maps` packages in standard library
      - `min()` and `max()` built-in functions
      - `clear()` built-in for maps and slices
      - PGO (Profile-Guided Optimization) generally available
      - `go.mod` toolchain directive
      - Forward compatibility (`GOTOOLCHAIN` environment variable)
      
      **Breaking Changes:**
      - `go.mod` now tracks toolchain version
      - Panic on `nil` pointer dereference in more cases
      - `net/http` minor behavior changes
      
      **Migration Commands:**
      ```bash
      # Update go.mod
      go mod edit -go=1.21
      go mod tidy
      
      # Adopt slog for structured logging
      rg "log\.Printf|log\.Println" .  # candidates for slog migration
      
      # Replace sort.Slice with slices.SortFunc
      rg "sort\.Slice\b" .  # consider slices.SortFunc
      ```
      
      ### Go 1.22 (from 1.21)
      
      **Key Features Gained:**
      - `for range` over integers (`for i := range 10`)
      - Enhanced `net/http` routing (method + path patterns)
      - Loop variable fix (each iteration gets its own variable)
      - `math/rand/v2` package
      - `go/version` package
      - `slices.Concat`
      
      **Breaking Changes:**
      - Loop variable semantics change (each iteration gets a copy -- fixes the classic goroutine-in-loop bug)
      - `math/rand` global functions deterministic without seed
      
      **Migration Commands:**
      ```bash
      go mod edit -go=1.22
      go mod tidy
      
      # The loop variable change is backward compatible but may fix hidden bugs
      # Review goroutine closures in loops that relied on shared variable
      
      # Adopt enhanced routing
      # Old: mux.HandleFunc("/users", handler) + manual method check
      # New: mux.HandleFunc("GET /users/{id}", handler)
      rg "r\.Method ==" .  # candidates for enhanced routing
      ```
      
      ### Go 1.23 (from 1.22)
      
      **Key Features Gained:**
      - Iterators (`iter.Seq`, `iter.Seq2`) and `range over func`
      - `unique` package (interning/canonicalization)
      - `structs` package (struct layout control)
      - Timer/Ticker changes (garbage collected when unreferenced)
      - `slices` and `maps` moved from `golang.org/x/exp` to standard library
      - OpenTelemetry-compatible `log/slog` handlers
      
      **Breaking Changes:**
      - `time.Timer` and `time.Ticker` behavior change (channels drained on Stop/Reset)
      - `os/exec` `LookPath` behavior on Windows (security fix)
      
      **Migration Commands:**
      ```bash
      go mod edit -go=1.23
      go mod tidy
      
      # Replace x/exp/slices and x/exp/maps with standard library versions
      rg "golang.org/x/exp/slices" .
      rg "golang.org/x/exp/maps" .
      # Replace with: "slices" and "maps"
      
      # Check Timer/Ticker usage
      rg "\.Stop\(\)" . --glob "*.go"  # Review timer stop behavior
      rg "\.Reset\(" . --glob "*.go"   # Review timer reset behavior
      ```
      
      ### Go 1.24 – 1.26 (from 1.23)
      
      **Key Features Gained:**
      - **1.24**: generic type aliases; `tool` directives in go.mod (tracked tool deps); `os.Root` (filesystem-scoped file access); Swiss-table map implementation (runtime perf); `weak` package; `runtime.AddCleanup` (successor to `SetFinalizer`)
      - **1.25**: container-aware `GOMAXPROCS` (respects cgroup CPU limits); `testing/synctest` for concurrent-code tests; experimental green-tea GC
      - **1.26**: current stable line — as with every Go release, review the release notes for `go vet`/runtime deltas; language changes remain rare and gated on the `go` directive
      
      **Breaking Changes:**
      - Effectively none at the language level (Go 1 compatibility promise); behavior deltas are gated on the `go` directive version in go.mod, so bumps activate deliberately
      
      **Migration Commands:**
      ```bash
      go mod edit -go=1.26
      go mod tidy
      go vet ./...
      govulncheck ./...
      
      # Adopt tool directives (1.24+) — replaces tools.go pattern
      go get -tool golang.org/x/tools/cmd/stringer
      ```
      
      ### Go Version Upgrade Summary
      
      | From → To | Key Action | Biggest Risk |
      |-----------|-----------|--------------|
      | 1.20 → 1.21 | Update go.mod toolchain, adopt slog | Toolchain directive in go.mod |
      | 1.21 → 1.22 | Enjoy loop variable fix, adopt enhanced routing | Loop variable semantics (usually fixes bugs) |
      | 1.22 → 1.23 | Replace x/exp packages, adopt iterators | Timer/Ticker behavior change |
      | 1.23 → 1.26 | Bump go directive stepwise, adopt tool directives + os.Root | Runtime perf deltas (Swiss maps, GC) in hot paths |
      
      ---
      
      ## Rust Edition 2021 to 2024
      
      ### Key Features in Edition 2024
      
      - **RPITIT** (Return Position Impl Trait in Traits): use `-> impl Trait` in trait definitions
      - **Async fn in traits**: `async fn` directly in trait definitions (no need for `async-trait` crate)
      - **`let` chains**: `if let Some(x) = a && let Some(y) = b { ... }`
      - **`gen` blocks** (experimental): generator-based iterators
      - **Lifetime capture rules**: all in-scope lifetimes captured by default in `-> impl Trait`
      - **`unsafe_op_in_unsafe_fn`** lint: must use `unsafe {}` blocks inside `unsafe fn`
      - **Precise capturing** with `use<>` syntax
      - **`#[diagnostic]` attribute** namespace for custom diagnostics
      - **Reserving `gen` keyword** for generators
      - **Temporary lifetime extension** changes in `match` and `if let`
      
      ### Breaking Changes
      
      | Change | Impact | Fix |
      |--------|--------|-----|
      | `unsafe_op_in_unsafe_fn` is deny by default | `unsafe fn` bodies need explicit `unsafe {}` blocks | Wrap unsafe operations in `unsafe {}` |
      | Lifetime capture rules change | `-> impl Trait` captures all in-scope lifetimes | Use `use<'a>` for precise control |
      | `gen` is a reserved keyword | Cannot use `gen` as identifier | Rename `gen` variables/functions |
      | `never` type fallback changes | `!` type fallback now `!` instead of `()` | May affect type inference in rare cases |
      | Temporary lifetime changes | Temporaries in `match` scrutinee have shorter lifetime | Store temporaries in `let` bindings |
      | `unsafe extern` blocks | `extern` items implicitly unsafe to reference | Add `safe` keyword to safe extern items |
      | Disallow references to `static mut` | `&STATIC_MUT` is forbidden | Use `addr_of!()` / `addr_of_mut!()` |
      
      ### Migration Commands
      
      ```bash
      # Automatic edition migration
      cargo fix --edition
      
      # Update Cargo.toml
      # edition = "2024"
      
      # Fix unsafe_op_in_unsafe_fn warnings
      cargo clippy --fix -- -W unsafe_op_in_unsafe_fn
      
      # Check for gen keyword conflicts
      rg "\bgen\b" src/ --glob "*.rs"
      
      # Remove async-trait crate if adopting native async traits
      rg "async.trait" Cargo.toml
      rg "#\[async_trait\]" src/
      ```
      
      ### Verification Steps
      
      ```bash
      cargo build
      cargo test
      cargo clippy -- -D warnings
      cargo doc --no-deps  # check documentation builds
      ```
      
      ---
      
      ## PHP 8.1 to 8.5
      
      ### PHP 8.2 (from 8.1)
      
      **Key Features Gained:**
      - Readonly classes
      - Disjunctive Normal Form (DNF) types
      - `null`, `false`, `true` as standalone types
      - Constants in traits
      - Enum improvements
      - Random extension (`\Random\Randomizer`)
      - `SensitiveParameter` attribute
      - Fibers improvements
      
      **Breaking Changes:**
      - Dynamic properties deprecated (use `#[AllowDynamicProperties]` or `__get`/`__set`)
      - Implicit nullable parameter declarations deprecated
      - `${var}` string interpolation deprecated (use `{$var}`)
      - `utf8_encode` / `utf8_decode` deprecated
      - Various internal class changes
      
      **Migration Commands:**
      ```bash
      # Rector automated fixes
      composer require rector/rector --dev
      vendor/bin/rector process src --set php82
      
      # Check for dynamic properties
      rg "->(\w+)\s*=" src/ --glob "*.php"  # review for undeclared properties
      
      # Check deprecated string interpolation
      rg '"\$\{' src/ --glob "*.php"
      ```
      
      ### PHP 8.3 (from 8.2)
      
      **Key Features Gained:**
      - Typed class constants
      - `json_validate()` function
      - `#[\Override]` attribute
      - Deep cloning of readonly properties in `__clone()`
      - Dynamic class constant fetch (`$class::{$constant}`)
      - `Randomizer::getBytesFromString()`
      - `mb_str_pad()` function
      - Improved `unserialize()` error handling
      
      **Breaking Changes:**
      - `array_sum()` and `array_product()` behavior changes
      - `proc_get_status()` multiple calls return same result
      - `range()` type checking stricter
      - `number_format()` behavior change with negative zero
      
      **Migration Commands:**
      ```bash
      vendor/bin/rector process src --set php83
      
      # Adopt #[Override] attribute on methods
      # This catches parent method renames at compile time
      
      # Adopt typed constants
      # Old: const STATUS = 'active';
      # New: const string STATUS = 'active';
      
      # Use json_validate() instead of json_decode() for validation
      rg "json_decode.*json_last_error" src/ --glob "*.php"
      ```
      
      ### PHP 8.4 (from 8.3)
      
      **Key Features Gained:**
      - Property hooks (get/set)
      - Asymmetric visibility (`public private(set)`)
      - `#[\Deprecated]` attribute
      - `new` without parentheses in chained expressions
      - HTML5 DOM parser (`\Dom\HTMLDocument`)
      - Lazy objects (`ReflectionClass::newLazyProxy()`)
      - `array_find()`, `array_find_key()`, `array_any()`, `array_all()`
      - `Multibyte` functions for `trim`, `ltrim`, `rtrim`
      - `request_parse_body()` for non-POST requests
      
      **Breaking Changes:**
      - Implicitly nullable parameter types trigger deprecation notice
      - `E_STRICT` constant deprecated
      - `session_set_save_handler()` with `open`/`close` etc. deprecated
      - `strtolower()` and `strtoupper()` locale-insensitive
      - Various DOM API changes for HTML5 compliance
      
      **Migration Commands:**
      ```bash
      vendor/bin/rector process src --set php84
      
      # Adopt property hooks (optional but recommended)
      # Old:
      # private string $name;
      # public function getName(): string { return $this->name; }
      # public function setName(string $name): void { $this->name = $name; }
      # New:
      # public string $name {
      #     get => $this->name;
      #     set(string $value) => $this->name = strtolower($value);
      # }
      
      # Adopt asymmetric visibility
      # public private(set) string $name;
      
      # Check for implicit nullable types
      rg "function \w+\([^)]*\w+ \$\w+ = null" src/ --glob "*.php"
      ```
      
      ### PHP 8.5 (from 8.4)
      
      **Key Features Gained:**
      - Pipe operator `|>` for left-to-right call chaining
      - `array_first()` and `array_last()`
      - `#[\NoDiscard]` attribute (warn when a return value is ignored)
      - Fatal errors now include backtraces
      - `clone with` — update readonly/other properties during clone
      
      **Breaking Changes:**
      - Minor-release discipline: mostly new deprecations rather than removals; run the suite with deprecations surfaced (`error_reporting(E_ALL)`) before and after
      
      **Migration Commands:**
      ```bash
      vendor/bin/rector process src --set php85
      
      # Adopt the pipe operator where nested calls hurt readability
      # Old: trim(strtolower($name))
      # New: $name |> strtolower(...) |> trim(...)
      
      # Replace reset()/end() misuse for first/last element
      rg "\breset\(|\bend\(" src/ --glob "*.php"   # candidates for array_first/array_last
      ```
      
      ### PHP Version Upgrade Summary
      
      | From → To | Key Action | Biggest Risk |
      |-----------|-----------|--------------|
      | 8.1 → 8.2 | Fix dynamic properties, deprecation warnings | Dynamic properties deprecated |
      | 8.2 → 8.3 | Adopt typed constants, #[Override] | array_sum/array_product behavior |
      | 8.3 → 8.4 | Adopt property hooks, asymmetric visibility | Implicit nullable deprecation |
      | 8.4 → 8.5 | Adopt pipe operator, array_first/array_last | New deprecations surfacing in dependencies |
      
      ---
      
      ## Cross-Language Upgrade Checklist
      
      Regardless of which language you are upgrading:
      
      ```
      [ ] CI matrix includes both old and new versions during transition
      [ ] Linter/formatter updated to support new syntax
      [ ] IDE / editor language server updated
      [ ] Docker base images updated
      [ ] Deployment pipeline runtime version updated
      [ ] New language features documented in team style guide
      [ ] Deprecated API usage eliminated before upgrade
      [ ] All tests pass on new version
      [ ] Performance benchmarks compared pre/post upgrade
      [ ] Third-party dependencies verified compatible
      ```
      
  • scripts
    • .gitkeep 0 B · in bundle
    • check-migrate-facts.py 14.3 KB
      #!/usr/bin/env python3
      """Staleness verifier for migrate-ops: the framework/language target versions
      the skill hardcodes must stay stated where it claims, and must not silently lag
      reality.
      
      migrate-ops commits to specific target versions in its description and body —
      React 19, Laravel 11, Python 3.12, Node 22, TypeScript 5, Go 1.22, Rust 2024,
      PHP 8.4. Those are exactly the facts that drift silently
      (SKILL-RESOURCE-PROTOCOL.md §7): a line gets rewritten and drops a version, or
      the world moves (Python 3.12 → 3.13, Node 22 → 24) and the skill still names
      the old target. Two modes:
      
        --offline (default, safe for PR CI): structural consistency, no network.
          * assets/migrate-facts.json parses and carries the schema + an as_of date
          * every catalogued claim's regex still matches in each location it is
            recorded as appearing (description vs body) — the catalog can't drift
            from the docs
          * SKILL.md still carries a dated "verified as of <year>" currency note
        --live (scheduled freshness job, never a PR gate): resolves the current
          stable version of each product via endoflife.date (python, nodejs, laravel,
          php, go) and registry.npmjs.org (react, typescript), and flags any
          documented target that is no longer the latest stable major/line.
      
      Usage:   check-migrate-facts.py [--offline | --live] [--catalog FILE] [--skill DIR] [--json] [--timeout S]
      Input:   argv flags only (no stdin).
      Output:  stdout = findings (plain rows, or a --json envelope). Data only.
      Stderr:  the verdict line, notices, errors.
      Exit:    0 ok, 2 usage, 3 catalog/skill missing, 4 catalog unparseable,
               7 endoflife.date/npm unreachable (live, advisory — never a real failure),
               10 drift found (offline: claim missing from a recorded location or
                  currency note gone; live: a documented target lags the latest stable)
      
      Examples:
        check-migrate-facts.py --offline                 # PR CI: catalog ⇆ prose consistency
        check-migrate-facts.py --live                    # weekly: any target lagging latest?
        check-migrate-facts.py --offline --json | jq '.data[]'
      """
      from __future__ import annotations
      
      import argparse
      import datetime
      import json
      import re
      import sys
      import urllib.error
      import urllib.parse
      import urllib.request
      from pathlib import Path
      
      EX_OK = 0
      EX_USAGE = 2
      EX_NOTFOUND = 3
      EX_UNPARSEABLE = 4
      EX_UNAVAILABLE = 7
      EX_DRIFT = 10
      
      SCHEMA = "claude-mods.migrate-ops.facts/v1"
      
      HERE = Path(__file__).resolve().parent
      DEFAULT_CATALOG = HERE.parent / "assets" / "migrate-facts.json"
      DEFAULT_SKILL = HERE.parent
      
      ENDOFLIFE = "https://endoflife.date/api"
      NPM_REGISTRY = "https://registry.npmjs.org"
      
      CURRENCY_RE = re.compile(r"verified as of\s+(\d{4})", re.IGNORECASE)
      AS_OF_RE = re.compile(r"^\d{4}-\d{2}-\d{2}$")
      
      
      class Finding:
          __slots__ = ("check", "status", "detail")
      
          def __init__(self, check: str, status: str, detail: str) -> None:
              self.check = check
              self.status = status  # ok | drift | unavailable
              self.detail = detail
      
          def as_dict(self) -> dict:
              return {"check": self.check, "status": self.status, "detail": self.detail}
      
      
      def load_catalog(path: Path) -> dict:
          if not path.is_file():
              print(f"error: facts catalog not found: {path}", file=sys.stderr)
              raise SystemExit(EX_NOTFOUND)
          try:
              data = json.loads(path.read_text(encoding="utf-8"))
              if not isinstance(data, dict) or data.get("schema") != SCHEMA:
                  raise ValueError(f"schema must be {SCHEMA!r}")
              if not AS_OF_RE.match(str(data.get("as_of", ""))):
                  raise ValueError(f"as_of must be YYYY-MM-DD, got {data.get('as_of')!r}")
              if not isinstance(data.get("claims"), list) or not data["claims"]:
                  raise ValueError("'claims' must be a non-empty array")
              for c in data["claims"]:
                  for k in ("label", "version", "where", "pattern"):
                      if k not in c:
                          raise ValueError(f"claim missing {k}: {c!r}")
              return data
          except (json.JSONDecodeError, KeyError, TypeError, ValueError) as exc:
              print(f"error: could not parse catalog {path}: {exc}", file=sys.stderr)
              raise SystemExit(EX_UNPARSEABLE)
      
      
      def split_skill(skill_md: str) -> tuple[str, str]:
          """Split SKILL.md into (description_value, body_text).
      
          description = the quoted value of the frontmatter `description:` field.
          body        = everything after the closing `---` fence."""
          lines = skill_md.splitlines()
          if not lines or lines[0].strip() != "---":
              return "", skill_md
          end = None
          for i in range(1, len(lines)):
              if lines[i].strip() == "---":
                  end = i
                  break
          if end is None:
              return "", skill_md
          desc = ""
          for ln in lines[1:end]:
              if ln.strip().startswith("description:"):
                  desc = ln.strip()[len("description:"):].strip()
                  if len(desc) >= 2 and desc[0] in "\"'" and desc[-1] == desc[0]:
                      desc = desc[1:-1]
                  break
          body = "\n".join(lines[end + 1:])
          return desc, body
      
      
      def read_corpus(skill_dir: Path) -> tuple[str, str, str]:
          """Returns (skill_md, description, body_corpus). body_corpus = SKILL.md body
          + references/*.md (the prose a claim can be recorded as appearing in)."""
          doc = skill_dir / "SKILL.md"
          if not doc.is_file():
              print(f"error: SKILL.md not found under {skill_dir}", file=sys.stderr)
              raise SystemExit(EX_NOTFOUND)
          skill_md = doc.read_text(encoding="utf-8", errors="replace")
          desc, body = split_skill(skill_md)
          parts = [body]
          ref_dir = skill_dir / "references"
          if ref_dir.is_dir():
              for ref in sorted(ref_dir.glob("*.md")):
                  parts.append(ref.read_text(encoding="utf-8", errors="replace"))
          return skill_md, desc, "\n".join(parts)
      
      
      def check_offline(catalog: dict, skill_dir: Path) -> list[Finding]:
          skill_md, desc, body_corpus = read_corpus(skill_dir)
          findings: list[Finding] = []
      
          if CURRENCY_RE.search(skill_md):
              m = CURRENCY_RE.search(skill_md)
              findings.append(Finding("currency-note", "ok", f"currency note dated {m.group(1)}"))
          else:
              findings.append(Finding("currency-note", "drift",
                                      "no dated 'verified as of <year>' currency note in SKILL.md"))
      
          for claim in catalog.get("claims", []):
              label = claim["label"]
              regex = re.compile(claim["pattern"], re.IGNORECASE)
              for loc in claim["where"]:
                  text = desc if loc == "description" else body_corpus
                  if regex.search(text):
                      findings.append(Finding(f"claim:{label}:{loc}", "ok",
                                              f"{label} {claim['version']} present in {loc}"))
                  else:
                      findings.append(Finding(f"claim:{label}:{loc}", "drift",
                                              f"{label} {claim['version']} not found in {loc} "
                                              f"(pattern {claim['pattern']!r})"))
          return findings
      
      
      def _fetch(url: str, timeout: float, accept: str = "application/json") -> tuple[str, object]:
          req = urllib.request.Request(url, headers={"User-Agent": "claude-mods-migrate-ops-check/1",
                                                     "Accept": accept})
          try:
              with urllib.request.urlopen(req, timeout=timeout) as resp:
                  return "ok", resp.read().decode("utf-8", errors="replace")
          except urllib.error.HTTPError as exc:
              if exc.code in (404, 410):
                  return "notfound", exc.code
              return "unavailable", exc.code
          except (urllib.error.URLError, TimeoutError, OSError):
              return "unavailable", None
      
      
      def _vtup(s: str) -> tuple[int, ...]:
          return tuple(int(x) for x in re.findall(r"\d+", str(s))) or (0,)
      
      
      def _leading_int(s: str) -> str:
          m = re.match(r"\s*(\d+)", str(s))
          return m.group(1) if m else ""
      
      
      def _endoflife_latest(product: str, timeout: float) -> tuple[str, str | None]:
          """Return (status, latest_supported_cycle_or_None). status in ok|unavailable."""
          url = f"{ENDOFLIFE}/{urllib.parse.quote(product, safe='')}.json"
          status, payload = _fetch(url, timeout)
          if status != "ok":
              return "unavailable", None
          try:
              cycles = json.loads(payload)
          except json.JSONDecodeError:
              return "unavailable", None
          today = datetime.date.today()
          supported: list[str] = []
          for c in cycles:
              cyc = str(c.get("cycle", ""))
              if not cyc:
                  continue
              eol = c.get("eol", False)
              is_eol = False
              if isinstance(eol, str):
                  try:
                      is_eol = datetime.date.fromisoformat(eol[:10]) < today
                  except ValueError:
                      is_eol = False
              elif eol is False:
                  is_eol = False
              else:
                  is_eol = bool(eol)
              if not is_eol:
                  supported.append(cyc)
          pool = supported or [str(c.get("cycle", "")) for c in cycles if c.get("cycle")]
          if not pool:
              return "ok", None
          return "ok", max(pool, key=_vtup)
      
      
      def _npm_latest(pkg: str, timeout: float) -> tuple[str, str]:
          """Return (status, version-or-detail). status in ok|notfound|unavailable."""
          url = f"{NPM_REGISTRY}/{urllib.parse.quote(pkg, safe='')}/latest"
          status, payload = _fetch(url, timeout)
          if status != "ok":
              return status, str(payload)
          try:
              return "ok", json.loads(payload).get("version", "")
          except json.JSONDecodeError:
              return "unavailable", "bad-json"
      
      
      def check_live(catalog: dict, timeout: float) -> list[Finding]:
          findings: list[Finding] = []
          for claim in catalog.get("claims", []):
              live = claim.get("live")
              label = claim["label"]
              if not live:
                  findings.append(Finding(f"live:{label}", "ok", "not live-tracked (edition / no registry source)"))
                  continue
              source = live.get("source")
              compare = live.get("compare", "major")
              documented = str(live.get("documented", ""))
      
              if source == "endoflife":
                  status, latest = _endoflife_latest(live["product"], timeout)
                  if status != "ok" or latest is None:
                      findings.append(Finding(f"live:{label}", "unavailable",
                                              f"endoflife.date/{live['product']} unreachable"))
                      continue
                  if compare == "major":
                      latest_major, doc_major = _leading_int(latest), _leading_int(documented)
                      if latest_major and latest_major != doc_major:
                          findings.append(Finding(f"live:{label}", "drift",
                                                  f"{label} documented {documented} lags latest stable {latest}"))
                      else:
                          findings.append(Finding(f"live:{label}", "ok", f"latest stable {latest}"))
                  else:  # line: compare MAJOR.MINOR
                      if _vtup(latest)[:2] != _vtup(documented)[:2]:
                          findings.append(Finding(f"live:{label}", "drift",
                                                  f"{label} documented {documented} lags latest stable {latest}"))
                      else:
                          findings.append(Finding(f"live:{label}", "ok", f"latest stable {latest}"))
              elif source == "npm":
                  status, ver = _npm_latest(live["product"], timeout)
                  if status == "notfound":
                      findings.append(Finding(f"live:{label}", "drift",
                                              f"{live['product']} gone from npm — renamed/removed"))
                      continue
                  if status != "ok":
                      findings.append(Finding(f"live:{label}", "unavailable",
                                              f"npm/{live['product']} unreachable"))
                      continue
                  latest_major, doc_major = _leading_int(ver), _leading_int(documented)
                  if latest_major and latest_major != doc_major:
                      findings.append(Finding(f"live:{label}", "drift",
                                              f"{label} documented {documented} lags latest {ver}"))
                  else:
                      findings.append(Finding(f"live:{label}", "ok", f"latest {ver}"))
              else:
                  findings.append(Finding(f"live:{label}", "drift", f"unknown live source {source!r}"))
          return findings
      
      
      def main(argv: list[str]) -> int:
          p = argparse.ArgumentParser(
              prog="check-migrate-facts.py",
              description="Verify migrate-ops' hardcoded target versions stay stated (offline) and current (live).",
          )
          mode = p.add_mutually_exclusive_group()
          mode.add_argument("--offline", action="store_true", help="structural consistency, no network (default)")
          mode.add_argument("--live", action="store_true", help="resolve current versions via endoflife.date + npm")
          p.add_argument("--catalog", default=str(DEFAULT_CATALOG), help="facts catalog JSON")
          p.add_argument("--skill", default=str(DEFAULT_SKILL), help="skill directory (SKILL.md + references/)")
          p.add_argument("--timeout", type=float, default=10.0, help="per-request timeout seconds (live)")
          p.add_argument("--json", action="store_true", help="emit a JSON envelope")
          p.add_argument("-q", "--quiet", action="store_true", help="suppress stderr progress/summary")
          try:
              args = p.parse_args(argv)
          except SystemExit as exc:
              return EX_USAGE if exc.code not in (0, None) else (exc.code or EX_OK)
      
          catalog = load_catalog(Path(args.catalog))
          live = args.live and not args.offline
          mode_name = "live" if live else "offline"
          findings = check_live(catalog, args.timeout) if live else check_offline(catalog, Path(args.skill))
      
          n_drift = sum(1 for f in findings if f.status == "drift")
          n_unavail = sum(1 for f in findings if f.status == "unavailable")
      
          if args.json:
              print(json.dumps({
                  "data": [f.as_dict() for f in findings],
                  "meta": {"mode": mode_name, "count": len(findings),
                           "drift": n_drift, "unavailable": n_unavail, "schema": SCHEMA},
              }, indent=2))
          else:
              for f in findings:
                  print(f"{f.check}\t{f.status}\t{f.detail}")
      
          for f in findings:
              if f.status != "ok":
                  print(f"  [{f.status.upper()}] {f.check}: {f.detail}", file=sys.stderr)
          if not args.quiet:
              print(f"-- {len(findings)} checks: {n_drift} drift, {n_unavail} unavailable", file=sys.stderr)
      
          if n_drift:
              return EX_DRIFT
          if n_unavail:
              return EX_UNAVAILABLE
          return EX_OK
      
      
      if __name__ == "__main__":
          sys.exit(main(sys.argv[1:]))
      
  • tests
    • run.sh 5.2 KB
      #!/usr/bin/env bash
      # Offline self-test for the migrate-ops skill — structure, frontmatter, and the
      # staleness-verifier contract (SKILL-RESOURCE-PROTOCOL §7, §10).
      #
      # Usage:   tests/run.sh
      # Input:   none (self-contained; no network)
      # Output:  TAP-ish progress on stderr; final PASS/FAIL line.
      # Exit:    0 all pass (or skipped on unsupported platform), 1 any failure.
      #
      # Examples:
      #   tests/run.sh
      #   bash skills/migrate-ops/tests/run.sh
      set -uo pipefail
      
      here="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
      fail=0
      pass=0
      note() { printf '  %s %s\n' "$1" "$2" >&2; }
      ok()   { pass=$((pass+1)); note "ok  " "$1"; }
      bad()  { fail=$((fail+1)); note "FAIL" "$1"; }
      
      # Resolve a *working* python (python3, else python). The bare `command -v` is not
      # enough on Windows, where `python3` is a Microsoft Store stub that exits nonzero.
      PY=""
      for cand in python3 python; do
        if command -v "$cand" >/dev/null 2>&1 && "$cand" --version >/dev/null 2>&1; then
          PY="$cand"; break
        fi
      done
      if [ -z "$PY" ]; then
        echo "SKIP: no working python interpreter on this platform" >&2
        exit 0
      fi
      
      # 1. Required directories exist
      for d in scripts references assets tests; do
        [ -d "$here/$d" ] && ok "dir $d/ exists" || bad "missing dir $d/"
      done
      
      # 2. SKILL.md frontmatter house rules
      skill="$here/SKILL.md"
      if [ -f "$skill" ]; then
        ok "SKILL.md present"
        grep -q '^name: migrate-ops$' "$skill" && ok "name matches directory" || bad "name != migrate-ops"
        grep -q '^license: MIT$' "$skill" && ok "license: MIT" || bad "missing license: MIT"
        grep -q '^  author: claude-mods$' "$skill" && ok "metadata.author" || bad "missing metadata.author"
      else
        bad "SKILL.md missing"
      fi
      
      # 3. Every reference on disk is cited from SKILL.md (no dead weight)
      for ref in "$here"/references/*.md; do
        base="references/$(basename "$ref")"
        grep -qF "$base" "$skill" && ok "cited: $base" || bad "uncited reference: $base"
      done
      
      # 4. Every SKILL.md-cited bundled resource exists on disk
      for res in assets/migrate-facts.json scripts/check-migrate-facts.py; do
        [ -f "$here/$res" ] && ok "resource present: $res" || bad "missing resource: $res"
      done
      
      # 5. check-migrate-facts.py — staleness verifier contract (§7, §10), offline-safe
      verifier="$here/scripts/check-migrate-facts.py"
      catalog="$here/assets/migrate-facts.json"
      ec() { local want="$1" lbl="$2"; shift 2; "$@" >/dev/null 2>&1; local got=$?
             [ "$got" = "$want" ] && ok "$lbl (exit $got)" || bad "$lbl (want $want got $got)"; }
      if [ -f "$verifier" ]; then
        "$PY" -m py_compile "$verifier" && ok "verifier: py_compile clean" || bad "verifier: py_compile failed"
        grep -qE '^Examples:$' "$verifier" && ok "verifier: has Examples block" || bad "verifier: no Examples block (docstring)"
        "$PY" "$verifier" --help >/dev/null 2>&1 && ok "verifier: --help exits 0" || bad "verifier: --help nonzero"
        # Offline mode must pass on the skill's own content (internal consistency).
        ec 0 "verifier: --offline consistent"  "$PY" "$verifier" --offline
        # Bad flag → USAGE (exit 2); conflicting modes → USAGE.
        ec 2 "verifier: bad flag → exit 2"     "$PY" "$verifier" --bogus
        ec 2 "verifier: --offline --live → 2"  "$PY" "$verifier" --offline --live
        # stdout is data-only: --offline --json must emit parseable JSON.
        "$PY" "$verifier" --offline --json -q 2>/dev/null \
          | "$PY" -c 'import json,sys; d=json.load(sys.stdin); assert d["meta"]["schema"]=="claude-mods.migrate-ops.facts/v1"' \
          && ok "verifier: --json envelope parses (stdout clean)" || bad "verifier: --json envelope broken"
        # Error paths: missing catalog → 3, malformed catalog → 4, drift catalog → 10.
        TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
        ec 3 "verifier: missing catalog -> 3"  "$PY" "$verifier" --offline --catalog "$TMP/nope.json"
        printf '{"packages":"x"}' > "$TMP/bad.json"
        ec 4 "verifier: malformed catalog -> 4" "$PY" "$verifier" --offline --catalog "$TMP/bad.json"
        # Minimal drift catalog (argv path so MSYS translates it): a claim whose
        # pattern matches nothing in the real skill prose -> drift -> exit 10.
        printf '%s\n' '{"schema":"claude-mods.migrate-ops.facts/v1","as_of":"2026-07-05","claims":[{"label":"Fakeweb","version":"99","where":["description","body"],"pattern":"fakeweb[^\\n]*\\b99\\b"}]}' > "$TMP/drift.json"
        ec 10 "verifier: missing claim -> 10" "$PY" "$verifier" --offline --catalog "$TMP/drift.json"
        # cited from SKILL.md
        grep -qF "scripts/check-migrate-facts.py" "$skill" && ok "verifier: cited from SKILL.md" || bad "verifier: uncited"
      else
        bad "check-migrate-facts.py missing"
      fi
      
      # 6. migrate-facts.json — parses, carries schema + the 8 catalogued targets
      "$PY" -c "
      import json, sys
      d = json.load(open(sys.argv[1], encoding='utf-8'))
      assert d['schema'] == 'claude-mods.migrate-ops.facts/v1'
      labels = {c['label'] for c in d['claims']}
      for need in ['React','Laravel','Python','Node','TypeScript','Go','Rust','PHP']:
          assert need in labels, f'missing claim {need}'
      assert all(c['version'] for c in d['claims']), 'claim missing version'
      " "$catalog" && ok "migrate-facts.json schema + 8 targets" || bad "migrate-facts.json invalid"
      
      # 7. Currency note present near the top of the body
      grep -qE 'verified as of [0-9]{4}' "$skill" && ok "currency note present" || bad "no dated currency note"
      
      echo "migrate-ops self-test: $pass passed, $fail failed" >&2
      [ "$fail" -eq 0 ]
      
  • SKILL.md 14.1 KB
    ---
    name: migrate-ops
    description: "Framework and language migration patterns - version upgrades, breaking changes, dependency audit, safe rollback. Use for: migrate, migration, upgrade, version bump, breaking changes, deprecation, dependency audit, npm audit, pip-audit, codemod, jscodeshift, rector, rollback, semver, changelog, framework upgrade, language upgrade, React 19, Vue 3, Next.js App Router, Laravel 13, Angular, Python 3.14, Node 26, TypeScript 6, Go 1.26, Rust 2024, PHP 8.5."
    license: MIT
    allowed-tools: "Read Edit Write Bash Glob Grep Agent"
    metadata:
      author: claude-mods
      related-skills: testing-ops, debug-ops, git-ops, refactor-ops
    ---
    
    # Migrate Operations
    
    Comprehensive migration skill covering framework upgrades, language version bumps, dependency auditing, breaking change detection, codemods, and rollback strategies.
    
    > Ecosystem facts verified as of 2026-07-05.
    
    ## Migration Strategy Decision Tree
    
    ```
    What kind of migration are you performing?
    │
    ├─ Small library update (patch/minor version)
    │  └─ In-place upgrade
    │     Update dependency, run tests, deploy
    │
    ├─ Major framework version (React 18→19, Vue 2→3, Laravel 12→13)
    │  │
    │  ├─ Codebase < 50k LOC, good test coverage (>70%)
    │  │  └─ Big Bang Migration
    │  │     Upgrade everything at once in a feature branch
    │  │     Pros: clean cutover, no dual-version complexity
    │  │     Cons: high risk, long branch life, merge conflicts
    │  │
    │  ├─ Codebase > 50k LOC, partial test coverage
    │  │  └─ Incremental Migration
    │  │     Upgrade module by module, use compatibility layers
    │  │     Pros: lower risk per step, continuous delivery
    │  │     Cons: dual-version code, longer total duration
    │  │
    │  ├─ Monolith → microservice or complete architecture shift
    │  │  └─ Strangler Fig Pattern
    │  │     Route new features to new system, migrate old features gradually
    │  │     Pros: zero-downtime, reversible, production-validated
    │  │     Cons: routing complexity, data sync challenges
    │  │
    │  └─ High-risk data pipeline or financial system
    │     └─ Parallel Run
    │        Run old and new systems simultaneously, compare outputs
    │        Pros: highest confidence, catch subtle differences
    │        Cons: double infrastructure cost, comparison logic
    │
    └─ Language version upgrade (Python 3.12→3.14, Node 22→26)
       └─ In-place upgrade with CI matrix
          Test against both old and new versions in CI
          Drop old version support once all tests pass
    ```
    
    ## Framework Upgrade Decision Tree
    
    ```
    Which framework are you upgrading?
    │
    ├─ React 18 → 19
    │  ├─ Check: Remove forwardRef wrappers (ref is now a regular prop)
    │  ├─ Check: Replace <Context.Provider> with <Context>
    │  ├─ Check: Adopt useActionState / useFormStatus for forms
    │  ├─ Check: Replace manual memoization if using React Compiler
    │  ├─ Codemod: npx codemod@latest react/19/migration-recipe
    │  └─ Load: ./references/framework-upgrades.md
    │
    ├─ Next.js Pages Router → App Router
    │  ├─ Check: Move pages/ to app/ with new file conventions
    │  ├─ Check: Replace getServerSideProps/getStaticProps with async components
    │  ├─ Check: Convert _app.tsx and _document.tsx to layout.tsx
    │  ├─ Check: Update data fetching to use fetch() with caching options
    │  ├─ Codemod: npx @next/codemod@latest
    │  └─ Load: ./references/framework-upgrades.md
    │
    ├─ Vue 2 → 3
    │  ├─ Check: Replace Options API with Composition API (optional but recommended)
    │  ├─ Check: Replace Vuex with Pinia
    │  ├─ Check: Replace event bus with mitt or provide/inject
    │  ├─ Check: Update v-model syntax (modelValue prop)
    │  ├─ Tool: Migration build (@vue/compat) for incremental migration
    │  └─ Load: ./references/framework-upgrades.md
    │
    ├─ Laravel 12 → 13
    │  ├─ Check: PHP 8.3 is now the minimum (8.5 supported)
    │  ├─ Check: Cache/Redis key prefixes now use hyphenated suffixes
    │  ├─ Check: Adopt native PHP attributes (models, jobs, controllers) — optional
    │  ├─ Check: Queue routing by class via Queue::route(...) — optional
    │  ├─ Tool: laravel shift (automated upgrade service)
    │  └─ Load: ./references/framework-upgrades.md (covers 10→11 in depth; 12→13 is near zero-break)
    │
    ├─ Angular (any major version)
    │  ├─ Check: Run ng update for guided migration
    │  ├─ Check: Review Angular Update Guide (update.angular.io)
    │  ├─ Tool: ng update @angular/core @angular/cli
    │  └─ Load: ./references/framework-upgrades.md
    │
    └─ Django (any major version)
       ├─ Check: Run python -Wd manage.py test for deprecation warnings
       ├─ Check: Review Django release notes for removals
       ├─ Tool: django-upgrade (automatic fixer)
       └─ Load: ./references/framework-upgrades.md
    ```
    
    ## Dependency Audit Workflow
    
    ```
    Ecosystem?
    │
    ├─ JavaScript / Node.js
    │  ├─ npm audit / npm audit fix
    │  ├─ npx audit-ci --moderate (CI integration)
    │  └─ Socket.dev for supply chain analysis
    │
    ├─ Python
    │  ├─ pip-audit
    │  ├─ safety check
    │  └─ pip-audit --fix (auto-update vulnerable packages)
    │
    ├─ Rust
    │  ├─ cargo audit
    │  └─ cargo deny check advisories
    │
    ├─ Go
    │  ├─ govulncheck ./...
    │  └─ go list -m -u all (list available updates)
    │
    ├─ PHP
    │  ├─ composer audit
    │  └─ composer outdated --direct
    │
    └─ Multi-ecosystem
       └─ Trivy, Snyk, or Dependabot across all
    ```
    
    ## Pre-Migration Checklist
    
    ```
    [ ] Test coverage measured and documented (target: >70% for critical paths)
    [ ] CI pipeline green on current version
    [ ] All dependencies up to date (or pinned with rationale)
    [ ] Database backup taken (if applicable)
    [ ] Git state clean — migration branch created from latest main
    [ ] Rollback plan documented and tested
    [ ] Breaking change list reviewed from upstream changelog
    [ ] Team notified of migration window
    [ ] Feature flags in place for gradual rollout (if applicable)
    [ ] Monitoring and alerting configured for regression detection
    [ ] Performance baseline captured (response times, memory, CPU)
    [ ] Lock file committed (package-lock.json, yarn.lock, Cargo.lock, etc.)
    ```
    
    ## Breaking Change Detection Patterns
    
    ```
    How do you detect breaking changes?
    │
    ├─ Semver Analysis
    │  ├─ Major version bump → breaking changes guaranteed
    │  ├─ Check CHANGELOG.md or BREAKING_CHANGES.md in repo
    │  └─ npm: npx npm-check-updates --target major
    │
    ├─ Changelog Parsing
    │  ├─ Search for: "BREAKING", "removed", "deprecated", "renamed"
    │  ├─ GitHub: compare releases page between versions
    │  └─ Read migration guide if one exists
    │
    ├─ Compiler / Runtime Warnings
    │  ├─ Enable all deprecation warnings before upgrading
    │  ├─ Python: python -Wd (turn deprecation warnings to errors)
    │  ├─ Node: node --throw-deprecation
    │  └─ TypeScript: strict mode catches type-level breaks
    │
    ├─ Codemods (automated detection + fix)
    │  ├─ jscodeshift — JavaScript/TypeScript AST transforms
    │  ├─ ast-grep — language-agnostic structural search/replace
    │  ├─ rector — PHP automated refactoring
    │  ├─ gofmt / gofumpt — Go formatting changes
    │  └─ 2to3 — Python 2 to 3 (legacy)
    │
    └─ Type Checking
       ├─ TypeScript: tsc --noEmit catches API shape changes
       ├─ Python: mypy / pyright after upgrade
       └─ Go: go vet ./... after upgrade
    ```
    
    ## Codemod Quick Reference
    
    | Ecosystem | Tool | Command | Use Case |
    |-----------|------|---------|----------|
    | **JS/TS** | jscodeshift | `npx jscodeshift -t transform.ts src/` | Custom AST transforms |
    | **JS/TS** | ast-grep | `sg --pattern 'old($$$)' --rewrite 'new($$$)'` | Structural find/replace |
    | **React** | react-codemod | `npx codemod@latest react/19/migration-recipe` | React version upgrades |
    | **Next.js** | next-codemod | `npx @next/codemod@latest` | Next.js version upgrades |
    | **Vue** | vue-codemod | `npx @vue/codemod src/` | Vue 2 to 3 transforms |
    | **PHP** | Rector | `vendor/bin/rector process src` | PHP version + framework upgrades |
    | **Python** | pyupgrade | `pyupgrade --py314-plus *.py` | Python version syntax upgrades |
    | **Python** | django-upgrade | `django-upgrade --target-version 5.0 *.py` | Django version upgrades |
    | **Go** | gofmt | `gofmt -w .` | Go formatting updates |
    | **Go** | gofix | `go fix ./...` | Go API changes |
    | **Rust** | cargo fix | `cargo fix --edition` | Rust edition migration |
    | **Multi** | ast-grep | `sg scan --rule rules.yml` | Any language with custom rules |
    
    ## Rollback Strategy Decision Tree
    
    ```
    Migration failed or caused issues — how to roll back?
    │
    ├─ Code-only change, no data migration
    │  ├─ Small number of commits
    │  │  └─ Git Revert
    │  │     git revert --no-commit HEAD~N..HEAD && git commit
    │  │     Pros: clean history, safe for shared branches
    │  │     Cons: merge conflicts if code has diverged
    │  │
    │  └─ Entire feature branch
    │     └─ Revert merge commit
    │        git revert -m 1 <merge-commit-sha>
    │
    ├─ Feature flag controlled
    │  └─ Toggle flag off
    │     Instant rollback, no deployment needed
    │     Keep old code path until new path is proven
    │
    ├─ Database schema changed
    │  ├─ Reversible migration exists
    │  │  └─ Run down migration
    │  │     rails db:rollback / php artisan migrate:rollback / alembic downgrade
    │  │
    │  └─ Irreversible migration (dropped column, changed type)
    │     └─ Restore from backup + replay write-ahead log
    │        This is why you take backups BEFORE migration
    │
    └─ Infrastructure / deployment
       ├─ Blue-Green deployment
       │  └─ Switch traffic back to blue (old) environment
       │
       ├─ Canary deployment
       │  └─ Route 100% traffic back to stable version
       │
       └─ Container orchestration (K8s)
          └─ kubectl rollout undo deployment/app
    ```
    
    ## Common Gotchas
    
    | Gotcha | Why It Happens | Prevention |
    |--------|---------------|------------|
    | Upgrading multiple major versions at once | Each major version may have sequential breaking changes that compound | Upgrade one major version at a time, verify, then proceed |
    | Lock file not committed before migration | Cannot reproduce pre-migration dependency state | Always commit lock files; take a snapshot branch before starting |
    | Running codemods without committing first | Cannot diff what the codemod changed vs your manual changes | Commit clean state, run codemod, commit codemod changes separately |
    | Ignoring deprecation warnings in current version | Deprecated APIs are removed in next major version | Fix all deprecation warnings BEFORE upgrading |
    | Testing only happy paths after migration | Edge cases and error paths are most likely to break | Run full test suite plus manual exploratory testing |
    | Not checking transitive dependencies | A direct dep upgrade may pull in incompatible transitive deps | Use `npm ls`, `pip show`, `cargo tree` to inspect dependency tree |
    | Assuming codemods catch everything | Codemods handle common patterns, not all patterns | Review codemod output manually; check for skipped files |
    | Skipping the migration guide | Framework authors document known pitfalls and workarounds | Read the official migration guide end-to-end before starting |
    | Migrating in a long-lived branch | Main branch diverges, causing painful merge conflicts | Use feature flags for incremental migration on main |
    | Not updating CI to test both versions | CI passes on old version but new version has failures | Add matrix testing for both versions during transition |
    | Database migration without backup | Irreversible schema changes with no recovery path | Always backup before migration; test rollback procedure |
    | Forgetting to update Docker/CI base images | Code upgraded but runtime is still old version | Update Dockerfile FROM, CI config, and deployment manifests |
    
    ## Reference Files
    
    | File | Contents | Lines |
    |------|----------|-------|
    | `references/framework-upgrades.md` | React 18→19, Next.js Pages→App Router, Vue 2→3, Laravel 10→13, Angular, Django upgrade paths | ~700 |
    | `references/language-upgrades.md` | Python 3.9→3.14, Node 18→26, TypeScript 4→6, Go 1.20→1.26, Rust 2021→2024, PHP 8.1→8.5 | ~650 |
    | `references/dependency-management.md` | Audit tools, update strategies, lock files, monorepo deps, supply chain security | ~550 |
    
    ## Staleness verifier
    
    This skill hardcodes specific framework/language target versions (React 19, Laravel 13, Python 3.14, Node 26, TypeScript 6, Go 1.26, Rust 2024, PHP 8.5). [`scripts/check-migrate-facts.py`](scripts/check-migrate-facts.py) guards them against silent drift:
    
    ```bash
    # Structural (PR CI, no network): every catalogued target version still appears
    # where it is recorded (description vs body), and the currency note carries a year.
    python scripts/check-migrate-facts.py --offline        # exit 0 consistent, 10 drift
    
    # Live (freshness job, never blocks a PR): each target is resolved against
    # endoflife.date (python/nodejs/laravel/php/go) and npm (react/typescript).
    python scripts/check-migrate-facts.py --live            # exit 10 a target lags latest, 7 unreachable
    ```
    
    The canonical target-version list lives in [`assets/migrate-facts.json`](assets/migrate-facts.json); when you change a recommended target, update it to match or `--offline` fails CI. A `--live` drift means the skill is naming an older target than the ecosystem's current stable — review, don't auto-rewrite.
    
    ## See Also
    
    | Skill | When to Combine |
    |-------|----------------|
    | `testing-ops` | Ensuring test coverage before migration, writing regression tests after |
    | `debug-ops` | Diagnosing failures introduced by migration, bisecting breaking commits |
    | `git-ops` | Branch strategy for migration, git bisect to find breaking change |
    | `refactor-ops` | Code transformations that often accompany version upgrades |
    | `ci-cd-ops` | Updating CI pipelines to test against new versions, matrix builds |
    | `container-orchestration` | Updating base images, Dockerfile changes for new runtime versions |
    | `security-ops` | Vulnerability remediation that triggers dependency upgrades |
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related