agentsclimarketplace

Express to fastify

Skill Siddharth00/agent-revamp-skills/skills/04-migrate/backend/express-to-fastify

Production-grade migration skills for AI coding agents. Revamp any product, module, or feature from one stack to another.

Install
npx -y skills add Siddharth00/agent-revamp-skills --skill express-to-fastify

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Migrates one Express 4.x route at a time to Fastify 4.x via an in-process strangler-fig proxy, with contract tests verifying identical status codes, response shapes, and headers before each proxy cutover.

SKILL.md

30.4 KB, as published. Nobody here has run it

1. Purpose

This skill migrates an Express 4.x API server to Fastify 4.x one route at a time, using a strangler-fig approach where an in-process proxy (or nginx upstream block) forwards requests to either the Express instance or the Fastify instance based on path prefix. Both servers run in the same Node.js process during the transition — Fastify registers fastify.all('*', expressApp) as a catch-all fallback, so unmitigated routes are silently handled by Express without a deploy change. The migration is gated per route by contract tests: a supertest assertion suite runs against Express to capture the behavioral baseline, and a matching inject() suite runs against Fastify — both must produce identical status codes, response body shapes, and headers before the proxy is updated for that path. The key risks are not syntactic but semantic: Express middleware executes in a global ordered stack while Fastify hooks are scoped per plugin, so a route that relies on a globally-registered Express middleware (auth, rate-limiting, request-id injection) must have an equivalent hook registered in the correct Fastify plugin scope. Session handling carries additional risk — @fastify/session requires explicit cookie and store configuration that Express's express-session defaults assume implicitly; a mismatch silently issues new sessions instead of resuming existing ones.


2. Trigger Conditions

Use when:

  • A Phase 1 audit has produced a complete route inventory for the Express application — every route with its method, path, ordered middleware chain, and whether it reads or writes req.session.
  • A Phase 2 migration manifest exists and specifies the order in which routes are migrated (low-risk, stateless, session-free routes first; session-bearing and auth routes last).
  • The in-process proxy or nginx upstream is configured and smoke-tested — requests can be shifted per path prefix without a deploy.
  • The Fastify instance boots alongside the Express instance in the development environment and responds to GET /healthz on its port.
  • The pre-migration supertest contract test suite is green on the current commit for target_route.
  • @fastify/session, @fastify/cors, and @fastify/helmet are installed and configured — Phase 3 plugin scaffolding is complete.

Do NOT use when:

  • The route inventory from Phase 1 is incomplete — migrating routes without a full inventory risks silently dropping Express routes that have no Fastify counterpart.
  • Any Express middleware in the global stack has no Fastify equivalent identified — migrate or replicate the middleware in Phase 3 before migrating routes that depend on it.
  • req.session is used by target_route and @fastify/session has not been configured with a matching store, secret, and cookie options — silent session invalidation is worse than a 500.
  • The Express application uses app.router (Express 3.x pattern) or custom monkey-patched res methods — this skill targets stock Express 4.x only.
  • The Fastify instance is receiving production traffic — run this skill only in dev and staging until all routes have passed contract tests and a load test.
  • The Phase 1 audit flagged target_route as having untested error paths — write the missing tests before migrating; the contract tests cannot establish a baseline for behavior that is not tested.

3. Inputs

Required:

InputTypeDescription
target_routestringThe route to migrate in this invocation, in METHOD /path/pattern format (e.g., POST /api/users). Must match an entry in route_manifest_path.
repo_rootfile-pathAbsolute path to the repository root. All commands run from here.
route_manifest_pathfile-pathPath to the route manifest JSON produced by Phase 2 (e.g., output/revamp-spec-routes.json). Contains middleware chains, body shapes, session usage, and error paths per route.
express_entryfile-pathRepo-root-relative path to the file that creates and exports the Express app instance (e.g., src/app.ts). Used to read global middleware registration order.
fastify_entryfile-pathRepo-root-relative path to the Fastify server factory (e.g., src/fastify/app.ts). The new route plugin is registered here.
proxy_configfile-pathPath to the nginx location config or in-process proxy route table. Updated in Step 10 to shift traffic for target_route to Fastify.
fastify_portintegerPort the Fastify instance listens on during parallel running (e.g., 3001). Must differ from the Express port.

Optional:

