@mrciphersmith/keryx 0.2.164 → 0.3.1
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.
- package/README.md +4 -1
- package/dist/cli.js +82540 -50300
- package/dist/core.js +28967 -18937
- package/package.json +2 -2
- package/src/gdgraph/affected-report.ts +141 -0
- package/src/gdgraph/build.ts +170 -23
- package/src/gdgraph/service.ts +6 -0
- package/src/gdgraph/staleness.ts +253 -45
- package/src/gdskills/bundled/agents/codebase-navigator.md +55 -0
- package/src/gdskills/bundled/agents/design-advisor.md +64 -0
- package/src/gdskills/bundled/agents/docs-maintainer.md +56 -0
- package/src/gdskills/bundled/agents/end-to-end-tester.md +56 -0
- package/src/gdskills/bundled/agents/error-path-auditor.md +57 -0
- package/src/gdskills/bundled/agents/go-build-fixer.md +52 -0
- package/src/gdskills/bundled/agents/go-code-auditor.md +49 -0
- package/src/gdskills/bundled/agents/performance-auditor.md +63 -0
- package/src/gdskills/bundled/agents/python-build-fixer.md +52 -0
- package/src/gdskills/bundled/agents/python-code-auditor.md +49 -0
- package/src/gdskills/bundled/agents/refactoring-steward.md +61 -0
- package/src/gdskills/bundled/agents/security-auditor.md +62 -0
- package/src/gdskills/bundled/agents/test-first-driver.md +61 -0
- package/src/gdskills/bundled/agents/work-planner.md +62 -0
- package/src/gdskills/bundled/install-manifest.json +797 -0
- package/src/gdskills/bundled/rules/core/model-selection.mdc +51 -0
- package/src/gdskills/bundled/rules/core/skill-lifecycle.mdc +29 -1
- package/src/gdskills/bundled/rules/core/skills-storage-workflow.mdc +2 -2
- package/src/gdskills/bundled/skills/review/code-style-review/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/review/review-jev-rules/SKILL.md +267 -0
- package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.detail.md +26 -0
- package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.md +75 -247
- package/src/gdskills/bundled/skills/review/review-orchestrator/output-contract.schema.json +19 -0
- package/src/gdskills/bundled/skills/review/review-orchestrator/reviewer-finding.schema.json +10 -0
- package/src/gdskills/bundled/skills/review/review-orchestrator/reviewer-input.schema.json +5 -0
- package/src/gdskills/bundled/skills/review/review-orchestrator/templates/pr-comment-backend.md +50 -0
- package/src/gdskills/bundled/skills/review/review-orchestrator/templates/pr-comment-frontend.md +52 -0
- package/src/gdskills/bundled/skills/review/review-orchestrator/templates/review-report.md +143 -0
- package/src/gdskills/bundled/stacks/angular/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/angular/governance/eval.json +1751 -0
- package/src/gdskills/bundled/stacks/angular/governance/scout.json +32 -0
- package/src/gdskills/bundled/stacks/angular/pack.json +55 -0
- package/src/gdskills/bundled/stacks/angular/rules/coding-style.mdc +82 -0
- package/src/gdskills/bundled/stacks/angular/rules/patterns.mdc +84 -0
- package/src/gdskills/bundled/stacks/angular/rules/security.mdc +70 -0
- package/src/gdskills/bundled/stacks/angular/rules/testing.mdc +73 -0
- package/src/gdskills/bundled/stacks/angular/skills/angular-build-fix/SKILL.md +127 -0
- package/src/gdskills/bundled/stacks/angular/skills/angular-build-fix/evals.json +72 -0
- package/src/gdskills/bundled/stacks/angular/skills/angular-code-review/SKILL.md +98 -0
- package/src/gdskills/bundled/stacks/angular/skills/angular-code-review/evals.json +73 -0
- package/src/gdskills/bundled/stacks/angular/skills/angular-implementation/SKILL.md +112 -0
- package/src/gdskills/bundled/stacks/angular/skills/angular-implementation/evals.json +74 -0
- package/src/gdskills/bundled/stacks/angular/skills/angular-testing/SKILL.md +102 -0
- package/src/gdskills/bundled/stacks/angular/skills/angular-testing/evals.json +71 -0
- package/src/gdskills/bundled/stacks/go/agent-refs.json +3 -0
- package/src/gdskills/bundled/stacks/go/governance/eval.json +1745 -0
- package/src/gdskills/bundled/stacks/go/governance/scout.json +31 -0
- package/src/gdskills/bundled/stacks/go/pack.json +41 -0
- package/src/gdskills/bundled/stacks/go/rules/coding-style.mdc +85 -0
- package/src/gdskills/bundled/stacks/go/rules/patterns.mdc +65 -0
- package/src/gdskills/bundled/stacks/go/rules/security.mdc +73 -0
- package/src/gdskills/bundled/stacks/go/rules/testing.mdc +68 -0
- package/src/gdskills/bundled/stacks/go/skills/go-build-fix/SKILL.md +138 -0
- package/src/gdskills/bundled/stacks/go/skills/go-build-fix/evals.json +75 -0
- package/src/gdskills/bundled/stacks/go/skills/go-code-review/SKILL.md +121 -0
- package/src/gdskills/bundled/stacks/go/skills/go-code-review/evals.json +72 -0
- package/src/gdskills/bundled/stacks/go/skills/go-implementation/SKILL.md +122 -0
- package/src/gdskills/bundled/stacks/go/skills/go-implementation/evals.json +76 -0
- package/src/gdskills/bundled/stacks/go/skills/go-testing/SKILL.md +126 -0
- package/src/gdskills/bundled/stacks/go/skills/go-testing/evals.json +73 -0
- package/src/gdskills/bundled/stacks/mobx/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/mobx/governance/eval.json +904 -0
- package/src/gdskills/bundled/stacks/mobx/governance/scout.json +18 -0
- package/src/gdskills/bundled/stacks/mobx/pack.json +28 -0
- package/src/gdskills/bundled/stacks/mobx/rules/coding-style.mdc +91 -0
- package/src/gdskills/bundled/stacks/mobx/rules/patterns.mdc +122 -0
- package/src/gdskills/bundled/stacks/mobx/rules/security.mdc +56 -0
- package/src/gdskills/bundled/stacks/mobx/rules/testing.mdc +63 -0
- package/src/gdskills/bundled/stacks/mobx/skills/mobx-observable-testing/SKILL.md +124 -0
- package/src/gdskills/bundled/stacks/mobx/skills/mobx-observable-testing/evals.json +73 -0
- package/src/gdskills/bundled/stacks/mobx/skills/mobx-store-implementation/SKILL.md +149 -0
- package/src/gdskills/bundled/stacks/mobx/skills/mobx-store-implementation/evals.json +74 -0
- package/src/gdskills/bundled/stacks/nestjs/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/nestjs/governance/eval.json +1308 -0
- package/src/gdskills/bundled/stacks/nestjs/governance/scout.json +34 -0
- package/src/gdskills/bundled/stacks/nestjs/pack.json +53 -0
- package/src/gdskills/bundled/stacks/nestjs/rules/coding-style.mdc +70 -0
- package/src/gdskills/bundled/stacks/nestjs/rules/patterns.mdc +83 -0
- package/src/gdskills/bundled/stacks/nestjs/rules/security.mdc +73 -0
- package/src/gdskills/bundled/stacks/nestjs/rules/testing.mdc +69 -0
- package/src/gdskills/bundled/stacks/nestjs/skills/nestjs-build-fix/SKILL.md +157 -0
- package/src/gdskills/bundled/stacks/nestjs/skills/nestjs-build-fix/evals.json +70 -0
- package/src/gdskills/bundled/stacks/nestjs/skills/nestjs-implementation/SKILL.md +129 -0
- package/src/gdskills/bundled/stacks/nestjs/skills/nestjs-implementation/evals.json +71 -0
- package/src/gdskills/bundled/stacks/nestjs/skills/nestjs-testing/SKILL.md +143 -0
- package/src/gdskills/bundled/stacks/nestjs/skills/nestjs-testing/evals.json +69 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/governance/eval.json +2413 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/governance/scout.json +42 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/pack.json +42 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/rules/coding-style.mdc +69 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/rules/patterns.mdc +88 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/rules/security.mdc +72 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/rules/testing.mdc +64 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-build-fix/SKILL.md +147 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-build-fix/evals.json +75 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-code-review/SKILL.md +118 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-code-review/evals.json +76 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-implementation/SKILL.md +135 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-implementation/evals.json +78 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-testing/SKILL.md +116 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-testing/evals.json +75 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-upgrade-migration/SKILL.md +134 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-upgrade-migration/evals.json +76 -0
- package/src/gdskills/bundled/stacks/python/agent-refs.json +3 -0
- package/src/gdskills/bundled/stacks/python/governance/eval.json +1758 -0
- package/src/gdskills/bundled/stacks/python/governance/scout.json +34 -0
- package/src/gdskills/bundled/stacks/python/pack.json +41 -0
- package/src/gdskills/bundled/stacks/python/rules/coding-style.mdc +63 -0
- package/src/gdskills/bundled/stacks/python/rules/patterns.mdc +88 -0
- package/src/gdskills/bundled/stacks/python/rules/security.mdc +84 -0
- package/src/gdskills/bundled/stacks/python/rules/testing.mdc +77 -0
- package/src/gdskills/bundled/stacks/python/skills/python-build-fix/SKILL.md +144 -0
- package/src/gdskills/bundled/stacks/python/skills/python-build-fix/evals.json +74 -0
- package/src/gdskills/bundled/stacks/python/skills/python-code-review/SKILL.md +155 -0
- package/src/gdskills/bundled/stacks/python/skills/python-code-review/evals.json +72 -0
- package/src/gdskills/bundled/stacks/python/skills/python-implementation/SKILL.md +143 -0
- package/src/gdskills/bundled/stacks/python/skills/python-implementation/evals.json +78 -0
- package/src/gdskills/bundled/stacks/python/skills/python-testing/SKILL.md +132 -0
- package/src/gdskills/bundled/stacks/python/skills/python-testing/evals.json +73 -0
- package/src/gdskills/bundled/stacks/react/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/react/governance/eval.json +2188 -0
- package/src/gdskills/bundled/stacks/react/governance/scout.json +40 -0
- package/src/gdskills/bundled/stacks/react/pack.json +42 -0
- package/src/gdskills/bundled/stacks/react/rules/coding-style.mdc +58 -0
- package/src/gdskills/bundled/stacks/react/rules/patterns.mdc +79 -0
- package/src/gdskills/bundled/stacks/react/rules/security.mdc +70 -0
- package/src/gdskills/bundled/stacks/react/rules/testing.mdc +60 -0
- package/src/gdskills/bundled/stacks/react/skills/react-build-fix/SKILL.md +139 -0
- package/src/gdskills/bundled/stacks/react/skills/react-build-fix/evals.json +72 -0
- package/src/gdskills/bundled/stacks/react/skills/react-code-review/SKILL.md +148 -0
- package/src/gdskills/bundled/stacks/react/skills/react-code-review/evals.json +74 -0
- package/src/gdskills/bundled/stacks/react/skills/react-implementation/SKILL.md +140 -0
- package/src/gdskills/bundled/stacks/react/skills/react-implementation/evals.json +74 -0
- package/src/gdskills/bundled/stacks/react/skills/react-testing/SKILL.md +142 -0
- package/src/gdskills/bundled/stacks/react/skills/react-testing/evals.json +83 -0
- package/src/gdskills/bundled/stacks/react/skills/react-upgrade-migration/SKILL.md +155 -0
- package/src/gdskills/bundled/stacks/react/skills/react-upgrade-migration/evals.json +74 -0
- package/src/gdskills/bundled/stacks/ts-js-node/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/ts-js-node/governance/eval.json +2155 -0
- package/src/gdskills/bundled/stacks/ts-js-node/governance/scout.json +40 -0
- package/src/gdskills/bundled/stacks/ts-js-node/pack.json +41 -0
- package/src/gdskills/bundled/stacks/ts-js-node/rules/coding-style.mdc +73 -0
- package/src/gdskills/bundled/stacks/ts-js-node/rules/patterns.mdc +61 -0
- package/src/gdskills/bundled/stacks/ts-js-node/rules/security.mdc +71 -0
- package/src/gdskills/bundled/stacks/ts-js-node/rules/testing.mdc +63 -0
- package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-build-fix/SKILL.md +137 -0
- package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-build-fix/evals.json +73 -0
- package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-code-review/SKILL.md +124 -0
- package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-code-review/evals.json +74 -0
- package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-esm-migration/SKILL.md +152 -0
- package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-esm-migration/evals.json +71 -0
- package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-implementation/SKILL.md +127 -0
- package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-implementation/evals.json +72 -0
- package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-testing/SKILL.md +134 -0
- package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-testing/evals.json +70 -0
- package/src/gdskills/bundled/stacks/vue/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/vue/governance/eval.json +2215 -0
- package/src/gdskills/bundled/stacks/vue/governance/scout.json +42 -0
- package/src/gdskills/bundled/stacks/vue/pack.json +42 -0
- package/src/gdskills/bundled/stacks/vue/rules/coding-style.mdc +73 -0
- package/src/gdskills/bundled/stacks/vue/rules/patterns.mdc +84 -0
- package/src/gdskills/bundled/stacks/vue/rules/security.mdc +60 -0
- package/src/gdskills/bundled/stacks/vue/rules/testing.mdc +69 -0
- package/src/gdskills/bundled/stacks/vue/skills/vue-build-fix/SKILL.md +137 -0
- package/src/gdskills/bundled/stacks/vue/skills/vue-build-fix/evals.json +72 -0
- package/src/gdskills/bundled/stacks/vue/skills/vue-code-review/SKILL.md +120 -0
- package/src/gdskills/bundled/stacks/vue/skills/vue-code-review/evals.json +71 -0
- package/src/gdskills/bundled/stacks/vue/skills/vue-implementation/SKILL.md +122 -0
- package/src/gdskills/bundled/stacks/vue/skills/vue-implementation/evals.json +72 -0
- package/src/gdskills/bundled/stacks/vue/skills/vue-testing/SKILL.md +115 -0
- package/src/gdskills/bundled/stacks/vue/skills/vue-testing/evals.json +72 -0
- package/src/gdskills/bundled/stacks/vue/skills/vue2-to-vue3-migration/SKILL.md +135 -0
- package/src/gdskills/bundled/stacks/vue/skills/vue2-to-vue3-migration/evals.json +71 -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
|
+
}
|