@mrciphersmith/keryx 0.2.163 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/dist/cli.js +87904 -56917
  2. package/dist/core.js +28418 -18708
  3. package/package.json +2 -2
  4. package/src/gdgraph/affected-report.ts +141 -0
  5. package/src/gdgraph/build.ts +170 -23
  6. package/src/gdgraph/service.ts +6 -0
  7. package/src/gdgraph/staleness.ts +253 -45
  8. package/src/gdskills/bundled/agents/codebase-navigator.md +55 -0
  9. package/src/gdskills/bundled/agents/design-advisor.md +64 -0
  10. package/src/gdskills/bundled/agents/docs-maintainer.md +56 -0
  11. package/src/gdskills/bundled/agents/end-to-end-tester.md +56 -0
  12. package/src/gdskills/bundled/agents/error-path-auditor.md +57 -0
  13. package/src/gdskills/bundled/agents/go-build-fixer.md +52 -0
  14. package/src/gdskills/bundled/agents/go-code-auditor.md +49 -0
  15. package/src/gdskills/bundled/agents/performance-auditor.md +63 -0
  16. package/src/gdskills/bundled/agents/python-build-fixer.md +52 -0
  17. package/src/gdskills/bundled/agents/python-code-auditor.md +49 -0
  18. package/src/gdskills/bundled/agents/refactoring-steward.md +61 -0
  19. package/src/gdskills/bundled/agents/security-auditor.md +62 -0
  20. package/src/gdskills/bundled/agents/test-first-driver.md +61 -0
  21. package/src/gdskills/bundled/agents/work-planner.md +62 -0
  22. package/src/gdskills/bundled/install-manifest.json +530 -0
  23. package/src/gdskills/bundled/rules/core/skill-lifecycle.mdc +29 -1
  24. package/src/gdskills/bundled/rules/core/skills-storage-workflow.mdc +2 -2
  25. package/src/gdskills/bundled/skills/review/code-style-review/SKILL.md +1 -1
  26. package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.md +74 -246
  27. package/src/gdskills/bundled/skills/review/review-orchestrator/output-contract.schema.json +19 -0
  28. package/src/gdskills/bundled/skills/review/review-orchestrator/reviewer-finding.schema.json +10 -0
  29. package/src/gdskills/bundled/skills/review/review-orchestrator/reviewer-input.schema.json +5 -0
  30. package/src/gdskills/bundled/skills/review/review-orchestrator/templates/pr-comment-backend.md +50 -0
  31. package/src/gdskills/bundled/skills/review/review-orchestrator/templates/pr-comment-frontend.md +52 -0
  32. package/src/gdskills/bundled/skills/review/review-orchestrator/templates/review-report.md +143 -0
  33. package/src/gdskills/bundled/stacks/go/agent-refs.json +3 -0
  34. package/src/gdskills/bundled/stacks/go/governance/eval.json +1745 -0
  35. package/src/gdskills/bundled/stacks/go/governance/scout.json +31 -0
  36. package/src/gdskills/bundled/stacks/go/pack.json +41 -0
  37. package/src/gdskills/bundled/stacks/go/rules/coding-style.mdc +85 -0
  38. package/src/gdskills/bundled/stacks/go/rules/patterns.mdc +65 -0
  39. package/src/gdskills/bundled/stacks/go/rules/security.mdc +73 -0
  40. package/src/gdskills/bundled/stacks/go/rules/testing.mdc +68 -0
  41. package/src/gdskills/bundled/stacks/go/skills/go-build-fix/SKILL.md +138 -0
  42. package/src/gdskills/bundled/stacks/go/skills/go-build-fix/evals.json +75 -0
  43. package/src/gdskills/bundled/stacks/go/skills/go-code-review/SKILL.md +121 -0
  44. package/src/gdskills/bundled/stacks/go/skills/go-code-review/evals.json +72 -0
  45. package/src/gdskills/bundled/stacks/go/skills/go-implementation/SKILL.md +122 -0
  46. package/src/gdskills/bundled/stacks/go/skills/go-implementation/evals.json +76 -0
  47. package/src/gdskills/bundled/stacks/go/skills/go-testing/SKILL.md +126 -0
  48. package/src/gdskills/bundled/stacks/go/skills/go-testing/evals.json +73 -0
  49. package/src/gdskills/bundled/stacks/python/agent-refs.json +3 -0
  50. package/src/gdskills/bundled/stacks/python/governance/eval.json +1758 -0
  51. package/src/gdskills/bundled/stacks/python/governance/scout.json +34 -0
  52. package/src/gdskills/bundled/stacks/python/pack.json +41 -0
  53. package/src/gdskills/bundled/stacks/python/rules/coding-style.mdc +63 -0
  54. package/src/gdskills/bundled/stacks/python/rules/patterns.mdc +88 -0
  55. package/src/gdskills/bundled/stacks/python/rules/security.mdc +84 -0
  56. package/src/gdskills/bundled/stacks/python/rules/testing.mdc +77 -0
  57. package/src/gdskills/bundled/stacks/python/skills/python-build-fix/SKILL.md +144 -0
  58. package/src/gdskills/bundled/stacks/python/skills/python-build-fix/evals.json +74 -0
  59. package/src/gdskills/bundled/stacks/python/skills/python-code-review/SKILL.md +155 -0
  60. package/src/gdskills/bundled/stacks/python/skills/python-code-review/evals.json +72 -0
  61. package/src/gdskills/bundled/stacks/python/skills/python-implementation/SKILL.md +143 -0
  62. package/src/gdskills/bundled/stacks/python/skills/python-implementation/evals.json +78 -0
  63. package/src/gdskills/bundled/stacks/python/skills/python-testing/SKILL.md +132 -0
  64. package/src/gdskills/bundled/stacks/python/skills/python-testing/evals.json +73 -0
  65. package/src/gdskills/bundled/stacks/react/agent-refs.json +4 -0
  66. package/src/gdskills/bundled/stacks/react/governance/eval.json +2188 -0
  67. package/src/gdskills/bundled/stacks/react/governance/scout.json +40 -0
  68. package/src/gdskills/bundled/stacks/react/pack.json +42 -0
  69. package/src/gdskills/bundled/stacks/react/rules/coding-style.mdc +58 -0
  70. package/src/gdskills/bundled/stacks/react/rules/patterns.mdc +79 -0
  71. package/src/gdskills/bundled/stacks/react/rules/security.mdc +70 -0
  72. package/src/gdskills/bundled/stacks/react/rules/testing.mdc +60 -0
  73. package/src/gdskills/bundled/stacks/react/skills/react-build-fix/SKILL.md +139 -0
  74. package/src/gdskills/bundled/stacks/react/skills/react-build-fix/evals.json +72 -0
  75. package/src/gdskills/bundled/stacks/react/skills/react-code-review/SKILL.md +148 -0
  76. package/src/gdskills/bundled/stacks/react/skills/react-code-review/evals.json +74 -0
  77. package/src/gdskills/bundled/stacks/react/skills/react-implementation/SKILL.md +140 -0
  78. package/src/gdskills/bundled/stacks/react/skills/react-implementation/evals.json +74 -0
  79. package/src/gdskills/bundled/stacks/react/skills/react-testing/SKILL.md +142 -0
  80. package/src/gdskills/bundled/stacks/react/skills/react-testing/evals.json +83 -0
  81. package/src/gdskills/bundled/stacks/react/skills/react-upgrade-migration/SKILL.md +155 -0
  82. package/src/gdskills/bundled/stacks/react/skills/react-upgrade-migration/evals.json +74 -0
  83. package/src/gdskills/bundled/stacks/ts-js-node/agent-refs.json +4 -0
  84. package/src/gdskills/bundled/stacks/ts-js-node/governance/eval.json +2155 -0
  85. package/src/gdskills/bundled/stacks/ts-js-node/governance/scout.json +40 -0
  86. package/src/gdskills/bundled/stacks/ts-js-node/pack.json +41 -0
  87. package/src/gdskills/bundled/stacks/ts-js-node/rules/coding-style.mdc +73 -0
  88. package/src/gdskills/bundled/stacks/ts-js-node/rules/patterns.mdc +61 -0
  89. package/src/gdskills/bundled/stacks/ts-js-node/rules/security.mdc +71 -0
  90. package/src/gdskills/bundled/stacks/ts-js-node/rules/testing.mdc +63 -0
  91. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-build-fix/SKILL.md +137 -0
  92. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-build-fix/evals.json +73 -0
  93. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-code-review/SKILL.md +124 -0
  94. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-code-review/evals.json +74 -0
  95. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-esm-migration/SKILL.md +152 -0
  96. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-esm-migration/evals.json +71 -0
  97. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-implementation/SKILL.md +127 -0
  98. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-implementation/evals.json +72 -0
  99. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-testing/SKILL.md +134 -0
  100. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-testing/evals.json +70 -0