InputTypeDefaultDescription
schema_formatenum(zod, json-schema)json-schemaHow to write request/response schemas. zod requires zod-to-json-schema; json-schema uses native Fastify schema objects.
dry_runbooleanfalseAnalyze and write the proposed route file and contract tests, but do not update the proxy config or register the route in fastify_entry.
test_timeout_msinteger5000Timeout per contract test assertion (both supertest and inject). Increase for routes that call slow external services.
k6_vusinteger50Number of virtual users for the p99 latency load test in Step 11.
k6_durationstring30sDuration of the k6 load test.
<!-- STOP CONDITIONS: - If `target_route` does not match any entry in `route_manifest_path`, halt: "target_route '<value>' is not in the migration manifest. Add it to <route_manifest_path> or check for a method/path typo." - If `route_manifest_path` does not exist, halt: "I need a route manifest at <route_manifest_path>. Run /revamp-spec to generate it, or provide the path to an existing manifest." - If `@fastify/session` is not in package.json and the manifest entry for target_route marks session_usage: true, halt: "@fastify/session is not installed. Install and configure it (Phase 3) before migrating a session-bearing route." -->

4. Steps

  1. Read route_manifest_path. Locate the entry for target_route. Extract and record:

    • middleware_chain: ordered list of middleware names applied globally and per-route (e.g., [requestId, rateLimit, requireAuth, validateBody]).
    • body_shape: JSON schema or TypeScript type of the expected request body (null for GET/DELETE).
    • response_shape: JSON schema or TypeScript type of the success response body.
    • session_usage: boolean; which session fields are read (request.session.userId) or written.
    • error_paths: list of conditions that trigger non-200 responses and their expected status codes.
    • custom_headers_set: any response headers the handler sets explicitly beyond Content-Type.
    • If any field is marked unknown or missing in the manifest: STOP. Ask the user to fill in the manifest entry before proceeding — do not infer.
  2. Read express_entry. Extract the ordered list of globally-registered Express middleware (the sequence of app.use(...) calls). Cross-reference with middleware_chain from Step 1 to identify which global middleware the route implicitly depends on. For each global middleware, confirm a Fastify equivalent is registered in fastify_entry (either as a global fastify.addHook or as a plugin-scoped hook). Write any gap as a blocking item in the migration log — do not proceed past Step 2 if a gap exists.

  3. Read the Express handler file(s) for target_route. Identify every use of:

    • next(err) — must become throw err in Fastify (or reply.send(err) for explicit control).
    • res.status(N).json(body) — must become reply.status(N).send(body).
    • req.body.* — body is already parsed by Fastify if Content-Type: application/json; add a note if the Express handler has a guard for missing Content-Type.
    • req.session.* — maps to request.session.* under @fastify/session; flag if the session key names differ.
    • res.set(header, value) — maps to reply.header(header, value). Write the translation map to the migration log.
  4. Write the Fastify route plugin file. Apply the translation map from Step 3.

    Before (Express) — illustrative example for POST /api/users:

    // src/routes/users.ts
    import { Router } from 'express';
    import { body, validationResult } from 'express-validator';
    import { db } from '../db';
    import { requireAuth } from '../middleware/auth';
    
    const router = Router();
    
    router.post(
      '/api/users',
      requireAuth,                          // route-level middleware
      body('email').isEmail(),
      body('name').notEmpty().trim(),
      async (req, res, next) => {
        const errors = validationResult(req);
        if (!errors.isEmpty()) {
          return res.status(422).json({ errors: errors.array() });
        }
        try {
          const user = await db.user.create({
            data: { email: req.body.email, name: req.body.name },
          });
          res.status(201).json({ id: user.id, email: user.email, name: user.name });
        } catch (err) {
          next(err);                        // reaches the global Express error handler
        }
      }
    );
    
    export default router;
    

    After (Fastify) — translated equivalent:

    // src/fastify/routes/users.ts
    import { FastifyPluginAsync } from 'fastify';
    import { z } from 'zod';
    import { zodToJsonSchema } from 'zod-to-json-schema';   // omit if schema_format === 'json-schema'
    import { db } from '../../db';
    
    // Schema replaces the express-validator chain.
    // Fastify rejects invalid bodies with 400 before the handler runs — no manual validationResult() check.
    const CreateUserBody = z.object({
      email: z.string().email(),
      name:  z.string().min(1).trim(),
    });
    
    // Response schema enables fast-json-stringify serialization and strips undeclared fields.
    const UserCreatedReply = z.object({
      id:    z.string(),
      email: z.string(),
      name:  z.string(),
    });
    
    const usersRoutes: FastifyPluginAsync = async (fastify) => {
      fastify.post<{
        Body:  z.infer<typeof CreateUserBody>;
        Reply: z.infer<typeof UserCreatedReply>;
      }>(
        '/api/users',
        {
          // preHandler runs after global hooks; replaces route-level middleware arrays.
          // fastify.authenticate must be decorated on the instance — registered in fastify_entry.
          preHandler: [fastify.authenticate],
    
          schema: {
            body:     zodToJsonSchema(CreateUserBody),
            response: { 201: zodToJsonSchema(UserCreatedReply) },
            // Fastify returns 400 (not 422) for schema violations by default.
            // If Express returned 422, override the error handler or set a custom 400 → 422 mapping.
          },
        },
        async (request, reply) => {
          // request.body is validated — access fields directly.
          const { email, name } = request.body;
    
          // Errors thrown here reach setErrorHandler, not Express's next(err) stack.
          // Ensure fastify_entry registers a setErrorHandler that returns the same
          // shape as the Express global error handler for DB errors.
          const user = await db.user.create({ data: { email, name } });
    
          return reply.status(201).send({ id: user.id, email: user.email, name: user.name });
        }
      );
    };
    
    export default usersRoutes;
    

    Translation decisions applied above — adapt these for target_route:

    Express patternFastify equivalentRisk note
    Route-level middleware arraypreHandler array in route optionsOrder is preserved; verify global hooks don't double-apply
    express-validator chainSchema object (body key)Fastify returns 400; Express-validator can return 422 — check contract
    next(err)throw errFastify passes thrown errors to setErrorHandler; no equivalent to Express's 4-arg error middleware
    res.status(N).json(body)reply.status(N).send(body)send with an object sets Content-Type: application/json automatically
    req.body.*request.body.*Fastify requires Content-Type: application/json; Express with body-parser is more lenient — add addContentTypeParser if clients omit the header
    req.session.*request.session.*Field names must match; cookie options (maxAge, secure) must be re-verified in @fastify/session config
    res.set(k, v)reply.header(k, v)Direct rename; no behavior difference
    • If this fails to compile (npx tsc --noEmit): check that the Fastify generic type parameters match the schema, and that fastify.authenticate is declared as a decoration in a .d.ts file.
  5. Register the new plugin in fastify_entry:

    // src/fastify/app.ts  (add this line alongside other route registrations)
    fastify.register(usersRoutes);   // or: fastify.register(import('./routes/users'))
    

    Run npx tsc --noEmit from repo_root. Confirm 0 errors before proceeding.

  6. Write a supertest contract test for target_route against the Express server. The test must assert every item in the error_paths list and the success case. Save to src/__tests__/contracts/<route-slug>-express.test.ts.

    Run: npx jest src/__tests__/contracts/<route-slug>-express.test.ts --testTimeout=<test_timeout_ms>. Must exit 0.

    • If it fails: STOP. The Express behavior is not fully known. Fix or extend the test until it passes — the Fastify contract test is meaningless without a green Express baseline.
  7. Write a matching inject() contract test for target_route against the Fastify server. Use the same inputs and assert the same status codes, body shapes, and headers as the Express test. Save to src/__tests__/contracts/<route-slug>-fastify.test.ts.

    Example structure:

    // src/__tests__/contracts/post-api-users-fastify.test.ts
    import { buildFastify } from '../../fastify/app';
    
    describe('POST /api/users (Fastify)', () => {
      const app = buildFastify({ logger: false });
      beforeAll(() => app.ready());
      afterAll(() => app.close());
    
      it('returns 201 with {id, email, name} for valid input', async () => {
        const res = await app.inject({
          method:  'POST',
          url:     '/api/users',
          payload: { email: '[email protected]', name: 'Alice' },
          headers: { 'content-type': 'application/json', authorization: 'Bearer <test-token>' },
        });
        expect(res.statusCode).toBe(201);
        expect(res.json()).toMatchObject({ email: '[email protected]', name: 'Alice' });
        expect(res.headers['content-type']).toMatch(/application\/json/);
      });
    
      it('returns 400 for invalid email', async () => {
        const res = await app.inject({
          method:  'POST',
          url:     '/api/users',
          payload: { email: 'not-an-email', name: 'Alice' },
          headers: { 'content-type': 'application/json', authorization: 'Bearer <test-token>' },
        });
        // NOTE: Fastify schema validation returns 400; Express-validator returned 422.
        // If the contract test requires 422, add a custom Fastify error handler that maps
        // FST_ERR_VALIDATION to 422 — document this decision in the migration log.
        expect(res.statusCode).toBe(400);
      });
    
      it('returns 401 when authorization header is absent', async () => {
        const res = await app.inject({
          method: 'POST', url: '/api/users',
          payload: { email: '[email protected]', name: 'Alice' },
          headers: { 'content-type': 'application/json' },
        });
        expect(res.statusCode).toBe(401);
      });
    });
    

    Run: npx jest src/__tests__/contracts/<route-slug>-fastify.test.ts --testTimeout=<test_timeout_ms>. Must exit 0.

    • If status code differs from Express: check the translation table in Step 4. If the divergence is intentional (e.g., 400 vs 422 for validation), document it explicitly in the migration log and get sign-off before proceeding. Do not silently accept a status code change.
    • If body shape differs: re-read the response schema. fast-json-stringify strips fields not declared in the schema — if a field is missing from the Fastify response, add it to UserCreatedReply.
    • If headers differ: check for headers set by global Express middleware (e.g., X-Request-Id, X-RateLimit-*) that may not be replicated in the Fastify hook chain.
  8. → Hand off to equivalence-validator (see Section 5. Agent Handoffs) to diff both contract test outputs side by side and produce a formal divergence report. Wait for output/express-to-fastify-divergence-<route-slug>-<timestamp>.md before continuing. If the divergence report contains any item marked BREAKING, resolve it before Step 9.

  9. If dry_run is true: write the migration log with the route file, contract tests, and divergence report, then STOP. Do not update the proxy config.

  10. Update proxy_config to route requests matching target_route's path to the Fastify port.

    nginx example (add inside the server block, before the Express fallback location):

    # Migrated by skills/04-migrate/backend/express-to-fastify — <timestamp>
    # Route: POST /api/users
    location = /api/users {
      limit_except POST { deny all; }
      proxy_pass         http://127.0.0.1:<fastify_port>;
      proxy_http_version 1.1;
      proxy_set_header   Connection       "";
      proxy_set_header   Host             $host;
      proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
      proxy_set_header   X-Migrated-By    fastify;   # marker for log filtering
    }
    

    In-process proxy example (update the route table before the Express catch-all):

    // src/proxy/routeTable.ts
    // Prepend; order matters — first match wins.
    { method: 'POST', path: '/api/users', target: 'fastify' },
    

    Reload the proxy config (nginx -s reload or restart the Node process). Send a test request and confirm the response contains X-Migrated-By: fastify (or equivalent marker). If the marker is absent, the proxy is still routing to Express — check the location block order.

  11. Deploy to staging. Monitor the error log for 5 minutes:

    # Tail the access log and filter for 5xx on the migrated path
    tail -f <access_log> | grep '<target_path>' | grep -E ' 5[0-9]{2} '
    
    • If any 5xx is observed on target_route: this is the hard rollback trigger. Remove the nginx location block (or revert the route table entry), reload the proxy, confirm Express handles the route again, write the incident to the migration log, STOP: "Rolled back <target_route> — 5xx observed in staging. See migration log for request details."
    • If no 5xx after 5 minutes: run the p99 latency test (Section 6 p99-latency test) and record the result.
  12. Write all outputs declared in Section 7. Run every Equivalence Test in Section 6 and record results in output/express-to-fastify-equiv-<timestamp>.md. Evaluate every item in Section 9 Done Criteria; report pass/fail inline, then print the final verdict.


5. Agent Handoffs

equivalence-validator

  • File: agents/equivalence-validator.md
  • Triggered by: Step 8
  • Prompt template:
    ORIGINAL:    Express handler for <target_route> (src/routes/<route-file>.ts)
    MIGRATED:    Fastify handler for <target_route> (src/fastify/routes/<route-file>.ts)
    REPO_ROOT:   <repo_root>
    TEST_SUITE:  src/__tests__/contracts/<route-slug>-*.test.ts
    OUTPUT_FILE: output/express-to-fastify-divergence-<route-slug>-<timestamp>.md
    ENV:         staging
    TASK:        Diff the contract test outputs for both suites. For each test case, report
                 whether status codes, response body fields, and response headers match
                 exactly. Mark any divergence as BREAKING (different status code or missing
                 required field) or NON-BREAKING (additional field in Fastify response,
                 cosmetic header difference). Include a VERDICT: PASS | FAIL | INDETERMINATE.
    

code-archaeologist

  • File: agents/code-archaeologist.md
  • Triggered by: Step 2 (invoked inline if the middleware gap analysis in Step 2 cannot be completed by reading express_entry alone — e.g., middleware is registered across multiple files via app.use(require('./middleware/...')))
  • Prompt template:
    TASK:        Trace the complete ordered middleware chain for <target_route> in the
                 Express application. Start from <express_entry> and follow every
                 app.use(), router.use(), and route-level middleware reference.
                 For each middleware, report: name, file path, line number, and whether
                 it reads or writes req.session, req.user, req.body, or sets response headers.
    REPO_ROOT:   <repo_root>
    SCOPE:       <repo_root>/src
    OUTPUT_FILE: output/express-to-fastify-middleware-chain-<route-slug>-<timestamp>.md
    FORMAT:      markdown
    