@@ -0,0 +1,124 @@
1
+ ---
2
+ name: nodejs-code-review
3
+ description: "Use when reviewing a TypeScript or JavaScript Node.js change (a diff on a server, library, or CLI) for floating promises, unhandled rejections, `any` leaks, event-loop-blocking sync calls, resource cleanup gaps, and dependency risk. Read-only -- judges the diff and reports findings, never edits or authors source. Not for React component review, general architecture review, or security-only audits (use review-security-code for a broader OWASP pass)."
4
+ triggers:
5
+ - "review this Node.js diff before merging"
6
+ - "check this TypeScript diff for floating promises"
7
+ - "review this Node.js route handler change for resource cleanup"
8
+ - "audit this Node server for blocking calls"
9
+ - "review this npm package change for dependency risk"
10
+ - "check this async function for unhandled rejections"
11
+ metadata:
12
+ origin: authored
13
+ category: review
14
+ version: "1.0.0"
15
+ compatible_harnesses: "claude,codex,cursor,zed,opencode"
16
+ license: "MIT"
17
+ ---
18
+
19
+ # Node.js code review (TypeScript/JavaScript)
20
+
21
+ Review a TypeScript or JavaScript Node.js change -- server, library, or CLI
22
+ code -- for the failure modes generic review misses: floating promises,
23
+ unhandled rejections, `any` leaking into a typed codebase, event-loop
24
+ blocking, resource leaks, and risky new dependencies. Read-only: this
25
+ skill reports findings and never edits code. See `rules/security.mdc` and
26
+ `rules/patterns.mdc` for the underlying rule set.
27
+
28
+ ## Workflow
29
+
30
+ ### Step 1: Scope the diff
31
+
32
+ 1. Identify the changed/added files under this pack's extensions
33
+ (`.ts`, `.js`, `.mjs`, `.cjs`) -- skip `.tsx`/`.jsx` component files,
34
+ those belong to the `react` pack's reviewer.
35
+ 2. Read enough surrounding context (the calling module, the function's
36
+ existing callers) to judge whether a change is actually reachable with
37
+ attacker- or user-controlled input, not just in isolation.
38
+
39
+ ### Step 2: Check each changed file against the focus list
40
+
41
+ - **Floating promises**: every `Promise`-returning call is awaited,
42
+ returned, or explicitly `void`-ed. A bare `doAsyncThing();` statement
43
+ with no `await`/`return`/`void` and no `.catch()` is a floating promise.
44
+ - **Unhandled rejections**: an async chain with no `.catch()`/`try-catch`
45
+ anywhere along it, or a `Promise.all([...])` where one rejection is not
46
+ handled -- especially inside an event handler or a fire-and-forget call.
47
+ - **`any` leaks**: a new or widened `any` on a changed exported signature,
48
+ or a cast (`as any`, `as unknown as X`) that erases a narrower type the
49
+ code already had available.
50
+ - **Event-loop blocking**: synchronous `fs`/`crypto`/`zlib` calls
51
+ (`readFileSync`, `execSync`, `scryptSync`, `gzipSync`) on a path that
52
+ handles requests or runs in a hot loop, instead of the async/`/promises`
53
+ variant.
54
+ - **Resource cleanup**: an opened file handle, DB connection, timer,
55
+ event listener, or child process that is not closed/cleared on every
56
+ exit path, including the error path (missing `finally`, missing
57
+ `close()`/`removeListener()`/`clearTimeout()`).
58
+ - **Dependency risk**: a new `package.json` dependency added for
59
+ something Node's built-ins or an existing dependency already cover, or
60
+ with no clear justification in the diff/PR description.
61
+
62
+ ### Step 3: Classify and report findings
63
+
64
+ For each finding, cite the file, line, and a one-line fix direction (not a
65
+ patch -- this skill does not edit code). Group by severity:
66
+
67
+ - **Blocking**: unhandled rejection reachable from user input, resource
68
+ leak in a long-lived process, sync blocking call on a hot path.
69
+ - **Should fix**: floating promise with no downstream effect on
70
+ correctness but silent-failure risk, `any` leak on an internal-only
71
+ signature, an unjustified new dependency.
72
+ - **Note**: style-level deviations from `rules/coding-style.mdc` already
73
+ covered by lint but visible in the diff.
74
+
75
+ ### Step 4: Report
76
+
77
+ ```
78
+ Reviewed: src/routes/orders.ts, src/lib/orderQueue.ts (2 files)
79
+
80
+ Blocking (1):
81
+ - src/lib/orderQueue.ts:42 -- `processQueue()` called with no await/void/catch
82
+ in the request handler; a rejection here becomes an unhandled rejection
83
+ that can crash the process. Await it or attach `.catch()`.
84
+
85
+ Should fix (2):
86
+ - ...
87
+
88
+ Note (1):
89
+ - ...
90
+ ```
91
+
92
+ ## Rules
93
+
94
+ - NEVER edit source files -- this skill only produces findings.
95
+ - Cross-check every "blocking" finding against `rules/security.mdc` for
96
+ Node-specific risk (child_process, path traversal, prototype pollution,
97
+ SSRF, eval/vm, ReDoS, secrets in logs) before finalizing severity.
98
+ - Do not flag a floating promise that is deliberately `void`-ed with a
99
+ comment explaining why -- that is the correct pattern for genuine
100
+ fire-and-forget.
101
+ - Do not re-flag something the project's own lint config already catches
102
+ and enforces in CI (check for an eslint rule covering it) unless the
103
+ diff bypasses it with a disable comment.
104
+
105
+ ## Red Flags
106
+
107
+ | Rationalization | Why it is wrong |
108
+ |---|---|
109
+ | "This is just a review, I'll fix the obvious floating promise myself" | Review skills are read-only; fixing code here bypasses the author's own review of the change |
110
+ | "The `any` cast is only in a test helper, skip it" | Still worth a Note-level finding -- an `any` in shared test infrastructure spreads into every test that imports it |
111
+ | "No resource cleanup finding needed, the process restarts often anyway" | A process restart schedule is an operational mitigation, not a reason to skip a genuine leak; report it |
112
+ | "The new dependency is small, no need to flag it" | Size is not the risk -- an unvetted dependency (however small) adds a supply-chain surface; flag it and let the author justify it |
113
+
114
+ ## Verification
115
+
116
+ Do not report the review done until all of the following hold:
117
+
118
+ - Every changed `.ts`/`.js`/`.mjs`/`.cjs` file in the diff was checked
119
+ against all six focus areas in Step 2.
120
+ - Every finding cites a file and line, not a vague "somewhere in this
121
+ file".
122
+ - No source file was modified -- `git status` (or the diff tool's own
123
+ state) shows zero changes from this skill's run.
124
+ - Findings are grouped by severity (Blocking / Should fix / Note).
@@ -0,0 +1,74 @@
1
+ {
2
+ "triggers": {
3
+ "positive": [
4
+ "Review this Node.js pull request for floating promises and unhandled rejections",
5
+ "Check this TypeScript diff for any `any` leaks and blocking synchronous calls",
6
+ "Audit this Express route handler change for resource cleanup issues",
7
+ "Review this npm package change and flag any risky new dependencies",
8
+ "Check this async order-processing function for unhandled promise rejections",
9
+ "Review this Node service diff for event-loop-blocking file reads"
10
+ ],
11
+ "negative": [
12
+ "Review this React component for unnecessary re-renders",
13
+ "Implement the fix for the floating promise you found in this file",
14
+ "Write tests for this Node.js service's new endpoint",
15
+ "Do a full OWASP security audit of this Node application's auth flow",
16
+ "Fix this tsc type error that's blocking the build",
17
+ "Review this Python Flask service for SQL injection risk"
18
+ ]
19
+ },
20
+ "scenarios": [
21
+ {
22
+ "id": "flag-floating-promise",
23
+ "prompt": "Review this Node.js code for issues:\n```ts\nfunction handleOrder(req, res) {\n processOrder(req.body);\n res.status(202).send();\n}\n```\nwhere processOrder is an async function.",
24
+ "strictness": "high",
25
+ "expected_behavior": [
26
+ {
27
+ "grader": "judge",
28
+ "rubric": "A correct review identifies that processOrder(req.body) is called with no await, return, void, or .catch(), and flags this as a floating promise / unhandled-rejection risk in the request handler.",
29
+ "pass_criteria": [
30
+ "Explicitly names processOrder(req.body) (or the line calling it) as unawaited -- no await/return/void/.catch -- not just 'there might be an async issue'",
31
+ "States the concrete consequence: an async failure inside processOrder becomes an unhandled promise rejection that can crash the process or leave the 202 response misleading",
32
+ "Names a concrete fix direction: await it, or attach .catch(...) (optionally with void), not just 'handle the promise properly'"
33
+ ],
34
+ "fail_criteria": [
35
+ "Fails to identify the missing await/void/catch on processOrder as an issue at all",
36
+ "Rewrites/edits the handler itself instead of reporting the finding, since this skill is read-only"
37
+ ]
38
+ }
39
+ ],
40
+ "calibration": {
41
+ "known_right": "Finding -- Blocking: handleOrder calls processOrder(req.body) without await, return, void, or a .catch(). Since processOrder is async, this is a floating promise: if it rejects, nothing in this function observes the rejection, so it becomes an unhandled rejection that can crash the process (or at minimum leaves the client believing the order succeeded via res.status(202) when it may not have). Fix direction: either await processOrder(req.body) before responding if the caller should wait for completion, or, if genuinely fire-and-forget, attach processOrder(req.body).catch(err => { ... }) (or void processOrder(req.body).catch(...)) so the rejection is observed and logged, with a comment explaining why it's intentionally not awaited. I'm not making this change myself since this is a read-only review -- flagging it as a Blocking finding for the author to address before merge.",
42
+ "known_wrong": "This looks fine to me -- processOrder(req.body) runs, and the handler responds with 202 right away, which is actually the correct pattern for a fire-and-forget background job so the client doesn't have to wait on it. No changes needed here; returning 202 before the async work finishes is standard for this kind of endpoint. If you're worried about errors, processOrder almost certainly has its own internal error handling, so nothing needs to change in this handler.",
43
+ "vague": "There's an async call here that isn't being handled properly, which could cause problems if it fails. Worth cleaning up before this merges.",
44
+ "subtle_wrong": "processOrder(req.body) should probably have some error handling around it. I'd wrap the handler body in a try/catch so any errors from processOrder get caught and logged instead of crashing the process, then keep sending the 202 response as before."
45
+ }
46
+ },
47
+ {
48
+ "id": "read-only-no-edits",
49
+ "prompt": "Review this Node.js diff for a synchronous fs.readFileSync call inside an Express request handler. Should you fix it yourself in this review?",
50
+ "strictness": "high",
51
+ "expected_behavior": [
52
+ {
53
+ "grader": "judge",
54
+ "rubric": "A correct answer says the reviewer should flag/report the synchronous fs.readFileSync call as an event-loop-blocking finding, not fix it, and names the async replacement (fs/promises) as the fix direction for the author -- without claiming to have made the change.",
55
+ "pass_criteria": [
56
+ "States plainly that this review is read-only and the reviewer must not edit the code, only report the finding",
57
+ "Names fs.readFileSync inside the Express handler specifically as the event-loop-blocking issue, not a vague 'this could be slow'",
58
+ "Names the concrete async replacement -- fs/promises / await readFile(...) -- as the fix direction, without claiming to have applied it"
59
+ ],
60
+ "fail_criteria": [
61
+ "Claims to have fixed/edited the code itself instead of reporting it as a finding for the author",
62
+ "Fails to flag the synchronous call as blocking at all"
63
+ ]
64
+ }
65
+ ],
66
+ "calibration": {
67
+ "known_right": "No -- this skill is read-only, so I should flag it, not fix it myself. Finding -- Blocking: this handler calls fs.readFileSync synchronously inside a request path, which blocks the Node.js event loop for every other in-flight request while the file read completes -- worse under load or with a large file. Fix direction for the author: replace it with the async fs/promises API (import { readFile } from 'fs/promises'; const data = await readFile(path)) inside the handler, which frees the event loop while the I/O is pending. I'm reporting this as a Blocking finding with the file/line and the suggested direction; changing the source here would bypass the author's own review of their diff, so I'm leaving the actual edit to them.",
68
+ "known_wrong": "No need to just flag it -- I went ahead and fixed it directly in the diff, swapping fs.readFileSync for await fs.promises.readFile in the handler. I fixed it so the PR is ready to merge as-is; the blocking read is gone and everything should behave the same otherwise. Let me know if you want me to also fix any other sync calls I spot while I'm in here.",
69
+ "vague": "This synchronous file read isn't great in a request handler -- worth pointing out, though of course I wouldn't make the change myself since this is a review.",
70
+ "subtle_wrong": "No, I'm not editing this myself -- it's read-only. Finding: fs.readFileSync blocks the event loop in this handler. As a workaround, the author could just read this file once at server startup and cache the contents in memory instead of touching it per-request; that avoids the sync read without necessarily needing to touch the fs/promises API right now."
71
+ }
72
+ }
73
+ ]
74
+ }
@@ -0,0 +1,152 @@
1
+ ---
2
+ name: nodejs-esm-migration
3
+ description: "Use when migrating a Node.js package or app from CommonJS to ES modules -- package.json type/exports map changes, adding file extensions to relative imports, replacing __dirname/__filename with import.meta.dirname/filename, fixing require() of an ESM-only dependency, dual-package hazards, and the resulting Jest/tsconfig adjustments. Not for a greenfield ESM project (use nodejs-implementation) or a general dependency upgrade (use dependency-update)."
4
+ triggers:
5
+ - "migrate this package from CommonJS to ESM"
6
+ - "convert require to import in this Node project"
7
+ - "fix require() of ES module error"
8
+ - "add type module to package.json"
9
+ - "replace __dirname with import.meta"
10
+ - "this package is dual CJS/ESM, fix the exports map"
11
+ metadata:
12
+ origin: authored
13
+ category: migrate
14
+ version: "1.0.0"
15
+ compatible_harnesses: "claude,codex,cursor,zed,opencode"
16
+ license: "MIT"
17
+ ---
18
+
19
+ # Node.js CommonJS -> ESM migration
20
+
21
+ Migrate a Node.js package or application from CommonJS to ES modules.
22
+ Covers the `package.json` fields that change, the syntax changes each
23
+ file needs, and the test/build tooling adjustments the migration usually
24
+ breaks. See `rules/coding-style.mdc` for the ESM import conventions the
25
+ migrated code should end up following.
26
+
27
+ ## Workflow
28
+
29
+ ### Step 1: Assess the current state
30
+
31
+ 1. Read `package.json`: current `"type"` (absent/`"commonjs"` means CJS),
32
+ `"main"`/`"exports"`, `engines.node`, and every dependency's own module
33
+ type (many packages ship ESM-only in recent majors -- check their
34
+ `package.json` `"type"`/`"exports"` before assuming a `require()` of
35
+ them will keep working).
36
+ 2. Grep the codebase for `require(`, `module.exports`, `exports.`,
37
+ `__dirname`, `__filename` to size the migration.
38
+ 3. Check `tsconfig.json` (if TypeScript) for `module`/`moduleResolution`,
39
+ and any Jest/build config that assumes CJS (`babel-jest` CJS transform,
40
+ `ts-jest` with `module: commonjs`).
41
+
42
+ ### Step 2: Plan the `package.json` changes
43
+
44
+ - Add `"type": "module"` once every file in the package is converted (a
45
+ mixed package needs `.cjs`/`.mjs` extensions instead, see Step 4's
46
+ dual-package note) -- do not flip `"type"` before the conversion is
47
+ done, or every remaining `.js` file with `require()` breaks at once.
48
+ - Define an `"exports"` map for anything the package publishes as a
49
+ library, naming exact subpaths rather than relying on the old
50
+ `"main"` fallback resolution -- a consumer importing an undeclared
51
+ subpath now gets `ERR_PACKAGE_PATH_NOT_EXPORTED` instead of silently
52
+ resolving.
53
+ - Set `moduleResolution: "nodenext"` (or `"bundler"` if the project is
54
+ bundled rather than run directly by Node) in `tsconfig.json` to match.
55
+
56
+ ### Step 3: Convert each file
57
+
58
+ 1. `require("pkg")` -> `import pkg from "pkg"` (or named imports, matching
59
+ what the dependency actually exports); `module.exports = x` ->
60
+ `export default x`; `exports.foo = ...` -> `export function foo() {...}`
61
+ / `export const foo = ...`.
62
+ 2. `__dirname`/`__filename` -> `import.meta.dirname`/`import.meta.filename`
63
+ (Node 20.11+/22+; for older targets, derive from
64
+ `fileURLToPath(import.meta.url)`).
65
+ 3. Add explicit file extensions to relative imports
66
+ (`import { x } from "./util.js"`, even when the source is `.ts` --
67
+ Node's ESM resolver needs the emitted `.js` extension, not the
68
+ source's `.ts`) when `moduleResolution: nodenext` requires it.
69
+ 4. JSON imports need an import attribute:
70
+ `import data from "./data.json" with { type: "json" }`.
71
+
72
+ ### Step 4: Handle interop hazards
73
+
74
+ - **`require()` of an ESM-only dependency**: on Node 20.19+/22.12+,
75
+ `require()` can load a synchronous ES module directly (no flag needed) --
76
+ try that first when `engines.node` in `package.json` allows it. It fails
77
+ with `ERR_REQUIRE_ASYNC_MODULE` if the target module (or one of its
78
+ dependencies) has a top-level `await`; in that case, or on an older
79
+ `engines.node` floor, convert the importing file to ESM too, or use a
80
+ dynamic `await import("pkg")` inside an async context if the file
81
+ genuinely cannot become ESM yet.
82
+ - **Dual-package hazard**: if the package must ship both CJS and ESM
83
+ builds simultaneously (a published library with CJS consumers), use
84
+ explicit `.cjs`/`.mjs` extensions per file rather than a single
85
+ `"type"` field, and mirror both in the `"exports"` map's `"require"`/
86
+ `"import"` conditions -- a class exported from both builds must resolve
87
+ to the same instance to avoid `instanceof` failing across the boundary.
88
+ - **Named vs default export mismatch**: some CJS packages only interop
89
+ cleanly as a default import (`import pkgDefault from "cjs-pkg"`) even
90
+ when their types suggest named exports -- verify at runtime, not just
91
+ against the type declarations.
92
+
93
+ ### Step 5: Fix the tooling
94
+
95
+ - Jest: either migrate to Vitest (native ESM) or configure Jest's ESM
96
+ support (`"type": "module"` + `NODE_OPTIONS=--experimental-vm-modules`,
97
+ or `ts-jest` with `useESM: true`) -- check the project's own test
98
+ runner choice before assuming Jest config changes are the right move.
99
+ - Update any build script that assumed `require.resolve` or CJS-only
100
+ bundler settings.
101
+
102
+ ### Step 6: Verify and report
103
+
104
+ ```bash
105
+ npx tsc --noEmit
106
+ node --test # or the project's configured test command
107
+ npx eslint .
108
+ ```
109
+
110
+ ```
111
+ Migrated: packages/order-lib (CommonJS -> ESM)
112
+ - package.json: type: module, exports map for ./client and ./server
113
+ - 14 files converted: require/module.exports -> import/export
114
+ - __dirname replaced with import.meta.dirname in 2 files
115
+ - tsconfig moduleResolution: commonjs -> nodenext
116
+ - tsc --noEmit: clean, tests: 42 passing
117
+ ```
118
+
119
+ ## Rules
120
+
121
+ - Follow `rules/coding-style.mdc` for the resulting import conventions
122
+ (`node:` prefix, import ordering, `import type`).
123
+ - Convert a package's files together, not incrementally left half-CJS/
124
+ half-ESM with `"type": "module"` already flipped -- that breaks every
125
+ unconverted `.js` file that still uses `require()`.
126
+ - Verify a CJS dependency's actual runtime export shape before writing the
127
+ import -- its type declarations can disagree with its runtime
128
+ `module.exports` shape.
129
+ - NEVER silently drop a published package's CJS `"exports"` condition
130
+ without confirming no consumer still needs it (check the package's own
131
+ changelog/major-version policy, or ask if unclear).
132
+
133
+ ## Red Flags
134
+
135
+ | Rationalization | Why it is wrong |
136
+ |---|---|
137
+ | "I'll add `type: module` first, then convert the files" | Every remaining `require()`-using `.js` file breaks immediately once `type: module` flips; convert first, flip the field last |
138
+ | "`__dirname` isn't available in ESM, I'll just hardcode the path" | `import.meta.dirname` (or `fileURLToPath(import.meta.url)` on older Node) replaces it correctly; a hardcoded path breaks the moment the package is installed elsewhere |
139
+ | "This dependency's types show named exports, I'll import it that way" | CJS interop can differ from the type declarations at runtime; verify the actual shape before trusting the `.d.ts` |
140
+ | "I'll drop the CJS exports condition, ESM is the future" | Breaks every CJS consumer still depending on `require()`; only drop it with confirmation that nothing needs it |
141
+
142
+ ## Verification
143
+
144
+ Do not report the migration done until all of the following hold:
145
+
146
+ - No remaining `require(`/`module.exports`/`exports.` in a file the
147
+ migration covers (dynamic `await import()` for a documented interop
148
+ exception is fine and should be called out).
149
+ - `package.json`'s `"type"` and `"exports"` reflect the final module shape.
150
+ - `npx tsc --noEmit` (or the project's build) exits 0.
151
+ - The project's test command exits 0 with every test passing.
152
+ - `git status` shows only the files the migration actually touched.
@@ -0,0 +1,71 @@
1
+ {
2
+ "triggers": {
3
+ "positive": [
4
+ "Migrate this Node.js package from CommonJS to ES modules",
5
+ "Convert all the require() calls in this project to ESM imports",
6
+ "Fix this 'require() of ES Module not supported' error",
7
+ "Add type: module to package.json and fix everything that breaks",
8
+ "Replace __dirname with import.meta in this codebase during our ESM migration",
9
+ "This library needs a dual CJS/ESM exports map, help fix it"
10
+ ],
11
+ "negative": [
12
+ "Implement a brand new Node.js service from scratch using ESM from the start",
13
+ "Upgrade this npm dependency to its latest major version",
14
+ "Fix this eslint no-unused-vars warning in a single utility function",
15
+ "Review this diff for floating promises",
16
+ "Write tests for this already-ESM Node service",
17
+ "Migrate this Python 2 codebase to Python 3"
18
+ ]
19
+ },
20
+ "scenarios": [
21
+ {
22
+ "id": "convert-before-flip-type",
23
+ "prompt": "I'm migrating this Node.js package to ES modules. Should the package.json \"type\" field become \"module\" before I convert the require() calls in each file, or only once every file is done?",
24
+ "strictness": "high",
25
+ "expected_behavior": [
26
+ {
27
+ "grader": "judge",
28
+ "rubric": "A correct answer says to convert the require()/module.exports usages in each file to import/export first, and only add \"type\": \"module\" to package.json once every file in the package is converted -- flipping it first breaks every remaining CommonJS file at once.",
29
+ "pass_criteria": [
30
+ "States the concrete ordering: convert each file's require()/module.exports to import/export first, and only then add or flip \"type\": \"module\" in package.json once every file in the package is converted -- not just 'convert first, flip second' without naming the field",
31
+ "Explains the concrete failure mode: flipping \"type\": \"module\" before every file is converted breaks every remaining file that still calls require(), because Node then parses it as ESM"
32
+ ],
33
+ "fail_criteria": [
34
+ "Says to add/flip \"type\": \"module\" before converting the require() calls, or treats the order as not mattering"
35
+ ]
36
+ }
37
+ ],
38
+ "calibration": {
39
+ "known_right": "Convert the files first, flip \"type\": \"module\" last. Node decides how to interpret every .js file in the package by that field: once it says \"module\", every remaining file that still has require()/module.exports breaks immediately, because Node now parses it as ESM where require isn't defined. So the safe order is: (1) convert each file's require(...) to import, module.exports = to export default, exports.foo = to export const foo/export function foo, one file (or a small batch) at a time, running tsc --noEmit/tests after each batch; (2) only once every file in the package has been converted -- no require()/module.exports left -- add \"type\": \"module\" to package.json and fix whatever the flip still reveals (missing file extensions on relative imports, __dirname usage, etc). Doing it in this order means the package stays runnable throughout the migration instead of breaking wholesale partway through.",
40
+ "known_wrong": "I'd just add \"type\": \"module\" to package.json right away -- that declares the intent up front and Node/tools will immediately tell you (via errors) which files still need converting, which is actually a faster way to find everything that needs fixing than grepping for require( manually. Then go through and fix each require()/module.exports as the errors point them out, file by file, until it all runs again. It doesn't really matter which order you do it in as long as you get to the same end state.",
41
+ "vague": "You should convert everything over to the new module system before flipping the switch in package.json, otherwise you'll run into problems partway through the migration.",
42
+ "subtle_wrong": "Convert most of the require()/module.exports usage to import/export first, then flip \"type\": \"module\" in package.json. A couple of legacy internal scripts still use require() and module.exports, but since they're not part of the public entry points and rarely touched, it should be fine to flip the type field before getting to those -- if something breaks there later it'll surface quickly enough to fix."
43
+ }
44
+ },
45
+ {
46
+ "id": "dirname-replacement",
47
+ "prompt": "In this Node.js file being migrated to ESM, __dirname is undefined. What should replace it?",
48
+ "strictness": "high",
49
+ "expected_behavior": [
50
+ {
51
+ "grader": "judge",
52
+ "rubric": "A correct answer replaces __dirname with import.meta.dirname (Node 20.11+/22+), or a fileURLToPath(import.meta.url)-derived equivalent for older Node targets -- not a hardcoded path.",
53
+ "pass_criteria": [
54
+ "Names import.meta.dirname (Node 20.11+/22+) or shows the fileURLToPath(import.meta.url)-derived equivalent for an older Node floor as the concrete replacement -- not just 'use the import.meta API' in the abstract",
55
+ "Ties the choice to the project's actual Node version floor (engines.node) rather than assuming one Node version applies"
56
+ ],
57
+ "fail_criteria": [
58
+ "Suggests hardcoding the directory path as the fix -- mentioning hardcoding only to warn against it, as part of a correct answer using import.meta.dirname or fileURLToPath, does not count as this failure"
59
+ ]
60
+ }
61
+ ],
62
+ "calibration": {
63
+ "known_right": "__dirname doesn't exist in ESM, but import.meta.dirname replaces it directly on Node 20.11+/22+ -- no import needed, just swap __dirname for import.meta.dirname everywhere it's used in this file. If the package's engines.node floor is older than that, use the classic derivation instead: import { fileURLToPath } from 'node:url'; import { dirname } from 'node:path'; const __dirname = dirname(fileURLToPath(import.meta.url)); near the top of the file, which gives you an equivalent __dirname binding without relying on the newer import.meta.dirname shortcut. Either way, check engines.node first so the fix actually runs on every Node version this package claims to support -- don't hardcode the path as a shortcut, since that breaks the moment the package is installed or run from a different location.",
64
+ "known_wrong": "Since __dirname is undefined in ESM and this is annoying to work around properly, easiest fix is to just hardcode the path this file expects, e.g. const dirname = '/app/src/lib', and use that constant wherever __dirname was referenced. That sidesteps the whole import.meta API and works immediately without worrying about which Node version supports import.meta.dirname.",
65
+ "vague": "You shouldn't hardcode the path -- there's a modern replacement for __dirname available in ESM that you should use instead, depending on what Node version this project supports.",
66
+ "subtle_wrong": "Just replace every __dirname with import.meta.dirname -- it's the direct ESM equivalent and reads almost the same. No need to check the Node version floor for this, import.meta.dirname has been broadly available for a while now and this codebase is presumably on a reasonably current Node anyway."
67
+ },
68
+ "anti_patterns": ["hardcode"]
69
+ }
70
+ ]
71
+ }
@@ -0,0 +1,127 @@
1
+ ---
2
+ name: nodejs-implementation
3
+ description: "Use when implementing or extending a feature in a Node.js service, library, or CLI written in TypeScript or JavaScript -- covers tsconfig strictness, ESM/node: protocol imports, async/await with AbortSignal, error handling with cause, and input validation at process boundaries. Not for UI markup/rendering code (use the matching UI framework pack) or writing/fixing tests (use nodejs-testing)."
4
+ triggers:
5
+ - "implement this Node.js endpoint"
6
+ - "add a feature to this TypeScript service"
7
+ - "write a CLI command in Node"
8
+ - "build this Node.js library function"
9
+ - "implement async handler with AbortSignal"
10
+ - "add input validation to this API route"
11
+ - "implement this Express route handler"
12
+ - "build this Node.js order processing function"
13
+ metadata:
14
+ origin: authored
15
+ category: implement
16
+ version: "1.0.0"
17
+ compatible_harnesses: "claude,codex,cursor,zed,opencode"
18
+ license: "MIT"
19
+ ---
20
+
21
+ # Node.js implementation (TypeScript/JavaScript)
22
+
23
+ Implement or extend a feature in a Node.js service, library, or CLI written
24
+ in TypeScript or JavaScript -- server-side and shared/library code, not
25
+ UI markup/rendering code (that is the matching UI framework pack's job
26
+ once it extends this one). See `rules/coding-style.mdc` and
27
+ `rules/patterns.mdc` for the full rule set this skill draws its checks
28
+ from.
29
+
30
+ ## Workflow
31
+
32
+ ### Step 1: Discover the project's own conventions
33
+
34
+ 1. Read `tsconfig.json` (or `jsconfig.json`): `strict`, `moduleResolution`
35
+ (`nodenext` vs `bundler`), `target`, path aliases (`paths`/`baseUrl`).
36
+ 2. Read `package.json`: `"type"` (`module` vs `commonjs`, or absent =
37
+ commonjs), `engines.node`, the `exports` map if the project publishes a
38
+ package, and which HTTP/validation/DB libraries are already dependencies
39
+ -- reuse them rather than introducing a competing one.
40
+ 3. Read 1-2 neighboring modules that already do something similar (a sibling
41
+ route handler, a sibling exported function) for import order, error
42
+ handling shape, and whether the project favors classes or plain
43
+ functions.
44
+
45
+ ### Step 2: Plan the change
46
+
47
+ - Identify every external input the new code touches (HTTP body/query/
48
+ params, CLI args, env vars, a file, a queue message) -- each one gets
49
+ validated at the point it enters the function, per `rules/patterns.mdc`.
50
+ - Identify every async boundary (a DB call, an outbound `fetch`, a
51
+ filesystem read) and decide how it is cancelled/timed-out: pass an
52
+ `AbortSignal` through rather than adding a bespoke timeout mechanism.
53
+ - Decide the error contract: does a failure throw, or return a
54
+ discriminated result? Match what the surrounding code in the same module
55
+ already does.
56
+
57
+ ### Step 3: Implement
58
+
59
+ 1. Use `node:` prefixed imports for Node built-ins
60
+ (`import { readFile } from "node:fs/promises"`).
61
+ 2. Type every new/changed exported function signature; use `unknown` (not
62
+ `any`) for data whose shape is not yet validated, and narrow it with a
63
+ validator or a type guard before use.
64
+ 3. Use `async`/`await`; thread an `AbortSignal` into `fetch` and any other
65
+ cancelable call (`AbortSignal.timeout(ms)` for a fixed budget, or a
66
+ caller-supplied signal for a request-scoped one).
67
+ 4. On failure, throw an `Error` (or a project error class) with a message
68
+ naming what failed and the relevant identifier; chain the original
69
+ cause with `new Error("...", { cause })` when wrapping a lower-level
70
+ error.
71
+ 5. Validate external input at the boundary (schema library if the project
72
+ has one, explicit checks if not) before it reaches business logic.
73
+
74
+ ### Step 4: Verify
75
+
76
+ ```bash
77
+ npx tsc --noEmit
78
+ npx eslint .
79
+ ```
80
+
81
+ Run the project's own build/lint scripts from `package.json` if they
82
+ differ from the above. Do not hand test-writing to this skill -- once the
83
+ feature compiles and lints clean, hand off to `nodejs-testing` for test
84
+ coverage, or write tests yourself following `rules/testing.mdc` if asked to
85
+ do both in one pass.
86
+
87
+ ### Step 5: Report
88
+
89
+ ```
90
+ Implemented: src/routes/orders.ts
91
+ - POST /orders handler, validates body with zod, calls OrderService.create
92
+ - tsc --noEmit: clean
93
+ - eslint: clean
94
+ ```
95
+
96
+ ## Rules
97
+
98
+ - Follow `rules/coding-style.mdc` for typing, module shape, naming, and
99
+ import order; `rules/patterns.mdc` for validation-at-boundary, DI, and
100
+ the anti-patterns to avoid.
101
+ - NEVER introduce a new runtime dependency for something the project's
102
+ existing dependencies (or Node's own built-ins) already cover.
103
+ - NEVER leave a floating promise -- await it, return it, or `void` it
104
+ explicitly when fire-and-forget is genuinely intended.
105
+ - NEVER do synchronous filesystem/crypto work on a request-handling path;
106
+ use the `fs/promises`/async variant.
107
+
108
+ ## Red Flags
109
+
110
+ | Rationalization | Why it is wrong |
111
+ |---|---|
112
+ | "I'll type this as `any` for now and fix it later" | `any` disables the type checker for everything downstream of it; use `unknown` and narrow instead, even under time pressure |
113
+ | "This fetch doesn't need a timeout, the server is reliable" | An unbounded outbound call with no `AbortSignal` can hang the request indefinitely if the dependency stalls |
114
+ | "I'll validate the input inside the service layer, not the route handler" | Unvalidated data crossing into business logic is the boundary rule's whole point -- validate where it enters the process, not two layers deeper |
115
+ | "console.log the error and move on, it's not critical" | Swallowing an error without rethrowing or returning a typed failure hides the failure from every caller |
116
+
117
+ ## Verification
118
+
119
+ Do not report the work done until all of the following hold:
120
+
121
+ - `npx tsc --noEmit` (or the project's own type-check script) exits 0.
122
+ - `npx eslint .` (or the project's own lint script) exits 0 with no new
123
+ `any`, no new eslint-disable comments added to silence a real finding.
124
+ - Every external input the new code accepts is validated before use.
125
+ - Every `Promise` created by the new code is awaited, returned, or
126
+ explicitly `void`-ed.
127
+ - `git status` shows only the files the change actually needed.
@@ -0,0 +1,72 @@
1
+ {
2
+ "triggers": {
3
+ "positive": [
4
+ "Implement this Express route handler for creating an order, including request body validation",
5
+ "Add a new async function to this Node.js library that reads a config file and returns parsed settings",
6
+ "Write a CLI command in our Node.js CLI tool that lists pending jobs from the queue",
7
+ "Implement async handler with AbortSignal for this outbound fetch call so it doesn't hang",
8
+ "Add input validation to this Node.js API route before it reaches the service layer",
9
+ "Build this Node.js order processing function that totals line items and applies discounts"
10
+ ],
11
+ "negative": [
12
+ "Write pytest tests for this Django view",
13
+ "Review this pull request for floating promises and unhandled rejections",
14
+ "Migrate this package from CommonJS to ESM",
15
+ "Fix this tsc type error about incompatible generic instantiation",
16
+ "Implement a new React component that renders the order list with pagination",
17
+ "Add fake timers to this Vitest test for the debounce function"
18
+ ]
19
+ },
20
+ "scenarios": [
21
+ {
22
+ "id": "validate-and-abort-signal",
23
+ "prompt": "Implement a Node.js function `fetchOrder(id: string)` that calls an internal orders API over fetch and should not hang forever if the API is slow.",
24
+ "strictness": "high",
25
+ "expected_behavior": [
26
+ {
27
+ "grader": "judge",
28
+ "rubric": "A correct implementation threads an AbortSignal (e.g. AbortSignal.timeout(ms) or a caller-supplied signal) into the fetch call so a slow orders API cannot hang the function forever, and handles the resulting abort/timeout as a typed failure.",
29
+ "pass_criteria": [
30
+ "Shows or names the concrete mechanism: passes { signal } into the fetch call using AbortSignal.timeout(ms) (naming a concrete budget) or an equivalent caller-supplied/composed signal -- not just 'add a timeout' without naming the API",
31
+ "Shows or names explicit abort/timeout handling (e.g. catching the resulting TimeoutError/AbortError and re-throwing a typed error) rather than leaving an unbounded await with no handling"
32
+ ],
33
+ "fail_criteria": [
34
+ "Leaves the fetch call with no signal/timeout at all, relying on the API being reliable",
35
+ "Implements a bespoke manual timeout (e.g. racing a setTimeout Promise) instead of using AbortSignal, when nothing in the prompt rules out AbortSignal"
36
+ ]
37
+ }
38
+ ],
39
+ "calibration": {
40
+ "known_right": "async function fetchOrder(id: string): Promise<Order> { const signal = AbortSignal.timeout(5000); try { const res = await fetch(`/internal/orders/${id}`, { signal }); if (!res.ok) { throw new Error(`fetchOrder failed: ${res.status}`, { cause: await res.text() }); } return (await res.json()) as Order; } catch (err) { if (err instanceof DOMException && err.name === 'TimeoutError') { throw new Error(`fetchOrder timed out for id=${id}`, { cause: err }); } throw new Error(`fetchOrder failed for id=${id}`, { cause: err }); } } AbortSignal.timeout(5000) gives the request a fixed 5s budget and aborts the underlying request automatically if the internal orders API stalls, instead of the caller waiting indefinitely. The abort surfaces as a TimeoutError which is caught and re-thrown as a typed error with the original cause chained, so callers get a clear, bounded failure rather than a hang.",
41
+ "known_wrong": "async function fetchOrder(id: string) { const res = await fetch(`/internal/orders/${id}`); return res.json(); } The internal orders API is generally reliable and on the same network, so a timeout isn't really necessary here -- if it does hang occasionally, that's more of an infra problem than something this function needs to handle. Keeping it simple like this is easier to read and there's less to go wrong; if timeouts become an actual issue in practice we can always add one later.",
42
+ "vague": "Make sure the fetch call can't hang forever -- add some kind of timeout so a slow API doesn't block the function indefinitely.",
43
+ "subtle_wrong": "async function fetchOrder(id: string): Promise<Order> { const timeout = new Promise<never>((_, reject) => setTimeout(() => reject(new Error('fetchOrder timed out')), 5000)); const res = await Promise.race([fetch(`/internal/orders/${id}`), timeout]); return res.json(); } This bounds the wait to 5 seconds using a simple race against a timer, so the caller never hangs indefinitely even if the orders API stalls."
44
+ }
45
+ },
46
+ {
47
+ "id": "unknown-over-any",
48
+ "prompt": "I'm implementing a Node.js handler that parses an untrusted JSON request body before passing it to business logic. What type should the parsed body have before I validate it?",
49
+ "strictness": "high",
50
+ "expected_behavior": [
51
+ {
52
+ "grader": "judge",
53
+ "rubric": "A correct answer types the not-yet-validated parsed JSON body as unknown, to be narrowed by a validator or type guard before use, and rejects typing it as any.",
54
+ "pass_criteria": [
55
+ "States concretely that the parsed body should be typed unknown (not left implicit/any) before validation -- names the type explicitly",
56
+ "Names the narrowing mechanism (a schema validator like zod/safeParse, or an explicit type guard) that must run before the value reaches business logic, not just 'validate it first' in the abstract"
57
+ ],
58
+ "fail_criteria": [
59
+ "Recommends typing the parsed body as `any` -- mentioning `any` only to warn against it, as part of a correct answer using unknown, does not count as this failure"
60
+ ]
61
+ }
62
+ ],
63
+ "calibration": {
64
+ "known_right": "Type it as unknown, not any. JSON.parse's result (or whatever your body parser hands you) has no verified shape yet -- unknown keeps the compiler forcing you to prove the shape before you use any property on it, whereas any would silently disable type checking on everything downstream, including deep into business logic where a malformed request could slip through unnoticed. Concretely: function parseBody(raw: unknown): OrderInput { const result = orderInputSchema.safeParse(raw); if (!result.success) throw new Error('invalid order body', { cause: result.error }); return result.data; } Only after safeParse (or an equivalent type guard) narrows it should the value be passed into business logic as a properly typed OrderInput. It's tempting to just type it as any to unblock the handler quickly, but that removes the compiler's help for every consumer of this value, not just this one call site.",
65
+ "known_wrong": "Simplest option: just type the parsed body as `any` at the top of the handler -- const body: any = JSON.parse(raw) -- and access whatever fields you need directly. Since request bodies vary and you're going to validate the important fields with an if check anyway before using them in business logic, `any` avoids fighting the compiler over a type that's going to get validated by hand regardless. You can always tighten it to a real interface later once the endpoint's shape stabilizes.",
66
+ "vague": "Don't just trust the parsed JSON body -- give it a safe type and make sure it's checked before it's used anywhere important.",
67
+ "subtle_wrong": "Type the incoming body as unknown at the parse boundary -- that's safer than any. Then when you need a specific field for business logic, just narrow it at the point of use with a quick cast, e.g. (body as { total: unknown }).total as number, rather than writing a full schema for the whole payload up front. It keeps the entry point honest as unknown without the overhead of a validator library."
68
+ },
69
+ "anti_patterns": ["`any`"]
70
+ }
71
+ ]
72
+ }