6. Equivalence Tests

<!-- All tests are run in Step 12. Results written to output/express-to-fastify-equiv-<timestamp>.md. <route-slug> is the kebab-case version of target_route (e.g., "post-api-users"). -->
Test NameInputExpected OutputTool
express-contractnpx jest src/__tests__/contracts/<route-slug>-express.test.ts (Step 6 result)Exit code 0. All test cases pass. This is the behavioral baseline — if it fails, there is no valid target to match.Bash — captured in Step 6.
fastify-contractnpx jest src/__tests__/contracts/<route-slug>-fastify.test.ts (Step 7 result)Exit code 0. All test cases pass with status codes, body shapes, and headers identical to the Express contract results.Bash — captured in Step 7.
contract-divergence-cleanDivergence report at output/express-to-fastify-divergence-<route-slug>-<timestamp>.md (Step 8 result)Report verdict is PASS. Zero items marked BREAKING. Non-breaking divergences are logged but do not block.Read — check for VERDICT: PASS and absence of BREAKING.
no-5xx-in-stagingtail -f <access_log> | grep '<target_path>' | grep -E ' 5[0-9]{2} ' monitored for 5 minutes after Step 11 proxy cutoverZero matching lines — grep finds no 5xx responses for the migrated path.Bash — output captured in migration log during Step 11.
session-parityOnly if session_usage: true in manifest: send authenticated request with a pre-seeded session cookie to both Express (supertest) and Fastify (inject)Session data read and written identically; Set-Cookie response has same httpOnly, secure, sameSite, and maxAge attributes on both.Bash: supertest + inject with --cookie flag; diff Set-Cookie headers.
error-handler-paritySend a request to target_route that triggers the error handler (e.g., payload causing a DB constraint violation via a test fixture)Status code and {error, message} body shape are identical between Express and Fastify responses. Fastify setErrorHandler must mirror Express global error handler output.Bash: supertest + inject with error-triggering payload.
p99-latencyk6 run --vus <k6_vus> --duration <k6_duration> output/express-to-fastify-k6-<route-slug>.js against Fastify in stagingp99 latency ≤ Express p99 baseline recorded in the migration log. A regression of ≤5ms is acceptable; >5ms warrants investigation before proceeding to the next route.Bash: k6 output parsed for http_req_duration{p(99)} value.

7. Outputs

ArtifactPath PatternFormatDescription
Middleware gap analysisoutput/express-to-fastify-middleware-chain-<route-slug>-<timestamp>.mdmarkdownProduced by code-archaeologist if invoked in Step 2. Complete ordered middleware chain for target_route; consumed in Steps 3–4 to verify every middleware has a Fastify hook equivalent.
Fastify route filesrc/fastify/routes/<route-slug>.tsTypeScriptThe migrated Fastify route plugin. Committed to the repo as a permanent artifact.
Express contract testsrc/__tests__/contracts/<route-slug>-express.test.tsTypeScriptSupertest behavioral baseline for target_route. Committed; runs in CI against Express until Express is fully removed.
Fastify contract testsrc/__tests__/contracts/<route-slug>-fastify.test.tsTypeScriptinject() assertions mirroring the Express contract. Committed; becomes the permanent test for this route after Express is removed.
Divergence reportoutput/express-to-fastify-divergence-<route-slug>-<timestamp>.mdmarkdownProduced by equivalence-validator in Step 8. Side-by-side diff of contract test outputs with BREAKING/NON-BREAKING classification and a VERDICT.
k6 load test scriptoutput/express-to-fastify-k6-<route-slug>.jsjavascriptk6 script targeting target_route on Fastify in staging. Generated in Step 12 for the p99-latency equivalence test.
Migration logoutput/express-to-fastify-log-<timestamp>.mdmarkdownTranslation decisions, middleware gap items, status code divergences, p99 baseline, rollback events (if any), confidence level, and numbered assumptions list. Consumed by /validate and the PR reviewer.
Equivalence test resultsoutput/express-to-fastify-equiv-<timestamp>.mdmarkdownPass/fail verdict for every row in Section 6, with raw command output attached. Required by Section 9 Done Criteria.

8. References

  • references/strangler-fig-pattern.md — the in-process proxy with Express catch-all is the "Facade / Proxy" mechanism described here; the fastify.all('*', expressApp) pattern is the traffic-shifting seam.
  • references/migration-anti-patterns.md — "Migrating Shared Infrastructure Last" (§6) applies to the auth and session middleware: migrate those in Phase 3, not here. "Confidence Without Evidence" (§7) is why every translation decision in Step 3 must cite a line number.
  • references/stack-compatibility-matrix.md — "Framework Compatibility: Express 4 → Fastify 4" row; check before assuming middleware API parity.
  • skills/03-prepare/ — plugin scaffolding (@fastify/session, @fastify/cors, @fastify/helmet), the in-process proxy setup, and the Fastify instance bootstrap must be complete before this skill runs.
  • skills/05-validate/ — run /validate after all routes are migrated and Express is removed; the full contract test suite and a production load test are the final gates.
  • agents/equivalence-validator.md — primary agent in Step 8; review its VERDICT semantics before interpreting the divergence report.
  • agents/code-archaeologist.md — optional agent in Step 2; invoke when middleware is registered across multiple files.
  • https://fastify.dev/docs/latest/Guides/Migration-Guide-V4/ — Fastify v4 migration guide; consult for plugin API changes if upgrading from Fastify v3.
  • https://fastify.dev/docs/latest/Reference/Hooks/ — hook lifecycle order; essential for correctly scoping preHandler, onRequest, and onSend equivalents of Express middleware.

9. Done Criteria

<!-- Claude evaluates each item and reports pass/fail before declaring this skill complete. Any unchecked item means the skill is NOT complete for this route invocation. The first four gates (1–4) apply to the final state of the full migration (all routes done). Gates 5–10 are per-invocation and must pass for every individual route before the next route begins. Mandatory universal gates are 11–15. -->
  • [Final state] express is absent from package.json dependenciesgrep '"express"' <repo_root>/package.json returns no matches. This gate is only checked after all routes in the manifest have been migrated. Skip and note if this is not the final route.
  • [Final state] All routes in route_manifest_path have a corresponding entry in src/fastify/routes/ and are registered in fastify_entry — glob src/fastify/routes/*.ts and cross-reference with the manifest; no manifest entry is unmapped.
  • [Final state] The full contract test suite (npx jest src/__tests__/contracts/) exits 0 — all Express and Fastify contract tests pass simultaneously in CI.
  • [Final state] p99 latency for all migrated routes is equal to or better than the Express baseline — all p99-latency results in the migration log show no regression >5ms.
  • express-contract equivalence test recorded as pass — the behavioral baseline for target_route is established and green.
  • fastify-contract equivalence test recorded as pass — Fastify handler produces identical status codes, body shapes, and headers.
  • contract-divergence-clean equivalence test recorded as pass — divergence report verdict is PASS; zero BREAKING items.
  • no-5xx-in-staging equivalence test recorded as pass — no 5xx observed for target_route during the 5-minute post-cutover monitoring window.
  • error-handler-parity equivalence test recorded as pass — error response shape is identical between Express and Fastify for the error-triggering payload.
  • If session_usage: true in manifest: session-parity equivalence test recorded as pass. If session_usage: false, mark this gate N/A and note.
  • All output files listed in Section 7 exist at their declared paths — verify each with a file read. (The middleware gap analysis is only required if code-archaeologist was invoked in Step 2.)
  • Every equivalence test in Section 6 has a recorded result in output/express-to-fastify-equiv-<timestamp>.md — no test name is missing from the results file. (Session-parity is marked N/A if not applicable, not absent.)
  • No equivalence test in Section 6 is recorded as fail — grep the results file for fail; zero matches required. N/A entries do not count as fail.
  • The migration log includes a confidence level (High / Medium / Low) — grep output/express-to-fastify-log-<timestamp>.md for Confidence:.
  • The migration log includes a numbered assumptions list — grep output/express-to-fastify-log-<timestamp>.md for Assumptions:.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.