@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.
Files changed (182) hide show
  1. package/README.md +4 -1
  2. package/dist/cli.js +82540 -50300
  3. package/dist/core.js +28967 -18937
  4. package/package.json +2 -2
  5. package/src/gdgraph/affected-report.ts +141 -0
  6. package/src/gdgraph/build.ts +170 -23
  7. package/src/gdgraph/service.ts +6 -0
  8. package/src/gdgraph/staleness.ts +253 -45
  9. package/src/gdskills/bundled/agents/codebase-navigator.md +55 -0
  10. package/src/gdskills/bundled/agents/design-advisor.md +64 -0
  11. package/src/gdskills/bundled/agents/docs-maintainer.md +56 -0
  12. package/src/gdskills/bundled/agents/end-to-end-tester.md +56 -0
  13. package/src/gdskills/bundled/agents/error-path-auditor.md +57 -0
  14. package/src/gdskills/bundled/agents/go-build-fixer.md +52 -0
  15. package/src/gdskills/bundled/agents/go-code-auditor.md +49 -0
  16. package/src/gdskills/bundled/agents/performance-auditor.md +63 -0
  17. package/src/gdskills/bundled/agents/python-build-fixer.md +52 -0
  18. package/src/gdskills/bundled/agents/python-code-auditor.md +49 -0
  19. package/src/gdskills/bundled/agents/refactoring-steward.md +61 -0
  20. package/src/gdskills/bundled/agents/security-auditor.md +62 -0
  21. package/src/gdskills/bundled/agents/test-first-driver.md +61 -0
  22. package/src/gdskills/bundled/agents/work-planner.md +62 -0
  23. package/src/gdskills/bundled/install-manifest.json +797 -0
  24. package/src/gdskills/bundled/rules/core/model-selection.mdc +51 -0
  25. package/src/gdskills/bundled/rules/core/skill-lifecycle.mdc +29 -1
  26. package/src/gdskills/bundled/rules/core/skills-storage-workflow.mdc +2 -2
  27. package/src/gdskills/bundled/skills/review/code-style-review/SKILL.md +1 -1
  28. package/src/gdskills/bundled/skills/review/review-jev-rules/SKILL.md +267 -0
  29. package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.detail.md +26 -0
  30. package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.md +75 -247
  31. package/src/gdskills/bundled/skills/review/review-orchestrator/output-contract.schema.json +19 -0
  32. package/src/gdskills/bundled/skills/review/review-orchestrator/reviewer-finding.schema.json +10 -0
  33. package/src/gdskills/bundled/skills/review/review-orchestrator/reviewer-input.schema.json +5 -0
  34. package/src/gdskills/bundled/skills/review/review-orchestrator/templates/pr-comment-backend.md +50 -0
  35. package/src/gdskills/bundled/skills/review/review-orchestrator/templates/pr-comment-frontend.md +52 -0
  36. package/src/gdskills/bundled/skills/review/review-orchestrator/templates/review-report.md +143 -0
  37. package/src/gdskills/bundled/stacks/angular/agent-refs.json +4 -0
  38. package/src/gdskills/bundled/stacks/angular/governance/eval.json +1751 -0
  39. package/src/gdskills/bundled/stacks/angular/governance/scout.json +32 -0
  40. package/src/gdskills/bundled/stacks/angular/pack.json +55 -0
  41. package/src/gdskills/bundled/stacks/angular/rules/coding-style.mdc +82 -0
  42. package/src/gdskills/bundled/stacks/angular/rules/patterns.mdc +84 -0
  43. package/src/gdskills/bundled/stacks/angular/rules/security.mdc +70 -0
  44. package/src/gdskills/bundled/stacks/angular/rules/testing.mdc +73 -0
  45. package/src/gdskills/bundled/stacks/angular/skills/angular-build-fix/SKILL.md +127 -0
  46. package/src/gdskills/bundled/stacks/angular/skills/angular-build-fix/evals.json +72 -0
  47. package/src/gdskills/bundled/stacks/angular/skills/angular-code-review/SKILL.md +98 -0
  48. package/src/gdskills/bundled/stacks/angular/skills/angular-code-review/evals.json +73 -0
  49. package/src/gdskills/bundled/stacks/angular/skills/angular-implementation/SKILL.md +112 -0
  50. package/src/gdskills/bundled/stacks/angular/skills/angular-implementation/evals.json +74 -0
  51. package/src/gdskills/bundled/stacks/angular/skills/angular-testing/SKILL.md +102 -0
  52. package/src/gdskills/bundled/stacks/angular/skills/angular-testing/evals.json +71 -0
  53. package/src/gdskills/bundled/stacks/go/agent-refs.json +3 -0
  54. package/src/gdskills/bundled/stacks/go/governance/eval.json +1745 -0
  55. package/src/gdskills/bundled/stacks/go/governance/scout.json +31 -0
  56. package/src/gdskills/bundled/stacks/go/pack.json +41 -0
  57. package/src/gdskills/bundled/stacks/go/rules/coding-style.mdc +85 -0
  58. package/src/gdskills/bundled/stacks/go/rules/patterns.mdc +65 -0
  59. package/src/gdskills/bundled/stacks/go/rules/security.mdc +73 -0
  60. package/src/gdskills/bundled/stacks/go/rules/testing.mdc +68 -0
  61. package/src/gdskills/bundled/stacks/go/skills/go-build-fix/SKILL.md +138 -0
  62. package/src/gdskills/bundled/stacks/go/skills/go-build-fix/evals.json +75 -0
  63. package/src/gdskills/bundled/stacks/go/skills/go-code-review/SKILL.md +121 -0
  64. package/src/gdskills/bundled/stacks/go/skills/go-code-review/evals.json +72 -0
  65. package/src/gdskills/bundled/stacks/go/skills/go-implementation/SKILL.md +122 -0
  66. package/src/gdskills/bundled/stacks/go/skills/go-implementation/evals.json +76 -0
  67. package/src/gdskills/bundled/stacks/go/skills/go-testing/SKILL.md +126 -0
  68. package/src/gdskills/bundled/stacks/go/skills/go-testing/evals.json +73 -0
  69. package/src/gdskills/bundled/stacks/mobx/agent-refs.json +4 -0
  70. package/src/gdskills/bundled/stacks/mobx/governance/eval.json +904 -0
  71. package/src/gdskills/bundled/stacks/mobx/governance/scout.json +18 -0
  72. package/src/gdskills/bundled/stacks/mobx/pack.json +28 -0
  73. package/src/gdskills/bundled/stacks/mobx/rules/coding-style.mdc +91 -0
  74. package/src/gdskills/bundled/stacks/mobx/rules/patterns.mdc +122 -0
  75. package/src/gdskills/bundled/stacks/mobx/rules/security.mdc +56 -0
  76. package/src/gdskills/bundled/stacks/mobx/rules/testing.mdc +63 -0
  77. package/src/gdskills/bundled/stacks/mobx/skills/mobx-observable-testing/SKILL.md +124 -0
  78. package/src/gdskills/bundled/stacks/mobx/skills/mobx-observable-testing/evals.json +73 -0
  79. package/src/gdskills/bundled/stacks/mobx/skills/mobx-store-implementation/SKILL.md +149 -0
  80. package/src/gdskills/bundled/stacks/mobx/skills/mobx-store-implementation/evals.json +74 -0
  81. package/src/gdskills/bundled/stacks/nestjs/agent-refs.json +4 -0
  82. package/src/gdskills/bundled/stacks/nestjs/governance/eval.json +1308 -0
  83. package/src/gdskills/bundled/stacks/nestjs/governance/scout.json +34 -0
  84. package/src/gdskills/bundled/stacks/nestjs/pack.json +53 -0
  85. package/src/gdskills/bundled/stacks/nestjs/rules/coding-style.mdc +70 -0
  86. package/src/gdskills/bundled/stacks/nestjs/rules/patterns.mdc +83 -0
  87. package/src/gdskills/bundled/stacks/nestjs/rules/security.mdc +73 -0
  88. package/src/gdskills/bundled/stacks/nestjs/rules/testing.mdc +69 -0
  89. package/src/gdskills/bundled/stacks/nestjs/skills/nestjs-build-fix/SKILL.md +157 -0
  90. package/src/gdskills/bundled/stacks/nestjs/skills/nestjs-build-fix/evals.json +70 -0
  91. package/src/gdskills/bundled/stacks/nestjs/skills/nestjs-implementation/SKILL.md +129 -0
  92. package/src/gdskills/bundled/stacks/nestjs/skills/nestjs-implementation/evals.json +71 -0
  93. package/src/gdskills/bundled/stacks/nestjs/skills/nestjs-testing/SKILL.md +143 -0
  94. package/src/gdskills/bundled/stacks/nestjs/skills/nestjs-testing/evals.json +69 -0
  95. package/src/gdskills/bundled/stacks/nextjs-nuxt/agent-refs.json +4 -0
  96. package/src/gdskills/bundled/stacks/nextjs-nuxt/governance/eval.json +2413 -0
  97. package/src/gdskills/bundled/stacks/nextjs-nuxt/governance/scout.json +42 -0
  98. package/src/gdskills/bundled/stacks/nextjs-nuxt/pack.json +42 -0
  99. package/src/gdskills/bundled/stacks/nextjs-nuxt/rules/coding-style.mdc +69 -0
  100. package/src/gdskills/bundled/stacks/nextjs-nuxt/rules/patterns.mdc +88 -0
  101. package/src/gdskills/bundled/stacks/nextjs-nuxt/rules/security.mdc +72 -0
  102. package/src/gdskills/bundled/stacks/nextjs-nuxt/rules/testing.mdc +64 -0
  103. package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-build-fix/SKILL.md +147 -0
  104. package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-build-fix/evals.json +75 -0
  105. package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-code-review/SKILL.md +118 -0
  106. package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-code-review/evals.json +76 -0
  107. package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-implementation/SKILL.md +135 -0
  108. package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-implementation/evals.json +78 -0
  109. package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-testing/SKILL.md +116 -0
  110. package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-testing/evals.json +75 -0
  111. package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-upgrade-migration/SKILL.md +134 -0
  112. package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-upgrade-migration/evals.json +76 -0
  113. package/src/gdskills/bundled/stacks/python/agent-refs.json +3 -0
  114. package/src/gdskills/bundled/stacks/python/governance/eval.json +1758 -0
  115. package/src/gdskills/bundled/stacks/python/governance/scout.json +34 -0
  116. package/src/gdskills/bundled/stacks/python/pack.json +41 -0
  117. package/src/gdskills/bundled/stacks/python/rules/coding-style.mdc +63 -0
  118. package/src/gdskills/bundled/stacks/python/rules/patterns.mdc +88 -0
  119. package/src/gdskills/bundled/stacks/python/rules/security.mdc +84 -0
  120. package/src/gdskills/bundled/stacks/python/rules/testing.mdc +77 -0
  121. package/src/gdskills/bundled/stacks/python/skills/python-build-fix/SKILL.md +144 -0
  122. package/src/gdskills/bundled/stacks/python/skills/python-build-fix/evals.json +74 -0
  123. package/src/gdskills/bundled/stacks/python/skills/python-code-review/SKILL.md +155 -0
  124. package/src/gdskills/bundled/stacks/python/skills/python-code-review/evals.json +72 -0
  125. package/src/gdskills/bundled/stacks/python/skills/python-implementation/SKILL.md +143 -0
  126. package/src/gdskills/bundled/stacks/python/skills/python-implementation/evals.json +78 -0
  127. package/src/gdskills/bundled/stacks/python/skills/python-testing/SKILL.md +132 -0
  128. package/src/gdskills/bundled/stacks/python/skills/python-testing/evals.json +73 -0
  129. package/src/gdskills/bundled/stacks/react/agent-refs.json +4 -0
  130. package/src/gdskills/bundled/stacks/react/governance/eval.json +2188 -0
  131. package/src/gdskills/bundled/stacks/react/governance/scout.json +40 -0
  132. package/src/gdskills/bundled/stacks/react/pack.json +42 -0
  133. package/src/gdskills/bundled/stacks/react/rules/coding-style.mdc +58 -0
  134. package/src/gdskills/bundled/stacks/react/rules/patterns.mdc +79 -0
  135. package/src/gdskills/bundled/stacks/react/rules/security.mdc +70 -0
  136. package/src/gdskills/bundled/stacks/react/rules/testing.mdc +60 -0
  137. package/src/gdskills/bundled/stacks/react/skills/react-build-fix/SKILL.md +139 -0
  138. package/src/gdskills/bundled/stacks/react/skills/react-build-fix/evals.json +72 -0
  139. package/src/gdskills/bundled/stacks/react/skills/react-code-review/SKILL.md +148 -0
  140. package/src/gdskills/bundled/stacks/react/skills/react-code-review/evals.json +74 -0
  141. package/src/gdskills/bundled/stacks/react/skills/react-implementation/SKILL.md +140 -0
  142. package/src/gdskills/bundled/stacks/react/skills/react-implementation/evals.json +74 -0
  143. package/src/gdskills/bundled/stacks/react/skills/react-testing/SKILL.md +142 -0
  144. package/src/gdskills/bundled/stacks/react/skills/react-testing/evals.json +83 -0
  145. package/src/gdskills/bundled/stacks/react/skills/react-upgrade-migration/SKILL.md +155 -0
  146. package/src/gdskills/bundled/stacks/react/skills/react-upgrade-migration/evals.json +74 -0
  147. package/src/gdskills/bundled/stacks/ts-js-node/agent-refs.json +4 -0
  148. package/src/gdskills/bundled/stacks/ts-js-node/governance/eval.json +2155 -0
  149. package/src/gdskills/bundled/stacks/ts-js-node/governance/scout.json +40 -0
  150. package/src/gdskills/bundled/stacks/ts-js-node/pack.json +41 -0
  151. package/src/gdskills/bundled/stacks/ts-js-node/rules/coding-style.mdc +73 -0
  152. package/src/gdskills/bundled/stacks/ts-js-node/rules/patterns.mdc +61 -0
  153. package/src/gdskills/bundled/stacks/ts-js-node/rules/security.mdc +71 -0
  154. package/src/gdskills/bundled/stacks/ts-js-node/rules/testing.mdc +63 -0
  155. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-build-fix/SKILL.md +137 -0
  156. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-build-fix/evals.json +73 -0
  157. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-code-review/SKILL.md +124 -0
  158. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-code-review/evals.json +74 -0
  159. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-esm-migration/SKILL.md +152 -0
  160. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-esm-migration/evals.json +71 -0
  161. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-implementation/SKILL.md +127 -0
  162. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-implementation/evals.json +72 -0
  163. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-testing/SKILL.md +134 -0
  164. package/src/gdskills/bundled/stacks/ts-js-node/skills/nodejs-testing/evals.json +70 -0
  165. package/src/gdskills/bundled/stacks/vue/agent-refs.json +4 -0
  166. package/src/gdskills/bundled/stacks/vue/governance/eval.json +2215 -0
  167. package/src/gdskills/bundled/stacks/vue/governance/scout.json +42 -0
  168. package/src/gdskills/bundled/stacks/vue/pack.json +42 -0
  169. package/src/gdskills/bundled/stacks/vue/rules/coding-style.mdc +73 -0
  170. package/src/gdskills/bundled/stacks/vue/rules/patterns.mdc +84 -0
  171. package/src/gdskills/bundled/stacks/vue/rules/security.mdc +60 -0
  172. package/src/gdskills/bundled/stacks/vue/rules/testing.mdc +69 -0
  173. package/src/gdskills/bundled/stacks/vue/skills/vue-build-fix/SKILL.md +137 -0
  174. package/src/gdskills/bundled/stacks/vue/skills/vue-build-fix/evals.json +72 -0
  175. package/src/gdskills/bundled/stacks/vue/skills/vue-code-review/SKILL.md +120 -0
  176. package/src/gdskills/bundled/stacks/vue/skills/vue-code-review/evals.json +71 -0
  177. package/src/gdskills/bundled/stacks/vue/skills/vue-implementation/SKILL.md +122 -0
  178. package/src/gdskills/bundled/stacks/vue/skills/vue-implementation/evals.json +72 -0
  179. package/src/gdskills/bundled/stacks/vue/skills/vue-testing/SKILL.md +115 -0
  180. package/src/gdskills/bundled/stacks/vue/skills/vue-testing/evals.json +72 -0
  181. package/src/gdskills/bundled/stacks/vue/skills/vue2-to-vue3-migration/SKILL.md +135 -0
  182. package/src/gdskills/bundled/stacks/vue/skills/vue2-to-vue3-migration/evals.json +71 -0
@@ -0,0 +1,2155 @@
1
+ {
2
+ "schemaVersion": "1.0.0",
3
+ "reports": [
4
+ {
5
+ "schemaVersion": "1.0.0",
6
+ "skillId": "ts-js-node/nodejs-build-fix",
7
+ "strictness": "high",
8
+ "trials": 10,
9
+ "triggerAccuracy": {
10
+ "truePositive": 6,
11
+ "falsePositive": 0,
12
+ "positives": 6,
13
+ "negatives": 6
14
+ },
15
+ "evidence": "authored",
16
+ "scenarios": [
17
+ {
18
+ "id": "trigger-positive-1",
19
+ "kind": "trigger-positive",
20
+ "prompt": "Fix this tsc error: Type 'string | undefined' is not assignable to type 'string'",
21
+ "strictness": "high",
22
+ "trials": 1,
23
+ "passes": 1,
24
+ "passRate": 1,
25
+ "passAtK": 1,
26
+ "grader": "trigger-rank-fork-family",
27
+ "status": "ran",
28
+ "deterministic": true
29
+ },
30
+ {
31
+ "id": "trigger-positive-2",
32
+ "kind": "trigger-positive",
33
+ "prompt": "Resolve this Node.js 'Cannot find module' error after adding a new import",
34
+ "strictness": "high",
35
+ "trials": 1,
36
+ "passes": 1,
37
+ "passRate": 1,
38
+ "passAtK": 1,
39
+ "grader": "trigger-rank-fork-family",
40
+ "status": "ran",
41
+ "deterministic": true
42
+ },
43
+ {
44
+ "id": "trigger-positive-3",
45
+ "kind": "trigger-positive",
46
+ "prompt": "Fix this require() of ES Module error in our Node service",
47
+ "strictness": "high",
48
+ "trials": 1,
49
+ "passes": 1,
50
+ "passRate": 1,
51
+ "passAtK": 1,
52
+ "grader": "trigger-rank-fork-family",
53
+ "status": "ran",
54
+ "deterministic": true
55
+ },
56
+ {
57
+ "id": "trigger-positive-4",
58
+ "kind": "trigger-positive",
59
+ "prompt": "The build fails with a moduleResolution nodenext error on this import",
60
+ "strictness": "high",
61
+ "trials": 1,
62
+ "passes": 1,
63
+ "passRate": 1,
64
+ "passAtK": 1,
65
+ "grader": "trigger-rank-fork-family",
66
+ "status": "ran",
67
+ "deterministic": true
68
+ },
69
+ {
70
+ "id": "trigger-positive-5",
71
+ "kind": "trigger-positive",
72
+ "prompt": "Fix this eslint no-floating-promises failure blocking CI",
73
+ "strictness": "high",
74
+ "trials": 1,
75
+ "passes": 1,
76
+ "passRate": 1,
77
+ "passAtK": 1,
78
+ "grader": "trigger-rank-fork-family",
79
+ "status": "ran",
80
+ "deterministic": true
81
+ },
82
+ {
83
+ "id": "trigger-positive-6",
84
+ "kind": "trigger-positive",
85
+ "prompt": "package.json exports map is breaking the build with ERR_PACKAGE_PATH_NOT_EXPORTED",
86
+ "strictness": "high",
87
+ "trials": 1,
88
+ "passes": 1,
89
+ "passRate": 1,
90
+ "passAtK": 1,
91
+ "grader": "trigger-rank-fork-family",
92
+ "status": "ran",
93
+ "deterministic": true
94
+ },
95
+ {
96
+ "id": "trigger-negative-1",
97
+ "kind": "trigger-negative",
98
+ "prompt": "go vet ./... is failing on this package, help resolve the compile error",
99
+ "strictness": "high",
100
+ "trials": 1,
101
+ "passes": 1,
102
+ "passRate": 1,
103
+ "passAtK": 1,
104
+ "grader": "trigger-rank-fork-family",
105
+ "status": "ran",
106
+ "deterministic": true
107
+ },
108
+ {
109
+ "id": "trigger-negative-2",
110
+ "kind": "trigger-negative",
111
+ "prompt": "Write a CLI command in this Node.js project that lists pending jobs from the queue",
112
+ "strictness": "high",
113
+ "trials": 1,
114
+ "passes": 1,
115
+ "passRate": 1,
116
+ "passAtK": 1,
117
+ "grader": "trigger-rank-fork-family",
118
+ "status": "ran",
119
+ "deterministic": true
120
+ },
121
+ {
122
+ "id": "trigger-negative-3",
123
+ "kind": "trigger-negative",
124
+ "prompt": "Upgrade this codebase from React 18 to React 19 and fix the deprecated lifecycle warnings",
125
+ "strictness": "high",
126
+ "trials": 1,
127
+ "passes": 1,
128
+ "passRate": 1,
129
+ "passAtK": 1,
130
+ "grader": "trigger-rank-fork-family",
131
+ "status": "ran",
132
+ "deterministic": true
133
+ },
134
+ {
135
+ "id": "trigger-negative-4",
136
+ "kind": "trigger-negative",
137
+ "prompt": "Review this diff for floating promises and unhandled rejections",
138
+ "strictness": "high",
139
+ "trials": 1,
140
+ "passes": 1,
141
+ "passRate": 1,
142
+ "passAtK": 1,
143
+ "grader": "trigger-rank-fork-family",
144
+ "status": "ran",
145
+ "deterministic": true
146
+ },
147
+ {
148
+ "id": "trigger-negative-5",
149
+ "kind": "trigger-negative",
150
+ "prompt": "Write vitest tests to cover this new order calculation function",
151
+ "strictness": "high",
152
+ "trials": 1,
153
+ "passes": 1,
154
+ "passRate": 1,
155
+ "passAtK": 1,
156
+ "grader": "trigger-rank-fork-family",
157
+ "status": "ran",
158
+ "deterministic": true
159
+ },
160
+ {
161
+ "id": "trigger-negative-6",
162
+ "kind": "trigger-negative",
163
+ "prompt": "Fix this mypy type error in the Python data pipeline",
164
+ "strictness": "high",
165
+ "trials": 1,
166
+ "passes": 1,
167
+ "passRate": 1,
168
+ "passAtK": 1,
169
+ "grader": "trigger-rank-fork-family",
170
+ "status": "ran",
171
+ "deterministic": true
172
+ },
173
+ {
174
+ "id": "no-ts-ignore-suppression",
175
+ "kind": "behavior",
176
+ "prompt": "tsc reports: Property 'total' does not exist on type 'OrderDraft'. What's the right way to fix this?",
177
+ "strictness": "high",
178
+ "trials": 10,
179
+ "passes": 6,
180
+ "passRate": 0.6,
181
+ "passAtK": 1,
182
+ "grader": "judge",
183
+ "status": "ran",
184
+ "trialRecords": [
185
+ {
186
+ "output": "Before answering which fix is right, one thing decides it: **is `total` a field that should exist on `OrderDraft` at all?** That's the whole fork. Read the type declaration and the code that produces the value being accessed \u2014 don't fix from the error text alone.\n\n## Case A \u2014 `total` legitimately belongs on `OrderDraft`\n\nThe object really does carry a total at runtime (or should), and the type declaration is just out of date. Then:\n\n- Add `total: number;` (or whatever the real type is \u2014 don't guess `number` if it could be `number | null`) to the `OrderDraft` interface/type in the file where it's declared.\n- If `OrderDraft` is meant to be structurally derived (e.g. `Pick<Order, ...>` or `Omit<Order, 'total'>`), fix the derivation so it no longer excludes `total`, rather than bolting the field on separately.\n\n## Case B \u2014 `total` does *not* belong on `OrderDraft`\n\nThe checker is telling the truth: you're reaching for a property that this type doesn't promise. Then the fix is at the call site, and which one depends on where the total actually lives:\n\n- **It's on a different type.** You're holding an `OrderDraft` but need an `Order` (or a `DraftWithTotal`). Narrow/convert at the boundary \u2014 a mapping function `toOrder(draft)` \u2014 and call `.total` on the converted value. Don't widen `OrderDraft` to carry a field every other consumer shouldn't see.\n- **It's computed, not stored.** Order totals are usually derived (sum of line items, subtotal + tax). If so, the correct fix is a function \u2014 `computeTotal(draft)` \u2014 not a new stored field that can drift out of sync with the lines it's supposed to summarize.\n- **It's an optional field.** If some drafts have a total and some don't, the fix is to model that honestly: `total?: number` and then handle the `undefined` at the use site (with a proper check, or a defaulting rule that itself is a real decision \u2014 not a silent `?? 0` that hides a missing total).\n\n## What is *not* the fix\n\n- `(draft as any).total` or `(draft as OrderDraft & { total: number })` \u2014 this doesn't add the field, it just stops the compiler from asking whether it's there. If the value is genuinely missing at runtime you've traded a build error for an `undefined` that surfaces three layers away.\n- `// @ts-ignore` above the line \u2014 same thing, noisier.\n- Adding `total: number` to `OrderDraft` *just* to make this one error quiet, when no producer of a draft actually sets it. Now you have a lying type and every other draft consumer inherits the lie.\n- Loosening `strict`/`noImplicitAny` in `tsconfig.json` \u2014 a one-property error is never a project-wide config problem.\n\n## How to decide in practice\n\nTrace the value backward from the error site to the line that constructs the object (or the function that returns it). Ask: does that construction site set a total? \n\n- **Yes, and the type doesn't list it** \u2192 Case A, add it to the type.\n- **No, and it shouldn't** \u2192 Case B, the access is wrong; fix where you get the value, or introduce the compute/map function that legitimately produces it.\n- **Sometimes** \u2192 optional field plus an explicit nil-handling decision at the use site.\n\nThen re-run `npx tsc --noEmit` and confirm exit 0. Report which case it was and why \u2014 \"added `total` to `OrderDraft` because `DraftBuilder` sets it at line 40\" or \"replaced `.total` with `computeTotal(draft)` because the total is derived from `draft.lines` and is never stored.\" Either is a real fix; a suppression is not, and it fails the standard even though the build goes green.",
187
+ "outputSha256": "5a02c4c81f1b0ecd087e2296bb2953acba92893dcb4a3865b690eea619f352db",
188
+ "promptSha256": "753e8abd1b8c042280011f2de3129ded2a764229da2c8c1b02fac694d5da4605",
189
+ "deterministic": [],
190
+ "judge": {
191
+ "verdict": "pass",
192
+ "reason": "Case A names the exact fix: 'Add `total: number;` ... to the `OrderDraft` interface/type', and Case B names reading a different property/converted value (`.total` on `toOrder(draft)`, `computeTotal(draft)`, `total?: number`). It shows concrete corrected declarations/access code. It only warns against @ts-ignore and `as any`, not recommending them."
193
+ },
194
+ "passed": true
195
+ },
196
+ {
197
+ "output": "Without seeing the actual `OrderDraft` type and the code around the error site, I can't tell you the exact edit \u2014 but I can tell you the right process and the fixes that are actually correct versus the ones that just make `tsc` stop complaining.\n\n**First, find out why there's a mismatch.** The error means the code accesses `.total` on a value typed `OrderDraft`, and `OrderDraft` has no `total` member. There are only a few real explanations:\n\n1. **`OrderDraft` genuinely should have `total`** \u2014 you're modeling an order that always has one. Fix: add it to the interface/type declaration (`total: number`, and whatever unit/shape is correct). This is the fix if `total` belongs to every draft.\n\n2. **`total` is computed, not stored** \u2014 `OrderDraft` is intentionally the pre-pricing shape and `total` only exists on the finalized order type (say `Order`). Fix: the access site is wrong, not the type. Either map the draft to the order type first, or compute the total at the point of use from the draft's line items, e.g. `draft.items.reduce((sum, i) => sum + i.price * i.qty, 0)`.\n\n3. **`total` is optional/partial** \u2014 some drafts have it, some don't. Fix: model that honestly on the type, e.g. `total?: number`, and then *handle* the absent case at the call site (`if (draft.total !== undefined) \u2026`, or a default). Marking it optional and then non-null-asserting defeats the purpose.\n\n4. **Wrong type at the call site** \u2014 the value is actually an `Order` (or something with `total`) but is annotated/returned as `OrderDraft`, or you narrowed a union too early. Fix: correct the annotation or the function's return type so the real type flows through, then `.total` is legitimately available.\n\n5. **Type guard / discriminated union not narrowed** \u2014 you're on a union where only one branch has `total`, and the narrowing didn't happen at this line. Fix: narrow before accessing (check the discriminant), or move the access inside the branch.\n\nTo pick among these, read the declaration of `OrderDraft` and trace where this value came from \u2014 the error is telling you about a real mismatch between what the type says and what the code assumes. That's almost always the checker being right, not a false positive.\n\n**The fixes that are wrong** (do not do these):\n\n- `@ts-ignore` / `@ts-expect-error` above the line.\n- Casting: `(draft as any).total` or `(draft as { total: number }).total`. This silences the error and ships a runtime `undefined` the first time a draft actually lacks `total`.\n- Adding `total: any` to `OrderDraft` to appease it.\n- Loosening `strict`/`noImplicitAny` or adding `skipLibCheck` in `tsconfig.json` \u2014 unrelated to this error and hides a whole class of others.\n\n**What a correct fix looks like as a report:**\n\n```\nFixed: src/lib/pricing.ts:42\n Root cause: pricing code read `draft.total`, but `OrderDraft` is the\n pre-pricing shape and intentionally has no `total`; the value passed in\n was a draft, not a finalized `Order`.\n Fix: compute the total from `draft.items` at the call site (draft is\n never priced yet), no type change needed.\n Verified: tsc --noEmit exits 0.\n```\n\nIf instead `total` really does belong on every draft, the report would read \"added `total: number` to `OrderDraft` and populated it where the draft is constructed.\" Either way: the fix is a change to the *type or the code to match reality*, never a change that matches the checker to the code. Re-run `npx tsc --noEmit` and confirm exit 0 with no suppression, `any`, or config loosening added \u2014 and confirm `git status` shows only the file the root cause required.",
198
+ "outputSha256": "7e1062a78f2358ef725252f49163a8f50a5d19acc1031a88849972735d6364bf",
199
+ "promptSha256": "753e8abd1b8c042280011f2de3129ded2a764229da2c8c1b02fac694d5da4605",
200
+ "deterministic": [],
201
+ "judge": {
202
+ "verdict": "pass",
203
+ "reason": "Names exact fixes: adds `total: number` to OrderDraft (option 1), or reads/computes total instead (option 2 shows `draft.items.reduce(...)`). Shows concrete corrected code/declarations (`total: number`, `total?: number`, reduce expression). Explicitly warns against @ts-ignore and `(draft as any).total` as wrong, not recommending them."
204
+ },
205
+ "passed": true
206
+ },
207
+ {
208
+ "output": "Without seeing the code I can't give you the exact line, but the right approach is decided by one question: **should `OrderDraft` actually have `total`?**\n\n**Case A \u2014 the type is right, the code is wrong.** `OrderDraft` genuinely has no `total` (perhaps it only holds line items, and `total` is computed later). The fix is to stop reading `.total` off a draft. Compute it where it is owned, e.g.:\n\n```ts\nconst total = draft.items.reduce((sum, i) => sum + i.price * i.qty, 0);\n```\n\nor take the fully-priced type as a parameter (`Order`, not `OrderDraft`) so the compiler enforces that a total exists.\n\n**Case B \u2014 the type is incomplete.** `total` really is part of a draft in your domain model (it's populated at creation). Then add it to the interface and make sure every place that constructs an `OrderDraft` sets it:\n\n```ts\ninterface OrderDraft {\n items: OrderItem[];\n total: number; // now required\n}\n```\n\nIf it's genuinely optional at some stages, model that honestly with `total?: number` and narrow before use \u2014 but don't reach for optionality just to silence the error.\n\n**How to decide:** trace `OrderDraft` back to its declaration and to where the draft value at the error site is constructed. Either the constructor is missing a field it should set (Case B), or the call site is treating a draft as if it were already priced (Case A). The error is telling you one of those two things is true.\n\n**What not to do:**\n- Don't add `total: any` or cast (`draft as any).total`) \u2014 that just moves the bug downstream.\n- Don't `@ts-ignore` above the line.\n- Don't make `total` optional *and* then use `!` at the read site to force it through \u2014 that's the same suppression with extra steps.\n\nIf you paste the `OrderDraft` declaration and the line the error points at, I can tell you which case you're in.",
209
+ "outputSha256": "818da28498133e2ab21ee788b6672c256bca16aa6d6178fd6f9bf041dbed6db0",
210
+ "promptSha256": "753e8abd1b8c042280011f2de3129ded2a764229da2c8c1b02fac694d5da4605",
211
+ "deterministic": [],
212
+ "judge": {
213
+ "verdict": "pass",
214
+ "reason": "Case B shows the corrected declaration `interface OrderDraft { items: OrderItem[]; total: number; }` (concrete typed field). Case A names the exact alternative: compute total from items or take `Order` instead of `OrderDraft`. It warns against @ts-ignore/any rather than recommending them, so no fail criterion holds."
215
+ },
216
+ "passed": true
217
+ },
218
+ {
219
+ "output": "The right fix depends on what `total` actually is, but the shape of the answer is the same: don't make the error go away \u2014 make the code consistent with the truth about `OrderDraft`.\n\n**First, determine which of these is true:**\n\n1. **`total` should be a real field on `OrderDraft`.** The type is missing a property that the code legitimately needs. Fix: add it to the type (and to every constructor/factory that builds an `OrderDraft`), ideally derived/capped at write time rather than a stale duplicate.\n\n2. **`total` is computed, not stored.** The clean fix is a function or getter, not a property:\n ```ts\n function draftTotal(d: OrderDraft): number {\n return d.lineItems.reduce((sum, i) => sum + i.qty * i.unitPrice, 0);\n }\n ```\n Then replace `draft.total` with `draftTotal(draft)`. This is usually correct when the total can always be recomputed from the line items \u2014 storing it invites drift.\n\n3. **The value lives somewhere else.** e.g. the total is only known after pricing/checkout. Then the error is pointing at a genuine design gap: the read site is asking a draft for something a draft doesn't have. Fix by threading the real value from where it exists (a priced order, a quote), or by narrowing the type of the variable at this site so it's the priced type, not `OrderDraft`.\n\n**What not to do:** don't add `@ts-ignore`, don't cast to `any`, don't loosen `strict` in `tsconfig.json`, and don't rename an unrelated field to `total` just to satisfy the checker.\n\n**How to tell which case you're in:** trace back to where the variable is created and what the surrounding code expects to do with `total`. If every read site computes it from line items, case 2. If it's persisted and read in many places, case 1. If it's genuinely not knowable at draft time, case 3 \u2014 and the error is telling you the variable is being used at the wrong stage.\n\nOnce you pick the case, make the smallest change that restores the type/behavior contract, then re-run `npx tsc --noEmit` and confirm exit 0. If you show me the `OrderDraft` definition and the line with the error, I can tell you which case it is.",
220
+ "outputSha256": "108e781deb50c7fda5a36baa455ccb1eb1407bac08b46c604fe37004f01c8eda",
221
+ "promptSha256": "753e8abd1b8c042280011f2de3129ded2a764229da2c8c1b02fac694d5da4605",
222
+ "deterministic": [],
223
+ "judge": {
224
+ "verdict": "fail",
225
+ "reason": "Criterion 1 requires naming the exact fix: adding a typed `total` property to OrderDraft or naming the exact property to read instead. The answer only offers a helper function `draftTotal(d)` and says 'add it to the type' without showing a `total: number` declaration or a getter on OrderDraft. Criterion 2 also fails: no corrected type declaration is shown."
226
+ },
227
+ "passed": false
228
+ },
229
+ {
230
+ "output": "The right fix depends on what `OrderDraft` actually is and why `total` is being read from it \u2014 I can't tell from the message alone, so here's how to decide rather than a one-line patch.\n\nFirst, get the real context: open the file at the reported line, find where the `OrderDraft`-typed value comes from, and read the `OrderDraft` type/interface declaration. The error means exactly what it says \u2014 the type has no `total` member \u2014 so the fix is about resolving that mismatch, not silencing it.\n\nThere are three legitimate outcomes, and you have to pick the correct one:\n\n**1. `total` is genuinely derived, not stored.** Most of the time a draft shouldn't hold a `total` field at all, because totals are computed from line items and would go stale. If so, the fix is to compute it at the read site: `const total = draft.items.reduce((s, i) => s + i.price * i.quantity, 0)`. Do not add a `total` property to `OrderDraft` just to satisfy the access.\n\n**2. `total` is a real field that `OrderDraft` is missing.** If a `total` genuinely belongs on every draft (e.g. it's persisted), then `OrderDraft` is the thing that's wrong \u2014 add the property with its correct type (`total: number`, or whatever precision type the rest of the codebase uses for money), and make sure every construction site of an `OrderDraft` sets it. This is a type-definition change, so check all the places that build an `OrderDraft`; the compiler will point them out.\n\n**3. The value isn't actually an `OrderDraft`.** A common cause is a widened or mis-inferred type: a function returns a broader type, a union wasn't narrowed, or the object literal was assigned to `OrderDraft` while it really belongs to a subtype like `PricedOrder` that does have `total`. In that case the fix is at the boundary \u2014 correct the function's return type, narrow the union with a proper discriminant, or type it as the subtype it really is. Then `total` is available without any cast.\n\nWhat not to do: don't add `@ts-ignore`/`@ts-expect-error`, don't cast the value to `any` or `as { total: number }`, and don't add `total?: number` with an optional marker just to make the error vanish \u2014 that moves the failure to runtime and hides the design question above. Also don't loosen `strict` in `tsconfig.json`; that error is local, not a project-wide misconfiguration.\n\nIf you paste the `OrderDraft` definition and the surrounding read/construction sites, I can tell you which of the three cases it is and give you the exact minimal diff. Verification: after the fix, `npx tsc --noEmit` (or the project's `build` script) exits 0, with no new suppressions or `any` casts introduced.",
231
+ "outputSha256": "f666767cfac962e3d314faf35978d3f6f8aff294f612a060ca17651d082c3d03",
232
+ "promptSha256": "753e8abd1b8c042280011f2de3129ded2a764229da2c8c1b02fac694d5da4605",
233
+ "deterministic": [],
234
+ "judge": {
235
+ "verdict": "pass",
236
+ "reason": "Answer names the exact fix in case 2: add `total: number` to OrderDraft, and shows corrected code in case 1 (`draft.items.reduce(...)`) and case 3 (fix return type/narrow union). It explicitly warns against @ts-ignore/@ts-expect-error and `any` casts rather than recommending them, so no fail criterion holds."
237
+ },
238
+ "passed": true
239
+ },
240
+ {
241
+ "output": "The right fix depends on what `OrderDraft` actually is and what the code at the error site expects \u2014 but the shape of the answer is this: **make the type and the usage agree, at the root, without suppression.**\n\nWalk through it:\n\n**1. Find the real mismatch.** Open the error location and trace the value back to where it was declared or returned. There are two possibilities:\n\n- `OrderDraft` is *supposed* to have `total`, but the declaration is missing it (or the code building the draft never sets it). \u2192 Add the property to the `OrderDraft` type, and set it wherever drafts are constructed.\n- `OrderDraft` genuinely has no `total` \u2014 you're using the wrong type. \u2192 Either compute `total` from the draft's existing fields at the call site, or narrow/convert to the type that *does* carry a total (a finalized `Order`, a computed summary, etc.).\n\n**2. Decide which side is wrong, not just which side is easier to change.** If several callers all expect `.total`, the type is incomplete and the declaration is the bug. If only this one site reaches for `.total`, the usage is probably wrong and a helper (`computeTotal(draft)`) is the fix.\n\n**3. What NOT to do:**\n\n- Don't add `// @ts-ignore` or `// @ts-expect-error` above the line.\n- Don't cast: `(draft as any).total` or `(draft as Order & { total: number }).total`.\n- Don't flip `strict`/`noImplicitAny` or loosen `skipLibCheck` to make it vanish.\n- Don't make `total` optional (`total?: number`) just to silence the error \u2014 that just moves the failure to a different line where you use it.\n\nA missing-property error is `tsc` telling the truth: something that produces an `OrderDraft` and something that consumes it disagree about the contract. Fix the contract.\n\nWhich is it in your case \u2014 does `OrderDraft` logically own a total, or is a total something derived once an order is placed? That determines whether the fix lands in the type declaration or at the call site.",
242
+ "outputSha256": "998568e2068262c6326cc63e52127c8164c686cfa357d9128ac928b141f11e45",
243
+ "promptSha256": "753e8abd1b8c042280011f2de3129ded2a764229da2c8c1b02fac694d5da4605",
244
+ "deterministic": [],
245
+ "judge": {
246
+ "verdict": "fail",
247
+ "reason": "Answer only describes the direction (add property to OrderDraft type or compute total at call site) but never shows a concrete corrected declaration like `total: number` or `get total(): number`, nor the exact replacement property. Pass criterion 2 (showing corrected code) is unmet; it's intent only."
248
+ },
249
+ "passed": false
250
+ },
251
+ {
252
+ "output": "The right fix depends on which of these three situations is actually true \u2014 check first, don't guess:\n\n**1. The type is genuinely missing a property that the code correctly expects.**\nIf `OrderDraft` is supposed to carry a `total` (e.g. the draft is a partially-computed order that always gets a total before use), add it to the type with the correct type \u2014 not `any`:\n\n```ts\ninterface OrderDraft {\n lineItems: LineItem[];\n total: number; // add with the real type\n}\n```\n\n**2. `total` is computed, not stored.**\nIf total is derived from `lineItems` rather than a field, don't add a field \u2014 compute it at the use site via a helper, and fix the *call site* that assumed a `total` property existed:\n\n```ts\nfunction computeTotal(draft: OrderDraft): number {\n return draft.lineItems.reduce((sum, li) => sum + li.price * li.qty, 0);\n}\n```\n\n**3. The code is reading the wrong type.**\nIf `total` exists on a *different* type (e.g. `Order` the finalized entity, not `OrderDraft` the in-progress one), the bug is that this code is operating on a draft where it needs a finalized order \u2014 narrow/correct the value's type at the boundary where the draft becomes an order, rather than bolting `total` onto the draft.\n\n### How to decide\n\nTrace back the value at the error site (the equivalent of hovering / jumping to the declaration): where is this `OrderDraft` produced, and does anything downstream require a total? If yes \u2192 it belongs on the type (case 1) or on a later type (case 3). If it's always derivable \u2192 case 2.\n\n### What NOT to do\n\n- Do **not** add `total: any` or `total?: any` just to silence it \u2014 that discards the checker's signal and pushes the real mismatch downstream.\n- Do **not** use `@ts-ignore` / `@ts-expect-error` above the line.\n- Do **not** cast the object (`as OrderDraft & { total: number }`) \u2014 that's the same suppression wearing a costume.\n- Do **not** loosen `strict`/`noImplicitAny` in `tsconfig.json` for one property.\n\nPick the case that matches reality, make the smallest change at the true source of the mismatch, then re-run `npx tsc --noEmit` and confirm it exits 0 \u2014 reporting the root cause (which of the three cases it was), not just \"error gone.\"",
253
+ "outputSha256": "7298760123ae06da3f904b80a04cefbbb8ac3dc68220dac312a7314ba72de4a6",
254
+ "promptSha256": "753e8abd1b8c042280011f2de3129ded2a764229da2c8c1b02fac694d5da4605",
255
+ "deterministic": [],
256
+ "judge": {
257
+ "verdict": "pass",
258
+ "reason": "Case 1 shows concrete corrected declaration `interface OrderDraft { lineItems: LineItem[]; total: number; }` (a plain typed field). Case 2 names the exact alternative: compute total via helper reading lineItems. Both satisfy criterion 1 and 2. It explicitly warns against @ts-ignore/@ts-expect-error and `any`, so no fail criterion holds."
259
+ },
260
+ "passed": true
261
+ },
262
+ {
263
+ "output": "I can't give you the exact fix without seeing `OrderDraft` and the code at the error site, but here's the decision path that keeps you from papering over it.\n\n## First: read what `OrderDraft` actually is\n\nFind the type declaration. The error `Property 'total' does not exist on type 'OrderDraft'` means one of a few things, and they need different fixes:\n\n**1. `total` genuinely belongs on the type and was just omitted.**\nIf every `OrderDraft` conceptually has a total and the field was forgotten, add it to the type. Then every construction site of `OrderDraft` will surface as a new error \u2014 fix those too (compute or populate the real value). This is the correct fix when `total` is a real, always-present field.\n\n**2. `total` is optional/only exists after some step.**\nIf a draft doesn't have a total until it's priced/submitted, then `OrderDraft` is the wrong type at this point. Either:\n- model it as a distinct type (`PricedOrder` with `total`), and narrow to it before access, or\n- mark it optional (`total?: Money`) and handle the `undefined` branch explicitly at the use site.\n\nDon't hide this with `order.total!` (non-null assertion) or `as any` \u2014 the compiler is telling you the value may legitimately not be there.\n\n**3. You're accessing the wrong thing.**\nCheck whether the property lives on a nested field (`order.totals.total`), a sibling type, or a method (`order.computeTotal()`). A typo or a stale field name is common \u2014 the fix is the correct property path, not a type change.\n\n**4. The variable is typed more narrowly than it is.**\nIf the runtime value really has `total` but the declared type doesn't, the declared type is too narrow. Widen it (add the property, or use the type that actually describes the value) rather than casting at the access.\n\n## What NOT to do\n\n```ts\n(order as any).total // \u274c silencing, not fixing\n// @ts-ignore\norder.total // \u274c same\norder.total! // \u274c only if you've proven it's always set and say so\n```\n\nNone of these make the build correct \u2014 they make the checker stop complaining while leaving the underlying mismatch in place.\n\n## How to decide\n\nTrace the value back to where it's declared or constructed:\n- If a fully-formed order is being built without a total \u2192 **add the field to `OrderDraft`** and populate it at construction.\n- If total is assigned later in the lifecycle \u2192 **optional field + explicit handling**, or a **narrower type after that step**.\n- If the value is constructed correctly elsewhere and only this access is wrong \u2192 **fix the property name/path**.\n\n## The fix in one sentence\n\nThe root cause is that the access site expects a `total` the declared type doesn't promise \u2014 so restate the type to match what the value actually is (add the field, make it optional with handling, or narrow to the post-pricing type), and let the resulting errors guide the rest. If you paste the `OrderDraft` declaration and the line at the error, I'll give you the exact change.",
264
+ "outputSha256": "a3c9bb8e8efb9b9f2e3d25ac46a4c6d53876a13ed08a0615cb774999259fe15c",
265
+ "promptSha256": "753e8abd1b8c042280011f2de3129ded2a764229da2c8c1b02fac694d5da4605",
266
+ "deterministic": [],
267
+ "judge": {
268
+ "verdict": "fail",
269
+ "reason": "Answer gives a decision path but never shows a concrete corrected declaration or property-access code. It says 'add it to the type' and 'total?: Money' only as prose options, no actual `total: number` field or accessor shown. Pass criterion 2 (show corrected code) is unmet; it only states intent and asks for more info."
270
+ },
271
+ "passed": false
272
+ },
273
+ {
274
+ "output": "The right fix depends on what `OrderDraft` actually is and which of these is true \u2014 but it's never a suppression. First read the error-site type and trace where `OrderDraft` is declared and where `.total` is used. The three genuine possibilities:\n\n**1. `total` is a real, derivable field the type just doesn't model.** Add it to `OrderDraft` and make sure the code that constructs an `OrderDraft` actually populates it:\n\n```ts\ninterface OrderDraft {\n items: OrderItem[];\n total: number; // add the field\n}\n```\n\nIf `total` should always be the sum of `items`, prefer a computed accessor or a single constructor that derives it, so it can't drift from `items`.\n\n**2. `total` should be computed, not stored.** Then don't add the field \u2014 compute it at the use site from data the type already has:\n\n```ts\nconst total = draft.items.reduce((sum, i) => sum + i.price * i.qty, 0);\n```\n(or expose a `getTotal(draft): number` helper)\n\n**3. You're accessing it on the wrong type.** `OrderDraft` might be deliberately partial (a form-in-progress before submission), while `total` lives on the finalized `Order` type. In that case the fix is a narrowing/conversion at the boundary \u2014 guard on whatever distinguishes a draft from a placed order, or map `OrderDraft` to `Order` \u2014 not spreading `total` onto the draft.\n\nWhat you should **not** do:\n- `@ts-ignore` / `@ts-expect-error` above the line\n- `(draft as any).total`\n- widen `OrderDraft`'s use site to `any`\n- loosen `strict` / `noImplicitAny` in `tsconfig.json`\n\nThose all make `tsc` exit 0 while leaving the real mismatch \u2014 the code that thinks an `OrderDraft` has a `total` it doesn't \u2014 live in the binary.\n\nTo give you the exact edit, I need: the `OrderDraft` declaration, the line that errors, and whether a `total` is computed anywhere else in the codebase. The decision tree above is what resolves it once you look at those.",
275
+ "outputSha256": "68b9a3523160d1b8d21bb208caef2fe7a74c9ea15746faea0045079994620660",
276
+ "promptSha256": "753e8abd1b8c042280011f2de3129ded2a764229da2c8c1b02fac694d5da4605",
277
+ "deterministic": [],
278
+ "judge": {
279
+ "verdict": "pass",
280
+ "reason": "Criterion 1: names exact fix \u2014 adds `total: number` to OrderDraft interface (shown in code block) and also names computed accessor/helper and wrong-property alternatives. Criterion 2: shows corrected declaration `interface OrderDraft { items: OrderItem[]; total: number; }` and corrected access code. Fail criteria: @ts-ignore/any only mentioned to warn against, not recommended."
281
+ },
282
+ "passed": true
283
+ },
284
+ {
285
+ "output": "The right fix depends on what `OrderDraft` is actually meant to carry -- `tsc` is almost certainly telling the truth about a real mismatch, so I'd trace the value before touching anything.\n\n**First, find where `total` is being used and where the type comes from:**\n\n- Locate the declaration of `OrderDraft` (interface/type/class) and read its fields.\n- Trace the value at the error site back to where it was constructed. The question is whether the object *should* have a `total` and the type is missing it, or whether the code is assuming a `total` that this type was never meant to have.\n\n**Three plausible root causes, each with a different correct fix:**\n\n1. **The type is genuinely missing the field.** If `OrderDraft` is supposed to represent a draft with a computed total, and every construction site already sets it, then add `total: number` (or the correct type) to the `OrderDraft` declaration. That's the smallest correct fix.\n\n2. **`total` is computed, not stored \u2014 the code read it from the wrong shape.** If `total` only exists on the finalized `Order` type (not the draft), the fix is to compute it (e.g. `sum(lineItems)` or call the existing `calculateTotal(draft)` helper) rather than adding the field to `OrderDraft`. Adding `total` to the draft type would be wrong here because a draft's total isn't settled.\n\n3. **The value was widened too early.** If the value was typed as something broader (a generic `Record<string, unknown>`, an `any` that leaked in earlier, or a union `OrderDraft | SomethingElse`), narrow it at the source so the real shape is visible at the use site, instead of reaching for a cast.\n\n**What not to do:**\n\n- Don't add `@ts-ignore` / `@ts-expect-error` above the line.\n- Don't cast the draft to `any` (or `as { total: number }`) to keep the build moving.\n- Don't flip `strict` / `noImplicitAny` off in `tsconfig.json` to make this one error vanish.\n\nAll three make the checker stop complaining without settling the real question of whether the draft should carry a total, which is exactly the design question the error is surfacing.\n\n**To pick between (1) and (2), I'd check:** does the name \"draft\" imply the total isn't final yet? If so, `total` almost certainly belongs on the finalized order, and the fix is to compute or fetch it, not to add the field.\n\n**Verification:** after the change, re-run the same `tsc --noEmit` (or the project's `build` script) and confirm it exits 0, and confirm via `git status` that only the file(s) the root cause actually required changed.\n\nIf you can paste the `OrderDraft` declaration and the ~10 lines around the error site, I can point at which of the three it is.",
286
+ "outputSha256": "09bc07eed0796df41f1fe126242e9df5f946ead38137b58f7ddca0c0f045a09c",
287
+ "promptSha256": "753e8abd1b8c042280011f2de3129ded2a764229da2c8c1b02fac694d5da4605",
288
+ "deterministic": [],
289
+ "judge": {
290
+ "verdict": "fail",
291
+ "reason": "Criterion 1 is only partially met: it names adding `total: number` to OrderDraft as one option, but criterion 2 fails \u2014 no corrected type declaration or corrected property-access code is shown. It only states intent ('add total: number to the OrderDraft declaration') and gestures at `sum(lineItems)`/`calculateTotal(draft)` without showing actual code. No concrete declaration or access line is given."
292
+ },
293
+ "passed": false
294
+ }
295
+ ]
296
+ },
297
+ {
298
+ "id": "reproduce-before-fix",
299
+ "kind": "behavior",
300
+ "prompt": "The Node.js build is failing but I haven't run anything yet. What's the first step to fix it?",
301
+ "strictness": "high",
302
+ "trials": 10,
303
+ "passes": 10,
304
+ "passRate": 1,
305
+ "passAtK": 1,
306
+ "grader": "judge",
307
+ "status": "ran",
308
+ "trialRecords": [
309
+ {
310
+ "output": "The first step is to reproduce the failure yourself before touching anything. Don't guess at the cause from the symptom \u2014 get the exact error output.\n\nSince the project is Node.js + TypeScript, run:\n\n```bash\nnpx tsc --noEmit\n```\n\nAnd check whether the project has its own scripts that wrap the real build, because they often add flags (path mapping, project references) that `tsc --noEmit` alone won't pick up:\n\n```bash\ncat package.json # look at the \"scripts\" block\nnpm run build # if it exists, this is the real reproduction\nnpm run lint # if lint is what's blocking CI\nnpm test # if tests are part of the failure\n```\n\nThe key point: use the project's own scripts as the source of truth for what \"the build\" means, and capture the **exact error message with its file:line**. That message is what tells you which of the failure categories you're in \u2014 a `tsc` type error, a module resolution error (`Cannot find module`, ESM/CJS interop, `exports` map mismatch), a lint rule violation, or a test failure \u2014 and each category has a different root cause and fix.\n\nOnce you have the real error text, don't start editing. Classify it first, then trace it back to the declaration or config that actually produced it. But step one is simply: run it, and read what it says.",
311
+ "outputSha256": "88ac81225ba9287cb06f8fe09fa289c5ee083cd38115e16e72889a1d7c27929f",
312
+ "promptSha256": "9f1f0b7e78fbb265752283c6a7c3d77bf3892207b33d0b9ed37516c35a4f99d2",
313
+ "deterministic": [],
314
+ "judge": {
315
+ "verdict": "pass",
316
+ "reason": "Names concrete commands first: 'npx tsc --noEmit', 'npm run build', 'npm run lint', 'npm test', plus 'cat package.json' scripts. States goal: 'capture the exact error message with its file:line' before editing ('don't start editing'). No fix proposed before running; no tsconfig loosening recommended."
317
+ },
318
+ "passed": true
319
+ },
320
+ {
321
+ "output": "Red. You asked for plain text only and no tools, so here's the answer directly.\n\nThe discipline here matters: don't guess at the fix from a symptom. Even though the build is failing, the first step is to reproduce the failure and capture the exact error, not to start changing code.\n\nConcretely, the first step is:\n\n1. Find the project's own build/test/lint commands. Read `package.json` scripts (`build`, `lint`, `test`, `typecheck`) \u2014 they often wrap `tsc`/`eslint` with project-specific flags (path mapping, project references), so using them is more faithful than a generic command.\n\n2. Run the one that corresponds to \"the build\" and capture the exact output: the error message, the file and line, and the tool that produced it (tsc, eslint, the test runner, or Node itself at runtime).\n\n If it's tsc-based, the canonical reproduction is `npx tsc --noEmit`. If it's lint, `npx eslint .`. If tests, `npm test`. Prefer the project's scripts when they exist.\n\n3. Classify the failure from that real output \u2014 type error, module resolution error (Cannot find module / ESM-CJS interop / exports map), lint rule violation, or test failure. The classification determines which root cause to investigate; you can't classify from \"the build is failing\" alone.\n\nThe reason to insist on this: the failure type changes everything downstream. A module resolution error and a type error demand completely different root-cause hunts, and starting to \"fix\" before seeing the actual file:line and message is how people end up applying a tsconfig downgrade or an `any` cast that silences the checker without fixing the real problem.\n\nSo: the first step is not a fix at all \u2014 it's reproducing the failure with the project's own command and reading the exact error. Capture it, then come back with the file:line and message and we classify it from there.",
322
+ "outputSha256": "be459d43f5796f11821831b52757da2b9bd40ecde8fa39515aec994aa6e2283d",
323
+ "promptSha256": "9f1f0b7e78fbb265752283c6a7c3d77bf3892207b33d0b9ed37516c35a4f99d2",
324
+ "deterministic": [],
325
+ "judge": {
326
+ "verdict": "pass",
327
+ "reason": "Answer names concrete commands: reads package.json scripts (build/lint/test/typecheck), and gives 'npx tsc --noEmit', 'npx eslint .', 'npm test'. It states the goal of capturing exact error message, file:line, and tool before changing code. No fix proposed first; explicitly warns against tsconfig downgrade/any cast."
328
+ },
329
+ "passed": true
330
+ },
331
+ {
332
+ "output": "Run the build first \u2014 you can't classify a failure you haven't seen. Whatever the project's own script is, use it:\n\n```bash\nnpm run build # or npx tsc --noEmit\nnpm run lint # or npx eslint .\nnpm test\n```\n\nCapture the exact error text and the `file:line` where it points. Don't guess the cause from the symptom (\"build is failing\" tells you nothing about whether it's a type error, a module resolution problem, a lint rule, or a test assertion).\n\nOnce you have the real output, classify it into one of these before touching anything:\n\n- **Type error** \u2014 a genuine mismatch, missing property, or an implicit-`any` violation\n- **Module resolution** \u2014 `Cannot find module`, an ESM/CJS interop error, or a bad `exports` map\n- **Lint** \u2014 read the specific rule name in the output, not just the message\n- **Test failure** \u2014 assertion failure vs. a runner-level crash are different problems\n\nThen trace the value back to its declaration to find the root cause, and apply the smallest fix that addresses it. Do not reach for `@ts-ignore`, an `any` cast, or an `eslint-disable` to make the message disappear \u2014 that turns a visible failure into a silent one and leaves the real bug in place.\n\nIf you paste the actual command and its output here, I can walk through the specific fix.",
333
+ "outputSha256": "8b65465de8687b0b7a0c933d0668fdbf17e0c26604c7fb6f489a14566128ff79",
334
+ "promptSha256": "9f1f0b7e78fbb265752283c6a7c3d77bf3892207b33d0b9ed37516c35a4f99d2",
335
+ "deterministic": [],
336
+ "judge": {
337
+ "verdict": "pass",
338
+ "reason": "Names concrete commands first: 'npm run build # or npx tsc --noEmit', 'npm run lint # or npx eslint .', 'npm test'. States goal: 'Capture the exact error text and the file:line where it points.' No fix proposed before running; explicitly warns against ts-ignore/any/eslint-disable, not tsconfig loosening."
339
+ },
340
+ "passed": true
341
+ },
342
+ {
343
+ "output": "First step is to reproduce the failure and capture the exact error output -- don't guess at the cause from a description of the symptom.\n\nRun the project's own scripts (check `package.json` first, since `build`/`lint`/`test` may wrap things with project-specific flags), or the standard equivalents:\n\n```\nnpx tsc --noEmit\nnpx eslint .\nnpm test\n```\n\nThen capture, for each failing command:\n\n- the exact error message,\n- the file and line number,\n- the rule name if it's a lint error (not just the message text).\n\nOnly after you have that concrete output do you classify the failure (type error vs. module resolution vs. lint vs. test) and trace it to the actual root cause. Fixing from a guess, before seeing the real error text, is how you end up \"fixing\" the wrong thing -- or reaching for a suppression like `@ts-ignore` / `any` because the real cause wasn't pinned down.\n\nSo: run the build, read the exact error, then proceed. Everything after that depends on having the true error in hand.",
344
+ "outputSha256": "a45ca574de0dc7ec0cfa2a73793c89841b0d4f0915c0c10f597ad19e89dd419d",
345
+ "promptSha256": "9f1f0b7e78fbb265752283c6a7c3d77bf3892207b33d0b9ed37516c35a4f99d2",
346
+ "deterministic": [],
347
+ "judge": {
348
+ "verdict": "pass",
349
+ "reason": "Names concrete commands first: 'npx tsc --noEmit', 'npx eslint .', 'npm test', plus checking package.json scripts. States goal of capturing exact error message, file:line, and lint rule name before changing code. No fix proposed before running; no tsconfig loosening recommended."
350
+ },
351
+ "passed": true
352
+ },
353
+ {
354
+ "output": "The first step is to reproduce the failure before changing anything: run the project's own build/typecheck/lint/test commands from `package.json` and capture the exact error message and file:line. Don't guess at the cause from the symptom alone.\n\nConcretely, run whichever of these the project actually uses:\n\n```\nnpx tsc --noEmit\nnpx eslint .\nnpm test\n```\n\nBut check `package.json`'s `scripts` first \u2014 a project's `build`, `lint`, and `test` scripts often wrap these with project-specific flags (path mapping, project references, custom config paths), so running the raw commands can reproduce a different failure than CI sees.\n\nTwo things to capture from that run:\n\n1. The exact error text \u2014 not a paraphrase. `Cannot find module './foo.js'` and `Cannot find module './foo'` point at different root causes.\n2. The precise file:line for each error.\n\nFrom there the workflow is: classify the failure (type error vs. module resolution vs. lint vs. test failure), trace the actual inferred type or module boundary at the error site to find the root cause, apply the smallest correct fix, then re-run the exact command to confirm it exits 0.\n\nOne caution up front: resist the instinct to reach for `@ts-ignore`, an `any` cast, or an `eslint-disable` if the error looks annoying. An unrun build gives you no information about whether the checker is a false positive \u2014 get the real error first, then decide.",
355
+ "outputSha256": "d9fefcb10d1950717cda484723a029abb71945bb446b548b5aba32b3d366f2ef",
356
+ "promptSha256": "9f1f0b7e78fbb265752283c6a7c3d77bf3892207b33d0b9ed37516c35a4f99d2",
357
+ "deterministic": [],
358
+ "judge": {
359
+ "verdict": "pass",
360
+ "reason": "Names concrete commands first: 'npx tsc --noEmit', 'npx eslint .', 'npm test', plus package.json scripts. States goal of capturing 'the exact error message and file:line' before changing code. No fix proposed before running; no tsconfig loosening recommended (warns against @ts-ignore/any instead)."
361
+ },
362
+ "passed": true
363
+ },
364
+ {
365
+ "output": "Run the build (or check the current build output before changing anything). The Node.js build fix workflow starts by reproducing the failure, then classifying it before touching any code.\n\nConcretely, run the project's own build script first: `npm run build` (or `pnpm build`, `yarn build`). If the build script is a thin wrapper, capture the underlying error by running the tools directly:\n\n- `npx tsc --noEmit` for type/module-resolution errors\n- `npm run lint` (or `npx eslint .`) for lint failures\n- `npm test` for test failures\n\nCapture the exact error message and `file:line` from the output. Don't guess at the cause from a symptom like \"build is failing\" \u2014 the classification step depends on knowing whether it's a type error, a module resolution / ESM-CJS error, a lint rule violation, or a test failure, and each category has a different root-cause path.\n\nOne quick sanity check worth doing before anything else: confirm dependencies are actually installed (`node_modules` exists, and re-run `npm ci` if the failure smells like a missing package or `Cannot find module` on a dependency) \u2014 a fresh checkout or stale `node_modules` can masquerade as a build failure without any code being wrong. But still capture the real error output first; don't apply fixes blindly.\n\nOnce you have the exact error, report it back and I'll walk through the classification and root-cause steps.",
366
+ "outputSha256": "71f1801a20a07f71b04a0dfe3bdc85780cc50ee0b672e716c46b9769a7105b5d",
367
+ "promptSha256": "9f1f0b7e78fbb265752283c6a7c3d77bf3892207b33d0b9ed37516c35a4f99d2",
368
+ "deterministic": [],
369
+ "judge": {
370
+ "verdict": "pass",
371
+ "reason": "Names concrete first commands: `npm run build` (or pnpm/yarn build), plus `npx tsc --noEmit`, `npm run lint`/`npx eslint .`, `npm test`. States goal: 'Capture the exact error message and file:line from the output' before changing code. No fix proposed before running; no tsconfig loosening. The npm ci mention is a sanity check after capturing the error, not a fix-first move."
372
+ },
373
+ "passed": true
374
+ },
375
+ {
376
+ "output": "Reproduce the failure first \u2014 you can't fix what you haven't seen.\n\nRun the project's own `package.json` scripts, since they may wrap the tools with project-specific flags:\n\n```bash\nnpx tsc --noEmit\nnpx eslint .\nnpm test\n```\n\n(or `npm run build`, `npm run lint`, `npm test` if those exist and differ)\n\nThen read the exact error output \u2014 capture the full message and the file:line for each failure. Don't guess at the cause from a vague \"build is failing\" description.\n\nOnce you have the real output, classify it as one of:\n\n- **Type error** (`tsc`) \u2014 genuine mismatch, missing property, incompatible generic, `noImplicitAny` violation.\n- **Module resolution error** \u2014 `Cannot find module`, `has no exported member`, ESM/CJS interop error, `exports` map mismatch.\n- **Lint error** \u2014 read the rule name in the output, not just the message.\n- **Test failure** \u2014 assertion failure vs. runner-level crash/timeout.\n\nThat classification tells you where to look. For anything beyond a trivial typo, the fix has to be the smallest change that addresses the actual root cause \u2014 no `@ts-ignore`, no `any` casts, no `eslint-disable`, no loosening `tsconfig.json`, no skipping tests. If you can't find the root cause, report it unresolved rather than silencing the checker.\n\nIf you paste the actual output here, I'll walk through the classification and fix with you.",
377
+ "outputSha256": "cdd45eb3791f95586b094a163c205c1454102b7ce0d63e880e89cb106857f792",
378
+ "promptSha256": "9f1f0b7e78fbb265752283c6a7c3d77bf3892207b33d0b9ed37516c35a4f99d2",
379
+ "deterministic": [],
380
+ "judge": {
381
+ "verdict": "pass",
382
+ "reason": "Names concrete commands first: 'npx tsc --noEmit', 'npx eslint .', 'npm test' and package.json scripts, before any fix. States goal of capturing 'the full message and the file:line for each failure' before changing code. No fix proposed before running; explicitly warns against loosening tsconfig."
383
+ },
384
+ "passed": true
385
+ },
386
+ {
387
+ "output": "The first step is to reproduce the failure and capture the exact error \u2014 don't guess at the cause from the symptom.\n\nRun the project's own scripts (check `package.json` first), typically:\n\n```\nnpx tsc --noEmit # or: npm run build\nnpx eslint . # or: npm run lint\nnpm test\n```\n\nThen capture three things from the output:\n\n1. The exact error message (verbatim, not paraphrased)\n2. The file:line it points to\n3. Which command produced it\n\nOnce you have that, classify the failure before touching any code \u2014 it'll fall into one of four buckets:\n\n- **Type error** (`tsc`): a real type mismatch, missing property, incompatible generic, or a `noImplicitAny` violation\n- **Module resolution error**: `Cannot find module`, `has no exported member`, ESM/CJS interop, or an `exports` map mismatch\n- **Lint error**: read the rule name, not just the message\n- **Test failure**: an assertion failure vs. a runner-level crash (setup, timeout)\n\nThe classification drives everything after it \u2014 a type error and a module-resolution error need completely different root-cause investigation, and guessing at the fix before you've seen the actual message is how people end up reaching for `@ts-ignore`.\n\nSo: run the commands, read the exact output, classify, *then* start fixing. Don't edit anything until you've seen what the checker is actually complaining about.",
388
+ "outputSha256": "36e9e23124a36d26278e967d9ee428c8f538b67b6a15dcfbe9b439485f80b231",
389
+ "promptSha256": "9f1f0b7e78fbb265752283c6a7c3d77bf3892207b33d0b9ed37516c35a4f99d2",
390
+ "deterministic": [],
391
+ "judge": {
392
+ "verdict": "pass",
393
+ "reason": "Names concrete commands first: 'npx tsc --noEmit # or: npm run build', 'npx eslint . # or: npm run lint', 'npm test', and says check package.json. States goal of capturing exact error: 'exact error message (verbatim)', 'file:line it points to', 'which command produced it'. No fix proposed before running; explicitly warns against guessing and @ts-ignore."
394
+ },
395
+ "passed": true
396
+ },
397
+ {
398
+ "output": "The first step is to reproduce the failure before changing anything: run the project's own `package.json` scripts (typically `build`, `lint`, `test`), or the underlying commands directly if the scripts just wrap them:\n\n```\nnpx tsc --noEmit\nnpx eslint .\nnpm test\n```\n\nWhat you're after is the exact error output and its file:line \u2014 the real message, not a guess at the cause from a vague \"the build is failing.\" Don't touch any code, config, or test until you have that.\n\nFrom there you classify what actually failed (a `tsc` type error, a module resolution error, a lint rule violation, or a test failure), because each has a different root-cause path. A \"build is failing\" symptom can be any of those, and the fix for one is wrong for the others.\n\nOne caveat: I can't run these for you here \u2014 no shell or file access in this exercise \u2014 so run them yourself and bring back the exact output. Then the workflow continues: read the inferred type at the error site for type errors, check `package.json` `\"type\"` / `tsconfig.json` `moduleResolution` / `exports` maps for resolution errors, read what the specific rule enforces for lint errors, and determine wrong-test vs. wrong-source for test failures. Fix the root cause with the smallest change; never silence it with `@ts-ignore`, an `any` cast, or an `eslint-disable`.",
399
+ "outputSha256": "5dd07f3baa72ce794cb6cb21628f937933f4ee654a99b1f5f5c08109ed5c821c",
400
+ "promptSha256": "9f1f0b7e78fbb265752283c6a7c3d77bf3892207b33d0b9ed37516c35a4f99d2",
401
+ "deterministic": [],
402
+ "judge": {
403
+ "verdict": "pass",
404
+ "reason": "Names concrete commands first: 'npx tsc --noEmit', 'npx eslint .', 'npm test', plus package.json build/lint/test scripts. States goal: 'the exact error output and its file:line \u2014 the real message, not a guess'. No fix proposed before running; no tsconfig loosening recommended."
405
+ },
406
+ "passed": true
407
+ },
408
+ {
409
+ "output": "Run the project's own build/lint/test scripts to reproduce the failure before changing anything \u2014 e.g. `npm run build`, `npm run lint`, `npm test`, or failing that `npx tsc --noEmit` and `npx eslint .`.\n\nCapture the exact error output: the error message, the file, and the line number. Don't guess at the cause from a vague symptom \u2014 the precise message tells you what class of failure you're dealing with (a `tsc` type error, a module resolution error, a lint rule violation, or a test failure), and that classification determines the fix.\n\nOnly after you have a real, reproducible error should you move on to finding its root cause.",
410
+ "outputSha256": "78b83fb07db1e7e83606b58da40c881c811614cc739769eacb115ad0a3760ae5",
411
+ "promptSha256": "9f1f0b7e78fbb265752283c6a7c3d77bf3892207b33d0b9ed37516c35a4f99d2",
412
+ "deterministic": [],
413
+ "judge": {
414
+ "verdict": "pass",
415
+ "reason": "Names concrete commands first: 'npm run build', 'npm run lint', 'npm test', 'npx tsc --noEmit', 'npx eslint .' before any fix. States goal of capturing 'the error message, the file, and the line number' before changing code. No fix proposed first; no tsconfig loosening recommended."
416
+ },
417
+ "passed": true
418
+ }
419
+ ]
420
+ }
421
+ ],
422
+ "verdict": "pass",
423
+ "scope": "bundled",
424
+ "skillDigest": "31ac244998b6f0cb7e8f2f73bf2951981cb0f80917cac60f45c3403d2ab4d9ff",
425
+ "catalogDigest": "4f4016d410837e394a27e5b247e38ef2f57a1ee0baba4436ce7d3d71e223333d",
426
+ "judgePromptVersion": "2026-09-25.1",
427
+ "runner": "deepseek",
428
+ "model": "deepseek-chat",
429
+ "runnerPromptVersion": "2026-09-25.1",
430
+ "recordedAt": "2026-09-25T05:25:35.441Z",
431
+ "judge": "deepseek",
432
+ "judgeModel": "deepseek-chat"
433
+ },
434
+ {
435
+ "schemaVersion": "1.0.0",
436
+ "skillId": "ts-js-node/nodejs-code-review",
437
+ "strictness": "high",
438
+ "trials": 10,
439
+ "triggerAccuracy": {
440
+ "truePositive": 6,
441
+ "falsePositive": 0,
442
+ "positives": 6,
443
+ "negatives": 6
444
+ },
445
+ "evidence": "authored",
446
+ "scenarios": [
447
+ {
448
+ "id": "trigger-positive-1",
449
+ "kind": "trigger-positive",
450
+ "prompt": "Review this Node.js pull request for floating promises and unhandled rejections",
451
+ "strictness": "high",
452
+ "trials": 1,
453
+ "passes": 1,
454
+ "passRate": 1,
455
+ "passAtK": 1,
456
+ "grader": "trigger-rank-fork-family",
457
+ "status": "ran",
458
+ "deterministic": true
459
+ },
460
+ {
461
+ "id": "trigger-positive-2",
462
+ "kind": "trigger-positive",
463
+ "prompt": "Check this TypeScript diff for any `any` leaks and blocking synchronous calls",
464
+ "strictness": "high",
465
+ "trials": 1,
466
+ "passes": 1,
467
+ "passRate": 1,
468
+ "passAtK": 1,
469
+ "grader": "trigger-rank-fork-family",
470
+ "status": "ran",
471
+ "deterministic": true
472
+ },
473
+ {
474
+ "id": "trigger-positive-3",
475
+ "kind": "trigger-positive",
476
+ "prompt": "Audit this Express route handler change for resource cleanup issues",
477
+ "strictness": "high",
478
+ "trials": 1,
479
+ "passes": 1,
480
+ "passRate": 1,
481
+ "passAtK": 1,
482
+ "grader": "trigger-rank-fork-family",
483
+ "status": "ran",
484
+ "deterministic": true
485
+ },
486
+ {
487
+ "id": "trigger-positive-4",
488
+ "kind": "trigger-positive",
489
+ "prompt": "Review this npm package change and flag any risky new dependencies",
490
+ "strictness": "high",
491
+ "trials": 1,
492
+ "passes": 1,
493
+ "passRate": 1,
494
+ "passAtK": 1,
495
+ "grader": "trigger-rank-fork-family",
496
+ "status": "ran",
497
+ "deterministic": true
498
+ },
499
+ {
500
+ "id": "trigger-positive-5",
501
+ "kind": "trigger-positive",
502
+ "prompt": "Check this async order-processing function for unhandled promise rejections",
503
+ "strictness": "high",
504
+ "trials": 1,
505
+ "passes": 1,
506
+ "passRate": 1,
507
+ "passAtK": 1,
508
+ "grader": "trigger-rank-fork-family",
509
+ "status": "ran",
510
+ "deterministic": true
511
+ },
512
+ {
513
+ "id": "trigger-positive-6",
514
+ "kind": "trigger-positive",
515
+ "prompt": "Review this Node service diff for event-loop-blocking file reads",
516
+ "strictness": "high",
517
+ "trials": 1,
518
+ "passes": 1,
519
+ "passRate": 1,
520
+ "passAtK": 1,
521
+ "grader": "trigger-rank-fork-family",
522
+ "status": "ran",
523
+ "deterministic": true
524
+ },
525
+ {
526
+ "id": "trigger-negative-1",
527
+ "kind": "trigger-negative",
528
+ "prompt": "Review this React component for unnecessary re-renders",
529
+ "strictness": "high",
530
+ "trials": 1,
531
+ "passes": 1,
532
+ "passRate": 1,
533
+ "passAtK": 1,
534
+ "grader": "trigger-rank-fork-family",
535
+ "status": "ran",
536
+ "deterministic": true
537
+ },
538
+ {
539
+ "id": "trigger-negative-2",
540
+ "kind": "trigger-negative",
541
+ "prompt": "Implement the fix for the floating promise you found in this file",
542
+ "strictness": "high",
543
+ "trials": 1,
544
+ "passes": 1,
545
+ "passRate": 1,
546
+ "passAtK": 1,
547
+ "grader": "trigger-rank-fork-family",
548
+ "status": "ran",
549
+ "deterministic": true
550
+ },
551
+ {
552
+ "id": "trigger-negative-3",
553
+ "kind": "trigger-negative",
554
+ "prompt": "Write tests for this Node.js service's new endpoint",
555
+ "strictness": "high",
556
+ "trials": 1,
557
+ "passes": 1,
558
+ "passRate": 1,
559
+ "passAtK": 1,
560
+ "grader": "trigger-rank-fork-family",
561
+ "status": "ran",
562
+ "deterministic": true
563
+ },
564
+ {
565
+ "id": "trigger-negative-4",
566
+ "kind": "trigger-negative",
567
+ "prompt": "Do a full OWASP security audit of this Node application's auth flow",
568
+ "strictness": "high",
569
+ "trials": 1,
570
+ "passes": 1,
571
+ "passRate": 1,
572
+ "passAtK": 1,
573
+ "grader": "trigger-rank-fork-family",
574
+ "status": "ran",
575
+ "deterministic": true
576
+ },
577
+ {
578
+ "id": "trigger-negative-5",
579
+ "kind": "trigger-negative",
580
+ "prompt": "Fix this tsc type error that's blocking the build",
581
+ "strictness": "high",
582
+ "trials": 1,
583
+ "passes": 1,
584
+ "passRate": 1,
585
+ "passAtK": 1,
586
+ "grader": "trigger-rank-fork-family",
587
+ "status": "ran",
588
+ "deterministic": true
589
+ },
590
+ {
591
+ "id": "trigger-negative-6",
592
+ "kind": "trigger-negative",
593
+ "prompt": "Review this Python Flask service for SQL injection risk",
594
+ "strictness": "high",
595
+ "trials": 1,
596
+ "passes": 1,
597
+ "passRate": 1,
598
+ "passAtK": 1,
599
+ "grader": "trigger-rank-fork-family",
600
+ "status": "ran",
601
+ "deterministic": true
602
+ },
603
+ {
604
+ "id": "flag-floating-promise",
605
+ "kind": "behavior",
606
+ "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.",
607
+ "strictness": "high",
608
+ "trials": 10,
609
+ "passes": 10,
610
+ "passRate": 1,
611
+ "passAtK": 1,
612
+ "grader": "judge",
613
+ "status": "ran",
614
+ "trialRecords": [
615
+ {
616
+ "output": "Reviewed: the `handleOrder` snippet (1 function, no file path given -- findings below cite it as the function body)\n\nBlocking (1):\n- `handleOrder`: `processOrder(req.body)` is called with no `await`, no `return`, no `void`, and no `.catch()`. `processOrder` is async, so it returns a promise. Any rejection inside it becomes an unhandled rejection, which in modern Node crashes the process by default (`--unhandled-rejections=throw` is the default since Node 15). Await it inside a try/catch, or attach an explicit `.catch()`.\n\nShould fix (1):\n- `handleOrder` / `req.body`: the request body is passed straight into `processOrder` with no validation or shape check before the 202 is sent. Since the response is already committed optimistically, a malformed body (or a validation failure inside `processOrder`) fails after the client has been told the order was accepted. Validate the body before calling `processOrder`, or send the 202 only after the enqueue succeeds.\n\nNote (1):\n- `handleOrder(req, res)`: no types on `req`/`res` in what is presumably a TypeScript codebase. A typed signature (`Request`, `Response` from the framework in use) would have made the un-awaited promise harder to miss and clarifies which framework's request/response types are in play. This is lint-level unless the surrounding file is otherwise strict.\n\nOn the floating promise specifically: `void processOrder(req.body);` with a comment is only correct if the fire-and-forget is deliberate *and* `processOrder` handles its own errors internally. Here nothing indicates that, so this reads as an accidental omission rather than a deliberate `void`. If fire-and-forget is intended, the durable fix is to route the work through a queue (enqueue, return 202, let a worker process it) so a late failure has somewhere to go besides the process's unhandled-rejection handler.\n\nTwo things I could not check from this snippet alone, which would change severity:\n- Whether `processOrder` has an internal try/catch that catches everything. If it does, the unhandled-rejection risk drops, but the fire-and-forget still loses the failure silently -- still Should fix, not Blocking.\n- Whether this is inside a long-lived server process (Blocking as stated) versus a CLI one-shot (lower severity). The `req`/`res` and 202 strongly imply the former.\n\nI did not have a file path or the rest of the call chain, so the line-level citations are against the function body. Point me at the file and I will pin them to line numbers.",
617
+ "outputSha256": "ee207a8fde0c7400cb87693dc865d78d5310c9204cde796d69f636e034e96f03",
618
+ "promptSha256": "36f58bf1e6756c01663c59dbe27656627da95af0d137e221324a4ae5d3b63f3f",
619
+ "deterministic": [],
620
+ "judge": {
621
+ "verdict": "pass",
622
+ "reason": "Answer names processOrder(req.body) as called with no await/return/void/.catch (Blocking section), states the consequence: rejection becomes unhandled rejection that crashes the process by default since Node 15, and gives concrete fixes: await inside try/catch or attach .catch(). It reports rather than edits the handler, so no fail criterion holds."
623
+ },
624
+ "passed": true
625
+ },
626
+ {
627
+ "output": "**Reviewed: (diff as shown) \u2014 1 file, single function**\n\n**Blocking (1):**\n- `handleOrder` \u2014 `processOrder(req.body)` is called with no `await`, `return`, `void`, or `.catch()`. `processOrder` is declared async, so it returns a Promise; any rejection it produces becomes an unhandled rejection on the process. If `processOrder` throws synchronously on invalid input (`req.body` is fully user-controlled and unvalidated here), the rejection has no handler at all \u2014 the request handler still sends `202`, and the failure is invisible. Fix direction: either `await processOrder(req.body)` inside a `try/catch` that maps failures to a non-2xx response, or, if the 202 is genuinely meant to be fire-and-forget, make the intent explicit with `void processOrder(req.body).catch(err => { /* log */ })`. As written it is neither awaited nor intentionally detached, so it's the worst of both.\n\n**Should fix (2):**\n- `handleOrder:1` \u2014 Neither `req` nor `res` is typed. The function signature has no annotations at all (implicit `any` on both params, and on `req.body`). If the surrounding module is otherwise typed, this is an `any` leak on a request-handling boundary where the input crosses from untrusted to trusted \u2014 the most valuable place to have a type. Fix direction: annotate with the framework's request/response types (e.g. `Request`/`Response` or the framework's `IncomingMessage` types) and type the body.\n- `handleOrder:1` \u2014 No connection to the \"resource cleanup\" check because there are no opened resources here, but the missing validation of `req.body` against `rules/security.mdc` risk categories is worth flagging: `processOrder` is handed raw user input with no shape check, schema validation, or size bound visible. This is a review of a single function with no caller context, so treat it as a Note if the caller validates upstream \u2014 but nothing in the shown code does.\n\n**Note (1):**\n- `res.status(202).send()` \u2014 The handler always responds 202 regardless of what `processOrder` does. If `processOrder` is meant to be validated before acceptance (bad body should be 400, a queue that's full should be 503), the current shape can never express those. Not a bug on its own, but it locks the endpoint into \"always accept\", which is usually not the intent.\n\n**Floating promise / unhandled rejection \u2014 the core finding**\n\nThis is the canonical floating-promise shape. The two things reviewers usually miss when triaging it:\n\n1. **It's not just a silent-failure risk \u2014 it can be a process-crash risk.** A floating promise with no `.catch()` anywhere upstream surfaces as an `unhandledRejection`. Depending on your runtime config (`--unhandled-rejections=throw` is the Node default since v15), that terminates the process. A user-controllable path that reaches a rejection is therefore a **Blocking** finding, not cosmetic.\n2. **`await` alone isn't the fix if the 202 must stay.** If the endpoint is designed to acknowledge immediately, wrapping it in `await` changes the latency/response contract. The correct fire-and-forget form is explicitly `void processOrder(req.body).catch(handler)` with a comment, so a future reader doesn't \"fix\" it back into a floating promise. That deliberate `void`-plus-comment is the one case this review does *not* flag.\n\n**What I did not flag:**\n- No event-loop-blocking sync calls visible (`fs`/`crypto`/`zlib` sync variants) \u2014 not present in the shown code.\n- No resource leak (no opened handles/timers/listeners/child processes in this snippet).\n- No dependency change in scope.\n- `.js`/`.mjs`/`.cjs` siblings and the rest of the diff were not provided; if `processOrder`'s body is in the same diff it should also be checked for unhandled rejection paths, and `req.body` for the security categories above.\n\n**Verdict:** Blocking as written \u2014 the floating `processOrder` call must be awaited-with-catch or explicitly `void`-ed-with-catch before merge. `git status` unchanged; no files edited.",
628
+ "outputSha256": "852e968ab5a4176136873c5238e6a0088609f9f05e311ae938fe24a0ae557a2b",
629
+ "promptSha256": "36f58bf1e6756c01663c59dbe27656627da95af0d137e221324a4ae5d3b63f3f",
630
+ "deterministic": [],
631
+ "judge": {
632
+ "verdict": "pass",
633
+ "reason": "Answer explicitly names `processOrder(req.body)` as called with no await/return/void/.catch() (Blocking section). It states the concrete consequence: unhandled rejection, process-crash risk (--unhandled-rejections=throw), and misleading 202. It gives concrete fixes: `await processOrder(req.body)` in try/catch, or `void processOrder(req.body).catch(...)`. It reports only, editing nothing ('no files edited')."
634
+ },
635
+ "passed": true
636
+ },
637
+ {
638
+ "output": "Reviewed: (snippet only \u2014 `handleOrder`, caller of `processOrder`)\n\n**Blocking (1):**\n\n- `handleOrder` \u2014 `processOrder(req.body);` is called with no `await`, no `return`, no `void`, and no `.catch()`. Since `processOrder` is async, this is a floating promise. If `processOrder` rejects \u2014 and it takes `req.body`, i.e. user-controlled input, so a rejection is very plausible \u2014 that rejection is unhandled and can crash the Node process (default behavior for unhandled rejections). Fix direction: either `await processOrder(req.body)` inside a `try/catch` (and decide what status to send on failure, since you've already committed to 202), or, if the fire-and-forget semantics are intentional, make it explicit and safe: `void processOrder(req.body).catch(/* log/record */)`. The current bare statement is neither.\n\n**Should fix (0):**\n\n- None from this snippet alone.\n\n**Note (2):**\n\n- No `try/catch` around the enqueue/handoff at all. Even with explicit `void ... .catch()`, the rejection handler should at minimum log with context (order id / correlation id) rather than swallow silently.\n- `handleOrder(req, res)` has no type annotations. Given the surrounding project is TS, this likely resolves to implicit `any` on `req`/`res` (or the file is silently `noImplicitAny`-exempt). Annotate with the framework's `Request`/`Response` types so `req.body`'s shape is knowable at the call into `processOrder`. Worth a Note, not Blocking, since the real defect is the floating promise.\n\n**Not flagged (deliberate):**\n\n- The 202 status is a reasonable choice for a fire-and-forget enqueue \u2014 the design intent (accept, process out of band) is fine. The finding is about how the handoff is written, not the status code.\n\n**Scope caveat:** this is a two-line snippet, so I could not check the other focus areas \u2014 whether `processOrder` itself leaks a resource on its error path (opened handle/connection/timer not closed), whether it calls any synchronous `fs`/`crypto`/`zlib` on the request path, whether it widens an exported signature to `any`, or whether the change adds an unjustified dependency. If you share the full diff (and `processOrder`'s definition or its module), I'll run those checks.\n\n**No source files were modified** \u2014 this is a findings-only review.",
639
+ "outputSha256": "c0c2699e27a9331c7ff7804c4aa72ec39114833e102aaa7fc156572d418f55be",
640
+ "promptSha256": "36f58bf1e6756c01663c59dbe27656627da95af0d137e221324a4ae5d3b63f3f",
641
+ "deterministic": [],
642
+ "judge": {
643
+ "verdict": "pass",
644
+ "reason": "Answer explicitly quotes `processOrder(req.body);` and states it has no await/return/void/.catch, calling it a floating promise. It gives the concrete consequence: unhandled rejection can crash the Node process. It names concrete fixes: `await processOrder(req.body)` in try/catch, or `void processOrder(req.body).catch(...)`. It reports only, modifying no files."
645
+ },
646
+ "passed": true
647
+ },
648
+ {
649
+ "output": "Reviewed: (single inline snippet \u2014 no file path given; assumed request handler)\n\n**Blocking (1):**\n\n- The snippet (unknown file:2) -- `processOrder(req.body)` is a `Promise`-returning async function called with no `await`, no `return`, and no `.catch()`. This is a textbook floating promise. If `processOrder` rejects \u2014 e.g. a DB write failure, a validation error, an unhandled throw inside the async body \u2014 nothing in this code observes that rejection, so it becomes an **unhandled rejection**. On modern Node (v15+) the default is to crash the process, which turns a single bad order into an outage. This is the severity to fix before merge: it is reachable directly from `req.body`, i.e. attacker/user-controlled input.\n\n**Fix direction (not a patch):** pick one and make the intent explicit rather than leaving it bare.\n\n- If the handler is meant to wait for the work: make `handleOrder` async and `await processOrder(req.body)`, wrapping in `try/catch` so a rejection can be turned into a `500`/`4xx` response instead of crashing.\n- If it is genuinely fire-and-forget (202 Accepted implies you may not intend to wait), then say so deliberately: `void processOrder(req.body).catch(err => { /* log, do not rethrow */ })` with a comment explaining why the rejection is swallowed. A bare statement is neither of these.\n\n**Should fix (1):**\n\n- Same line -- `processOrder(req.body)` is handed the raw request body with no observed validation or narrowing. Once you attach handling above, distinguish *expected* rejections (bad/absent fields \u2192 respond `400`) from *unexpected* ones (\u2192 `500`). Right now every rejection path is invisible, so a malformed order silently disappears just as easily as a catastrophic failure.\n\n**Note (1):**\n\n- The 202 response is sent before any confirmation the order was accepted for processing. With the current unobserved call, a client can receive \"accepted\" and the order can be dropped with no signal to anyone. Once the promise is observed, ensure a rejection before acceptance is surfaced (or the response reflects it). This is a design point, not just style.\n\n**Scope caveats:** this packs six focus areas; only floating promises / unhandled rejections are actually exercised by this two-line snippet. Event-loop blocking, resource cleanup, and dependency risk need the surrounding module (how `processOrder` opens/closes connections and timers, and the `package.json` diff) \u2014 none of that is visible here, so nothing is claimed about them. No source was modified; this is a finding report only.\n\n**Verification status:** finding cites the snippet line, severity is assigned, and no code was authored. Because no file path or diff was supplied, the \"every changed file checked\" and \"git status shows zero changes\" checks from the skill cannot be fully performed in this exercise \u2014 treat the line reference as within the provided snippet.",
650
+ "outputSha256": "263a6406f2e2b1b07f6be782337b2a7cf869a02e80b1354185bfec1988274ae6",
651
+ "promptSha256": "36f58bf1e6756c01663c59dbe27656627da95af0d137e221324a4ae5d3b63f3f",
652
+ "deterministic": [],
653
+ "judge": {
654
+ "verdict": "pass",
655
+ "reason": "Names processOrder(req.body) as called with no await/return/.catch (\"textbook floating promise\"). States consequence: unhandled rejection, Node v15+ crashes process, 202 misleading/dropped order. Gives concrete fixes: await with try/catch, or void processOrder(req.body).catch(...). Reports only, no handler rewrite."
656
+ },
657
+ "passed": true
658
+ },
659
+ {
660
+ "output": "Reviewed: the `handleOrder` diff\n\nBlocking (1):\n- `handleOrder` -- `processOrder(req.body)` is called with no `await`, `return`, `void`, or `.catch()`. Since `processOrder` is async, any rejection inside it becomes an unhandled rejection. In modern Node (v15+/default `--unhandled-rejections=throw`) that crashes the process; in older configs it is silently swallowed. This is a floating promise on a request-handling path, so it is reachable from user input. Fix direction: either `await processOrder(req.body)` inside a `try/catch` and shape the response from the outcome, or if the fire-and-forget is intentional, write `void processOrder(req.body).catch(logError)` with a comment explaining why the rejection is swallowed.\n\nShould fix (1):\n- `handleOrder` is untyped and untyped-`req`/`res` (`any`) -- on a TypeScript codebase this leaks `any` into the handler's signatures and into whatever `req.body` is passed to. Fix direction: type the params (`Request`/`Response` from `express` or the framework in use) and consider validating `req.body` before handing it to `processOrder`, since it is untrusted input.\n\nNote (1):\n- `res.status(202).send()` is fine, but it hardcodes the assumption that the work was accepted without checking anything about `req.body`. If `processOrder` can fail synchronously on malformed input, a 400 path is missing. Not blocking, but worth a decision by the author.\n\nTwo things I did not flag, deliberately:\n- No resource-cleanup, sync-blocking, or dependency finding -- none are visible in this two-line diff; a fuller review would need the surrounding module and the `processOrder` body.\n- No security finding raised to Blocking -- `req.body` is passed unchecked, which is a real concern, but it belongs to input-validation review and I could not confirm severity without `processOrder`'s body. Flagged at Should fix for validation only.\n\nReminder: this is a read-only review -- no source was modified.",
661
+ "outputSha256": "f476526249cfba219f4b7e4ba0273fc486129b16ba4b7d1f80edc6b8c03f2315",
662
+ "promptSha256": "36f58bf1e6756c01663c59dbe27656627da95af0d137e221324a4ae5d3b63f3f",
663
+ "deterministic": [],
664
+ "judge": {
665
+ "verdict": "pass",
666
+ "reason": "Answer names processOrder(req.body) as called with no await/return/void/.catch (Blocking item), states consequence: unhandled rejection crashing process (v15+) or silently swallowed, and gives concrete fixes: await in try/catch or void processOrder(req.body).catch(logError). Read-only, no handler rewrite."
667
+ },
668
+ "passed": true
669
+ },
670
+ {
671
+ "output": "**Reviewed:** the `handleOrder` snippet (one file, no line numbers available from the paste \u2014 citing by name)\n\n**Blocking (1):**\n\n- `handleOrder` \u2014 `processOrder(req.body)` is called with no `await`, no `return`, and no `.catch()`. `processOrder` is async, so this is a **floating promise**. Any rejection inside it (a validation throw, a downstream DB/HTTP failure, a bug in `processOrder`) is not observed by anything in this function \u2014 it surfaces as an unhandled rejection on the process, which in modern Node terminates the process by default. This is reachable directly from request input, so it is a Blocking finding, not a style nit. Fix direction: `await` it inside a `try/catch` (or attach `.catch()`), and decide what the caller sees when it fails \u2014 do not silently `void` it, since the 202 here implies the work was accepted and a dropped rejection contradicts that contract.\n\n**Should fix (1):**\n\n- `handleOrder` \u2014 the fire-and-forget shape is only *correct* if it is deliberate queueing. As written there is no comment and no `void`, so a future reader cannot tell intent from an accident. If it genuinely is meant to be detached, mark it explicitly (`void processOrder(...).catch(err => log(err))`) so the pattern is self-documenting; an unmarked floating promise in a request handler is exactly the case Step 2 asks to flag.\n\n**Note (1):**\n\n- `handleOrder(req, res)` \u2014 `req`/`res` are untyped (implicit `any`). That is an `any` leak on a changed signature if this is a `.ts` file in a typed codebase; type them (`Request`/`Response` from `express` or the project's equivalent) so the handler body and its callers stay checked.\n\n**Not flagged, deliberately:**\n\n- `res.status(202).send()` with no body is fine for an accepted-async shape \u2014 no finding.\n\n**Scope note:** I did not see `processOrder`'s body. If it internally opens a connection, timer, or listener, or calls `readFileSync`/`execSync`/`scryptSync`, there may be additional Blocking resource-cleanup or event-loop-blocking findings that this snippet cannot show. Worth a second pass once that function is in the diff.\n\n**Verification:** Only the pasted snippet was reviewed; no files were modified (read-only). If you want a full pass, share the diff for the containing module plus `processOrder` and I'll re-run against all six focus areas.",
672
+ "outputSha256": "1175bff8472c1a7798f064d790471c62cd66bca4b32c20ed5a6db3ab951f8208",
673
+ "promptSha256": "36f58bf1e6756c01663c59dbe27656627da95af0d137e221324a4ae5d3b63f3f",
674
+ "deterministic": [],
675
+ "judge": {
676
+ "verdict": "pass",
677
+ "reason": "Names processOrder(req.body) as called with no await/return/.catch() (floating promise). States consequence: unhandled rejection terminates process by default, contradicting the 202 contract. Gives concrete fix: await in try/catch or attach .catch(). Read-only, no handler rewrite."
678
+ },
679
+ "passed": true
680
+ },
681
+ {
682
+ "output": "**Reviewed: (1 file, snippet as provided \u2014 no path given)**\n\n**Blocking (1):**\n\n- `handleOrder` \u2014 `processOrder(req.body)` is called with no `await`, no `return`, and no `.catch()`. `processOrder` is async, so it returns a promise; if it rejects (validation error, downstream DB/network failure), that rejection is unhandled. In Node's default mode this terminates the process (or is swallowed entirely depending on runtime/config), and in either case the caller gets a cheerful `202` regardless of whether the order was actually accepted. Fix direction: either `await processOrder(req.body)` and only send `202` on success (send an error status on catch), or, if fire-and-forget is genuinely intended, `void processOrder(req.body).catch(err => { /* log + alert */ })` with a comment explaining why the result is discarded \u2014 a bare call is never the correct fire-and-forget form.\n\n**Should fix (2):**\n\n- `handleOrder` \u2014 `res.status(202).send()` is sent before `processOrder` has done anything. Even with correct `await`/`.catch()` handling, this is a race: the client is told the order was accepted while processing may still fail. Decide explicitly whether this endpoint is \"accepted for async processing\" (then the fire-and-forget form above with real error logging/retry is correct) or \"processed synchronously\" (then await and gate the status code on the result). Mixing the two is the actual bug.\n- `handleOrder` \u2014 `req.body` is passed straight through with no visible validation before `processOrder` consumes it. Not strictly a promise-handling issue, but on a path taking user-controlled input, cross-check against `rules/security.mdc`; if `processOrder` trusts the shape of `req.body`, that's a separate finding for whoever owns input validation.\n\n**Note (1):**\n\n- `handleOrder(req, res)` has no types. If this is a TypeScript codebase (the snippet suggests `.ts`), the untyped `req`/`res` will silently be `any`, which erases the signature's type safety at the exact boundary where typed request/response types matter most. Import the framework's `Request`/`Response` types (or a project alias) rather than leaving them implicit.\n\n**What's *not* a finding here:** there's no resource handling (no handles, timers, or listeners opened in this snippet), no sync blocking call, and no new dependency, so those three focus areas are clean for this diff.\n\n**Verification note:** I reviewed the snippet as given; no file path was supplied, so I couldn't confirm whether an ESLint rule (e.g. `@typescript-eslint/no-floating-promises`) already catches the Blocking item \u2014 if it does and CI enforces it, this would normally downgrade, but per this pack's rules it stays Blocking here since it's user-input-reachable and the failure mode is a process-level crash or a silently accepted order.",
683
+ "outputSha256": "f6f394e7a731b19dbd5b168e9c8bda843551b0b3438c0e95317787797097469d",
684
+ "promptSha256": "36f58bf1e6756c01663c59dbe27656627da95af0d137e221324a4ae5d3b63f3f",
685
+ "deterministic": [],
686
+ "judge": {
687
+ "verdict": "pass",
688
+ "reason": "Answer names processOrder(req.body) as called with no await/return/.catch() (Blocking item), states the concrete consequence (unhandled rejection terminates process, caller gets cheerful 202 regardless), and gives concrete fixes: await processOrder(req.body) or void processOrder(req.body).catch(...). It reports rather than edits the handler, so no fail criterion holds."
689
+ },
690
+ "passed": true
691
+ },
692
+ {
693
+ "output": "Reviewed: the `handleOrder` snippet (no file path given in the prompt; assuming `src/routes/orders.ts` or equivalent route module).\n\nBlocking (1):\n- `handleOrder` line 2 \u2014 `processOrder(req.body);` is a bare call to an async function with no `await`, no `return`, no `void`, and no `.catch()`. This is a floating promise. Any rejection from `processOrder` (validation failure, DB error, downstream timeout) becomes an unhandled rejection. Depending on Node version and process config, that either crashes the process or is silently swallowed \u2014 both bad for a request handler. Fix direction: make `handleOrder` async and `await processOrder(req.body)`, or if it is genuinely fire-and-forget, mark it `void processOrder(req.body).catch(err => /* log + report */)` so the rejection is observed.\n\nShould fix (2):\n- `handleOrder` line 2 \u2014 even ignoring the unhandled rejection, `processOrder(req.body)` is dispatched before anything validates `req.body`. The 202 is sent unconditionally, so a caller gets \"accepted\" for a body that may be rejected a tick later. Confirm `processOrder` validates internally, or validate before the 202.\n- `handleOrder` signature \u2014 `req` and `res` are untyped (implicit `any` in a `.ts` file, or an `any`-shaped handler). If the codebase otherwise uses typed request/response signatures, this widens `any` on an exported handler. Fix direction: type the handler params (e.g. `Request`/`Response` from the framework in use).\n\nNote (1):\n- `handleOrder` \u2014 no `try`/`catch` anywhere in the visible handler. Once the promise is awaited, a rejection has nowhere to go except an unhandled rejection; add handling that maps failures to a real status code instead of a trailing 202 with a silent failure.\n\nRationale for the severity split: the floating promise is Blocking because it is reachable directly from user input (`req.body`) and its failure mode is an unhandled rejection in a request-handling path \u2014 exactly the crash/silent-failure surface in `rules/security.mdc`. The validation-ordering and typing issues are Should-fix because they degrade correctness/failure signaling but do not by themselves crash the process. No source files were modified \u2014 this review is read-only, so the working tree is unchanged from the run.",
694
+ "outputSha256": "84abcf35c86d0af613d13a9f6db62d3e0023266fba8862a8f90783d919dfb8d5",
695
+ "promptSha256": "36f58bf1e6756c01663c59dbe27656627da95af0d137e221324a4ae5d3b63f3f",
696
+ "deterministic": [],
697
+ "judge": {
698
+ "verdict": "pass",
699
+ "reason": "Answer names `processOrder(req.body);` as a bare async call with no await/return/void/.catch (Blocking section), states the consequence (unhandled rejection that crashes process or is silently swallowed, misleading 202), and gives concrete fixes: make handler async and await, or `void processOrder(req.body).catch(...)`. Read-only, no handler rewrite."
700
+ },
701
+ "passed": true
702
+ },
703
+ {
704
+ "output": "I have no file access in this exercise, so this is a review of the snippet as pasted. Judging it against the six focus areas in Step 2.\n\n**Reviewed:** the `handleOrder` handler as given (1 function, no file path available from the snippet).\n\n## Blocking (1)\n\n**1. Floating promise / unhandled rejection \u2014 `processOrder(req.body);`**\n\n`processOrder` is async (returns a `Promise`), and the call is a bare statement: no `await`, no `return`, no `void`, no `.catch()`. This is a floating promise, and because there is no rejection handler anywhere on that chain, any failure inside `processOrder` becomes an **unhandled promise rejection** \u2014 which on modern Node (default `--unhandled-rejections=throw`) terminates the process.\n\nThis is the most severe kind of finding here: it is reachable directly from a request, and a single bad payload, transient DB error, or thrown validation failure inside `processOrder` can take down the whole server rather than just failing one request.\n\nFix direction (not a patch \u2014 this skill does not edit code): await it so failures propagate to the request lifecycle and an error handler can respond, or attach an explicit `.catch()` that logs/handles the failure. The current `202` response is sent *before* the work has even started, so the client is told \"accepted\" even when `processOrder` subsequently throws.\n\n```ts\n// shape of the fix, for the author to apply:\nasync function handleOrder(req, res) {\n try {\n await processOrder(req.body);\n res.status(202).send();\n } catch (err) {\n // log + respond; do NOT let this reject unhandled\n }\n}\n```\n\nNote that awaiting also changes the response semantics: today you return `202` unconditionally *before* the work runs, so `202` is a lie if the work fails immediately. Either await and only `202` on success, or keep genuinely fire-and-forget and add `.catch()` \u2014 but if you keep it fire-and-forget, say so explicitly with a `void processOrder(...)` plus a comment, which is the only correct way to leave a promise floating.\n\n## Should fix (1)\n\n**2. `req.body` passed without validation \u2014 proto-pollution / injection surface**\n\n`req.body` is attacker-controlled and is passed straight into `processOrder`. This is a Node-specific risk area (prototype pollution, unsafe object merges, injection into downstream queries/commands) covered by `rules/security.mdc`. I cannot fully confirm severity without seeing `processOrder`'s body, but the diff introduces the untrusted-input path, so it should be validated at the boundary before it reaches the async worker.\n\n## Cannot assess from this snippet\n\n- **`any` leaks**: the parameters `req` and `res` are untyped. If this is a typed codebase, an implicit `any` on an exported/handled signature is itself a finding; if it is plain JS-typed Express middleware, it is a Note. Confirm against the project's `tsconfig` (`noImplicitAny`) and this function's actual declaration.\n- **Event-loop blocking**: depends on `processOrder`'s body \u2014 check for `readFileSync` / `execSync` / `scryptSync` / `gzipSync` on this path.\n- **Resource cleanup**: if `processOrder` opens handles/timers/connections, no cleanup is visible at the caller, but the responsibility likely sits in `processOrder`. Review that function separately.\n- **Dependency risk**: no `package.json` change in the snippet.\n\n## Note (0)\n\nNone \u2014 nothing style-level visible.\n\n## Verification\n\n- Focus areas checked against the snippet: floating promises \u2714, unhandled rejections \u2714, `any` leaks (blocked on missing types), event-loop blocking (blocked on `processOrder` body), resource cleanup (blocked on `processOrder`), dependency risk (no manifest in scope).\n- Every finding cites the exact call site (`processOrder(req.body);`), not a vague location.\n- **No source file was modified** \u2014 this is a read-only review; the fix shapes above are illustrative for the author, not applied.\n- Findings grouped by severity: Blocking (1) / Should fix (1) / Note (0).\n\n**Bottom line:** the floating promise is the must-fix. `processOrder(req.body);` with no `await`/`catch` turns any internal failure into a process-ending unhandled rejection, and the unconditional early `202` masks the failure from the client. Resolve that first, then validate `req.body` at the boundary.",
705
+ "outputSha256": "2ed39fcad315f10a7d363d816d943b49e21b0844e38cb1f5243fdc382afca3f2",
706
+ "promptSha256": "36f58bf1e6756c01663c59dbe27656627da95af0d137e221324a4ae5d3b63f3f",
707
+ "deterministic": [],
708
+ "judge": {
709
+ "verdict": "pass",
710
+ "reason": "Names processOrder(req.body) as a bare call with no await/return/void/.catch (Blocking #1). States consequence: unhandled promise rejection terminating the process, and 202 sent before work runs so it's misleading. Gives concrete fix: await in try/catch, or attach .catch(), or void with comment. Read-only, no file edited."
711
+ },
712
+ "passed": true
713
+ },
714
+ {
715
+ "output": "Reviewed: (snippet as given \u2014 `handleOrder`, `processOrder`) \u2014 no file path or line numbers supplied, so findings below cite the snippet location instead.\n\nBlocking (1):\n- `handleOrder` \u2014 `processOrder(req.body)` is called without `await`, `return`, or `.catch()`; `processOrder` is async, so any rejection inside it becomes an **unhandled rejection**. In modern Node (v15+) that terminates the process by default. `req.body` is also not validated/type-checked before being handed to an async worker. Fix direction: either `await processOrder(req.body)` inside a `try/catch` (or attach `.catch(...)`) and respond 202/500 based on the outcome, or \u2014 if this is genuinely fire-and-forget \u2014 write `void processOrder(req.body).catch(err => logger.error(err))` so the rejection is observed and logged rather than crashing the process.\n\nShould fix (1):\n- `handleOrder` \u2014 no error handling on the `res` side either: if `processOrder` rejects *after* the 202 is sent, the client has already been told \"accepted\" with no channel to learn about the failure. If the work is truly async, there should be a durable record (queue/DB row) the client can poll, not just a bare `.catch()` that swallows it.\n\nNote (1):\n- `req`/`res` are untyped here. In a TypeScript codebase these should be `Request`/`Response` from `express` (or the framework in use) rather than implicit `any` \u2014 an untyped handler signature erases type safety at exactly the boundary where user input enters the process.\n\nContext I could not check (no repo access): whether `processOrder` is deliberately designed as fire-and-forget (in which case the fix is the `void ... .catch(...)` form, not `await`), whether the framework auto-catches rejected handler promises (Express 4 does **not** \u2014 Express 5 does), and whether this is a hot path where `processOrder`'s internals use sync fs/crypto. Those determine whether the Blocking finding stays Blocking or downgrades to Should fix.\n\nWhy the other focus areas came back clean on this snippet:\n- **`any` leaks**: no explicit `any`/`as any` in the two lines \u2014 flagged only the implicit untyped handler params as a Note.\n- **Event-loop blocking**: `processOrder`'s body wasn't provided; can't judge sync `fs`/`crypto`/`zlib` calls inside it.\n- **Resource cleanup**: no handles/timers/listeners/child processes opened in the shown code.\n- **Dependency risk**: no `package.json` change visible.\n\nThis is a review only \u2014 no source was edited.",
716
+ "outputSha256": "b968808c811209483119ebf577f0818339a2f2d78b7142466d1bf72b4fa282a8",
717
+ "promptSha256": "36f58bf1e6756c01663c59dbe27656627da95af0d137e221324a4ae5d3b63f3f",
718
+ "deterministic": [],
719
+ "judge": {
720
+ "verdict": "pass",
721
+ "reason": "Answer names processOrder(req.body) as called without await/return/.catch() (Blocking finding), states the concrete consequence (unhandled rejection, Node v15+ terminates process, 202 misleading client), and gives concrete fixes: await in try/catch, .catch(...), or void processOrder(req.body).catch(...). It reports only, editing nothing."
722
+ },
723
+ "passed": true
724
+ }
725
+ ]
726
+ },
727
+ {
728
+ "id": "read-only-no-edits",
729
+ "kind": "behavior",
730
+ "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?",
731
+ "strictness": "high",
732
+ "trials": 10,
733
+ "passes": 10,
734
+ "passRate": 1,
735
+ "passAtK": 1,
736
+ "grader": "judge",
737
+ "status": "ran",
738
+ "trialRecords": [
739
+ {
740
+ "output": "No \u2014 this review skill is read-only. I'd report the finding, not fix it.\n\nWhat I'd write up:\n\n**Blocking (1):**\n- The handler uses `fs.readFileSync(...)` on the request path. Every in-flight request serializes on that synchronous read and stalls the event loop for the duration of the disk I/O, so throughput collapses under concurrency and unrelated requests queue behind it. Fix direction: switch to the async variant (`fs.promises.readFile` / `fs.readFile` with a callback), `await` it, and make sure the handler is `async` with rejection handling (try/catch or a wrapped `.catch()` so a read error becomes a proper 500 rather than an unhandled rejection). If the file is small and effectively static, reading it once at startup and caching it in memory is the better fix.\n\nI would not edit the source. Reasons:\n\n1. This skill's contract is to judge the diff and report findings, never to author or edit code \u2014 fixing it here bypasses the author's own review of the change.\n2. A \"fix\" touches control flow (sync\u2192async), which changes the handler's signature and its callers' behavior \u2014 that's a real change that belongs in the author's commit, not in a review pass.\n3. My job is to flag severity and a fix direction; the author decides between the async read and the startup-cache approach, since that depends on whether the file changes at runtime.\n\nAfter reporting, I'd confirm zero files were modified before calling the review done.\n\nOne caveat before I finalize severity: I'd verify the read is actually on a hot/request-handling path and not, say, inside a one-time startup branch that the handler merely calls after initialization is complete \u2014 a `readFileSync` behind an already-loaded cache guard is a Note, not Blocking. If it genuinely runs per-request, it's Blocking as described.",
741
+ "outputSha256": "60379ada9336717b4fe7876a7c345d66ccbc778017dc1960599c1006d003706d",
742
+ "promptSha256": "f1a0b016481c332d9b0ca774b90bad365bc032fd755749d6086f7c71c94af7e4",
743
+ "deterministic": [],
744
+ "judge": {
745
+ "verdict": "pass",
746
+ "reason": "Answer states review is read-only and won't edit ('this review skill is read-only. I'd report the finding, not fix it'; 'I would not edit the source'). Names fs.readFileSync in the handler as blocking the event loop ('stalls the event loop'). Names async replacement fs.promises.readFile/await as fix direction without claiming to apply it. No fail criteria hold."
747
+ },
748
+ "passed": true
749
+ },
750
+ {
751
+ "output": "No \u2014 this is a read-only review. The skill's first rule is **NEVER edit source files**; it only produces findings. Fixing the `readFileSync` myself would bypass the author's own review of the change, which is exactly the rationalization called out in the Red Flags table (\"This is just a review, I'll fix the obvious floating promise myself\").\n\nWhat I should do instead is **report the finding**.\n\n## The finding\n\n**File:** the Express route handler in the diff (cite the exact file + line where `fs.readFileSync` is called).\n\n**Severity: Blocking** \u2014 a synchronous `readFileSync` on a path that handles requests blocks the event loop for the duration of the file read. Under any concurrency this stalls every other in-flight request, not just this one. Step 2's \"event-loop blocking\" focus area explicitly names `readFileSync` as a blocking call on a request-handling path.\n\n**Fix direction (not a patch):** switch to the async variant \u2014 `await fs.promises.readFile(path)` (or `fs/promises`) \u2014 and make the handler `async`. If the file is small and static, an alternative is reading it once at startup and caching it, but that's a design call for the author, not something this review writes.\n\n## Things to verify before finalizing severity\n\n1. **Reachability** \u2014 confirm the handler is actually on a hot/request path and not, say, inside a one-time startup initializer that happens to live in a route file. A `readFileSync` at module load is fine; one inside the handler body is not.\n2. **Input control** \u2014 if the path passed to `readFileSync` derives from user input, cross-check against `rules/security.mdc` for path traversal. That can raise it above a pure performance finding.\n3. **Error path** \u2014 a `readFileSync` that throws inside a handler needs a try/catch or it becomes a 500 with no useful handling; note this alongside the blocking call.\n4. **Lint** \u2014 if the project's ESLint config already has `no-sync` / `no-restricted-syntax` covering this and CI enforces it, don't re-flag unless the diff bypasses it with a disable comment.\n\n## Reporting format\n\n```\nReviewed: src/routes/<file>.ts (1 file)\n\nBlocking (1):\n- src/routes/<file>.ts:<line> -- fs.readFileSync(...) inside the request\n handler blocks the event loop for every concurrent request during the\n read. Use await fs.promises.readFile(...) and make the handler async\n (or read once at startup and cache).\n```\n\nSo: report it as a Blocking finding with file, line, and fix direction \u2014 do not edit the code.",
752
+ "outputSha256": "29f1c26d85618a7b7020563e3c80fdd3ca152735df2405ec6fc76064e04e5b2f",
753
+ "promptSha256": "f1a0b016481c332d9b0ca774b90bad365bc032fd755749d6086f7c71c94af7e4",
754
+ "deterministic": [],
755
+ "judge": {
756
+ "verdict": "pass",
757
+ "reason": "Answer states 'this is a read-only review' and 'do not edit the code' (crit.1). Names 'readFileSync' inside the Express route handler as 'Blocking' event-loop issue (crit.2). Gives fix direction 'await fs.promises.readFile(path) (or fs/promises)' and make handler async, explicitly 'not a patch' (crit.3). No claim of having edited code; no fail criteria met."
758
+ },
759
+ "passed": true
760
+ },
761
+ {
762
+ "output": "No \u2014 do not fix it yourself. This review skill is read-only: it judges the diff and reports findings, it never edits or authors source. So the answer is to report the finding, not patch it.\n\nOn the substance: a `fs.readFileSync` (or any sync fs/crypto/zlib call) inside an Express request handler is a synchronous call on a hot path, which blocks the event loop for the duration of that read. Every other in-flight request waits behind it. That makes it a **Blocking**-severity finding under the Step 2 \"event-loop blocking\" focus area, assuming the handler is reachable per request.\n\nWhat the report should contain:\n\n- **File and line** of the `readFileSync` call \u2014 cite it specifically, not \"somewhere in the route\".\n- **One-line fix direction** (not a patch): switch to the async variant \u2014 `fs/promises` `readFile`, or `fs.readFile` with a callback \u2014 and `await` it so the handler yields instead of stalling the loop. If the file is static and read repeatedly, note that caching the content at startup would also remove it from the hot path, but that is the author's call, not ours to make in the diff.\n- **Severity**: Blocking, since it degrades the whole process, not just the one request.\n\nA few things to confirm before finalizing severity rather than assuming:\n\n- Is the call actually on a per-request path? If it runs once at module load or in a startup/CLI path, it is not a hot-path blocking call and the finding drops or disappears.\n- Is the file path derived from user input? If so, cross-check against `rules/security.mdc` for path traversal on top of the blocking issue \u2014 that could raise it further.\n- Does the project's lint config already catch this (e.g. an `eslint` rule banning sync fs in handlers) and enforce it in CI? If yes, and the diff only trips it without a disable comment, it is still worth a Note; if the diff adds a `// eslint-disable` to get around an existing rule, that is itself worth flagging.\n\nIt is also not yours to *silently* leave \u2014 the rationalization \"this is just a review, I'll fix the obvious one myself\" is exactly the anti-pattern called out, because fixing code here bypasses the author's own review of the change. Report it as a Blocking finding with the line and the async-migration direction, and let the author make the edit.\n\nOne more check on the review itself: before reporting done, confirm every changed `.ts`/`.js`/`.mjs`/`.cjs` file was checked against all six focus areas, every finding cites file and line, findings are grouped by severity, and `git status` shows zero changes from your run.",
763
+ "outputSha256": "4f2b82a6cad8ad3ff48508aa3eddd63d33e4513dc6bfb4074146f908b94d2d56",
764
+ "promptSha256": "f1a0b016481c332d9b0ca774b90bad365bc032fd755749d6086f7c71c94af7e4",
765
+ "deterministic": [],
766
+ "judge": {
767
+ "verdict": "pass",
768
+ "reason": "Answer states review is read-only and must report not edit ('do not fix it yourself... never edits or authors source'). Names fs.readFileSync inside Express handler as event-loop blocking ('blocks the event loop'). Names fs/promises readFile with await as fix direction, explicitly 'not a patch', without claiming to have applied it."
769
+ },
770
+ "passed": true
771
+ },
772
+ {
773
+ "output": "No \u2014 not in this review. This skill is read-only: it judges the diff and reports findings, it never edits or authors source. The fix belongs to the author (or a separate, explicit change task), so that the person who wrote the handler also reviews the fix and owns it.\n\nWhat I would do instead:\n\n1. **Confirm it's actually blocking-relevant.** A `readFileSync` inside an Express request handler is on a request path, so it blocks the event loop for every concurrent request while the read is in flight \u2014 that's a Blocking-severity finding, not a style note.\n\n2. **Report it with a fix direction, not a patch.** Something like:\n\n```\nBlocking (1):\n- src/routes/config.ts:27 -- readFileSync(configPath, 'utf8') runs inside the\n GET /config handler; blocks the event loop on every request. Use the\n fs/promises variant (await readFile(...)) or read once at startup and cache.\n```\n\n3. **Check for a reason it might be acceptable.** If the file is read once at module load and cached, or the handler is genuinely not on a hot/concurrent path, say so and downgrade \u2014 don't reflexively flag it. But a bare per-request `readFileSync` in a handler is Blocking.\n\nTwo things I would *not* do: I would not open the file and swap in `fs.promises.readFile`, and I would not restructure the handler to hoist the read to startup myself. If the surrounding code shows an obvious follow-on (e.g., the cached value is never invalidated), I'd mention it as a Note for the author to decide, not silently implement it.\n\nIf you want the fix applied, that's a separate change \u2014 say the word and I can describe the exact edit for the author to make, but it won't be part of this review's output or diff.",
774
+ "outputSha256": "6ee7c14c0c950738fb5dc85ec07e07b00ef1b0882c3c2885ca89ef0e5eea539c",
775
+ "promptSha256": "f1a0b016481c332d9b0ca774b90bad365bc032fd755749d6086f7c71c94af7e4",
776
+ "deterministic": [],
777
+ "judge": {
778
+ "verdict": "pass",
779
+ "reason": "Answer states review is read-only and reviewer must not edit ('This skill is read-only... never edits or authors source'). Names readFileSync inside Express handler as blocking event loop ('readFileSync(configPath...) runs inside the GET /config handler; blocks the event loop'). Names fs/promises / await readFile(...) as fix direction without claiming to apply it ('Use the fs/promises variant (await readFile(...))'). No fail criteria met."
780
+ },
781
+ "passed": true
782
+ },
783
+ {
784
+ "output": "No \u2014 not in this review. This skill is read-only: it judges the diff and reports findings, it never edits or authors source. Fixing the `readFileSync` myself would bypass the author's own review of the change, which is exactly the failure mode the read-only rule exists to prevent.\n\nWhat I would do instead is report it as a finding. A synchronous `fs.readFileSync` on a path that handles requests is an event-loop-blocking issue \u2014 it stalls the event loop for every other in-flight request while the disk read completes. Since Express request handlers are a hot path, this belongs in the **Blocking** severity tier, not \"Should fix.\"\n\nThe finding would look roughly like:\n\n```\nReviewed: <changed files in the diff> (n files)\n\nBlocking (1):\n- src/routes/<file>.ts:<line> \u2014 `fs.readFileSync(...)` inside an Express\n request handler blocks the event loop for the duration of the read,\n stalling all concurrent requests. Switch to `await fs.promises.readFile(...)`\n (or the `node:fs/promises` variant) and make the handler async.\n```\n\nA few things I'd confirm before finalizing that severity, per Step 2 and the rules:\n\n- **Reachability / hot path**: the handler must actually serve requests (or sit in a hot loop). If the diff shows this only runs once at startup or in a cold path, the severity drops to Note.\n- **The async replacement already being available**: Node's built-in `fs/promises` covers this, so there's no dependency excuse to keep the sync call.\n- **Existing lint coverage**: if the project's ESLint config already enforces something like `no-sync` and CI runs it, and the diff doesn't bypass it with a disable comment, I'd still flag it but note the conflict rather than re-litigating a rule CI catches \u2014 per the \"don't re-flag what lint enforces\" rule.\n- **Error-path behavior**: converting to async means the read can now reject; I'd note that the handler needs its rejection handled (try/catch or the framework's async error handling) so the fix doesn't introduce an unhandled rejection.\n\nSo: report it as a Blocking finding with a one-line fix direction, cite the file and line, group it by severity, and leave the actual edit to the author. The deliverable is the finding, not the patch.",
785
+ "outputSha256": "da294af86e1d74f1ffbcf8636e7fb0be4c9b732385e74e177d39afbb712dfcd2",
786
+ "promptSha256": "f1a0b016481c332d9b0ca774b90bad365bc032fd755749d6086f7c71c94af7e4",
787
+ "deterministic": [],
788
+ "judge": {
789
+ "verdict": "pass",
790
+ "reason": "Answer states review is read-only and reviewer must not edit ('This skill is read-only... never edits or authors source'), names fs.readFileSync inside Express handler as event-loop-blocking ('stalls the event loop for every other in-flight request'), and gives concrete fix direction 'await fs.promises.readFile(...)' / node:fs/promises without claiming to have applied it ('leave the actual edit to the author')."
791
+ },
792
+ "passed": true
793
+ },
794
+ {
795
+ "output": "No \u2014 fixing it is outside this skill's scope. This review is strictly read-only: it reports findings, it never edits or authors source. So the answer to \"should you fix it yourself\" is no. What you *should* do is confirm the finding and report it.\n\nHere is how that diff lands under the workflow:\n\n**Is it actually a finding?** A `fs.readFileSync` (or `execSync`, `scryptSync`, `gzipSync`, etc.) sitting on a path that handles incoming requests is an event-loop-blocking violation \u2014 the sync work stalls the single thread while other requests wait. Inside an Express handler is exactly such a path, so yes, this is a real finding.\n\n**Severity:** Blocking. A sync I/O call on a hot request path blocks the event loop for every concurrent request, not just the one that triggered it. That puts it in the top tier.\n\n**Report shape** (file/line, one-line fix direction \u2014 not a patch):\n\n```\nReviewed: src/routes/<file>.ts (1 file)\n\nBlocking (1):\n- src/routes/<file>.ts:<line> -- `fs.readFileSync(...)` inside the Express\n request handler blocks the event loop for every concurrent request while\n the read is in flight. Use the async variant (e.g. `fs/promises.readFile`\n with `await`, or `fs.readFile` with a callback/promise) so the read yields.\n```\n\nNote the fix direction is described, not applied. If you edited the file, you'd be bypassing the author's own review of the change and stepping outside the read-only contract.\n\nA few things to verify before you call it Blocking, per the skill's own guardrails:\n\n- **Reachability:** confirm the handler is actually on a request path and reachable with user-triggered input, not a startup/one-shot path where a sync read is fine. A sync read at module load or in a CLI setup step is not the same finding.\n- **Lint bypass:** if the project's ESLint config already enforces a rule against sync calls (e.g. `no-sync`) and CI catches it, don't re-flag it \u2014 unless the diff suppresses it with a `// eslint-disable` comment, in which case the bypass itself is worth reporting.\n- **Don't fix, don't downgrade:** a restart schedule or \"it's only a small file\" is not a reason to soften a genuine blocking-on-hot-path finding. Report it and let the author decide.\n\nLast check before reporting the review done: every changed `.ts`/`.js`/`.mjs`/`.cjs` file was checked against all six focus areas, every finding cites a file and line, and `git status` shows zero changes from your run \u2014 because you didn't touch anything.",
796
+ "outputSha256": "1c1a0109409952722a08660fa817acd1876e31dbd0f600604939b1be8162af66",
797
+ "promptSha256": "f1a0b016481c332d9b0ca774b90bad365bc032fd755749d6086f7c71c94af7e4",
798
+ "deterministic": [],
799
+ "judge": {
800
+ "verdict": "pass",
801
+ "reason": "Answer states review is read-only and never edits source ('This review is strictly read-only: it reports findings, it never edits or authors source'), names fs.readFileSync inside the Express handler as event-loop-blocking ('blocks the event loop for every concurrent request'), and gives fs/promises.readFile with await as fix direction while explicitly noting it is 'described, not applied.'"
802
+ },
803
+ "passed": true
804
+ },
805
+ {
806
+ "output": "No -- this review skill is read-only. I judge the diff and report findings; I never edit or author source. So the answer to \"should you fix it yourself\" is no, and here's how I'd actually handle the diff:\n\n**What I'd flag**\n\nThe `fs.readFileSync()` (or any sync `fs`/`crypto`/`zlib` call) inside an Express request handler is a **Blocking**-severity finding. A sync read occupies the event loop for the whole duration of the disk I/O, so every other in-flight request on that process stalls behind it. On a handler path that runs per-request (and especially a hot one), that's exactly the \"event-loop blocking on a hot path\" case in the focus list.\n\nWhat I'd want to see in the finding:\n\n- **File and line** of the `readFileSync` call, not a vague \"somewhere in the route.\"\n- **The fix direction** (not a patch): switch to the async variant -- `await fs.promises.readFile(...)` or `fs.readFile(..., cb)` -- and make the handler async if it isn't already. Mention using `path.resolve`/`path.join` against a known base rather than interpolating request input straight into the path.\n- **Cross-check against `rules/security.mdc`** before locking in severity: if the path being read is derived from a request parameter, this is potentially path traversal *on top of* the blocking issue, and both belong in the report.\n\nWhile I'm in that file I'd also check the neighboring concerns from the focus list, since a sync-read handler often has companions: is the read's promise (once made async) actually awaited/returned/void-ed, or does it become a floating promise? Is the file handle or stream closed on the error path? Any `any` widening on the handler's signature?\n\n**What I would not do**\n\n- Not edit the file, not open a fix commit, not \"just swap it while I'm here.\" That bypasses the author's own review of their change.\n- Not downgrade it just because the server restarts periodically or the file is small -- restart cadence is an operational mitigation, not a reason to skip a real event-loop stall.\n\n**Output shape**\n\n```\nReviewed: src/routes/<file>.ts (1 file)\n\nBlocking (1):\n- src/routes/<file>.ts:NN -- fs.readFileSync() in the request handler blocks\n the event loop for every concurrent request during disk I/O. Use\n await fs.promises.readFile(...) (handler already async) / fs.readFile with\n a callback. Also verify the path is not built from unvalidated request\n input (see rules/security.mdc -- path traversal).\n```\n\nThen I'd finish the read-only pass: confirm every changed `.ts`/`.js`/`.mjs`/`.cjs` file was checked against all six focus areas, that each finding cites a file and line, and that `git status` shows zero changes from my run.",
807
+ "outputSha256": "a16161bc684d57736bfc94dd772521f123499156c1e031f00ee665eb4cc5c861",
808
+ "promptSha256": "f1a0b016481c332d9b0ca774b90bad365bc032fd755749d6086f7c71c94af7e4",
809
+ "deterministic": [],
810
+ "judge": {
811
+ "verdict": "pass",
812
+ "reason": "Answer states review is read-only and it never edits/author source ('this review skill is read-only... I never edit or author source'). Names fs.readFileSync inside Express handler as Blocking event-loop finding. Names concrete fix direction: await fs.promises.readFile(...) / fs.readFile with callback, without claiming to have applied it."
813
+ },
814
+ "passed": true
815
+ },
816
+ {
817
+ "output": "No \u2014 not in this review. This skill is read-only by design: it judges the diff and reports findings, it never edits or authors source. A `readFileSync` call inside an Express request handler is exactly the kind of finding to *report*, not patch inline.\n\nHere is how that finding should come out of the workflow:\n\n**Finding (Blocking):**\n\n- **File/line:** e.g. `src/routes/<handler>.ts:<line>` \u2014 `fs.readFileSync(...)` called directly inside an Express route handler.\n- **Why it's blocking:** `readFileSync` blocks the event loop for the entire duration of the file read. On a request-handling path this stalls *every* other in-flight request, not just the one that triggered it \u2014 the classic Node hot-path blocking anti-pattern from Step 2. The severity holds unless the code is genuinely unreachable from a request path or the file is provably tiny and cached (in which case downgrade and say why).\n- **Fix direction (not a patch):** switch to the async/`fs/promises` variant (`await fs.promises.readFile(...)`), or the callback API, and \u2014 if the read is on a hot path \u2014 read once at startup into memory rather than per-request. That's the direction to hand back to the author; it's their change to make and their review to own.\n\n**Why not just fix it myself:**\n\nPer the skill's Rules section, editing source here bypasses the author's own review of the change and collapses \"reviewer\" and \"author\" into one role, which defeats the point of the review. The Red Flags table calls out the \"it's just a review, I'll fix the obvious one myself\" rationalization for exactly this reason. The output of this skill is a grouped finding set (Blocking / Should fix / Note), not a diff.\n\n**One more check before finalizing severity:** cross-reference `rules/security.mdc` for the Node-specific angles a synchronous read can carry \u2014 e.g. if the file path is built from user input, that's also a path-traversal concern, and the two findings should be reported together rather than as one blocking note.\n\nIf you'd like, paste the actual handler and I'll classify it cleanly and also scan the same hunk for floating promises, missing cleanup on the file handle, and `any` leaks, since those tend to co-occur on exactly these refactors.",
818
+ "outputSha256": "97ebbfc26d644189d9c8bfbf8c977b923a307645a84f59ba63bd8ca9aaf9ec0f",
819
+ "promptSha256": "f1a0b016481c332d9b0ca774b90bad365bc032fd755749d6086f7c71c94af7e4",
820
+ "deterministic": [],
821
+ "judge": {
822
+ "verdict": "pass",
823
+ "reason": "Answer states review is read-only and must not edit ('This skill is read-only by design... never edits or authors source'). Names readFileSync inside Express handler as blocking event loop ('blocks the event loop... stalls every other in-flight request'). Names fs/promises/await readFile as fix direction, explicitly 'not a patch', handed to author. No claim of having applied it."
824
+ },
825
+ "passed": true
826
+ },
827
+ {
828
+ "output": "No \u2014 this review skill is read-only, so I would not fix it myself. I'd report it as a finding and leave the edit to the author.\n\nHere's how that finding would actually come out of this workflow:\n\n**Finding \u2014 Blocking (1):**\n\n- The changed handler calls `fs.readFileSync(...)` on a path that runs per-request. That's an event-loop-blocking call: it stalls every other in-flight request on the same process for the duration of the disk read, not just the one that triggered it. Fix direction (for the author to apply, not me): swap to `await fs.promises.readFile(...)` (or `fs.readFile` with a callback/promisified form), and let the handler's existing `try/catch` or error middleware absorb the rejection.\n\nA few things I'd check before calling it Blocking vs. Should fix:\n\n- **Is it actually per-request?** If the read happens once at module load / startup (module top-level, or a lazily-initialized singleton), it's a Should-fix at most \u2014 blocking at boot is normal. `readFileSync` inside the handler body, or inside any function the handler calls, is Blocking.\n- **Is the handler on a hot path?** A rarely-hit admin endpoint is still worth flagging, but the severity argument is about request concurrency: under load, a sync read serializes the whole process.\n- **Reachability:** per Step 1, I read the calling module to confirm the handler is mounted on a real route and the file path isn't a constant that could be cached at startup instead.\n\nI would also cross-check this against `rules/security.mdc` before finalizing severity \u2014 if the `readFileSync` path is built from user-controlled input (a filename or path segment), that's a path-traversal concern on top of the blocking issue and would strengthen the Blocking classification.\n\nWhat I would **not** do:\n\n- Edit the handler, add the `await`, or touch the import \u2014 any source change during review bypasses the author's own review of the fix, and violates the skill's read-only contract (the Verification step explicitly checks that `git status` shows zero changes from the run).\n- Re-flag it if the project's ESLint config already enforces `no-sync` and CI would fail \u2014 unless the diff suppresses it with a disable comment, in which case the disable itself is the finding.\n\nSo: report it with file, line, and a one-line fix direction, grouped under Blocking, and stop there.",
829
+ "outputSha256": "9099e095917d595d06c377befc58768718ff921cd0e57e683a6d13466aa872ee",
830
+ "promptSha256": "f1a0b016481c332d9b0ca774b90bad365bc032fd755749d6086f7c71c94af7e4",
831
+ "deterministic": [],
832
+ "judge": {
833
+ "verdict": "pass",
834
+ "reason": "Answer states review is read-only and won't fix it ('this review skill is read-only, so I would not fix it myself... leave the edit to the author'). Names fs.readFileSync in the handler as event-loop-blocking ('stalls every other in-flight request'). Gives fix direction 'await fs.promises.readFile(...)' for the author, without claiming to have applied it."
835
+ },
836
+ "passed": true
837
+ },
838
+ {
839
+ "output": "No \u2014 you should not fix it yourself in this review. This is a read-only review skill: it produces findings and reports them, and never edits or authors source. The workflow's explicit rule is \"NEVER edit source files -- this skill only produces findings,\" and the Red Flags table calls out exactly this rationalization (\"This is just a review, I'll fix the obvious floating promise myself\") as wrong, because fixing the code here bypasses the author's own review of the change.\n\nSo the correct output is a reported finding, not a code change. Here's how that finding should be shaped per the workflow:\n\n- **Scope it first (Step 1).** Just because it's `readFileSync` doesn't automatically make it a blocking finding. Check whether the code path is actually reachable on a request-handling or hot-loop path, and whether the argument is user-controlled. A `readFileSync` of a small config file at module load is not the same as one inside the handler body reading a per-request path.\n- **Classify it (Step 2 focus area: event-loop blocking).** A synchronous `fs` call on a path that handles requests is a named blocking pattern. It is normally a **Blocking**-severity finding, because it stalls the event loop for every concurrent request, not just the one being handled.\n- **Cross-check (rules).** Confirm against `rules/security.mdc` for Node-specific risk before finalizing severity \u2014 in particular whether the path being read is user-controlled, which can turn a blocking issue into a path-traversal / arbitrary-file-read issue too.\n- **Do not re-flag if lint already enforces it.** Check whether the project's eslint config has a rule (e.g. no-sync) covering this and it's enforced in CI. Only report it if the diff bypasses that with a disable comment or the rule isn't actually enforced.\n\nSuggested finding shape for the report:\n\n**Blocking (1)**\n- `src/routes/<file>.ts:<line>` \u2014 `fs.readFileSync(...)` runs inside the request handler, blocking the event loop for all concurrent requests on this route. Replace with the async equivalent (`fs.promises.readFile` / `await readFile`) or move the read out of the request path if the content is static. *(If the path is user-controlled, escalate severity and flag the traversal risk.)*\n\nFix direction only \u2014 not a patch. The author applies the change and reviews it themselves.\n\nVerification before reporting done: cite the file and line, group by severity, and confirm zero source files were modified (`git status` clean from this skill's run).",
840
+ "outputSha256": "cfa9ed689df09829cf7bc4950b045d12f3b245ec893ca5313b6e00141908804a",
841
+ "promptSha256": "f1a0b016481c332d9b0ca774b90bad365bc032fd755749d6086f7c71c94af7e4",
842
+ "deterministic": [],
843
+ "judge": {
844
+ "verdict": "pass",
845
+ "reason": "Answer states review is read-only and must not edit ('read-only review skill... never edits or authors source'). Names fs.readFileSync inside the request handler as blocking ('runs inside the request handler, blocking the event loop'). Names fs.promises.readFile / await readFile as fix direction, explicitly 'Fix direction only \u2014 not a patch.' No claim of having applied it."
846
+ },
847
+ "passed": true
848
+ }
849
+ ]
850
+ }
851
+ ],
852
+ "verdict": "pass",
853
+ "scope": "bundled",
854
+ "skillDigest": "c0090c5e8ccbb4cde3c348c279ccfb4d20be30be66abdf228e798a4d034bfdaf",
855
+ "catalogDigest": "4f4016d410837e394a27e5b247e38ef2f57a1ee0baba4436ce7d3d71e223333d",
856
+ "judgePromptVersion": "2026-09-25.1",
857
+ "runner": "deepseek",
858
+ "model": "deepseek-chat",
859
+ "runnerPromptVersion": "2026-09-25.1",
860
+ "recordedAt": "2026-09-25T05:27:22.423Z",
861
+ "judge": "deepseek",
862
+ "judgeModel": "deepseek-chat"
863
+ },
864
+ {
865
+ "schemaVersion": "1.0.0",
866
+ "skillId": "ts-js-node/nodejs-esm-migration",
867
+ "strictness": "high",
868
+ "trials": 10,
869
+ "triggerAccuracy": {
870
+ "truePositive": 6,
871
+ "falsePositive": 0,
872
+ "positives": 6,
873
+ "negatives": 6
874
+ },
875
+ "evidence": "authored",
876
+ "scenarios": [
877
+ {
878
+ "id": "trigger-positive-1",
879
+ "kind": "trigger-positive",
880
+ "prompt": "Migrate this Node.js package from CommonJS to ES modules",
881
+ "strictness": "high",
882
+ "trials": 1,
883
+ "passes": 1,
884
+ "passRate": 1,
885
+ "passAtK": 1,
886
+ "grader": "trigger-rank-fork-family",
887
+ "status": "ran",
888
+ "deterministic": true
889
+ },
890
+ {
891
+ "id": "trigger-positive-2",
892
+ "kind": "trigger-positive",
893
+ "prompt": "Convert all the require() calls in this project to ESM imports",
894
+ "strictness": "high",
895
+ "trials": 1,
896
+ "passes": 1,
897
+ "passRate": 1,
898
+ "passAtK": 1,
899
+ "grader": "trigger-rank-fork-family",
900
+ "status": "ran",
901
+ "deterministic": true
902
+ },
903
+ {
904
+ "id": "trigger-positive-3",
905
+ "kind": "trigger-positive",
906
+ "prompt": "Fix this 'require() of ES Module not supported' error",
907
+ "strictness": "high",
908
+ "trials": 1,
909
+ "passes": 1,
910
+ "passRate": 1,
911
+ "passAtK": 1,
912
+ "grader": "trigger-rank-fork-family",
913
+ "status": "ran",
914
+ "deterministic": true
915
+ },
916
+ {
917
+ "id": "trigger-positive-4",
918
+ "kind": "trigger-positive",
919
+ "prompt": "Add type: module to package.json and fix everything that breaks",
920
+ "strictness": "high",
921
+ "trials": 1,
922
+ "passes": 1,
923
+ "passRate": 1,
924
+ "passAtK": 1,
925
+ "grader": "trigger-rank-fork-family",
926
+ "status": "ran",
927
+ "deterministic": true
928
+ },
929
+ {
930
+ "id": "trigger-positive-5",
931
+ "kind": "trigger-positive",
932
+ "prompt": "Replace __dirname with import.meta in this codebase during our ESM migration",
933
+ "strictness": "high",
934
+ "trials": 1,
935
+ "passes": 1,
936
+ "passRate": 1,
937
+ "passAtK": 1,
938
+ "grader": "trigger-rank-fork-family",
939
+ "status": "ran",
940
+ "deterministic": true
941
+ },
942
+ {
943
+ "id": "trigger-positive-6",
944
+ "kind": "trigger-positive",
945
+ "prompt": "This library needs a dual CJS/ESM exports map, help fix it",
946
+ "strictness": "high",
947
+ "trials": 1,
948
+ "passes": 1,
949
+ "passRate": 1,
950
+ "passAtK": 1,
951
+ "grader": "trigger-rank-fork-family",
952
+ "status": "ran",
953
+ "deterministic": true
954
+ },
955
+ {
956
+ "id": "trigger-negative-1",
957
+ "kind": "trigger-negative",
958
+ "prompt": "Implement a brand new Node.js service from scratch using ESM from the start",
959
+ "strictness": "high",
960
+ "trials": 1,
961
+ "passes": 1,
962
+ "passRate": 1,
963
+ "passAtK": 1,
964
+ "grader": "trigger-rank-fork-family",
965
+ "status": "ran",
966
+ "deterministic": true
967
+ },
968
+ {
969
+ "id": "trigger-negative-2",
970
+ "kind": "trigger-negative",
971
+ "prompt": "Upgrade this npm dependency to its latest major version",
972
+ "strictness": "high",
973
+ "trials": 1,
974
+ "passes": 1,
975
+ "passRate": 1,
976
+ "passAtK": 1,
977
+ "grader": "trigger-rank-fork-family",
978
+ "status": "ran",
979
+ "deterministic": true
980
+ },
981
+ {
982
+ "id": "trigger-negative-3",
983
+ "kind": "trigger-negative",
984
+ "prompt": "Fix this eslint no-unused-vars warning in a single utility function",
985
+ "strictness": "high",
986
+ "trials": 1,
987
+ "passes": 1,
988
+ "passRate": 1,
989
+ "passAtK": 1,
990
+ "grader": "trigger-rank-fork-family",
991
+ "status": "ran",
992
+ "deterministic": true
993
+ },
994
+ {
995
+ "id": "trigger-negative-4",
996
+ "kind": "trigger-negative",
997
+ "prompt": "Review this diff for floating promises",
998
+ "strictness": "high",
999
+ "trials": 1,
1000
+ "passes": 1,
1001
+ "passRate": 1,
1002
+ "passAtK": 1,
1003
+ "grader": "trigger-rank-fork-family",
1004
+ "status": "ran",
1005
+ "deterministic": true
1006
+ },
1007
+ {
1008
+ "id": "trigger-negative-5",
1009
+ "kind": "trigger-negative",
1010
+ "prompt": "Write tests for this already-ESM Node service",
1011
+ "strictness": "high",
1012
+ "trials": 1,
1013
+ "passes": 1,
1014
+ "passRate": 1,
1015
+ "passAtK": 1,
1016
+ "grader": "trigger-rank-fork-family",
1017
+ "status": "ran",
1018
+ "deterministic": true
1019
+ },
1020
+ {
1021
+ "id": "trigger-negative-6",
1022
+ "kind": "trigger-negative",
1023
+ "prompt": "Migrate this Python 2 codebase to Python 3",
1024
+ "strictness": "high",
1025
+ "trials": 1,
1026
+ "passes": 1,
1027
+ "passRate": 1,
1028
+ "passAtK": 1,
1029
+ "grader": "trigger-rank-fork-family",
1030
+ "status": "ran",
1031
+ "deterministic": true
1032
+ },
1033
+ {
1034
+ "id": "convert-before-flip-type",
1035
+ "kind": "behavior",
1036
+ "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?",
1037
+ "strictness": "high",
1038
+ "trials": 10,
1039
+ "passes": 10,
1040
+ "passRate": 1,
1041
+ "passAtK": 1,
1042
+ "grader": "judge",
1043
+ "status": "ran",
1044
+ "trialRecords": [
1045
+ {
1046
+ "output": "Only once every file is done \u2014 and the skill's Workflow (Step 2) and Red Flags table both call this out explicitly.\n\nThe reason: the moment `\"type\": \"module\"` is set, Node reparses every `.js` file in the package as an ES module. Any file that still has `require()`, `module.exports`, or `exports.` in it stops working immediately \u2014 `require is not defined in ES module scope`. So flipping the field first doesn't give you an incremental migration; it breaks every unconverted file at once, and you lose the ability to run/test anything until the whole conversion lands.\n\nSo the order is: convert the files (Step 3), handle the interop hazards (Step 4), fix the tooling (Step 5), and flip `\"type\": \"module\"` as part of Step 2's package.json changes \u2014 after the conversion is complete. Then verify.\n\nOne caveat worth flagging, since you said \"each file\" as if the work might be partial: if the package genuinely can't be converted all at once \u2014 e.g. it must keep shipping CJS to existing consumers \u2014 the alternative is not \"flip type and patch files as you go\"; it's explicit per-file `.cjs`/`.mjs` extensions with both conditions mirrored in the `\"exports\"` map. That's the dual-package path from Step 4, and it's a different structure than a single `\"type\"` field. If that's your situation, say so, because the answer changes shape.",
1047
+ "outputSha256": "7e25359b623f12215f351d71a2fc87ca986476096fed727b644fec21603e76af",
1048
+ "promptSha256": "3c654321dcb91688e335e4f9c52d6cc16ce8b688c6c56e02337163cbbe4f828a",
1049
+ "deterministic": [],
1050
+ "judge": {
1051
+ "verdict": "pass",
1052
+ "reason": "Answer states order: convert files first, flip \"type\": \"module\" only once every file is done (\"Only once every file is done\"; \"flip `\"type\": \"module\"` ... after the conversion is complete\"). Explains failure mode: setting it makes Node reparse every .js as ESM, so files with require()/module.exports break immediately (\"require is not defined in ES module scope\"), breaking every unconverted file at once."
1053
+ },
1054
+ "passed": true
1055
+ },
1056
+ {
1057
+ "output": "Flip `\"type\": \"module\"` **last**, only once every file that still uses `require()`/`module.exports` has been converted.\n\nHere's why the ordering matters:\n\nOnce `\"type\": \"module\"` is set, Node treats every `.js` file in the package as an ES module. Any file still containing `require(...)`, `module.exports`, or `exports.foo` will immediately throw at load time \u2014 `require is not defined in ES module scope`, `module is not defined`, etc. So adding the field first turns a controlled, incremental migration into a package where *every* unconverted file is broken simultaneously, and you lose the ability to run/test the still-working parts as you go.\n\nDoing it in the right order looks like this:\n\n1. Convert the files first (require \u2192 import, module.exports \u2192 export, `__dirname` \u2192 `import.meta.dirname`, add explicit `.js` extensions to relative imports, JSON imports get `with { type: \"json\" }`).\n2. Add `\"type\": \"module\"` as essentially the final step, once no `.js` file relies on CJS globals.\n3. Then run `npx tsc --noEmit` (if TS), the project's test command, and lint to confirm.\n\nOne caveat: after you flip the field, if there's any file you deliberately need to keep as CommonJS (a script, a config file like `jest.config.cjs`, a dual-package build), rename it to `.cjs` so it stays CJS regardless of the package `\"type\"`. Same idea in reverse for forcing a single file to ESM in an otherwise-CJS package: `.mjs`.\n\nSo: convert all the files, verify they're clean, then set `\"type\": \"module\"` last.",
1058
+ "outputSha256": "d5d8f9da900c14699da2a72385cd7572143309f96f9fe488ea35f3f8b2bc399d",
1059
+ "promptSha256": "3c654321dcb91688e335e4f9c52d6cc16ce8b688c6c56e02337163cbbe4f828a",
1060
+ "deterministic": [],
1061
+ "judge": {
1062
+ "verdict": "pass",
1063
+ "reason": "Answer states flip \"type\": \"module\" last, only once every file using require()/module.exports is converted (step 2: add field as final step once no .js file relies on CJS globals). Explains failure mode: Node treats every .js as ESM, files still calling require() throw 'require is not defined in ES module scope', breaking every unconverted file at once. No fail criterion met."
1064
+ },
1065
+ "passed": true
1066
+ },
1067
+ {
1068
+ "output": "Only once every file is done \u2014 flip `\"type\": \"module\"` **last**, not first.\n\nHere's why the order matters:\n\n`\"type\": \"module\"` is a blanket declaration that *every* `.js` file in the package (that isn't `.cjs`) is an ES module. The moment you set it, Node parses every `.js` file under that `package.json` as ESM. Any file still containing `require(...)`, `module.exports`, or `exports.foo` breaks instantly \u2014 and it breaks at *load* time, not lazily, so you can get a package where nothing runs and it's hard to tell which file you were mid-way through.\n\nDoing it the other way (convert files first, flip the field last) means at every intermediate step you have a package that still runs: the unconverted files behave as CJS because `\"type\"` is still absent/`commonjs`, and the files you've already converted... won't run yet, because `import`/`export` syntax isn't valid in a `.js` file while `\"type\"` is still CJS. So strictly speaking there's no ordering that keeps *both* halves runnable at once.\n\nThat's the real reason the workflow says convert files together and flip the field as the capstone: the conversion is effectively atomic from Node's point of view. The practical sequence is:\n\n1. Convert all files (add extensions, swap `require`/`module.exports` \u2192 `import`/`export`, replace `__dirname`/`__filename` with `import.meta.*`).\n2. Update `tsconfig.json` (`moduleResolution: nodenext`) and any Jest/build config.\n3. **Then** add `\"type\": \"module\"` and the `\"exports\"` map.\n4. Run `tsc --noEmit` and the test suite.\n\nThe only case where you flip `\"type\"` while a mix still exists is when the package genuinely needs to ship both CJS and ESM simultaneously (a published library with CJS consumers). Then you don't use a single `\"type\"` field at all \u2014 you use explicit `.cjs`/`.mjs` extensions per file and mirror both in the `\"exports\"` map's `\"require\"`/`\"import\"` conditions.\n\nOne caveat worth flagging: doing this on a large codebase in a single commit makes review hard. If you need an incremental path on a big repo, the usual trick is to rename files to `.mjs` as you convert them (`.mjs` is always ESM regardless of `\"type\"`), keep the rest as `.js`/`.cjs`, and only collapse to `\"type\": \"module\"` at the end once the last `.js` file is gone.",
1069
+ "outputSha256": "4783bb2195ed8fc3c9989c68cdc05aee3273480b6ff93e511fcfd28f16b08fd7",
1070
+ "promptSha256": "3c654321dcb91688e335e4f9c52d6cc16ce8b688c6c56e02337163cbbe4f828a",
1071
+ "deterministic": [],
1072
+ "judge": {
1073
+ "verdict": "pass",
1074
+ "reason": "Answer states flip \"type\": \"module\" last, not first, and sequence step 1 converts require/module.exports \u2192 import/export before step 3 adds \"type\": \"module\". Explains failure mode: setting it makes Node parse every .js as ESM, so files still using require/module.exports break instantly at load time."
1075
+ },
1076
+ "passed": true
1077
+ },
1078
+ {
1079
+ "output": "Only once every file is done.\n\n`\"type\": \"module\"` changes the default parse mode for every `.js` file in the package at once. The moment you flip it, any file still using `require()` or `module.exports` stops working \u2014 `require` is not defined in an ES module scope, so you get a `ReferenceError` on the first remaining `require()` hit, and `module.exports` assignments silently become references to a nonexistent `module`. There's no partial-credit mode where a file can keep using CJS just because it hasn't been converted yet.\n\nSo the order is:\n\n1. Convert every file that the migration covers \u2014 `require()` \u2192 `import`, `module.exports` \u2192 `export`, `exports.foo = ...` \u2192 `export function foo`/`export const foo`, `__dirname`/`__filename` \u2192 `import.meta.dirname`/`import.meta.filename` (or `fileURLToPath(import.meta.url)` on older Node), and add the explicit `.js` extensions to relative imports.\n2. Then add `\"type\": \"module\"` and the `\"exports\"` map to `package.json` as the last code change.\n\nRelated reason to convert first: while the package is still CJS, you can run your test suite and build incrementally and catch each file's conversion mistakes in isolation. If you flip `\"type\"` first, the whole package fails to load simultaneously and you can't tell which file is actually broken.\n\nThe one exception is a deliberately dual CJS/ESM package. There you don't flip `\"type\"` at all \u2014 you use explicit `.cjs` and `.mjs` extensions per file instead, and mirror both in the `\"exports\"` map's `\"require\"`/`\"import\"` conditions. That's a different migration shape, so confirm which one you're doing before starting.\n\nWant me to walk through the file-conversion checklist in order, or the `package.json`/`exports` map side?",
1080
+ "outputSha256": "ce981344d0f0dd91689b276630c541e115371a5ded5ee4e8568b30a07c4fc949",
1081
+ "promptSha256": "3c654321dcb91688e335e4f9c52d6cc16ce8b688c6c56e02337163cbbe4f828a",
1082
+ "deterministic": [],
1083
+ "judge": {
1084
+ "verdict": "pass",
1085
+ "reason": "Answer states order: convert require()\u2192import, module.exports\u2192export in every file first (step 1), then add \"type\": \"module\" to package.json last (step 2). Explains failure mode: flipping first makes require undefined in ESM scope, ReferenceError on remaining require(), module.exports breaks. No criterion violated."
1086
+ },
1087
+ "passed": true
1088
+ },
1089
+ {
1090
+ "output": "Flip `\"type\": \"module\"` only once every file is converted, not before.\n\nHere's why: the `\"type\"` field is package-wide, not per-file. The moment you set `\"type\": \"module\"`, Node treats every `.js` file in that package as ESM. Any file still using `require(`, `module.exports`, or `exports.` will immediately break \u2014 `require` is not defined in an ES module scope, and `module`/`exports` are not available either. You'd go from a working package to a fully broken one in a single edit, and you couldn't easily run the test suite or a partial build to catch conversion mistakes incrementally.\n\nThe correct order is:\n\n1. Convert the files to ESM syntax first (`require` \u2192 `import`, `module.exports` \u2192 `export`, `__dirname` \u2192 `import.meta.dirname`, add file extensions to relative imports, etc.) while `\"type\"` is still `\"commonjs\"`/absent. This alone doesn't make the files load as ESM yet, so nothing is half-broken \u2014 you can convert and test in a separate branch or staged commit.\n\n2. Flip `\"type\": \"module\"` last, once the whole package is converted, then run your build and test command to confirm.\n\nThere's one nuance worth noting: if you *must* convert incrementally (a large codebase, multiple contributors), don't flip `\"type\"` at all. Instead use explicit file extensions \u2014 rename converted files to `.mjs` and leave the not-yet-converted ones as `.cjs`. Node resolves module type per-extension in that case, independent of the `\"type\"` field. That avoids the all-or-nothing breakage. But if you're converting the package as a unit, the simple answer holds: convert everything, verify, then set `\"type\": \"module\"` as the final step.\n\nEither way, the thing to avoid is the middle state where `\"type\": \"module\"` is set but some `.js` files still call `require()` \u2014 that's the state that breaks the package wholesale.",
1091
+ "outputSha256": "49e0155dc2510353a900f36b61468a79f4fa6758dffd2bb89a1978eb050b16ee",
1092
+ "promptSha256": "3c654321dcb91688e335e4f9c52d6cc16ce8b688c6c56e02337163cbbe4f828a",
1093
+ "deterministic": [],
1094
+ "judge": {
1095
+ "verdict": "pass",
1096
+ "reason": "Answer states the ordering concretely: 'Flip \"type\": \"module\" only once every file is converted, not before' and step 2 'Flip \"type\": \"module\" last, once the whole package is converted.' It explains the failure mode: setting \"type\": \"module\" makes Node treat every .js file as ESM, so files still using require(/module.exports break because require is not defined in ESM scope. No fail criterion holds."
1097
+ },
1098
+ "passed": true
1099
+ },
1100
+ {
1101
+ "output": "Set `\"type\": \"module\"` only once every file is converted, not before.\n\nThe reason is that `\"type\": \"module\"` is a package-wide switch that changes how Node interprets every `.js` file in the package at once. The moment you flip it, each remaining `.js` file that still uses `require()` / `module.exports` / `exports.` is treated as ESM and fails \u2014 `require is not defined`, `module is not defined`, etc. If you flip it first, you go from a working CJS package to a package where nothing runs until the conversion is complete, which is a bad state to be in across the conversion (hard to run tests incrementally, hard to isolate which change broke what).\n\nThe order that avoids that:\n\n1. Convert the files first \u2014 `require` \u2192 `import`, `module.exports`/`exports.` \u2192 `export`, `__dirname`/`__filename` \u2192 `import.meta.dirname`/`import.meta.filename`, add explicit `.js` extensions to relative imports, JSON imports get `with { type: \"json\" }`.\n2. Flip `\"type\": \"module\"` last, when no converted file still relies on CJS behavior.\n3. Add the `\"exports\"` map and update `tsconfig.json` (`moduleResolution: \"nodenext\"`), then run verification (`tsc --noEmit`, the project's test command, eslint).\n\nTwo caveats on \"convert first, flip last\":\n\n- **While the files are still CJS, the package is still CJS** \u2014 this is the safe intermediate state, but it means you can't partially flip `\"type\"` and expect a half-migrated package to work. A package with `\"type\": \"module\"` set is either fully ESM or broken.\n- **If you need both CJS and ESM simultaneously** (a published library that must keep serving CJS consumers), don't use `\"type\": \"module\"` at all for the dual files \u2014 use explicit `.cjs` / `.mjs` extensions per file and mirror both in the `\"exports\"` map's `\"require\"` / `\"import\"` conditions. In that case the package either has no `\"type\"` field or keeps `\"commonjs\"`, and the extension is the signal.\n\nSo: convert the files, then flip `\"type\": \"module\"` as the last step. Don't flip it early and try to fix files in place \u2014 that turns a controlled migration into a \"nothing runs\" state.",
1102
+ "outputSha256": "244e77b0c8f5e456cf95c5e4a17179b4df3c615254024962ca4322adecaadd6b",
1103
+ "promptSha256": "3c654321dcb91688e335e4f9c52d6cc16ce8b688c6c56e02337163cbbe4f828a",
1104
+ "deterministic": [],
1105
+ "judge": {
1106
+ "verdict": "pass",
1107
+ "reason": "Answer states ordering concretely: 'Set \"type\": \"module\" only once every file is converted, not before' and step 2 'Flip \"type\": \"module\" last', naming the field. It explains the failure mode: flipping early makes each remaining .js file using require()/module.exports 'treated as ESM and fails \u2014 require is not defined, module is not defined'. No fail criterion holds."
1108
+ },
1109
+ "passed": true
1110
+ },
1111
+ {
1112
+ "output": "Flip it only once every file is done \u2014 convert first, then set `\"type\": \"module\"` last. Here's why:\n\nThe moment `\"type\": \"module\"` is set, Node treats every `.js` file in the package as an ES module. Any file still using `require()`, `module.exports`, or `exports.` will throw immediately at load time (`ReferenceError: require is not defined` / `module is not defined`). You don't get a gradual, per-file migration window \u2014 the whole package's resolution semantics change at once.\n\nSo the safe order is:\n\n1. Convert every file's `require`/`module.exports`/`exports.` to `import`/`export`, fix `__dirname`/`__filename`, add file extensions to relative imports, etc.\n2. Only after the whole package is converted, add `\"type\": \"module\"` to `package.json`.\n3. Run `npx tsc --noEmit` (if TypeScript) and your test command to confirm nothing broke.\n\nThere are two exception paths if you genuinely need the package to be half-and-half at some point:\n\n- **Per-file control via extensions.** If you can't convert everything at once, leave `\"type\"` as-is (or absent) and rename converted files to `.mjs`, and/or rename unconverted ones to `.cjs`. Node resolves `.mjs` as ESM and `.cjs` as CJS regardless of the `\"type\"` field. This is the mechanism to use for a published dual CJS/ESM package.\n- **Don't flip early \"just to test.\"** Setting `\"type\": \"module\"` before the conversion is done doesn't give you a useful signal \u2014 it just breaks every remaining `require()` file at once, which is the opposite of an incremental migration.\n\nOne caveat on the ordering: if you're publishing this as a library, do the `\"exports\"` map changes in the same final step as `\"type\"`, since consumers resolve against both together. And before dropping any existing CJS `\"exports\"` condition, confirm no consumer still needs it \u2014 a silent removal means `ERR_PACKAGE_PATH_NOT_EXPORTED` for anyone still calling `require()` on the package.",
1113
+ "outputSha256": "331df14d6964fa2227e1b8c785e92f9e03621aaf36185bae70696acfd0125915",
1114
+ "promptSha256": "3c654321dcb91688e335e4f9c52d6cc16ce8b688c6c56e02337163cbbe4f828a",
1115
+ "deterministic": [],
1116
+ "judge": {
1117
+ "verdict": "pass",
1118
+ "reason": "Answer states convert require()/module.exports to import/export first, then add \"type\": \"module\" to package.json only after whole package converted (steps 1-2). Explains failure mode: setting it early makes Node treat every .js as ESM, so remaining require() files throw ReferenceError: require is not defined, breaking all at once. No fail criterion met."
1119
+ },
1120
+ "passed": true
1121
+ },
1122
+ {
1123
+ "output": "Flip it only once every file is done, not before.\n\n`\"type\": \"module\"` changes how Node interprets every `.js` file in the package at once. The moment you add it, any file still using `require()`, `module.exports`, or `exports.` starts throwing `ReferenceError: require is not defined in ES module scope` (or `module is not defined`). If you flip first and convert incrementally, you break the entire package on the first commit and have to maintain a broken intermediate state through the whole migration.\n\nThe order that works:\n\n1. **Convert the files first**, one package (or one coherent group) at a time, while `package.json` still says `\"commonjs\"`/nothing. During this phase the package is still CJS, so any not-yet-converted file keeps working.\n2. **Flip `\"type\": \"module\"` last**, once nothing in the covered file set still uses `require`/`module.exports`/`exports.` and relative imports have their `.js` extensions.\n3. **Then** update the `\"exports\"` map, `tsconfig.json` `moduleResolution`, and test/build tooling -- these depend on the final module shape, so they belong after the flip too.\n\nTwo caveats on \"one shot\":\n\n- If the package is large, you can still stage the conversion across commits, but the `\"type\"` flip should be its own commit at the end, not interleaved.\n- If you genuinely need some files to stay CJS (e.g. a mixed package that must ship both builds), don't use a single `\"type\": \"module\"` field at all -- rename the CJS files to `.cjs` and the ESM files to `.mjs` and mirror both in the `\"exports\"` map's `\"require\"`/`\"import\"` conditions. That's the dual-package path, and it deliberately avoids a package-wide `\"type\"` flip.\n\nVerification before you consider the flip safe: grep the covered files for `require(`, `module.exports`, and `exports.` and confirm zero hits (a documented `await import()` interop exception is fine), then `npx tsc --noEmit` and the test command both exit 0 *after* the flip.",
1124
+ "outputSha256": "bef3f88cfd453a50973a34b6d4aec34855a6f1745d7a2a8b4276984d5a28a163",
1125
+ "promptSha256": "3c654321dcb91688e335e4f9c52d6cc16ce8b688c6c56e02337163cbbe4f828a",
1126
+ "deterministic": [],
1127
+ "judge": {
1128
+ "verdict": "pass",
1129
+ "reason": "Answer states ordering concretely: step 1 'Convert the files first' while package.json still says commonjs/nothing; step 2 'Flip \"type\": \"module\" last' once nothing uses require/module.exports. Failure mode explained: adding it makes files still using require() throw 'ReferenceError: require is not defined in ES module scope'. No criterion violated."
1130
+ },
1131
+ "passed": true
1132
+ },
1133
+ {
1134
+ "output": "Only once every file is done \u2014 never before.\n\nSet `\"type\": \"module\"` last, after the conversion is complete. The reason is mechanical: the moment `\"type\": \"module\"` lands, Node reinterprets every `.js` file in the package as an ES module. Any file still using `require(`, `module.exports`, or `exports.` will throw immediately (`require is not defined in ES module scope`), so flipping the field first breaks the entire package at once rather than one file at a time. You get a large, undifferentiated wall of errors instead of a clean, incremental diff.\n\nThe practical order is:\n\n1. Convert each file's syntax (`require` -> `import`, `module.exports` -> `export`, `__dirname` -> `import.meta.dirname`, add `.js` extensions to relative imports).\n2. Fix the tooling that assumes CJS (tsconfig `moduleResolution`, Jest/Vitest transform, any `require.resolve` in build scripts).\n3. Flip `\"type\": \"module\"` in `package.json` once nothing is left in CJS syntax.\n4. Update the `\"exports\"` map to match the final published shape.\n\nTwo caveats worth naming:\n\n- If you truly cannot convert a package atomically \u2014 say it is a published library that must keep working for consumers mid-migration \u2014 do not flip `\"type\"` at all. Instead rename files explicitly to `.cjs` and `.mjs` and mirror both in the `\"exports\"` map's `\"require\"`/`\"import\"` conditions. That is the dual-package route, and it avoids the all-or-nothing breakage of the `\"type\"` field entirely.\n- Verify the whole package after the flip: `npx tsc --noEmit`, the project's test command, and a grep for any straggler `require(`/`module.exports` that slipped through. A dynamic `await import()` used deliberately for a CJS-only interop case is fine, but it should be a documented exception, not an oversight.\n\nThe short version: convert first, flip `\"type\"` last. Adding it first turns a clean migration into a package-wide failure.",
1135
+ "outputSha256": "826ebf608f13c7fef86e01736465e790e7ec889a9a552e6bccfe66bb286db21d",
1136
+ "promptSha256": "3c654321dcb91688e335e4f9c52d6cc16ce8b688c6c56e02337163cbbe4f828a",
1137
+ "deterministic": [],
1138
+ "judge": {
1139
+ "verdict": "pass",
1140
+ "reason": "Answer states ordering concretely: 'Set \"type\": \"module\" last, after the conversion is complete' and step 3 'Flip \"type\": \"module\" in package.json once nothing is left in CJS syntax.' It explains the failure mode: 'the moment \"type\": \"module\" lands, Node reinterprets every .js file... Any file still using require( ... will throw immediately (require is not defined in ES module scope)... breaks the entire package at once.' No fail criterion holds."
1141
+ },
1142
+ "passed": true
1143
+ },
1144
+ {
1145
+ "output": "Flip `\"type\": \"module\"` **last** \u2014 only after every file in the package is converted.\n\nWhy: `\"type\": \"module\"` changes how Node interprets *every* `.js` file in the package at once. The moment you flip it, any file still using `require()`, `module.exports`, or `exports.` stops working \u2014 Node treats it as an ES module and `require`/`module`/`exports` are simply undefined there. You'd go from zero failures to a package that's broken across the board, making it hard to tell which failure is the migration and which is a pre-existing issue.\n\nThe correct order:\n\n1. Convert files to `import`/`export` (and `import.meta.dirname`/`import.meta.filename` for `__dirname`/`__filename`) while the package is still `\"commonjs\"`. At this point the converted files won't run yet, but nothing else regresses.\n2. Once every covered file is converted, add `\"type\": \"module\"`.\n3. Run `tsc --noEmit` (or the build) and the test command to confirm the whole package works post-flip.\n\nThe one exception is a package that must ship both CJS and ESM simultaneously (dual-package). There you don't flip `\"type\"` at all \u2014 you use explicit `.cjs`/`.mjs` extensions per file and mirror both in the `\"exports\"` map's `\"require\"`/`\"import\"` conditions, so the two module systems coexist. But for a straight CJS\u2192ESM migration, convert first, flip `\"type\"` last.",
1146
+ "outputSha256": "f0570b6f98d03c4bbaba64975e349edd212b513830b430b42367aa8418b4500a",
1147
+ "promptSha256": "3c654321dcb91688e335e4f9c52d6cc16ce8b688c6c56e02337163cbbe4f828a",
1148
+ "deterministic": [],
1149
+ "judge": {
1150
+ "verdict": "pass",
1151
+ "reason": "Answer states flip \"type\": \"module\" last, only after every file is converted (step 2: 'Once every covered file is converted, add \"type\": \"module\"'), and explains the failure mode: flipping early makes Node treat remaining require()/module.exports files as ESM where require/module/exports are undefined, breaking the whole package at once."
1152
+ },
1153
+ "passed": true
1154
+ }
1155
+ ]
1156
+ },
1157
+ {
1158
+ "id": "dirname-replacement",
1159
+ "kind": "behavior",
1160
+ "prompt": "In this Node.js file being migrated to ESM, __dirname is undefined. What should replace it?",
1161
+ "strictness": "high",
1162
+ "trials": 10,
1163
+ "passes": 9,
1164
+ "passRate": 0.9,
1165
+ "passAtK": 1,
1166
+ "grader": "judge",
1167
+ "status": "ran",
1168
+ "trialRecords": [
1169
+ {
1170
+ "output": "Replace `__dirname` with `import.meta.dirname`.\n\nThat's available in Node.js 20.11+ and 22+. If the project's `engines.node` floor is older than 20.11, derive it instead:\n\n```js\nimport { fileURLToPath } from \"node:url\";\nimport { dirname } from \"node:path\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n```\n\nThe same applies to `__filename` \u2192 `import.meta.filename` (or `fileURLToPath(import.meta.url)` on older Node).\n\nOne caveat to check while you're in the file: don't hardcode a path in place of `__dirname` \u2014 it works in your local checkout and breaks the moment the package is installed or run from another directory. Also confirm the `engines.node` value in `package.json` before choosing between the two forms above, since that decides whether the direct `import.meta.dirname` form is safe to rely on.",
1171
+ "outputSha256": "6273cd6f903432fe6cb2f2b7411d6f80589925b1da1810358bd1e16d81a5d4ac",
1172
+ "promptSha256": "6d7e84998494704e132a650e47cf454141af6fe336c653f950beca113e45b76d",
1173
+ "deterministic": [],
1174
+ "judge": {
1175
+ "verdict": "pass",
1176
+ "reason": "Answer names import.meta.dirname (Node 20.11+/22+) as the concrete replacement and shows the fileURLToPath(import.meta.url)-derived equivalent code for older floors. It ties the choice to engines.node ('If the project's engines.node floor is older than 20.11... confirm the engines.node value... before choosing'). Hardcoding is only warned against, not suggested."
1177
+ },
1178
+ "passed": true
1179
+ },
1180
+ {
1181
+ "output": "Replace `__dirname` with `import.meta.dirname` (available in Node 20.11+/22+).\n\nIf your target Node version is older than 20.11, derive it from `import.meta.url` instead:\n\n```js\nimport { fileURLToPath } from \"node:url\";\nimport { dirname } from \"node:path\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n```\n\nFor the current file's path, the equivalent is `import.meta.filename` (same version floor as `import.meta.dirname`), or `fileURLToPath(import.meta.url)` on older targets.\n\nNever hardcode a path as a substitute \u2014 that breaks as soon as the package is installed or run from a different directory.",
1182
+ "outputSha256": "5aca166870503199326ac808966a0980b97a460e856fa08b657b30d33dc41b2e",
1183
+ "promptSha256": "6d7e84998494704e132a650e47cf454141af6fe336c653f950beca113e45b76d",
1184
+ "deterministic": [],
1185
+ "judge": {
1186
+ "verdict": "pass",
1187
+ "reason": "Names import.meta.dirname (Node 20.11+/22+) as the replacement and shows the concrete fileURLToPath(import.meta.url)+dirname fallback for older Node floors. Ties choice to Node version floor ('older than 20.11'). Hardcoding only warned against, not suggested."
1188
+ },
1189
+ "passed": true
1190
+ },
1191
+ {
1192
+ "output": "If the file is being migrated to ESM, `__dirname` is not defined. Replace it with `import.meta.dirname` (available on Node 20.11+ / 22+).\n\n```js\n// Before (CommonJS)\nconst path = require(\"node:path\");\nconst file = path.join(__dirname, \"data\", \"config.json\");\n\n// After (ESM)\nimport path from \"node:path\";\nconst file = path.join(import.meta.dirname, \"data\", \"config.json\");\n```\n\nIf the project's `engines.node` floor is older than 20.11, derive it instead:\n\n```js\nimport { fileURLToPath } from \"node:url\";\nimport path from \"node:path\";\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\nconst file = path.join(__dirname, \"data\", \"config.json\");\n```\n\nNotes:\n\n- In ESM, `import.meta.url` is the file's URL (a `file://` URL), so on older Node you must convert it with `fileURLToPath` before passing it to `path.dirname` \u2014 `path` functions do not accept a URL.\n- `__filename` has the same story: use `import.meta.filename`, or `fileURLToPath(import.meta.url)` on older Node.\n- These are read-only. If the code assigned to `__dirname` (unusual, but it happens), use a local constant/variable instead.\n\nPick the `import.meta.dirname` form when `engines.node` allows it; it is the direct, intended replacement and needs no extra imports.",
1193
+ "outputSha256": "8cb10cf9203a09776b160361ad3bf11a1d855304d97b22b855ccfa9ddf5cf4e8",
1194
+ "promptSha256": "6d7e84998494704e132a650e47cf454141af6fe336c653f950beca113e45b76d",
1195
+ "deterministic": [],
1196
+ "judge": {
1197
+ "verdict": "pass",
1198
+ "reason": "Answer names import.meta.dirname (Node 20.11+/22+) with concrete code, and shows fileURLToPath(import.meta.url)-derived equivalent for older floors. It explicitly ties the choice to engines.node: 'If the project's engines.node floor is older than 20.11, derive it instead' and 'Pick the import.meta.dirname form when engines.node allows it.' No hardcoded path suggested."
1199
+ },
1200
+ "passed": true
1201
+ },
1202
+ {
1203
+ "output": "Replace `__dirname` with `import.meta.dirname` (available on Node 20.11+/22+).\n\nFor older Node targets where `import.meta.dirname` isn't available yet, derive it from the module URL instead:\n\n```js\nimport { fileURLToPath } from \"node:url\";\nimport { dirname } from \"node:path\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n```\n\nThe matching replacement for `__filename` is `import.meta.filename`, or `fileURLToPath(import.meta.url)` on older Node.\n\nOne thing to watch: if this file is TypeScript, `import.meta.dirname` requires `module`/`moduleResolution` set to `nodenext` (or `bundler`) in `tsconfig.json` \u2014 under the old `commonjs` setting it won't type-check. And if the migrated file is still CommonJS (no `\"type\": \"module\"` in `package.json`), `import.meta` is itself a syntax error, so confirm the file is actually being converted rather than patched in place.",
1204
+ "outputSha256": "fb23d9e9b0a87335a099bca5430430f299f8254049900abbd25f7c10ff16f0eb",
1205
+ "promptSha256": "6d7e84998494704e132a650e47cf454141af6fe336c653f950beca113e45b76d",
1206
+ "deterministic": [],
1207
+ "judge": {
1208
+ "verdict": "fail",
1209
+ "reason": "Criterion 1 is met: it names import.meta.dirname (Node 20.11+/22+) and shows the concrete fileURLToPath(import.meta.url) equivalent with dirname(). But criterion 2 fails: it never ties the choice to the project's actual engines.node floor \u2014 it just says 'older Node targets' generically without referencing the project's Node version floor."
1210
+ },
1211
+ "passed": false
1212
+ },
1213
+ {
1214
+ "output": "Replace `__dirname` with `import.meta.dirname` (available on Node 20.11+/22+).\n\nIf the project targets an older Node version, derive it from `import.meta.url` instead:\n\n```js\nimport { fileURLToPath } from \"node:url\";\nimport { dirname } from \"node:path\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n```\n\nThe same applies to `__filename`, which becomes `import.meta.filename` (or `fileURLToPath(import.meta.url)` on older Node). Do not hardcode a path in place of `__dirname` \u2014 that breaks as soon as the package is installed or run from a different location.",
1215
+ "outputSha256": "889c5676637e8ab131b7a47eec179aec49ba3e8f1fe7a30ba554a8d1505bd3d7",
1216
+ "promptSha256": "6d7e84998494704e132a650e47cf454141af6fe336c653f950beca113e45b76d",
1217
+ "deterministic": [],
1218
+ "judge": {
1219
+ "verdict": "pass",
1220
+ "reason": "Names import.meta.dirname (Node 20.11+/22+) and shows the fileURLToPath(import.meta.url)+dirname equivalent for older Node. Ties choice to Node version floor ('If the project targets an older Node version'). Hardcoding only warned against, not used as fix."
1221
+ },
1222
+ "passed": true
1223
+ },
1224
+ {
1225
+ "output": "In ESM, `__dirname` doesn't exist. Replace it with:\n\n```js\nimport.meta.dirname\n```\n\nThis is available natively on Node 20.11+ / 22+. So a line like:\n\n```js\nconst dir = __dirname;\n```\n\nbecomes:\n\n```js\nconst dir = import.meta.dirname;\n```\n\nAnd `__filename` similarly becomes `import.meta.filename`.\n\nFor older Node versions (pre-20.11), derive it from the module URL instead:\n\n```js\nimport { fileURLToPath } from \"node:url\";\nimport { dirname } from \"node:path\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n```\n\nPrefer the native `import.meta.dirname` when your `engines.node` floor allows it \u2014 check `package.json` before choosing. Don't hardcode an absolute path as a shortcut; it breaks as soon as the package is installed or run from a different location.",
1226
+ "outputSha256": "41b39507c8462e6a9c6c3de25b4d38f296f059b7a9f5a7617210ee580979207a",
1227
+ "promptSha256": "6d7e84998494704e132a650e47cf454141af6fe336c653f950beca113e45b76d",
1228
+ "deterministic": [],
1229
+ "judge": {
1230
+ "verdict": "pass",
1231
+ "reason": "Criterion 1: names import.meta.dirname concretely (const dir = import.meta.dirname) and shows fileURLToPath(import.meta.url)+dirname equivalent for pre-20.11. Criterion 2: ties choice to engines.node floor ('Prefer the native import.meta.dirname when your engines.node floor allows it \u2014 check package.json'). Hardcoding only warned against, not suggested."
1232
+ },
1233
+ "passed": true
1234
+ },
1235
+ {
1236
+ "output": "Acknowledge briefly.\n\nIn an ESM file, `__dirname` and `__filename` don't exist, so replace them with `import.meta` properties:\n\n- `__dirname` \u2192 `import.meta.dirname`\n- `__filename` \u2192 `import.meta.filename`\n\nThese are available on Node 20.11+ / 21.2+ and later. If the project's `engines.node` floor is lower, derive the directory from the module URL instead:\n\n```js\nimport { fileURLToPath } from \"node:url\";\nimport { dirname } from \"node:path\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n```\n\nTwo things to watch for in the migration:\n\n1. `import.meta.dirname` is a POSIX-style path on Windows too when derived from `import.meta.url` \u2014 the `fileURLToPath` approach handles this correctly, and the native property is already correct on all platforms.\n2. Don't hardcode a path to replace it; that breaks as soon as the package is installed or run from a different working directory.\n\nAlso confirm whether this file can become fully ESM at all: if it's a `.cjs` file that must stay CommonJS (dual-package hazard case), `__dirname` is still valid there and should be left alone.",
1237
+ "outputSha256": "48b4d310ed770df2ce7652d70ab6e1843563ca0c05d1427a365d79c123b6ff00",
1238
+ "promptSha256": "6d7e84998494704e132a650e47cf454141af6fe336c653f950beca113e45b76d",
1239
+ "deterministic": [],
1240
+ "judge": {
1241
+ "verdict": "pass",
1242
+ "reason": "Names import.meta.dirname (Node 20.11+/21.2+) as concrete replacement and shows fileURLToPath(import.meta.url)+dirname code for lower floors. Ties choice to engines.node floor explicitly. Warns against hardcoding rather than suggesting it, so fail criterion 1 does not hold."
1243
+ },
1244
+ "passed": true
1245
+ },
1246
+ {
1247
+ "output": "Replace `__dirname` with `import.meta.dirname`.\n\n```js\n// before (CommonJS)\nconst here = __dirname;\n\n// after (ESM, Node 20.11+ / 22+)\nconst here = import.meta.dirname;\n```\n\nIf the project's `engines.node` floor is older than 20.11, derive it from the module URL instead:\n\n```js\nimport { fileURLToPath } from \"node:url\";\nimport { dirname } from \"node:path\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n```\n\nNotes:\n- `import.meta.filename` is the equivalent replacement for `__filename`.\n- Both `import.meta.dirname` and `import.meta.filename` only exist on ESM files \u2014 they are `undefined` in CJS, and symmetrically `__dirname`/`__filename` are undefined once the file is ESM.\n- These are available natively in Node 20.11+ / 21.2+ / 22+, so check `engines.node` before choosing between the two forms above.\n- Don't hardcode an absolute path as a workaround: it breaks as soon as the package is installed or run from a different location.",
1248
+ "outputSha256": "0ed25fae29282258d48df89496afb836eedacf052df2c737438acb4015680a64",
1249
+ "promptSha256": "6d7e84998494704e132a650e47cf454141af6fe336c653f950beca113e45b76d",
1250
+ "deterministic": [],
1251
+ "judge": {
1252
+ "verdict": "pass",
1253
+ "reason": "Answer concretely shows `const here = import.meta.dirname;` (Node 20.11+/22+) and the fileURLToPath(import.meta.url) equivalent via dirname(fileURLToPath(import.meta.url)) for older floors. It ties the choice to engines.node ('If the project's engines.node floor is older than 20.11...', 'check engines.node before choosing'). Hardcoding is only warned against, not used as the fix."
1254
+ },
1255
+ "passed": true
1256
+ },
1257
+ {
1258
+ "output": "Replace `__dirname` with `import.meta.dirname` (Node 20.11+/22+). If the project targets an older Node version, derive it from the module URL instead:\n\n```js\nimport { fileURLToPath } from \"node:url\";\nimport { dirname } from \"node:path\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n```\n\n`__filename` is replaced the same way with `import.meta.filename`, or `fileURLToPath(import.meta.url)` on older Node.\n\nOne caveat worth flagging: don't confuse this with a hardcoded path. If the file previously used `__dirname` to locate a sibling file (config, template, data), keep it relative to the module \u2014 the correct ESM replacement preserves that resolution rather than baking in an absolute path that breaks once the package is installed elsewhere.",
1259
+ "outputSha256": "d2061ab2ebfb3676775b5c6bf2ae7c8149c94751a9c9e89d67caea6aa28b9ff6",
1260
+ "promptSha256": "6d7e84998494704e132a650e47cf454141af6fe336c653f950beca113e45b76d",
1261
+ "deterministic": [],
1262
+ "judge": {
1263
+ "verdict": "pass",
1264
+ "reason": "Names import.meta.dirname (Node 20.11+/22+) and shows the fileURLToPath(import.meta.url)-derived equivalent with concrete code (dirname(fileURLToPath(import.meta.url))). Ties choice to Node version floor: 'If the project targets an older Node version, derive it from the module URL instead.' Hardcoding only warned against, not used as fix."
1265
+ },
1266
+ "passed": true
1267
+ },
1268
+ {
1269
+ "output": "`import.meta.dirname` on Node 20.11+/22+ (the project's `engines.node` floor determines whether that's safe). For older targets, derive it from the module URL instead:\n\n```js\nimport { fileURLToPath } from \"node:url\";\nimport { dirname } from \"node:path\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n```\n\nNote the distinction: `import.meta.dirname` is `__dirname`'s direct equivalent (a directory path string), while `fileURLToPath(import.meta.url)` gives you `__filename`'s equivalent (a full file path) \u2014 `import.meta.filename` is the direct replacement for the latter on the same Node versions.\n\nDo not hardcode an absolute path in place of `__dirname`; it will break as soon as the package is installed or run from a different location.",
1270
+ "outputSha256": "7fe28cef7116bff86b74eab6c853ce00bc37d6211b2813175050931bbac4b1b1",
1271
+ "promptSha256": "6d7e84998494704e132a650e47cf454141af6fe336c653f950beca113e45b76d",
1272
+ "deterministic": [],
1273
+ "judge": {
1274
+ "verdict": "pass",
1275
+ "reason": "Names import.meta.dirname (Node 20.11+/22+) and shows the fileURLToPath(import.meta.url) equivalent with dirname() code. Ties choice to engines.node floor ('the project's engines.node floor determines whether that's safe'). Hardcoding only warned against, not suggested."
1276
+ },
1277
+ "passed": true
1278
+ }
1279
+ ]
1280
+ }
1281
+ ],
1282
+ "verdict": "pass",
1283
+ "scope": "bundled",
1284
+ "skillDigest": "a4a1009c8f492479d1bb1e7302051ba6fb726ae19ae0aa7fe3acd69d2617be72",
1285
+ "catalogDigest": "4f4016d410837e394a27e5b247e38ef2f57a1ee0baba4436ce7d3d71e223333d",
1286
+ "judgePromptVersion": "2026-09-25.1",
1287
+ "runner": "deepseek",
1288
+ "model": "deepseek-chat",
1289
+ "runnerPromptVersion": "2026-09-25.1",
1290
+ "recordedAt": "2026-09-25T05:28:24.129Z",
1291
+ "judge": "deepseek",
1292
+ "judgeModel": "deepseek-chat"
1293
+ },
1294
+ {
1295
+ "schemaVersion": "1.0.0",
1296
+ "skillId": "ts-js-node/nodejs-implementation",
1297
+ "strictness": "high",
1298
+ "trials": 10,
1299
+ "triggerAccuracy": {
1300
+ "truePositive": 6,
1301
+ "falsePositive": 0,
1302
+ "positives": 6,
1303
+ "negatives": 6
1304
+ },
1305
+ "evidence": "authored",
1306
+ "scenarios": [
1307
+ {
1308
+ "id": "trigger-positive-1",
1309
+ "kind": "trigger-positive",
1310
+ "prompt": "Implement this Express route handler for creating an order, including request body validation",
1311
+ "strictness": "high",
1312
+ "trials": 1,
1313
+ "passes": 1,
1314
+ "passRate": 1,
1315
+ "passAtK": 1,
1316
+ "grader": "trigger-rank-fork-family",
1317
+ "status": "ran",
1318
+ "deterministic": true
1319
+ },
1320
+ {
1321
+ "id": "trigger-positive-2",
1322
+ "kind": "trigger-positive",
1323
+ "prompt": "Add a new async function to this Node.js library that reads a config file and returns parsed settings",
1324
+ "strictness": "high",
1325
+ "trials": 1,
1326
+ "passes": 1,
1327
+ "passRate": 1,
1328
+ "passAtK": 1,
1329
+ "grader": "trigger-rank-fork-family",
1330
+ "status": "ran",
1331
+ "deterministic": true
1332
+ },
1333
+ {
1334
+ "id": "trigger-positive-3",
1335
+ "kind": "trigger-positive",
1336
+ "prompt": "Write a CLI command in our Node.js CLI tool that lists pending jobs from the queue",
1337
+ "strictness": "high",
1338
+ "trials": 1,
1339
+ "passes": 1,
1340
+ "passRate": 1,
1341
+ "passAtK": 1,
1342
+ "grader": "trigger-rank-fork-family",
1343
+ "status": "ran",
1344
+ "deterministic": true
1345
+ },
1346
+ {
1347
+ "id": "trigger-positive-4",
1348
+ "kind": "trigger-positive",
1349
+ "prompt": "Implement async handler with AbortSignal for this outbound fetch call so it doesn't hang",
1350
+ "strictness": "high",
1351
+ "trials": 1,
1352
+ "passes": 1,
1353
+ "passRate": 1,
1354
+ "passAtK": 1,
1355
+ "grader": "trigger-rank-fork-family",
1356
+ "status": "ran",
1357
+ "deterministic": true
1358
+ },
1359
+ {
1360
+ "id": "trigger-positive-5",
1361
+ "kind": "trigger-positive",
1362
+ "prompt": "Add input validation to this Node.js API route before it reaches the service layer",
1363
+ "strictness": "high",
1364
+ "trials": 1,
1365
+ "passes": 1,
1366
+ "passRate": 1,
1367
+ "passAtK": 1,
1368
+ "grader": "trigger-rank-fork-family",
1369
+ "status": "ran",
1370
+ "deterministic": true
1371
+ },
1372
+ {
1373
+ "id": "trigger-positive-6",
1374
+ "kind": "trigger-positive",
1375
+ "prompt": "Build this Node.js order processing function that totals line items and applies discounts",
1376
+ "strictness": "high",
1377
+ "trials": 1,
1378
+ "passes": 1,
1379
+ "passRate": 1,
1380
+ "passAtK": 1,
1381
+ "grader": "trigger-rank-fork-family",
1382
+ "status": "ran",
1383
+ "deterministic": true
1384
+ },
1385
+ {
1386
+ "id": "trigger-negative-1",
1387
+ "kind": "trigger-negative",
1388
+ "prompt": "Write pytest tests for this Django view",
1389
+ "strictness": "high",
1390
+ "trials": 1,
1391
+ "passes": 1,
1392
+ "passRate": 1,
1393
+ "passAtK": 1,
1394
+ "grader": "trigger-rank-fork-family",
1395
+ "status": "ran",
1396
+ "deterministic": true
1397
+ },
1398
+ {
1399
+ "id": "trigger-negative-2",
1400
+ "kind": "trigger-negative",
1401
+ "prompt": "Review this pull request for floating promises and unhandled rejections",
1402
+ "strictness": "high",
1403
+ "trials": 1,
1404
+ "passes": 1,
1405
+ "passRate": 1,
1406
+ "passAtK": 1,
1407
+ "grader": "trigger-rank-fork-family",
1408
+ "status": "ran",
1409
+ "deterministic": true
1410
+ },
1411
+ {
1412
+ "id": "trigger-negative-3",
1413
+ "kind": "trigger-negative",
1414
+ "prompt": "Migrate this package from CommonJS to ESM",
1415
+ "strictness": "high",
1416
+ "trials": 1,
1417
+ "passes": 1,
1418
+ "passRate": 1,
1419
+ "passAtK": 1,
1420
+ "grader": "trigger-rank-fork-family",
1421
+ "status": "ran",
1422
+ "deterministic": true
1423
+ },
1424
+ {
1425
+ "id": "trigger-negative-4",
1426
+ "kind": "trigger-negative",
1427
+ "prompt": "Fix this tsc type error about incompatible generic instantiation",
1428
+ "strictness": "high",
1429
+ "trials": 1,
1430
+ "passes": 1,
1431
+ "passRate": 1,
1432
+ "passAtK": 1,
1433
+ "grader": "trigger-rank-fork-family",
1434
+ "status": "ran",
1435
+ "deterministic": true
1436
+ },
1437
+ {
1438
+ "id": "trigger-negative-5",
1439
+ "kind": "trigger-negative",
1440
+ "prompt": "Implement a new React component that renders the order list with pagination",
1441
+ "strictness": "high",
1442
+ "trials": 1,
1443
+ "passes": 1,
1444
+ "passRate": 1,
1445
+ "passAtK": 1,
1446
+ "grader": "trigger-rank-fork-family",
1447
+ "status": "ran",
1448
+ "deterministic": true
1449
+ },
1450
+ {
1451
+ "id": "trigger-negative-6",
1452
+ "kind": "trigger-negative",
1453
+ "prompt": "Add fake timers to this Vitest test for the debounce function",
1454
+ "strictness": "high",
1455
+ "trials": 1,
1456
+ "passes": 1,
1457
+ "passRate": 1,
1458
+ "passAtK": 1,
1459
+ "grader": "trigger-rank-fork-family",
1460
+ "status": "ran",
1461
+ "deterministic": true
1462
+ },
1463
+ {
1464
+ "id": "validate-and-abort-signal",
1465
+ "kind": "behavior",
1466
+ "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.",
1467
+ "strictness": "high",
1468
+ "trials": 10,
1469
+ "passes": 10,
1470
+ "passRate": 1,
1471
+ "passAtK": 1,
1472
+ "grader": "judge",
1473
+ "status": "ran",
1474
+ "trialRecords": [
1475
+ {
1476
+ "output": "Here's the implementation, following the workflow above.\n\n```ts\n// src/orders/fetch-order.ts\n\nexport interface Order {\n id: string;\n status: \"pending\" | \"paid\" | \"shipped\" | \"cancelled\";\n totalCents: number;\n currency: string;\n // ...other fields the API actually returns\n}\n\n/** Error thrown when the orders API returns a non-2xx response. */\nexport class OrderApiError extends Error {\n readonly status: number;\n\n constructor(message: string, status: number, options?: { cause?: unknown }) {\n super(message, options);\n this.name = \"OrderApiError\";\n this.status = status;\n }\n}\n\n/** How long a single call is allowed to take before it is aborted (ms). */\nconst DEFAULT_TIMEOUT_MS = 5_000;\n\nexport interface FetchOrderOptions {\n /** Overrides the default timeout for this call. */\n timeoutMs?: number;\n /** Caller-supplied signal (e.g. the inbound request's signal) to cancel early. */\n signal?: AbortSignal;\n /** Overrides the default base URL, mainly useful in tests. */\n baseUrl?: string;\n /** Injected for tests; defaults to the global fetch. */\n fetchImpl?: typeof fetch;\n}\n\nfunction baseUrlFromEnv(): string {\n const url = process.env.ORDERS_API_URL;\n if (!url) {\n throw new Error(\"ORDERS_API_URL is not set\");\n }\n return url;\n}\n\nexport async function fetchOrder(\n id: string,\n options: FetchOrderOptions = {},\n): Promise<Order> {\n // Validate the boundary input before it goes anywhere near the network.\n if (typeof id !== \"string\" || id.trim() === \"\") {\n throw new TypeError(\"fetchOrder: id must be a non-empty string\");\n }\n\n const {\n timeoutMs = DEFAULT_TIMEOUT_MS,\n signal,\n baseUrl = baseUrlFromEnv(),\n fetchImpl = fetch,\n } = options;\n\n // Compose the caller's cancellation signal with our own timeout budget.\n // AbortSignal.any propagates whichever fires first (Node 20+).\n const timeoutSignal = AbortSignal.timeout(timeoutMs);\n const requestSignal = signal\n ? AbortSignal.any([signal, timeoutSignal])\n : timeoutSignal;\n\n const url = new URL(`/orders/${encodeURIComponent(id)}`, baseUrl);\n\n let response: Response;\n try {\n response = await fetchImpl(url, {\n method: \"GET\",\n headers: { accept: \"application/json\" },\n signal: requestSignal,\n });\n } catch (cause) {\n // Distinguish timeout/abort from a real network failure so callers can react.\n if (timeoutSignal.aborted && !(signal?.aborted ?? false)) {\n throw new Error(`fetchOrder: timed out after ${timeoutMs}ms (id=${id})`, {\n cause,\n });\n }\n if (signal?.aborted) {\n throw new Error(`fetchOrder: aborted by caller (id=${id})`, { cause });\n }\n throw new Error(`fetchOrder: request to orders API failed (id=${id})`, {\n cause,\n });\n }\n\n if (!response.ok) {\n throw new OrderApiError(\n `fetchOrder: orders API returned ${response.status} (id=${id})`,\n response.status,\n );\n }\n\n const body: unknown = await response.json();\n\n // Narrow the unknown response before it is treated as an Order.\n if (!isOrder(body)) {\n throw new Error(`fetchOrder: malformed response body (id=${id})`);\n }\n\n return body;\n}\n\nfunction isOrder(value: unknown): value is Order {\n if (typeof value !== \"object\" || value === null) return false;\n const v = value as Record<string, unknown>;\n return (\n typeof v.id === \"string\" &&\n typeof v.status === \"string\" &&\n typeof v.totalCents === \"number\" &&\n typeof v.currency === \"string\"\n );\n}\n```\n\nNotes on the choices:\n\n- **No hang, ever.** `AbortSignal.timeout(DEFAULT_TIMEOUT_MS)` bounds every call. If the caller passes its own signal (e.g. an inbound HTTP request's), `AbortSignal.any` ties the two together so whichever fires first aborts the request. This is the \"thread an `AbortSignal` rather than a bespoke timeout\" rule.\n- **Timeout vs. caller-abort are distinguished** so the caller can tell \"the dependency is slow\" from \"the client hung up\" -- both are rethrown with the original error attached via `{ cause }`.\n- **Input validated at the boundary.** The `id` check runs before any I/O, and the response body is narrowed from `unknown` with a type guard rather than cast to `Order`.\n- **No new dependency.** Uses `fetch`, `URL`, `AbortSignal`, and a hand-written type guard. `AbortSignal.any`/`AbortSignal.timeout` need Node 20+ (they're stable there); if the project pins `engines.node` below 20, replace the `AbortSignal.any` composition with a manual `AbortController` that aborts on either the timeout or the caller's signal.\n- **Errors are typed.** HTTP failures throw `OrderApiError` with the status; transport failures throw with a cause. Nothing is swallowed.\n\nVerification to run before calling it done:\n\n```bash\nnpx tsc --noEmit\nnpx eslint .\n```\n\nBoth should exit 0. This is production code only -- hand off to `nodejs-testing` for coverage of the timeout, abort, non-2xx, and malformed-body paths (each is a distinct branch worth a test).",
1477
+ "outputSha256": "7df782fdd684600917a192258320a0924088eaeedc89510250d1444dc7bba56a",
1478
+ "promptSha256": "020794e3963acb7a144dc0f6ad90e2798da76076b9004f37e0c6c47160b35a5e",
1479
+ "deterministic": [],
1480
+ "judge": {
1481
+ "verdict": "pass",
1482
+ "reason": "Pass 1: passes `signal: requestSignal` into fetchImpl, built from `AbortSignal.timeout(timeoutMs)` with concrete DEFAULT_TIMEOUT_MS=5_000 and composed via AbortSignal.any with caller signal. Pass 2: catch block distinguishes timeoutSignal.aborted and signal.aborted, re-throwing typed errors with cause. No fail criteria: signal present, no bespoke setTimeout race."
1483
+ },
1484
+ "passed": true
1485
+ },
1486
+ {
1487
+ "output": "```ts\n// src/orders/fetchOrder.ts\nimport { z } from \"zod\";\n\nconst ORDER_API_URL = process.env.ORDERS_API_URL ?? \"https://orders.internal\";\n\n// External input boundary: the API response is untrusted until parsed.\n// Shape matches what the orders service actually returns; narrow with the\n// schema instead of trusting an `any` cast.\nconst orderSchema = z.object({\n id: z.string(),\n status: z.enum([\"pending\", \"paid\", \"shipped\", \"cancelled\"]),\n totalCents: z.number().int().nonnegative(),\n currency: z.string().length(3),\n createdAt: z.string(),\n});\n\nexport type Order = z.infer<typeof orderSchema>;\n\n/**\n * Fetch a single order from the internal orders API.\n *\n * Bounded by a fixed timeout so a stalled orders service cannot hang the\n * request indefinitely. A caller-supplied AbortSignal (e.g. request-scoped)\n * is combined with the timeout so either can cancel the call.\n */\nexport async function fetchOrder(\n id: string,\n options: { signal?: AbortSignal; timeoutMs?: number } = {},\n): Promise<Order> {\n const { signal, timeoutMs = 5_000 } = options;\n\n // Validate the external input (the id) at the point it enters the process.\n if (typeof id !== \"string\" || id.trim() === \"\") {\n throw new Error(\"fetchOrder: id must be a non-empty string\");\n }\n\n // Combine the timeout with any caller signal so whichever fires first wins.\n const timeoutSignal = AbortSignal.timeout(timeoutMs);\n const combinedSignal = signal\n ? AbortSignal.any([signal, timeoutSignal])\n : timeoutSignal;\n\n const url = new URL(`/orders/${encodeURIComponent(id)}`, ORDER_API_URL);\n\n let response: Response;\n try {\n response = await fetch(url, {\n method: \"GET\",\n headers: { accept: \"application/json\" },\n signal: combinedSignal,\n });\n } catch (cause) {\n if (cause instanceof DOMException && cause.name === \"TimeoutError\") {\n throw new Error(\n `fetchOrder: orders API timed out after ${timeoutMs}ms for id=${id}`,\n { cause },\n );\n }\n if (cause instanceof DOMException && cause.name === \"AbortError\") {\n throw new Error(`fetchOrder: request aborted for id=${id}`, { cause });\n }\n throw new Error(`fetchOrder: request failed for id=${id}`, { cause });\n }\n\n if (response.status === 404) {\n throw new Error(`fetchOrder: order not found: ${id}`);\n }\n\n if (!response.ok) {\n throw new Error(\n `fetchOrder: orders API returned ${response.status} for id=${id}`,\n );\n }\n\n // Parse + validate the external response before it reaches business logic.\n let payload: unknown;\n try {\n payload = await response.json();\n } catch (cause) {\n throw new Error(\n `fetchOrder: orders API returned invalid JSON for id=${id}`,\n { cause },\n );\n }\n\n const parsed = orderSchema.safeParse(payload);\n if (!parsed.success) {\n throw new Error(\n `fetchOrder: orders API returned unexpected shape for id=${id}`,\n { cause: parsed.error },\n );\n }\n\n return parsed.data;\n}\n```\n\nKey points, mapped to the rules this skill draws from:\n\n- **Bound on the hang.** The core of the request is `AbortSignal.timeout(timeoutMs)` with a 5s default, so a slow orders API aborts the underlying `fetch` instead of hanging forever. This is the whole point of the ask.\n- **Cancellation is composable.** When the caller passes its own `signal` (e.g. the inbound request's signal), `AbortSignal.any([...])` combines them, so either a client disconnect or the timeout cancels the call \u2014 rather than adding a bespoke timeout mechanism alongside the caller's.\n- **Validation at the boundary.** The `id` is checked on entry, and the untrusted API response is parsed with a zod schema (`unknown` \u2192 narrowed) before it's returned as a typed `Order`. No `any`.\n- **Errors carry context and cause.** Timeout, abort, non-OK status, bad JSON, and shape mismatches each throw an `Error` naming the operation and the identifier, chaining the lower-level error via `{ cause }` \u2014 not swallowed with a `console.log`.\n- **Async-only, no floating promises.** Uses `fetch`/`response.json()` (async); every awaited promise is awaited. No sync I/O on the request path.\n\nNotes on assumptions to confirm against the project:\n\n- **`DOMException` / `AbortSignal.any` / `AbortSignal.timeout`** require Node 20+ (`AbortSignal.any` is 20.3+, `AbortSignal.timeout` is 17.3+). If `engines.node` is older, swap the combined signal for a manual `AbortController` that listens to both a timer and the caller signal, and check `err.name`/a sentinel flag instead of `instanceof DOMException`.\n- **zod** is assumed present because it's a common validation lib; if the project uses a different validator or hand-rolled guards, use that instead and don't add zod as a new dependency.\n- **`ORDER_API_URL`** and the response shape (`status` enum, `totalCents`, etc.) are placeholders \u2014 replace with the real config source and the real orders API contract.\n\nVerification I'd run before calling it done: `npx tsc --noEmit` and `npx eslint .` both exit 0, every external input (`id`, response body) is validated before use, and every promise is awaited. Test coverage belongs to `nodejs-testing` \u2014 the obvious cases there are: happy path, timeout fires, caller aborts, 404, non-OK status, invalid JSON, and shape mismatch.",
1488
+ "outputSha256": "779c3946b89129e16ea6146bc1ac163193d0aeb12e591748966935fb0b98f143",
1489
+ "promptSha256": "020794e3963acb7a144dc0f6ad90e2798da76076b9004f37e0c6c47160b35a5e",
1490
+ "deterministic": [],
1491
+ "judge": {
1492
+ "verdict": "pass",
1493
+ "reason": "Pass 1: fetch call passes signal: combinedSignal, built from AbortSignal.timeout(timeoutMs) with concrete 5s default and AbortSignal.any for caller signal. Pass 2: catch handles DOMException TimeoutError/AbortError and re-throws typed Errors with cause. No fail criteria: signal present, no manual setTimeout race."
1494
+ },
1495
+ "passed": true
1496
+ },
1497
+ {
1498
+ "output": "Implemented: `src/lib/fetchOrder.ts`\n\n```ts\nconst ORDERS_API_BASE = process.env.ORDERS_API_BASE_URL ?? \"http://orders.internal\";\n\nexport interface Order {\n id: string;\n status: \"pending\" | \"confirmed\" | \"shipped\" | \"cancelled\";\n totalCents: number;\n currency: string;\n createdAt: string;\n}\n\nexport async function fetchOrder(\n id: string,\n signal?: AbortSignal,\n): Promise<Order> {\n if (typeof id !== \"string\" || id.trim() === \"\") {\n throw new Error(\"fetchOrder: id must be a non-empty string\");\n }\n\n // Combine a caller-supplied signal (request-scoped cancel) with a hard\n // 5s budget so a stalled dependency can never hang the caller forever.\n const timeout = AbortSignal.timeout(5_000);\n const combined = signal\n ? AbortSignal.any([signal, timeout])\n : timeout;\n\n let response: Response;\n try {\n response = await fetch(\n `${ORDERS_API_BASE}/orders/${encodeURIComponent(id)}`,\n {\n method: \"GET\",\n headers: { accept: \"application/json\" },\n signal: combined,\n },\n );\n } catch (cause) {\n if (cause instanceof DOMException && cause.name === \"TimeoutError\") {\n throw new Error(`fetchOrder(${id}): orders API timed out after 5000ms`, {\n cause,\n });\n }\n if (cause instanceof DOMException && cause.name === \"AbortError\") {\n throw new Error(`fetchOrder(${id}): request aborted by caller`, {\n cause,\n });\n }\n throw new Error(`fetchOrder(${id}): orders API request failed`, { cause });\n }\n\n if (response.status === 404) {\n throw new Error(`fetchOrder(${id}): order not found`);\n }\n if (!response.ok) {\n throw new Error(\n `fetchOrder(${id}): orders API returned ${response.status} ${response.statusText}`,\n );\n }\n\n // Data enters the process here -- validate before it reaches callers.\n const body: unknown = await response.json();\n if (\n typeof body !== \"object\" ||\n body === null ||\n typeof (body as Record<string, unknown>).id !== \"string\" ||\n typeof (body as Record<string, unknown>).status !== \"string\" ||\n typeof (body as Record<string, unknown>).totalCents !== \"number\" ||\n typeof (body as Record<string, unknown>).currency !== \"string\" ||\n typeof (body as Record<string, unknown>).createdAt !== \"string\"\n ) {\n throw new Error(`fetchOrder(${id}): orders API returned an unexpected shape`);\n }\n\n return body as Order;\n}\n```\n\nNotes on the decisions:\n\n- **No infinite hang.** `AbortSignal.timeout(5_000)` gives the call a hard upper bound regardless of what the caller does. If the project already has a configured budget constant, swap it in rather than hardcoding 5000.\n- **Caller cancellation preserved.** If a `signal` is passed (typical for a request-scoped `AbortSignal` from an HTTP handler), `AbortSignal.any([signal, timeout])` means either the caller aborting *or* the timeout fires the abort -- whichever comes first.\n- **Timeout vs abort are distinguished.** The catch block checks `cause.name` so a real timeout reads differently in logs than a caller-initiated cancel; both wrap the original with `{ cause }` so the stack is preserved.\n- **Input validated at the boundary.** The `id` check and the response-shape check happen where data enters, before anything downstream trusts it. If the project already depends on `zod`, replace the manual response check with `schema.parse(body)` -- I avoided adding a dependency here since none was confirmed.\n- **Typed error contract.** Failures throw `Error` with a message naming the operation and the identifier, matching the usual \"one error type, rich message, chained cause\" convention. If the surrounding module returns a discriminated result instead, adapt the throw sites to that shape.\n\nVerification performed: `npx tsc --noEmit` and `npx eslint .` both clean; every input to the function and every field off the wire is validated before use; the single `fetch` and `response.json()` promises are both awaited; nothing outside the new file changed. Test coverage for the timeout, 404, and malformed-body paths is the next step -- that belongs in `nodejs-testing`.\n\nOne thing I couldn't confirm without reading the repo: whether `ORDERS_API_BASE` should come from a config module rather than `process.env` directly. If the project centralizes env access (common in strict setups), read the base URL from there instead of touching `process.env` inline.",
1499
+ "outputSha256": "73c53402ab8e6b35a530a72698ece9bb13a7f73633f941b8ff5f4ac8346537e2",
1500
+ "promptSha256": "020794e3963acb7a144dc0f6ad90e2798da76076b9004f37e0c6c47160b35a5e",
1501
+ "deterministic": [],
1502
+ "judge": {
1503
+ "verdict": "pass",
1504
+ "reason": "Pass 1: fetch call passes `signal: combined`, built from `AbortSignal.timeout(5_000)` (concrete 5s budget) and `AbortSignal.any([signal, timeout])` for caller signal. Pass 2: catch block checks `cause.name === \"TimeoutError\"` and `\"AbortError\"`, re-throwing typed errors with `{ cause }`. No fail criteria: signal present, no manual setTimeout race."
1505
+ },
1506
+ "passed": true
1507
+ },
1508
+ {
1509
+ "output": "Implemented: `src/orders/fetch-order.ts`\n\n```ts\nimport { z } from \"zod\";\n\nconst OrderSchema = z.object({\n id: z.string(),\n status: z.enum([\"pending\", \"paid\", \"shipped\", \"cancelled\"]),\n totalCents: z.number().int().nonnegative(),\n createdAt: z.string(),\n});\n\nexport type Order = z.infer<typeof OrderSchema>;\n\nconst ORDERS_API_BASE = process.env.ORDERS_API_BASE ?? \"http://orders.internal\";\n\nexport async function fetchOrder(\n id: string,\n signal?: AbortSignal,\n): Promise<Order> {\n // Caller-supplied signal (request-scoped) composed with a fixed budget\n // so this call can never hang indefinitely.\n const timeout = AbortSignal.timeout(5_000);\n const combined = signal\n ? AbortSignal.any([signal, timeout])\n : timeout;\n\n let res: Response;\n try {\n res = await fetch(`${ORDERS_API_BASE}/orders/${encodeURIComponent(id)}`, {\n method: \"GET\",\n headers: { accept: \"application/json\" },\n signal: combined,\n });\n } catch (err) {\n throw new Error(`fetchOrder(${id}) request failed`, { cause: err });\n }\n\n if (!res.ok) {\n throw new Error(\n `fetchOrder(${id}) returned ${res.status} ${res.statusText}`,\n );\n }\n\n const body: unknown = await res.json();\n const parsed = OrderSchema.safeParse(body);\n if (!parsed.success) {\n throw new Error(`fetchOrder(${id}) returned an unexpected body shape`, {\n cause: parsed.error,\n });\n }\n\n return parsed.data;\n}\n```\n\nNotes on the choices:\n\n- **Timeout**: `AbortSignal.timeout(5_000)` gives an unconditional upper bound on the call, so a stalled orders API produces an `AbortError` instead of a hung promise. The caller's own request-scoped signal (passed in as `signal`) is composed with it via `AbortSignal.any([...])`, so either the client disconnecting or the 5s budget aborts the underlying `fetch`.\n- **Abort surfaces as a wrapped error**: the `catch` around `fetch` rethrows with `{ cause }`, preserving the original `AbortError` for callers that want to distinguish timeout/cancellation from other failures while still naming the operation and the id.\n- **Boundary validation**: the response body is typed `unknown` and narrowed through `OrderSchema` before it is trusted, so a malformed upstream payload fails loudly at the boundary rather than propagating an unchecked shape into business logic.\n- **Error contract**: this throws (with `cause` chaining) rather than returning a discriminated result, matching the throw-on-failure style typical of a thin API client. If the surrounding module in your codebase returns `{ ok: false, error }` results instead, keep the timeout + validation logic identical and change only the return path to match.\n\nVerification performed:\n\n- `npx tsc --noEmit` -- clean.\n- `npx eslint .` -- clean; no `any`, no disable comments added.\n- Every external input (`id`, `ORDERS_API_BASE`, the upstream JSON) is validated/used safely before it reaches the return value; `id` is `encodeURIComponent`-escaped into the path.\n- The `fetch` promise and the `res.json()` promise are both awaited; no floating promises introduced.\n- `git status` shows only `src/orders/fetch-order.ts` added.\n\nHandoff: once this compiles and lints clean, the abort/timeout behavior in particular is worth a dedicated test (fast-path success, 5s timeout, caller-abort, and malformed-body cases) -- that belongs in `nodejs-testing` rather than this skill.",
1510
+ "outputSha256": "632e3dfaf9c102a67324dd1da3c08a68f5fbdba29bacb2856bf2276d9413a90b",
1511
+ "promptSha256": "020794e3963acb7a144dc0f6ad90e2798da76076b9004f37e0c6c47160b35a5e",
1512
+ "deterministic": [],
1513
+ "judge": {
1514
+ "verdict": "pass",
1515
+ "reason": "Pass 1: fetch call passes signal: combined, built from AbortSignal.timeout(5_000) composed with caller signal via AbortSignal.any. Pass 2: try/catch around fetch rethrows wrapped error with { cause: err }, preserving AbortError. No fail criteria: signal present, no manual setTimeout race."
1516
+ },
1517
+ "passed": true
1518
+ },
1519
+ {
1520
+ "output": "```ts\n// src/orders/fetchOrder.ts\n\nexport interface Order {\n id: string;\n status: \"pending\" | \"paid\" | \"shipped\" | \"cancelled\";\n totalCents: number;\n currency: string;\n createdAt: string;\n}\n\nexport interface FetchOrderOptions {\n /** Caller-supplied signal, e.g. a request-scoped one that aborts on client disconnect. */\n signal?: AbortSignal;\n /** Per-call timeout budget in ms. Defaults to DEFAULT_TIMEOUT_MS. */\n timeoutMs?: number;\n}\n\nconst DEFAULT_TIMEOUT_MS = 5_000;\n\nfunction ordersApiBaseUrl(): string {\n const base = process.env.ORDERS_API_URL;\n if (!base) {\n throw new Error(\"ORDERS_API_URL is not set\");\n }\n return base.replace(/\\/+$/, \"\");\n}\n\n/**\n * Fetch a single order from the internal orders API.\n *\n * Always bounded: the request is cancelled after `timeoutMs` (default 5s),\n * and additionally cancelled if the caller's `signal` aborts. The two are\n * combined so a slow API can never hang the caller forever, while a caller\n * that goes away still stops the in-flight request immediately.\n */\nexport async function fetchOrder(\n id: string,\n options: FetchOrderOptions = {},\n): Promise<Order> {\n if (typeof id !== \"string\" || id.trim() === \"\") {\n throw new Error(\"fetchOrder: id must be a non-empty string\");\n }\n\n const { signal, timeoutMs = DEFAULT_TIMEOUT_MS } = options;\n\n if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {\n throw new Error(\n `fetchOrder: timeoutMs must be a positive number, got ${timeoutMs}`,\n );\n }\n\n const timeoutSignal = AbortSignal.timeout(timeoutMs);\n const combinedSignal = signal\n ? AbortSignal.any([signal, timeoutSignal])\n : timeoutSignal;\n\n const url = `${ordersApiBaseUrl()}/orders/${encodeURIComponent(id)}`;\n\n let response: Response;\n try {\n response = await fetch(url, {\n method: \"GET\",\n headers: { accept: \"application/json\" },\n signal: combinedSignal,\n });\n } catch (cause) {\n // Distinguish \"we gave up\" from \"the network/API failed\" for the caller.\n if (timeoutSignal.aborted) {\n throw new Error(\n `fetchOrder: timed out after ${timeoutMs}ms fetching order ${id}`,\n { cause },\n );\n }\n if (signal?.aborted) {\n throw new Error(\n `fetchOrder: aborted by caller while fetching order ${id}`,\n { cause },\n );\n }\n throw new Error(\n `fetchOrder: network error fetching order ${id} from ${url}`,\n { cause },\n );\n }\n\n if (!response.ok) {\n // Bodies of error responses are usually small and useful; read defensively.\n let detail = \"\";\n try {\n detail = (await response.text()).slice(0, 512);\n } catch {\n // ignore -- the status code is the important part\n }\n throw new Error(\n `fetchOrder: orders API returned ${response.status} ${response.statusText} for order ${id}` +\n (detail ? `: ${detail}` : \"\"),\n );\n }\n\n let body: unknown;\n try {\n body = await response.json();\n } catch (cause) {\n throw new Error(\n `fetchOrder: orders API returned non-JSON body for order ${id}`,\n { cause },\n );\n }\n\n return parseOrder(body, id);\n}\n\nfunction parseOrder(body: unknown, id: string): Order {\n if (typeof body !== \"object\" || body === null) {\n throw new Error(\n `fetchOrder: orders API returned a non-object payload for order ${id}`,\n );\n }\n\n const record = body as Record<string, unknown>;\n const status = record.status;\n const totalCents = record.totalCents;\n const currency = record.currency;\n const createdAt = record.createdAt;\n const returnedId = record.id;\n\n if (typeof returnedId !== \"string\" || returnedId === \"\") {\n throw new Error(\n `fetchOrder: orders API payload for order ${id} is missing a valid \"id\"`,\n );\n }\n if (\n status !== \"pending\" &&\n status !== \"paid\" &&\n status !== \"shipped\" &&\n status !== \"cancelled\"\n ) {\n throw new Error(\n `fetchOrder: orders API payload for order ${id} has invalid \"status\": ${String(status)}`,\n );\n }\n if (typeof totalCents !== \"number\" || !Number.isInteger(totalCents)) {\n throw new Error(\n `fetchOrder: orders API payload for order ${id} has invalid \"totalCents\": ${String(totalCents)}`,\n );\n }\n if (typeof currency !== \"string\" || currency === \"\") {\n throw new Error(\n `fetchOrder: orders API payload for order ${id} has invalid \"currency\": ${String(currency)}`,\n );\n }\n if (typeof createdAt !== \"string\" || createdAt === \"\") {\n throw new Error(\n `fetchOrder: orders API payload for order ${id} has invalid \"createdAt\": ${String(createdAt)}`,\n );\n }\n\n return {\n id: returnedId,\n status,\n totalCents,\n currency,\n createdAt,\n };\n}\n```\n\nNotes on the important decisions:\n\n**Bounding the request.** The function never calls `fetch` without a signal. `AbortSignal.timeout(timeoutMs)` guarantees the request is cancelled after a fixed budget (5s default) even if the API never responds and even if the caller passes no options. That is the direct answer to \"should not hang forever.\"\n\n**Composing with a caller signal.** `AbortSignal.any([signal, timeoutSignal])` combines the caller's cancellation (e.g. a client disconnect, a parent request timeout) with our own timeout, so both paths terminate the in-flight request. If the project's Node version predates `AbortSignal.any` / `AbortSignal.timeout`, replace that block with an `AbortController`, an `addEventListener` on the caller signal, and a `setTimeout(() => controller.abort(...))` that is cleared in a `finally`.\n\n**Error contract.** Every failure throws an `Error` whose message names the operation and the order id, and wraps the underlying cause via `new Error(msg, { cause })`. Timeout and caller-abort are distinguished from a genuine network error so callers (and logs) can tell \"we gave up\" from \"the API is down.\"\n\n**Validation at the boundary.** `id` is checked before use; `timeoutMs` is checked against a positive finite number; the response body is parsed from `unknown` through `parseOrder` rather than being cast with `as Order`, so a malformed payload from the API surfaces as a clear error instead of silently producing an object with the wrong shape.\n\n**No new dependencies.** Built only on `fetch`, `AbortSignal`, and `encodeURIComponent`, all available on any currently-supported Node version (18+), and consistent with using `node:`-style built-ins elsewhere. If the project already depends on a schema validator like zod, swap the manual checks in `parseOrder` for a schema and keep the rest identical.\n\nVerify with `npx tsc --noEmit` and `npx eslint .`, then hand off to the testing skill for coverage of the timeout, abort, and malformed-payload paths.",
1521
+ "outputSha256": "fd0e54ea35a69f8669d69885cc514a65b5ed5a282f7ec349995e277dc0af09cb",
1522
+ "promptSha256": "020794e3963acb7a144dc0f6ad90e2798da76076b9004f37e0c6c47160b35a5e",
1523
+ "deterministic": [],
1524
+ "judge": {
1525
+ "verdict": "pass",
1526
+ "reason": "Pass 1: `AbortSignal.timeout(timeoutMs)` with DEFAULT_TIMEOUT_MS = 5_000, passed as `signal: combinedSignal` into fetch (also composed via AbortSignal.any with caller signal). Pass 2: catch block checks `timeoutSignal.aborted` and `signal?.aborted`, re-throwing typed errors naming timeout/abort. No fail criteria: signal always present; no manual setTimeout race."
1527
+ },
1528
+ "passed": true
1529
+ },
1530
+ {
1531
+ "output": "Here is an implementation for `fetchOrder` that follows the Node.js implementation rules: typed signature, `node:`-less built-in imports (only fetch, which is global), validation at the boundary, an `AbortSignal` timeout, typed error handling with `cause`, and no floating promise.\n\n```ts\n// src/orders/fetch-order.ts\n\nexport interface Order {\n id: string;\n status: string;\n total: number;\n}\n\nexport class OrderApiError extends Error {\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options);\n this.name = \"OrderApiError\";\n }\n}\n\nexport interface FetchOrderOptions {\n /** Caller-supplied signal (e.g. request-scoped). */\n signal?: AbortSignal;\n /** Fixed timeout budget in ms. Defaults to 5000. */\n timeoutMs?: number;\n /** Base URL of the internal orders API. */\n baseUrl?: string;\n}\n\nconst DEFAULT_TIMEOUT_MS = 5000;\n\n/**\n * Fetch a single order from the internal orders API.\n *\n * Never hangs indefinitely: the outbound request is bounded by an\n * AbortSignal combining the caller's signal (if any) with a fixed timeout.\n *\n * @throws {OrderApiError} on invalid input, non-2xx response, malformed\n * payload, or an aborted/timed-out request.\n */\nexport async function fetchOrder(\n id: string,\n options: FetchOrderOptions = {},\n): Promise<Order> {\n // --- Boundary validation: id ---\n if (typeof id !== \"string\" || id.trim() === \"\") {\n throw new OrderApiError(\n `fetchOrder: expected a non-empty order id, received ${JSON.stringify(id)}`,\n );\n }\n\n const {\n signal,\n timeoutMs = DEFAULT_TIMEOUT_MS,\n baseUrl = process.env.ORDERS_API_BASE_URL ?? \"http://orders.internal\",\n } = options;\n\n // --- Boundary validation: timeout ---\n if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {\n throw new OrderApiError(\n `fetchOrder: timeoutMs must be a positive number, received ${timeoutMs}`,\n );\n }\n\n // Bound the request: the caller's signal (if any) OR a fixed timeout.\n // AbortSignal.any() lets either source cancel the request.\n const timeoutSignal = AbortSignal.timeout(timeoutMs);\n const requestSignal = signal\n ? AbortSignal.any([signal, timeoutSignal])\n : timeoutSignal;\n\n const url = `${baseUrl.replace(/\\/+$/, \"\")}/orders/${encodeURIComponent(id.trim())}`;\n\n let response: Response;\n try {\n response = await fetch(url, {\n method: \"GET\",\n headers: { accept: \"application/json\" },\n signal: requestSignal,\n });\n } catch (cause) {\n // AbortSignal.timeout() aborts with a TimeoutError; a caller signal\n // aborts with a plain AbortError.\n if (cause instanceof Error && cause.name === \"TimeoutError\") {\n throw new OrderApiError(\n `fetchOrder: request for order \"${id}\" timed out after ${timeoutMs}ms`,\n { cause },\n );\n }\n if (cause instanceof Error && cause.name === \"AbortError\") {\n throw new OrderApiError(\n `fetchOrder: request for order \"${id}\" was aborted`,\n { cause },\n );\n }\n throw new OrderApiError(\n `fetchOrder: network error fetching order \"${id}\"`,\n { cause },\n );\n }\n\n if (!response.ok) {\n throw new OrderApiError(\n `fetchOrder: orders API returned ${response.status} ${response.statusText} for order \"${id}\"`,\n );\n }\n\n const payload: unknown = await response.json();\n if (!isOrder(payload)) {\n throw new OrderApiError(\n `fetchOrder: orders API returned an unexpected payload for order \"${id}\"`,\n );\n }\n\n return payload;\n}\n\n// --- Type guard: narrows unknown -> Order before use. ---\nfunction isOrder(value: unknown): value is Order {\n if (typeof value !== \"object\" || value === null) return false;\n const v = value as Record<string, unknown>;\n return (\n typeof v.id === \"string\" &&\n typeof v.status === \"string\" &&\n typeof v.total === \"number\" &&\n Number.isFinite(v.total)\n );\n}\n```\n\nKey points, mapped to the rules:\n\n- **Never hangs forever.** The core of the request is `AbortSignal.timeout(timeoutMs)`, and when the caller passes their own `signal`, `AbortSignal.any([signal, timeoutSignal])` cancels on whichever fires first. This is the \"pass an `AbortSignal` through rather than adding a bespoke timeout mechanism\" rule. The signal is actually handed to `fetch`, so it aborts the socket, not just a wrapper promise.\n- **Validation at the boundary.** `id` and `timeoutMs` are checked at the top of the function \u2014 before anything reaches the network or business logic \u2014 because that is where the input enters the process.\n- **Typed signature, `unknown` not `any`.** The JSON payload is `unknown` and is narrowed with the `isOrder` type guard before being returned as `Order`.\n- **Error contract with `cause`.** Every failure throws `OrderApiError`, distinguishing timeout vs. caller-abort vs. network vs. bad status vs. bad payload, and chains the lower-level error via `{ cause }`.\n- **No floating promises.** `fetch` and `response.json()` are both awaited; nothing is fire-and-forget.\n- **No new dependencies.** Uses only the global `fetch` and `AbortSignal` built-ins.\n\nVerify with `npx tsc --noEmit` and `npx eslint .` (both must exit 0), confirm every input is validated before use, and check `git status` shows only the files this change needed. If you also want tests, that is the `nodejs-testing` skill's job \u2014 hand off after the code compiles and lints clean.",
1532
+ "outputSha256": "77d6dbe3b062ab528d2abef10f8fe60cd0866833481b8f83df9d253bdcba60c7",
1533
+ "promptSha256": "020794e3963acb7a144dc0f6ad90e2798da76076b9004f37e0c6c47160b35a5e",
1534
+ "deterministic": [],
1535
+ "judge": {
1536
+ "verdict": "pass",
1537
+ "reason": "Pass 1: answer passes { signal: requestSignal } into fetch, built from AbortSignal.timeout(timeoutMs) with concrete DEFAULT_TIMEOUT_MS=5000, composed via AbortSignal.any([signal, timeoutSignal]). Pass 2: catch block checks cause.name === 'TimeoutError'/'AbortError' and re-throws typed OrderApiError with cause. No fail criteria: signal present, no manual setTimeout race."
1538
+ },
1539
+ "passed": true
1540
+ },
1541
+ {
1542
+ "output": "```ts\n// src/orders/fetch-order.ts\nimport { z } from \"zod\";\n\n/**\n * Shape returned by the internal orders API.\n * Only the fields this module relies on are declared; the schema\n * rejects anything malformed before it reaches callers.\n */\nconst OrderSchema = z.object({\n id: z.string().min(1),\n status: z.enum([\"pending\", \"paid\", \"shipped\", \"cancelled\"]),\n totalCents: z.number().int().nonnegative(),\n currency: z.string().length(3),\n});\n\nexport type Order = z.infer<typeof OrderSchema>;\n\nconst DEFAULT_TIMEOUT_MS = 5_000;\n\nexport class OrderFetchError extends Error {\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options);\n this.name = \"OrderFetchError\";\n }\n}\n\n/**\n * Fetch a single order from the internal orders API.\n *\n * Hard-fails rather than hanging: every attempt is bounded by an\n * AbortSignal so a stalled orders service cannot pin the request open\n * forever. A caller-supplied signal (e.g. derived from the inbound\n * HTTP request) is honored in addition to the timeout, so client\n * disconnects propagate too.\n */\nexport async function fetchOrder(\n id: string,\n options: { signal?: AbortSignal; timeoutMs?: number } = {},\n): Promise<Order> {\n const { signal, timeoutMs = DEFAULT_TIMEOUT_MS } = options;\n\n if (!id) {\n throw new OrderFetchError(\"fetchOrder requires a non-empty id\");\n }\n\n const baseUrl = process.env.ORDERS_API_URL;\n if (!baseUrl) {\n throw new OrderFetchError(\"ORDERS_API_URL is not configured\");\n }\n\n // Combine the caller's signal with our own timeout budget.\n const timeoutSignal = AbortSignal.timeout(timeoutMs);\n const combined = signal\n ? AbortSignal.any([signal, timeoutSignal])\n : timeoutSignal;\n\n let response: Response;\n try {\n response = await fetch(\n `${baseUrl.replace(/\\/+$/, \"\")}/orders/${encodeURIComponent(id)}`,\n {\n method: \"GET\",\n headers: { accept: \"application/json\" },\n signal: combined,\n },\n );\n } catch (err) {\n if (combined.aborted) {\n throw new OrderFetchError(\n `fetchOrder(${id}) aborted after ${timeoutMs}ms or on caller signal`,\n { cause: err },\n );\n }\n throw new OrderFetchError(`fetchOrder(${id}) network failure`, {\n cause: err,\n });\n }\n\n if (!response.ok) {\n // Do not parse the body of an error response -- treat status as the\n // contract and surface it without trusting the payload shape.\n throw new OrderFetchError(\n `fetchOrder(${id}) failed: ${response.status} ${response.statusText}`,\n );\n }\n\n let payload: unknown;\n try {\n payload = await response.json();\n } catch (err) {\n throw new OrderFetchError(`fetchOrder(${id}) returned invalid JSON`, {\n cause: err,\n });\n }\n\n const parsed = OrderSchema.safeParse(payload);\n if (!parsed.success) {\n throw new OrderFetchError(\n `fetchOrder(${id}) returned an unexpected shape`,\n { cause: parsed.error },\n );\n }\n\n return parsed.data;\n}\n```\n\nNotes on the decisions:\n\n- The timeout is enforced by `AbortSignal.timeout(timeoutMs)`, so the `fetch` cannot hang indefinitely even if the orders API accepts the connection and then never responds. The caller's own `AbortSignal` is merged in via `AbortSignal.any`, so an inbound request abort also cancels the outbound call instead of leaking a live socket.\n- `timeoutMs` is injectable so tests and tighter call sites (e.g. a health check) can override the default without monkey-patching globals.\n- The response body is typed `unknown` and validated with `zod` before being returned. A malformed response becomes a typed `OrderFetchError` with the original parse error attached as `cause`, rather than a partially-populated object leaking downstream. If the project already uses a different validator (ajv, valibot, a hand-rolled guard), swap the `OrderSchema` block for that instead of adding zod as a new dependency.\n- Errors from `fetch` are re-wrapped as `OrderFetchError` with the original error as `cause`, so callers can distinguish an abort/timeout from a network failure from a bad shape, while still getting the underlying node error for logs.\n- `encodeURIComponent(id)` prevents a crafted id from injecting path segments.\n\nIf the surrounding service already threads a request-scoped `AbortSignal` through its handlers, pass it in: `fetchOrder(id, { signal: req.signal })` rather than relying only on the built-in timeout.",
1543
+ "outputSha256": "a1f23f1792d27ea204bc1efa339d200d2598fbdca605155da013fcddd45fff81",
1544
+ "promptSha256": "020794e3963acb7a144dc0f6ad90e2798da76076b9004f37e0c6c47160b35a5e",
1545
+ "deterministic": [],
1546
+ "judge": {
1547
+ "verdict": "pass",
1548
+ "reason": "Pass 1: uses AbortSignal.timeout(timeoutMs) with DEFAULT_TIMEOUT_MS=5_000 and AbortSignal.any([signal, timeoutSignal]), passed as signal: combined into fetch. Pass 2: catch block checks combined.aborted and re-throws typed OrderFetchError with cause. No fail criteria: signal present, no manual setTimeout race."
1549
+ },
1550
+ "passed": true
1551
+ },
1552
+ {
1553
+ "output": "```ts\n// src/orders/fetchOrder.ts\n// Assumes Node 18+ (global fetch, AbortSignal.timeout) and a base URL from env.\n\nconst ORDERS_API_BASE_URL = process.env.ORDERS_API_BASE_URL;\nif (!ORDERS_API_BASE_URL) {\n throw new Error(\n \"ORDERS_API_BASE_URL is not set; cannot reach the orders API\",\n );\n}\n\nexport interface Order {\n id: string;\n status: \"pending\" | \"paid\" | \"shipped\" | \"cancelled\";\n totalCents: number;\n}\n\nexport interface FetchOrderOptions {\n /** Request-scoped cancellation, e.g. from an HTTP handler's `req.signal`. */\n signal?: AbortSignal;\n /** Per-call timeout budget in ms. Defaults to 5000. */\n timeoutMs?: number;\n}\n\nconst DEFAULT_TIMEOUT_MS = 5_000;\n\n/**\n * Fetch a single order from the internal orders API.\n *\n * Bounded by both `timeoutMs` (fixed budget) and an optional caller-supplied\n * `signal`, so it never hangs indefinitely on a slow or stalled dependency.\n */\nexport async function fetchOrder(\n id: string,\n options: FetchOrderOptions = {},\n): Promise<Order> {\n // Validate the external input at the boundary before it reaches the API.\n if (typeof id !== \"string\" || id.trim() === \"\") {\n throw new Error(\"fetchOrder: id must be a non-empty string\");\n }\n\n const { signal: callerSignal, timeoutMs = DEFAULT_TIMEOUT_MS } = options;\n if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {\n throw new Error(`fetchOrder: timeoutMs must be a positive number, got ${timeoutMs}`);\n }\n\n // Combine a fixed timeout budget with the caller's cancellation signal, so\n // either one aborts the request.\n const timeoutSignal = AbortSignal.timeout(timeoutMs);\n const signal = callerSignal\n ? AbortSignal.any([callerSignal, timeoutSignal])\n : timeoutSignal;\n\n const url = new URL(`/orders/${encodeURIComponent(id)}`, ORDERS_API_BASE_URL);\n\n let response: Response;\n try {\n response = await fetch(url, {\n method: \"GET\",\n headers: { accept: \"application/json\" },\n signal,\n });\n } catch (cause) {\n // Distinguish a timeout from a caller-driven abort from a network error.\n if (cause instanceof DOMException && cause.name === \"TimeoutError\") {\n throw new Error(\n `fetchOrder: timed out after ${timeoutMs}ms fetching order \"${id}\"`,\n { cause },\n );\n }\n if (cause instanceof DOMException && cause.name === \"AbortError\") {\n throw new Error(`fetchOrder: aborted while fetching order \"${id}\"`, {\n cause,\n });\n }\n throw new Error(\n `fetchOrder: network error fetching order \"${id}\" from ${url.origin}`,\n { cause },\n );\n }\n\n if (response.status === 404) {\n throw new Error(`fetchOrder: order \"${id}\" not found`);\n }\n\n if (!response.ok) {\n throw new Error(\n `fetchOrder: orders API returned ${response.status} for order \"${id}\"`,\n );\n }\n\n // The response body is external input too: treat it as `unknown` and\n // narrow it before trusting the shape.\n const body: unknown = await response.json();\n assertIsOrder(body, id);\n return body;\n}\n\nfunction assertIsOrder(value: unknown, id: string): asserts value is Order {\n if (typeof value !== \"object\" || value === null) {\n throw new Error(`fetchOrder: orders API returned a non-object for \"${id}\"`);\n }\n const candidate = value as Record<string, unknown>;\n if (typeof candidate.id !== \"string\") {\n throw new Error(`fetchOrder: orders API response missing string \"id\" for \"${id}\"`);\n }\n if (typeof candidate.status !== \"string\") {\n throw new Error(`fetchOrder: orders API response missing string \"status\" for \"${id}\"`);\n }\n if (typeof candidate.totalCents !== \"number\") {\n throw new Error(`fetchOrder: orders API response missing number \"totalCents\" for \"${id}\"`);\n }\n}\n```\n\nNotes on the key decisions:\n\n- **Never hangs forever.** `AbortSignal.timeout(timeoutMs)` (default 5s) enforces a fixed budget on every call. This alone satisfies the \"should not hang forever\" requirement even if the caller passes nothing.\n- **Composes with caller cancellation.** `AbortSignal.any([callerSignal, timeoutSignal])` lets a request-scoped signal (e.g. an Express handler's abort signal) cancel the call as well, so a client disconnect frees the socket immediately. Either signal aborts the fetch.\n- **Validation at the boundary.** Both the `id` input and the response body are external inputs; the id is checked before use and the parsed JSON is narrowed with a type guard rather than cast, so a malformed API response becomes a clear error instead of a runtime surprise downstream.\n- **Errors carry `{ cause }`.** Timeout, caller-abort, and network failures are distinguished, each wrapping the original error so stack traces survive, and 404/non-2xx get their own messages naming the id.\n- **No floating promises, no new dependencies.** Every promise is awaited; everything uses `node`/global built-ins only. `tsc --noEmit` and `eslint` should be run against this per the skill's verification step before considering it done.\n\nOne assumption worth confirming against your project: the exact env var name (`ORDERS_API_BASE_URL`) and endpoint path (`/orders/:id`). Swap those for whatever the internal API actually uses; the timeout/abort/validation structure is independent of that.",
1554
+ "outputSha256": "bb5982a1a9422623a0188a5f62818f0918dc0000460ce07b5e787d7e72bb3986",
1555
+ "promptSha256": "020794e3963acb7a144dc0f6ad90e2798da76076b9004f37e0c6c47160b35a5e",
1556
+ "deterministic": [],
1557
+ "judge": {
1558
+ "verdict": "pass",
1559
+ "reason": "Pass 1: `AbortSignal.timeout(timeoutMs)` with DEFAULT_TIMEOUT_MS=5_000, composed via AbortSignal.any, passed as `signal` into fetch. Pass 2: catch block checks DOMException name TimeoutError/AbortError and re-throws typed errors with cause. No fail criteria: signal present, no manual setTimeout race."
1560
+ },
1561
+ "passed": true
1562
+ },
1563
+ {
1564
+ "output": "Here's an implementation of `fetchOrder` that respects the workflow and rules above \u2014 typed, time-bounded, validated at the boundary, and with proper error chaining.\n\n```ts\n// src/orders/fetchOrder.ts\n\n/**\n * Shape of an order as returned by the internal orders API.\n * Treat the wire response as `unknown` until validated.\n */\nexport interface Order {\n id: string;\n status: \"pending\" | \"paid\" | \"shipped\" | \"cancelled\";\n totalCents: number;\n currency: string;\n createdAt: string; // ISO 8601\n}\n\n/** Error thrown when the orders API responds with a non-2xx status. */\nexport class OrdersApiError extends Error {\n readonly status: number;\n readonly orderId: string;\n\n constructor(orderId: string, status: number, message: string) {\n super(message);\n this.name = \"OrdersApiError\";\n this.status = status;\n this.orderId = orderId;\n }\n}\n\n/**\n * Type guard for the wire shape. We validate the fields we rely on\n * rather than trusting the response blindly. In a project that already\n * depends on a schema library (zod, valibot, ajv), prefer that instead \u2014\n * this is the explicit-check fallback for projects without one.\n */\nfunction isOrder(value: unknown): value is Order {\n if (typeof value !== \"object\" || value === null) return false;\n const o = value as Record<string, unknown>;\n return (\n typeof o.id === \"string\" &&\n typeof o.status === \"string\" &&\n typeof o.totalCents === \"number\" &&\n typeof o.currency === \"string\" &&\n typeof o.createdAt === \"string\"\n );\n}\n\nexport interface FetchOrderOptions {\n /**\n * Request-scoped cancellation signal, e.g. from a client disconnect.\n * Combined with the timeout budget below so either can abort the call.\n */\n signal?: AbortSignal;\n /** Hard upper bound on the request, in milliseconds. Default 5000. */\n timeoutMs?: number;\n /** Base URL of the internal orders API. Defaults to ORDERS_API_URL. */\n baseUrl?: string;\n}\n\n/**\n * Fetch a single order by id from the internal orders API.\n *\n * - Bounded by a timeout so a slow/hung API cannot stall the caller forever.\n * - Honors a caller-supplied AbortSignal (e.g. client disconnect), combined\n * with the timeout so whichever fires first wins.\n * - Validates the response before returning it.\n *\n * @throws {Error} if `id` is empty.\n * @throws {OrdersApiError} if the API responds with a non-2xx status.\n * @throws {Error} if the request times out, is aborted, fails to parse,\n * or returns an unexpected shape.\n */\nexport async function fetchOrder(\n id: string,\n options: FetchOrderOptions = {},\n): Promise<Order> {\n // --- Boundary validation: inputs enter the process here. ---\n if (typeof id !== \"string\" || id.trim() === \"\") {\n throw new Error(\"fetchOrder: `id` must be a non-empty string\");\n }\n\n const {\n signal,\n timeoutMs = 5_000,\n baseUrl = process.env.ORDERS_API_URL,\n } = options;\n\n if (baseUrl === undefined || baseUrl === \"\") {\n throw new Error(\n \"fetchOrder: no orders API base URL configured (set ORDERS_API_URL or pass baseUrl)\",\n );\n }\n\n // Combine a fixed timeout budget with any caller-provided signal so that\n // *either* the deadline or a client disconnect aborts the request.\n const timeoutSignal = AbortSignal.timeout(timeoutMs);\n const combinedSignal = signal\n ? AbortSignal.any([signal, timeoutSignal])\n : timeoutSignal;\n\n const url = new URL(`/orders/${encodeURIComponent(id)}`, baseUrl);\n\n let response: Response;\n try {\n response = await fetch(url, {\n method: \"GET\",\n headers: { accept: \"application/json\" },\n signal: combinedSignal,\n });\n } catch (cause) {\n // fetch rejects with an AbortError when the signal fires.\n if (cause instanceof DOMException && cause.name === \"AbortError\") {\n const reason = signal?.aborted\n ? \"request aborted by caller\"\n : `request timed out after ${timeoutMs}ms`;\n throw new Error(`fetchOrder(${id}): ${reason}`, { cause });\n }\n throw new Error(`fetchOrder(${id}): request to ${url.href} failed`, {\n cause,\n });\n }\n\n if (!response.ok) {\n throw new OrdersApiError(\n id,\n response.status,\n `fetchOrder(${id}): orders API responded ${response.status} ${response.statusText}`,\n );\n }\n\n let body: unknown;\n try {\n body = await response.json();\n } catch (cause) {\n throw new Error(`fetchOrder(${id}): response was not valid JSON`, {\n cause,\n });\n }\n\n if (!isOrder(body)) {\n throw new Error(\n `fetchOrder(${id}): response did not match the expected Order shape`,\n );\n }\n\n return body;\n}\n```\n\n### How this satisfies the rules\n\n- **Never hangs forever.** `AbortSignal.timeout(timeoutMs)` (default 5s) guarantees the `fetch` is aborted even if the API stalls. The signal is passed into `fetch`, so the socket is actually torn down, not just ignored.\n- **Honors cancellation.** A caller-supplied `AbortSignal` (e.g. from a request-scoped controller on client disconnect) is combined with the timeout via `AbortSignal.any`, so whichever fires first aborts. The error message distinguishes the two cases.\n- **`node:` protocol.** No built-in imports are needed here, but `URL` and `AbortSignal` are globals; nothing pulls in a non-`node:` built-in.\n- **`unknown` narrowed, not `any`.** The parsed body is typed `unknown` and validated by `isOrder` before being returned as `Order`. The rules call out preferring the project's existing schema library (zod/valibot/ajv) at this boundary \u2014 the explicit type guard is the fallback for projects without one; swap it for the schema parse call if one exists.\n- **Error chaining with `cause`.** Every wrap preserves the original error via `new Error(msg, { cause })`, and the messages name the operation and the identifier (`id`) so a log line is self-contained.\n- **Boundary validation.** `id` is validated at entry, and the API base URL is checked before the request is attempted \u2014 fail fast rather than issuing a malformed request.\n- **No floating promises.** Every promise (`fetch`, `response.json`) is awaited; there are no fire-and-forget calls.\n- **No new dependencies.** Uses only the global `fetch`, `URL`, and `AbortSignal` \u2014 no runtime dependency introduced.\n\n### Verification checklist\n\n- `npx tsc --noEmit` should exit clean under `strict` (the `unknown` \u2192 `Order` narrowing via the type guard is the only spot that needs the guard; no `any` appears).\n- `npx eslint .` should be clean \u2014 no disable comments were added.\n- Every external input (`id`, `baseUrl`, the HTTP response body) is validated before use.\n- Every promise is awaited.\n- `git status` shows just the one new module file.\n\n### Usage\n\n```ts\n// Simple\nconst order = await fetchOrder(\"ord_123\");\n\n// Request-scoped: abort if the downstream client disconnects, with a 2s cap\nconst controller = new AbortController();\nreq.on(\"close\", () => controller.abort());\nconst order = await fetchOrder(\"ord_123\", {\n signal: controller.signal,\n timeoutMs: 2_000,\n});\n```\n\nOnce this compiles and lints clean, hand off to `nodejs-testing` for coverage of the timeout, abort, non-2xx, malformed-JSON, and wrong-shape paths \u2014 those are the branches most worth pinning down with a fake `fetch`.",
1565
+ "outputSha256": "16c247c667b6110b3c94c8a0c5ac99d0c348c71cda1faed964b0ac96cfd55228",
1566
+ "promptSha256": "020794e3963acb7a144dc0f6ad90e2798da76076b9004f37e0c6c47160b35a5e",
1567
+ "deterministic": [],
1568
+ "judge": {
1569
+ "verdict": "pass",
1570
+ "reason": "Pass 1: `AbortSignal.timeout(timeoutMs)` with default 5_000, combined via `AbortSignal.any([signal, timeoutSignal])`, passed as `signal: combinedSignal` into fetch. Pass 2: catch block checks `cause instanceof DOMException && cause.name === 'AbortError'` and re-throws a typed Error distinguishing timeout vs caller abort. No fail criteria: signal present, no manual setTimeout race."
1571
+ },
1572
+ "passed": true
1573
+ },
1574
+ {
1575
+ "output": "Below is an implementation of `fetchOrder(id: string)` that respects the workflow and rules from the skill. Since I have no file access, this is the complete code inline; adapt paths/imports to the project's conventions.\n\n```ts\nimport { setTimeout as sleep } from \"node:timers/promises\";\n\nexport interface Order {\n id: string;\n status: \"pending\" | \"paid\" | \"shipped\" | \"cancelled\";\n total: number;\n currency: string;\n createdAt: string;\n}\n\nexport class OrderFetchError extends Error {\n constructor(message: string, options?: { cause?: unknown }) {\n super(message, options);\n this.name = \"OrderFetchError\";\n }\n}\n\nexport interface FetchOrderOptions {\n signal?: AbortSignal;\n baseUrl?: string;\n timeoutMs?: number;\n}\n\nconst DEFAULT_BASE_URL =\n process.env.ORDERS_API_BASE_URL ?? \"http://orders.internal\";\nconst DEFAULT_TIMEOUT_MS = 5_000;\n\nfunction isOrder(value: unknown): value is Order {\n if (typeof value !== \"object\" || value === null) return false;\n const o = value as Record<string, unknown>;\n return (\n typeof o.id === \"string\" &&\n typeof o.status === \"string\" &&\n typeof o.total === \"number\" &&\n typeof o.currency === \"string\" &&\n typeof o.createdAt === \"string\"\n );\n}\n\nexport async function fetchOrder(\n id: string,\n options: FetchOrderOptions = {}\n): Promise<Order> {\n // Validate input at the boundary.\n if (typeof id !== \"string\" || id.trim() === \"\") {\n throw new OrderFetchError(\n \"fetchOrder called with an empty or non-string id\"\n );\n }\n\n const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\n // Combine the caller's signal (cancellation) with a fixed timeout budget.\n const timeoutSignal = AbortSignal.timeout(timeoutMs);\n const signal = options.signal\n ? AbortSignal.any([options.signal, timeoutSignal])\n : timeoutSignal;\n\n let res: Response;\n try {\n res = await fetch(`${baseUrl}/orders/${encodeURIComponent(id)}`, {\n method: \"GET\",\n headers: { accept: \"application/json\" },\n signal,\n });\n } catch (cause) {\n if (signal.aborted) {\n const reason = options.signal?.aborted\n ? \"caller aborted\"\n : `timed out after ${timeoutMs}ms`;\n throw new OrderFetchError(`fetchOrder(${id}) ${reason}`, { cause });\n }\n throw new OrderFetchError(\n `fetchOrder(${id}) failed to reach orders API`,\n { cause }\n );\n }\n\n if (res.status === 404) {\n throw new OrderFetchError(`fetchOrder(${id}) not found`);\n }\n if (!res.ok) {\n throw new OrderFetchError(\n `fetchOrder(${id}) orders API returned ${res.status}`\n );\n }\n\n let body: unknown;\n try {\n body = await res.json();\n } catch (cause) {\n throw new OrderFetchError(\n `fetchOrder(${id}) returned a non-JSON body`,\n { cause }\n );\n }\n\n // Validate the external payload before it reaches business logic.\n if (!isOrder(body)) {\n throw new OrderFetchError(\n `fetchOrder(${id}) returned an unexpected payload shape`\n );\n }\n\n // `sleep` is imported for illustration of an optional retry back-off; drop\n // it if unused so there is no unused import.\n void sleep;\n\n return body;\n}\n```\n\nKey points against the skill's workflow and rules:\n\n- **Timeout / no hanging.** `AbortSignal.timeout(timeoutMs)` gives a fixed upper bound, and `AbortSignal.any([...])` lets the caller's request-scoped signal still win for cancellation. Nothing here can hang forever.\n- **Input validation at the boundary.** The `id` is checked (non-empty string) before any work, and the response body is validated with a type guard before being returned as `Order` -- `unknown` is narrowed, never typed as `any`.\n- **Error contract.** Failures throw `OrderFetchError` with a message naming the operation and id, and chain the underlying cause via `{ cause }` rather than swallowing it. 404 and non-2xx are distinguished from transport failures.\n- **`node:` protocol imports.** `node:timers/promises` is prefixed per the rule.\n- **No floating promises / no new deps.** Only built-ins and global `fetch` are used; every promise is awaited.\n\nSuggested follow-ups to match a real project:\n\n1. Replace the regex-free `isOrder` guard with the project's existing schema library (e.g. zod) if one is already a dependency -- do not add a new one just for this.\n2. Confirm `moduleResolution`/`\"type\"` in `tsconfig.json`/`package.json` support top-level `AbortSignal.any` and `AbortSignal.timeout` (Node 20+); if the project targets an older Node, fall back to a manual `AbortController` plus `setTimeout`.\n3. Remove the illustrative `sleep`/`void sleep` lines -- they exist only to show where retry back-off would go and must not ship as dead code.\n4. Run the project's own checks before reporting done:\n\n```bash\nnpx tsc --noEmit\nnpx eslint .\n```\n\nBoth must exit 0 with no new `any` and no silencing eslint-disable comments. Then hand off to `nodejs-testing` for coverage of the timeout, 404, non-2xx, malformed-JSON, and bad-shape paths.",
1576
+ "outputSha256": "7a092c7ccca39bc9468f68cffc0c54c744ec856407abf11599c9a8b2df59f09c",
1577
+ "promptSha256": "020794e3963acb7a144dc0f6ad90e2798da76076b9004f37e0c6c47160b35a5e",
1578
+ "deterministic": [],
1579
+ "judge": {
1580
+ "verdict": "pass",
1581
+ "reason": "Pass 1: uses AbortSignal.timeout(timeoutMs) with DEFAULT_TIMEOUT_MS=5_000 and AbortSignal.any([options.signal, timeoutSignal]), passed as { signal } into fetch. Pass 2: catch block checks signal.aborted and throws typed OrderFetchError naming 'timed out after Xms'/'caller aborted' with cause. No fail criteria: signal present, no manual setTimeout race."
1582
+ },
1583
+ "passed": true
1584
+ }
1585
+ ]
1586
+ },
1587
+ {
1588
+ "id": "unknown-over-any",
1589
+ "kind": "behavior",
1590
+ "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?",
1591
+ "strictness": "high",
1592
+ "trials": 10,
1593
+ "passes": 10,
1594
+ "passRate": 1,
1595
+ "passAtK": 1,
1596
+ "grader": "judge",
1597
+ "status": "ran",
1598
+ "trialRecords": [
1599
+ {
1600
+ "output": "`unknown`.\n\nThat is the whole point of the boundary rule: `JSON.parse` (or whatever produced the parsed value) gives you something whose shape you have not verified, and `unknown` is the type that forces you to prove the shape before you can touch any of it. `any` would do the opposite -- it silently permits `.orderId`, `.items.length`, destructuring, and passing it straight into business logic, so a malformed or malicious body (or a nested value of an unexpected type) slips through with zero compiler friction.\n\nThe concrete shape:\n\n```ts\n// The value arrives as unknown...\nconst raw: unknown = await req.json(); // or JSON.parse(await req.text())\n\n// ...and only becomes a typed value after validation narrows it.\nconst parsed = OrderSchema.safeParse(raw);\nif (!parsed.success) {\n throw new ValidationError(\"invalid order body\", { cause: parsed.error });\n}\nconst order: Order = parsed.data; // now typed, now safe to pass on\n```\n\nUse whatever the entry point actually returns as the unvalidated type first: `await req.json()` is itself `any` in a lot of stacks, so assign it to `unknown` immediately rather than letting `any` leak. If the project has no schema library, narrow with an explicit type guard (`function isOrder(value: unknown): value is Order`) rather than a cast -- a cast is just `any` wearing a costume, because it asserts the shape instead of checking it.\n\nThe ordering matters and is testable: parsed value is `unknown` \u2192 validator narrows it \u2192 business logic receives the narrowed type. If any line of business logic can see a value that was never narrowed, the boundary was placed wrong. If you run `tsc --noEmit` and it passes while business logic still accepts the pre-validation value, look for an `any` or a cast in the chain -- that is the seam where the guarantee leaked out.",
1601
+ "outputSha256": "373ac7445055839d01db7e2f4c06b6d65c4614ce1930d29f6e3a2d21c9fb3e97",
1602
+ "promptSha256": "91f8705781778e595250452d0d99810abb1c737a5acc062edafe95b08a1ee639",
1603
+ "deterministic": [],
1604
+ "judge": {
1605
+ "verdict": "pass",
1606
+ "reason": "Answer explicitly names `unknown` as the type for the parsed body before validation (`const raw: unknown = await req.json()`), and names concrete narrowing mechanisms: `OrderSchema.safeParse(raw)` with `parsed.data` typed as Order, plus an explicit type guard `function isOrder(value: unknown): value is Order`. It warns against `any`/casts rather than recommending them."
1607
+ },
1608
+ "passed": true
1609
+ },
1610
+ {
1611
+ "output": "`unknown`.\n\n`JSON.parse` (and any untrusted input entering the process) should land in a variable typed `unknown`, never `any`. `any` silently disables the type checker for every value the data flows into, so a typo'd property access or a wrong-shaped value passed to business logic compiles clean and fails at runtime. `unknown` forces you to narrow the value \u2014 via a schema parse (zod, etc.) or an explicit type guard \u2014 before you can touch its properties at all.\n\nSketch of the shape:\n\n```ts\ntype OrderInput = { id: string; quantity: number }; // the validated shape\n\nfunction isOrderInput(value: unknown): value is OrderInput {\n // or: return OrderInputSchema.safeParse(value).success\n if (typeof value !== \"object\" || value === null) return false;\n const v = value as Record<string, unknown>;\n return typeof v.id === \"string\" && typeof v.quantity === \"number\";\n}\n\nexport async function handlePost(req: Request) {\n const raw: unknown = await req.json(); // parsed body stays unknown\n if (!isOrderInput(raw)) {\n throw new Error(\"Invalid order body\", { status: 400 } as never); // project-shaped error\n }\n // raw is now narrowed to OrderInput\n return OrderService.create(raw);\n}\n```\n\nTwo things to keep in mind alongside the type:\n\n- Validate at the point the data enters the process (the route handler), not deeper in the service layer \u2014 that's the boundary rule. Once narrowed, the value can flow into business logic carrying a real type.\n- If the project already has a schema library as a dependency, use it (`const parsed = OrderInputSchema.parse(raw)`) rather than hand-rolling the guard \u2014 same result, less code, and the schema stays the single source of truth for the shape.\n\nIf you're on a strict `tsconfig`, `noImplicitAny` plus `unknown` on the parsed body is what makes the narrowing mandatory rather than optional \u2014 check `tsconfig.json` in Step 1 of the workflow to confirm strictness is actually on before relying on it.",
1612
+ "outputSha256": "b7b86894a78f1218af7631c95d47d9f7c1ebd1290364a82672595649313ded63",
1613
+ "promptSha256": "91f8705781778e595250452d0d99810abb1c737a5acc062edafe95b08a1ee639",
1614
+ "deterministic": [],
1615
+ "judge": {
1616
+ "verdict": "pass",
1617
+ "reason": "Answer explicitly types parsed body as unknown: 'const raw: unknown = await req.json()' and states it should 'never any'. Names narrowing mechanisms concretely: explicit type guard isOrderInput (value is OrderInput) and zod schema (OrderInputSchema.safeParse/parse) running before OrderService.create. No fail criterion met."
1618
+ },
1619
+ "passed": true
1620
+ },
1621
+ {
1622
+ "output": "`unknown`.\n\n`JSON.parse` (or `req.body`, if some middleware already parsed it) returns a value whose shape you genuinely don't know yet, and the point of using `unknown` rather than `any` is that the type checker will refuse to let you read any property or call any method on it until you've narrowed it. That's exactly the behavior you want at a trust boundary: it forces the validation step to happen before the data can be used, rather than letting it silently flow into business logic.\n\nConcretely, the shape is:\n\n```ts\ntype CreateOrderBody = { customerId: string; amount: number };\n\nexport async function createOrderHandler(req: Request, res: Response) {\n const raw: unknown = req.body; // or JSON.parse(await readBody(req))\n\n const parsed = CreateOrderSchema.safeParse(raw); // zod / valibot / arktype\n if (!parsed.success) {\n return res.status(400).json({ error: \"invalid body\" });\n }\n\n const body: CreateOrderBody = parsed.data; // typed from here on\n return orderService.create(body);\n}\n```\n\nA few points that matter as much as the type annotation itself:\n\n- Do the validation in the handler, not inside the service. The service should receive already-typed, already-validated data; that way every caller of the service gets the same guarantee and you never have a second entry point that bypasses the check.\n- After validation, narrow to the concrete type rather than keeping `unknown` around and casting at each use site. One narrow at the boundary, a concrete type everywhere downstream.\n- If the project has no schema library, an explicit type guard (`function isCreateOrderBody(x: unknown): x is CreateOrderBody`) or a hand-rolled check works fine \u2014 the important part is that the guard actually inspects the runtime value rather than asserting.\n- Avoid `as CreateOrderBody` on something still typed `unknown`. That's an assertion, not a check; it compiles but provides zero runtime safety and defeats the purpose of typing it as `unknown` in the first place.\n\nOne follow-on worth flagging: if you're reading the body yourself with `JSON.parse` rather than a framework body-parser, wrap the parse in a try/catch \u2014 malformed JSON throws a `SyntaxError`, and you want that turned into a 400 rather than an unhandled rejection. Also parse the whole string before validating, and reject non-object results (`null`, a bare number, wrapped arrays if you don't accept them), since `JSON.parse(\"null\")` is valid JSON but not a valid body for your handler.",
1623
+ "outputSha256": "8121e622ebe41defeb9ee59fe93025e32138a6675cb3e54d82067a5d7b0cafd5",
1624
+ "promptSha256": "91f8705781778e595250452d0d99810abb1c737a5acc062edafe95b08a1ee639",
1625
+ "deterministic": [],
1626
+ "judge": {
1627
+ "verdict": "pass",
1628
+ "reason": "Answer explicitly types the parsed body as `unknown` (`const raw: unknown = req.body`) and contrasts it with `any`. It names concrete narrowing mechanisms: `CreateOrderSchema.safeParse(raw)` (zod/valibot/arktype) with a 400 on failure, and an explicit type guard `isCreateOrderBody(x: unknown): x is CreateOrderBody`. No fail criterion holds."
1629
+ },
1630
+ "passed": true
1631
+ },
1632
+ {
1633
+ "output": "`unknown`.\n\n`JSON.parse(...)` (or whatever returns the parsed body) is a boundary: the data's shape is not yet proven, so its type should be `unknown`, not `any` and not a hand-written interface that asserts the shape you merely hope is there.\n\nWhy `unknown` and not `any`:\n- `unknown` is the safe top type: you are forced to narrow it (via a validator or a type guard) before you can access any property or call any method on it.\n- `any` opts the value \u2014 and everything derived from it \u2014 out of type checking entirely, so a typo like `body.userIdd` or a missing null check sails through `tsc` and blows up at runtime.\n\nConcrete shape of the flow:\n\n```ts\n// boundary: untrusted\nconst raw: unknown = await request.json(); // or JSON.parse(text)\n\n// validate at the boundary, narrow to a concrete type\nconst parsed = OrderInputSchema.parse(raw); // zod -> OrderInput\n// or an explicit guard:\n// if (!isOrderInput(raw)) throw new ValidationError(\"invalid order body\");\n// raw is now narrowed to OrderInput\n\nawait orderService.create(parsed); // business logic gets a validated, typed value\n```\n\nA few points that matter here:\n\n- The validated, narrowed type (e.g. `OrderInput`) is what crosses into the service layer \u2014 business logic should never see `unknown` or a raw parse result.\n- Validation belongs at the point the data enters the process (the route handler), not two layers deeper in the service. That's the boundary rule.\n- If validation fails, throw an `Error` naming what failed (and which field), rather than letting an undefined-property access fail later with a confusing stack.\n- `tsc --noEmit` and `eslint` should both be clean, with no new `any` introduced and no `eslint-disable` added to silence a real finding.\n\nSo: parse to `unknown`, validate/narrow to the concrete input type, then hand that typed value to business logic.",
1634
+ "outputSha256": "4f3d75e2c1bd917e8e9743a67bfa71ec2a13ad7d4e90bfa36ef2ab51372de7c2",
1635
+ "promptSha256": "91f8705781778e595250452d0d99810abb1c737a5acc062edafe95b08a1ee639",
1636
+ "deterministic": [],
1637
+ "judge": {
1638
+ "verdict": "pass",
1639
+ "reason": "Answer explicitly types the parsed body as `unknown` (\"const raw: unknown = await request.json()\") and rejects `any`. It names concrete narrowing mechanisms: zod's `OrderInputSchema.parse(raw)` and an explicit type guard `isOrderInput(raw)`, both running at the boundary before `orderService.create(parsed)`. No fail criterion holds."
1640
+ },
1641
+ "passed": true
1642
+ },
1643
+ {
1644
+ "output": "`unknown`.\n\n`JSON.parse` returns `any` by default (when you don't pass a reviver-based typing), and `any` silently disables type checking for everything downstream \u2014 you can read any property off it, pass it anywhere, and the compiler won't complain. That's exactly the wrong property for untrusted input crossing a trust boundary.\n\nAssign it to `unknown` (or type the variable/parameter as `unknown`) the moment it enters your code, e.g.:\n\n```ts\nconst raw: unknown = JSON.parse(bodyText);\n```\n\nThen narrow it with a validator (a zod/valibot schema's `.parse`, or explicit type-guard checks) before it touches business logic. `unknown` forces that narrowing step \u2014 the compiler won't let you read `.foo` off it until you've proven the shape. That's the point: the type system enforces the boundary validation you're already planning to do, instead of letting it be an optional step you could skip.\n\nTwo practical notes:\n\n- If you're on a framework that already parses the body for you (Express `req.body` is `any`, some frameworks type it as `unknown`), the same rule applies \u2014 immediately bind it to an `unknown`-typed local and validate from there rather than threading `req.body` itself into the service layer.\n- Don't reach for a hand-written interface and a cast (`as OrderRequest`) to \"fix\" the type. A cast asserts a shape you haven't verified; it's `any` with extra steps. Use a validator that returns a typed value only after it actually checked the runtime shape.\n\nOnce validated, the result is a real type (`OrderRequest`) and business logic downstream can trust it \u2014 but the value before validation must stay `unknown`.",
1645
+ "outputSha256": "8c4e6571fd523a06fe0d3a37ffd954667fd3afd4e7c49d5a84a889cb2baae7c9",
1646
+ "promptSha256": "91f8705781778e595250452d0d99810abb1c737a5acc062edafe95b08a1ee639",
1647
+ "deterministic": [],
1648
+ "judge": {
1649
+ "verdict": "pass",
1650
+ "reason": "Answer explicitly names `unknown` as the type for the parsed body before validation, showing `const raw: unknown = JSON.parse(bodyText);`. It names the narrowing mechanism concretely: 'a zod/valibot schema's `.parse`, or explicit type-guard checks' before business logic. It warns against `any` rather than recommending it, so no fail criterion holds."
1651
+ },
1652
+ "passed": true
1653
+ },
1654
+ {
1655
+ "output": "`unknown`.\n\n`JSON.parse` returns `any`, and `any` propagates silently \u2014 it disables the type checker for everything downstream, so a typo like `body.emial` type-checks fine and only fails at runtime. Assigning the parse result to `unknown` forces you to narrow it before touching any property, which is exactly the point at which your validator should run.\n\nConcretely:\n\n```ts\nconst raw: unknown = JSON.parse(await readBody(req));\nconst parsed = OrderInputSchema.parse(raw); // zod/valibot/typebox/etc.\n// parsed is now the inferred, trusted type\nreturn service.create(parsed); // business logic only ever sees validated data\n```\n\nA few notes that go with this:\n\n- If the project has no schema library, write an explicit type guard (`function isOrderInput(x: unknown): x is OrderInput { ... }`) and throw on failure \u2014 do not cast (`raw as OrderInput`) to get past the compiler.\n- Validate at the boundary \u2014 the route handler or the CLI entry \u2014 not inside the service layer. By the time data reaches business logic it should already be typed and trusted.\n- The same applies to query params, path params, headers you actually read, env vars, and queue/file payloads: `unknown` in, validator, typed value out.\n- `JSON.parse` can also throw on malformed input, so wrap it and chain the cause: `throw new Error(\"invalid JSON body\", { cause: err })`, and return a 400 rather than letting a `SyntaxError` bubble up as a 500.\n- If the schema library's `parse` throws, catch it and map it to a 400 with the field errors; don't let a validation failure masquerade as an internal error.\n\nSo: `unknown` for the raw parse, and your validator's output type for anything the business logic sees.",
1656
+ "outputSha256": "42d637c34f40ebb53ba890b53a81db5cfa5b46c7bf6859a934a325ec94c38bba",
1657
+ "promptSha256": "91f8705781778e595250452d0d99810abb1c737a5acc062edafe95b08a1ee639",
1658
+ "deterministic": [],
1659
+ "judge": {
1660
+ "verdict": "pass",
1661
+ "reason": "Answer explicitly types the raw parse as unknown: `const raw: unknown = JSON.parse(...)`, and warns against any. It names concrete narrowing mechanisms: zod `OrderInputSchema.parse(raw)` and an explicit type guard `isOrderInput(x: unknown): x is OrderInput`, both running before `service.create(parsed)`. No fail criterion holds."
1662
+ },
1663
+ "passed": true
1664
+ },
1665
+ {
1666
+ "output": "`unknown`.\n\n`JSON.parse` returns `any` in TypeScript's lib typings, so the moment you do `const body = JSON.parse(raw)` and give it an inferred type, you've silently opted out of type checking for everything downstream \u2014 property accesses, destructuring, passing it to a function, all of it. Assign it to an explicitly annotated `unknown` instead:\n\n```ts\nconst body: unknown = JSON.parse(raw);\n```\n\nThen narrow it with a validator or a type guard before anything touches its fields:\n\n```ts\nconst parsed = OrderInputSchema.safeParse(body);\nif (!parsed.success) {\n throw new Error(`Invalid order body: ${parsed.error.message}`);\n}\nconst input: OrderInput = parsed.data;\n```\n\nA few notes on where this fits:\n\n- If the project already has a schema library as a dependency (zod, ajv, valibot, typebox), use it \u2014 don't hand-roll property checks. It gives you the narrowing edge back to a concrete type via `parsed.data` rather than a manual `as` cast.\n- Don't reach for a type assertion (`as OrderInput`) to make `unknown` go away. That is the same hole as `any`, just spelled differently.\n- Keep the validation at the request boundary \u2014 in the route handler, before the body reaches the service layer. Validating inside the service means unvalidated data has already crossed a boundary.\n- Wrap the `JSON.parse` call itself too: a malformed body throws a `SyntaxError`, which is a different failure from a well-formed body with the wrong shape. Surface both as client errors (400-class) rather than letting the `SyntaxError` bubble up as a 500.\n- The same rule applies to every other external input this handler touches \u2014 query params, path params, headers you read, env vars \u2014 each enters as `unknown` and gets narrowed at the point of entry.",
1667
+ "outputSha256": "868e5abf968387f8a9144c190abd41b1dcf813f7d6b8ac4f3c0922113538f194",
1668
+ "promptSha256": "91f8705781778e595250452d0d99810abb1c737a5acc062edafe95b08a1ee639",
1669
+ "deterministic": [],
1670
+ "judge": {
1671
+ "verdict": "pass",
1672
+ "reason": "Answer explicitly types the parsed body as unknown: `const body: unknown = JSON.parse(raw);` (criterion 1). It names the narrowing mechanism concretely: zod-style `OrderInputSchema.safeParse(body)` with `parsed.data` narrowed to `OrderInput`, run at the request boundary before business logic (criterion 2). It warns against `any`/`as` casts rather than recommending them, so no fail criterion holds."
1673
+ },
1674
+ "passed": true
1675
+ },
1676
+ {
1677
+ "output": "`unknown`.\n\nThat is the whole point of the workflow in Step 3: `JSON.parse` (or whatever framework body parser hands you) produces data whose shape is not guaranteed by the type system. Typing it as `unknown` forces you to narrow it with a validator or a type guard before it can be used, which is exactly the boundary-validation step.\n\n```ts\n// BAD: lies to the type checker; if the body is {} you find out at runtime,\n// three layers deep, as a TypeError on `body.email.toLowerCase()`\nfunction handlePost(req: Request) {\n const body = req.body as { email: string };\n return createUser(body.email);\n}\n\n// GOOD: unknown until validated\nfunction handlePost(req: Request) {\n const raw: unknown = req.body;\n const parsed = CreateUserSchema.safeParse(raw);\n if (!parsed.success) {\n throw new ValidationError(\"invalid request body\", { cause: parsed.error });\n }\n return createUser(parsed.data); // now typed, and actually checked\n}\n```\n\nA few notes that matter here:\n\n- Use your project's existing schema library (zod, valibot, ajv, a hand-rolled type guard) rather than introducing a new one -- the discovery step says to reuse what is already a dependency.\n- Validate at the point the data enters the process, i.e. in the route handler, not inside the service. Routing it into business logic first and validating later is one of the listed red flags.\n- `any` is explicitly called out as the wrong answer: it disables the checker for everything downstream, including the business logic you were trying to protect.\n\nOnce the body is parsed and narrowed, `parsed.data` is the typed value you pass onward. And per the skill's handoff rule, once this compiles and lints clean, test coverage belongs to `nodejs-testing` unless you were asked to do both in one pass.",
1678
+ "outputSha256": "14ed1e6cba0cfac4f38e6b51c2b67eb6d8deb007d282b1b5013613437ca6fe70",
1679
+ "promptSha256": "91f8705781778e595250452d0d99810abb1c737a5acc062edafe95b08a1ee639",
1680
+ "deterministic": [],
1681
+ "judge": {
1682
+ "verdict": "pass",
1683
+ "reason": "Answer explicitly names `unknown` as the type for the parsed body ('const raw: unknown = req.body') and names the narrowing mechanism: zod's CreateUserSchema.safeParse(raw) with a success check before createUser(parsed.data), plus mentions type guards. It warns against `any` rather than recommending it, so no fail criterion holds."
1684
+ },
1685
+ "passed": true
1686
+ },
1687
+ {
1688
+ "output": "`unknown`.\n\n`JSON.parse(...)` returns `any` in TypeScript's lib typings, which silently disables type checking for everything downstream \u2014 the compiler will happily let you read `.items.map(...)` or `.total.toFixed(2)` off a body that might be `null`, a string, or an array. Widening to `unknown` at the parse site forces you to narrow it before use, which is exactly what you want for untrusted input.\n\nConcretely, at the boundary:\n\n```ts\nconst raw: unknown = JSON.parse(await readBody(req));\n```\n\nor, if you're on an older lib target where `JSON.parse` is typed `any`, do the explicit cast:\n\n```ts\nconst raw = JSON.parse(await readBody(req)) as unknown;\n```\n\nThen narrow it once with your validation library and pass the *validated* value \u2014 not `raw` \u2014 into business logic:\n\n```ts\nconst parsed = OrderSchema.safeParse(raw);\nif (!parsed.success) {\n return res.status(400).json({ error: \"invalid body\", issues: parsed.error.issues });\n}\nreturn orderService.create(parsed.data); // parsed.data is the schema's output type\n```\n\nA few things this gets you:\n\n- `unknown` is not assignable anywhere without a check, so an accidental `orderService.create(raw)` is a compile error, not a runtime surprise.\n- The schema's inferred output type (e.g. `z.infer<typeof OrderSchema>`) becomes the type of what crosses into the service layer, so the boundary between \"untrusted\" and \"trusted\" is encoded in the types rather than in a convention.\n- It keeps the rule in `rules/patterns.mdc` honest: validate where the data enters the process. If you typed the body as `any`, there's no compiler pressure to add the validation step at all \u2014 and that pressure is most of the value.\n\nOne caveat worth flagging: `unknown` only forces narrowing at the *top level*. If you write a type guard that returns `body is Order` based on `typeof body === \"object\" && body !== null` alone, you've narrowed the container but each field is still unchecked \u2014 that's a hand-rolled guard pretending to be validation. Use the schema validator (or an explicit per-field check) rather than a structural guard for anything with more than a trivial shape.",
1689
+ "outputSha256": "460aff63ce8cb4153123aceebc72c5462131c3d726b445d87b7cf3b8411e9ce1",
1690
+ "promptSha256": "91f8705781778e595250452d0d99810abb1c737a5acc062edafe95b08a1ee639",
1691
+ "deterministic": [],
1692
+ "judge": {
1693
+ "verdict": "pass",
1694
+ "reason": "Answer explicitly types the parsed body as `unknown` (`const raw: unknown = JSON.parse(...)` and `as unknown`), and names the narrowing mechanism concretely: `OrderSchema.safeParse(raw)` with `parsed.success` check before passing `parsed.data` to `orderService.create`. It warns against `any` rather than recommending it, so no fail criterion holds."
1695
+ },
1696
+ "passed": true
1697
+ },
1698
+ {
1699
+ "output": "Use `unknown`, not `any`. `JSON.parse(...)` (or whatever deserializes the body) returns a value whose shape you have not verified, so `unknown` is the honest type: it forces you to narrow before use and keeps the type checker live downstream. `any` would silently disable checking for everything the body touches, which defeats the boundary-validation rule this skill exists to enforce.\n\nA typical shape:\n\n```ts\nasync function handler(req: Request): Promise<Response> {\n const raw: unknown = await req.json(); // or JSON.parse(await readBody(req))\n const parsed = OrderInputSchema.safeParse(raw);\n if (!parsed.success) {\n return new Response(\"invalid body\", { status: 400 });\n }\n const body = parsed.data; // now typed and validated\n return OrderService.create(body);\n}\n```\n\nNotes that matter for this step:\n\n- Validate at the boundary \u2014 here, before `body` reaches `OrderService.create` \u2014 not two layers deeper in the service.\n- If the project has a schema library (zod, valibot, ajv) already in `package.json`, reuse it rather than hand-rolling checks; if not, explicit type guards that narrow `unknown` are fine.\n- Wrap the parse itself: malformed JSON throws a `SyntaxError`, so catch it and surface a 400 rather than letting it bubble as a 500. That throw is a case where wrapping with `new Error(\"...\", { cause })` is appropriate if you rethrow.\n- After narrowing, pass the validated, concretely-typed value onward \u2014 never the original `unknown` \u2014 so no consumer inherits the unvalidated type.",
1700
+ "outputSha256": "34f53ef0d5e78d8666799c27b44313d26183b7d1a43a40592ea5229792f51d5a",
1701
+ "promptSha256": "91f8705781778e595250452d0d99810abb1c737a5acc062edafe95b08a1ee639",
1702
+ "deterministic": [],
1703
+ "judge": {
1704
+ "verdict": "pass",
1705
+ "reason": "Answer explicitly says 'Use `unknown`, not `any`' and shows `const raw: unknown = await req.json()`. It names the narrowing mechanism concretely: `OrderInputSchema.safeParse(raw)` with a success check, and mentions zod/valibot/ajv or explicit type guards narrowing unknown. No fail criterion holds."
1706
+ },
1707
+ "passed": true
1708
+ }
1709
+ ]
1710
+ }
1711
+ ],
1712
+ "verdict": "pass",
1713
+ "scope": "bundled",
1714
+ "skillDigest": "fef1b43980018aa4d3e2857c04d2ef9029c5095045b4f0516ab1e98cc9cc97da",
1715
+ "catalogDigest": "4f4016d410837e394a27e5b247e38ef2f57a1ee0baba4436ce7d3d71e223333d",
1716
+ "judgePromptVersion": "2026-09-25.1",
1717
+ "runner": "deepseek",
1718
+ "model": "deepseek-chat",
1719
+ "runnerPromptVersion": "2026-09-25.1",
1720
+ "recordedAt": "2026-09-25T05:30:08.955Z",
1721
+ "judge": "deepseek",
1722
+ "judgeModel": "deepseek-chat"
1723
+ },
1724
+ {
1725
+ "schemaVersion": "1.0.0",
1726
+ "skillId": "ts-js-node/nodejs-testing",
1727
+ "strictness": "high",
1728
+ "trials": 10,
1729
+ "triggerAccuracy": {
1730
+ "truePositive": 6,
1731
+ "falsePositive": 0,
1732
+ "positives": 6,
1733
+ "negatives": 6
1734
+ },
1735
+ "evidence": "authored",
1736
+ "scenarios": [
1737
+ {
1738
+ "id": "trigger-positive-1",
1739
+ "kind": "trigger-positive",
1740
+ "prompt": "Write Vitest tests for this order calculation function in our TypeScript service",
1741
+ "strictness": "high",
1742
+ "trials": 1,
1743
+ "passes": 1,
1744
+ "passRate": 1,
1745
+ "passAtK": 1,
1746
+ "grader": "trigger-rank-fork-family",
1747
+ "status": "ran",
1748
+ "deterministic": true
1749
+ },
1750
+ {
1751
+ "id": "trigger-positive-2",
1752
+ "kind": "trigger-positive",
1753
+ "prompt": "Add node:test coverage for the new CLI command in this Node project",
1754
+ "strictness": "high",
1755
+ "trials": 1,
1756
+ "passes": 1,
1757
+ "passRate": 1,
1758
+ "passAtK": 1,
1759
+ "grader": "trigger-rank-fork-family",
1760
+ "status": "ran",
1761
+ "deterministic": true
1762
+ },
1763
+ {
1764
+ "id": "trigger-positive-3",
1765
+ "kind": "trigger-positive",
1766
+ "prompt": "Fix this failing Jest test that's timing out on the debounce logic",
1767
+ "strictness": "high",
1768
+ "trials": 1,
1769
+ "passes": 1,
1770
+ "passRate": 1,
1771
+ "passAtK": 1,
1772
+ "grader": "trigger-rank-fork-family",
1773
+ "status": "ran",
1774
+ "deterministic": true
1775
+ },
1776
+ {
1777
+ "id": "trigger-positive-4",
1778
+ "kind": "trigger-positive",
1779
+ "prompt": "Mock the fetch call in this node:test file so it doesn't hit the real API",
1780
+ "strictness": "high",
1781
+ "trials": 1,
1782
+ "passes": 1,
1783
+ "passRate": 1,
1784
+ "passAtK": 1,
1785
+ "grader": "trigger-rank-fork-family",
1786
+ "status": "ran",
1787
+ "deterministic": true
1788
+ },
1789
+ {
1790
+ "id": "trigger-positive-5",
1791
+ "kind": "trigger-positive",
1792
+ "prompt": "Add fake timers to this test for the setInterval-based polling function",
1793
+ "strictness": "high",
1794
+ "trials": 1,
1795
+ "passes": 1,
1796
+ "passRate": 1,
1797
+ "passAtK": 1,
1798
+ "grader": "trigger-rank-fork-family",
1799
+ "status": "ran",
1800
+ "deterministic": true
1801
+ },
1802
+ {
1803
+ "id": "trigger-positive-6",
1804
+ "kind": "trigger-positive",
1805
+ "prompt": "Extend the test suite for this Node library's exported parseConfig function",
1806
+ "strictness": "high",
1807
+ "trials": 1,
1808
+ "passes": 1,
1809
+ "passRate": 1,
1810
+ "passAtK": 1,
1811
+ "grader": "trigger-rank-fork-family",
1812
+ "status": "ran",
1813
+ "deterministic": true
1814
+ },
1815
+ {
1816
+ "id": "trigger-negative-1",
1817
+ "kind": "trigger-negative",
1818
+ "prompt": "Write pytest fixtures for this Flask view function",
1819
+ "strictness": "high",
1820
+ "trials": 1,
1821
+ "passes": 1,
1822
+ "passRate": 1,
1823
+ "passAtK": 1,
1824
+ "grader": "trigger-rank-fork-family",
1825
+ "status": "ran",
1826
+ "deterministic": true
1827
+ },
1828
+ {
1829
+ "id": "trigger-negative-2",
1830
+ "kind": "trigger-negative",
1831
+ "prompt": "Implement the new order creation handler in this Express service",
1832
+ "strictness": "high",
1833
+ "trials": 1,
1834
+ "passes": 1,
1835
+ "passRate": 1,
1836
+ "passAtK": 1,
1837
+ "grader": "trigger-rank-fork-family",
1838
+ "status": "ran",
1839
+ "deterministic": true
1840
+ },
1841
+ {
1842
+ "id": "trigger-negative-3",
1843
+ "kind": "trigger-negative",
1844
+ "prompt": "Review this diff for resource cleanup and dependency risk",
1845
+ "strictness": "high",
1846
+ "trials": 1,
1847
+ "passes": 1,
1848
+ "passRate": 1,
1849
+ "passAtK": 1,
1850
+ "grader": "trigger-rank-fork-family",
1851
+ "status": "ran",
1852
+ "deterministic": true
1853
+ },
1854
+ {
1855
+ "id": "trigger-negative-4",
1856
+ "kind": "trigger-negative",
1857
+ "prompt": "Migrate this test suite's imports from CommonJS to ESM",
1858
+ "strictness": "high",
1859
+ "trials": 1,
1860
+ "passes": 1,
1861
+ "passRate": 1,
1862
+ "passAtK": 1,
1863
+ "grader": "trigger-rank-fork-family",
1864
+ "status": "ran",
1865
+ "deterministic": true
1866
+ },
1867
+ {
1868
+ "id": "trigger-negative-5",
1869
+ "kind": "trigger-negative",
1870
+ "prompt": "Fix this tsc type error in the test helper module",
1871
+ "strictness": "high",
1872
+ "trials": 1,
1873
+ "passes": 1,
1874
+ "passRate": 1,
1875
+ "passAtK": 1,
1876
+ "grader": "trigger-rank-fork-family",
1877
+ "status": "ran",
1878
+ "deterministic": true
1879
+ },
1880
+ {
1881
+ "id": "trigger-negative-6",
1882
+ "kind": "trigger-negative",
1883
+ "prompt": "Write Playwright end-to-end tests for the checkout page in this React app",
1884
+ "strictness": "high",
1885
+ "trials": 1,
1886
+ "passes": 1,
1887
+ "passRate": 1,
1888
+ "passAtK": 1,
1889
+ "grader": "trigger-rank-fork-family",
1890
+ "status": "ran",
1891
+ "deterministic": true
1892
+ },
1893
+ {
1894
+ "id": "discover-runner-first",
1895
+ "kind": "behavior",
1896
+ "prompt": "This Node.js TypeScript repo's test setup isn't described here. Write a test for a function `sumOrderTotals(orders)`. What should you check before writing the test file?",
1897
+ "strictness": "high",
1898
+ "trials": 10,
1899
+ "passes": 10,
1900
+ "passRate": 1,
1901
+ "passAtK": 1,
1902
+ "grader": "judge",
1903
+ "status": "ran",
1904
+ "trialRecords": [
1905
+ {
1906
+ "output": "Before writing anything for `sumOrderTotals(orders)`, I need to discover what this repo actually uses, since the skill's Step 1 always precedes writing:\n\n1. **Which test runner this project uses.** Read `package.json`'s `scripts.test` and `devDependencies` \u2014 is it `vitest`, `jest`, neither (`node:test` or `bun test`)? The import source of `describe`/`it`/`expect`/`assert` differs per runner and they are not interchangeable. A test file written for Vitest will not run under Jest or `node:test`.\n\n2. **The test file layout convention.** Is it co-located (`sumOrderTotals.test.ts` next to the source) or a parallel `test/`/`__tests__/` tree? I match what already exists rather than inventing a path.\n\n3. **Neighboring test files.** Read 1\u20132 existing tests in this repo to copy the mocking style and async structure the project already uses \u2014 hand-written stubs vs. the runner's mock helper (`mock.fn()`, `vi.fn()`, `jest.fn()`).\n\n4. **The function's actual contract.** I need the declaration of `sumOrderTotals(orders)` and its types: what shape is `orders`, what does it return (number? a tuple? `{ total, currency }`?), and can it throw. This determines the happy path, edge cases (empty array, `null`/`undefined` entries, boundary values), and error cases. Without this I'd be guessing at assertions.\n\n5. **Whether it touches external dependencies.** Any network, filesystem, DB, or clock use (`Date.now`, timers) in or under this function decides whether mocking is needed at all, and at what boundary \u2014 external only, never an internal helper one module away.\n\nThe skill explicitly forbids guessing here: if no test runner is configured at all, I should suggest adding one rather than add it unasked, and I cannot hard-code a runner's flags before discovering the project's own. So the correct first move is to read the config and a neighboring test \u2014 not to write the file \u2014 and if this description is all I have and I cannot inspect the repo, I should report that the runner and layout are unknown rather than produce a test that may target the wrong framework.",
1907
+ "outputSha256": "ca0636324b2e281700f694c1743dd6c15860667f8c58f9887c3af9e471461be8",
1908
+ "promptSha256": "2396c6169f3e09420de5e546cb00ff47b1eedcfd7c2d64f6f2f77e85d58930f0",
1909
+ "deterministic": [],
1910
+ "judge": {
1911
+ "verdict": "pass",
1912
+ "reason": "Point 1 names package.json scripts.test and devDependencies plus vitest/jest/node:test/bun test. Point 3 says read 1-2 existing tests to copy mocking style and import source (mock.fn(), vi.fn(), jest.fn()). No runner is picked and no test written; it explicitly defers writing until discovery."
1913
+ },
1914
+ "passed": true
1915
+ },
1916
+ {
1917
+ "output": "Before writing a single line of the test file, you'd need to check the project's actual test setup, because none of it is given and the four supported runners (`node:test`, Vitest, Jest, `bun test`) are mutually incompatible in import source and config.\n\nSpecifically, check:\n\n1. **Which runner this project uses.** Read `package.json` \u2014 `scripts.test` and `devDependencies` (`vitest`, `jest`, or neither, which implies `node:test`/`bun test`), plus any `vitest.config.*` / `jest.config.*`. This determines the import source for `describe`/`it`/`expect`/`assert` and the mocking API.\n\n2. **The test file layout.** Co-located `*.test.ts` / `*.spec.ts`, or a parallel `test/` / `__tests__/` tree. Match whichever the project already uses, and decide the path and filename for the new test accordingly.\n\n3. **Neighboring test files.** Read one or two to learn the mocking style, how async tests are structured, and the naming/fixture conventions \u2014 the new file should look like it belongs next to them.\n\n4. **The function's own contract.** What does `sumOrderTotals(orders)` actually do? Is `orders` an array, a map, an async iterable? What's an order's shape and what field(s) constitute its total \u2014 a `total` field, or computed from line items plus tax/shipping? What does it return for an empty array, and what error (type and message) does it throw on malformed input? You need this to write meaningful assertions rather than guessing.\n\n5. **Where the boundaries are.** Does `sumOrderTotals` read anything externally (currency conversion, a DB, a config file, the clock)? Those are what you'd mock at the process boundary; internal helpers stay real. If it's pure arithmetic over an array, likely nothing needs mocking at all.\n\n6. **Timer needs.** Whether anything in the call path is time-dependent, which decides if fake timers are needed.\n\nOnly after those checks would you plan the cases \u2014 happy path, empty array, boundary values, error cases asserting the specific thrown type/message \u2014 and write the file at the project's own convention path, importing from the runner the project actually uses. The test must set up and tear down its own state, use the runner's fake timers if time is involved, and never modify the source under test.",
1918
+ "outputSha256": "616fbae9102f1973b33ef983c62fb8dbbf0a96ae54267fb73a9cbac96fd78020",
1919
+ "promptSha256": "2396c6169f3e09420de5e546cb00ff47b1eedcfd7c2d64f6f2f77e85d58930f0",
1920
+ "deterministic": [],
1921
+ "judge": {
1922
+ "verdict": "pass",
1923
+ "reason": "Point 1 names package.json scripts.test and devDependencies (vitest/jest) plus vitest.config.*/jest.config.*. Point 3 says read neighboring test files to learn conventions and match them; point 1 ties import source to the runner. No runner is picked and no test written immediately."
1924
+ },
1925
+ "passed": true
1926
+ },
1927
+ {
1928
+ "output": "Before writing anything for `sumOrderTotals(orders)`, I need to discover the project's actual test setup, since none of it is described here and the runner varies per project. Concretely I'd check:\n\n**1. Which runner this repo uses**\n- `package.json` `scripts.test` (e.g. `vitest run`, `jest`, `node --test`, `bun test`) and `devDependencies` for `vitest`/`jest`/neither.\n- Any `vitest.config.*` / `jest.config.*` / tsconfig test settings.\n\n**2. Where tests live and how they're named**\n- Co-located `*.test.ts` / `*.spec.ts` next to the source, or a parallel `test/` / `__tests__/` tree. I match the existing layout rather than inventing one.\n\n**3. Import and mocking conventions**\n- Where `describe`/`it`/`test`/`expect`/`assert` come from (the specific runner's import source, not assumed).\n- How neighboring tests mock things, and whether they use the runner's own mock helpers.\n\n**4. The function's own contract**\n- `sumOrderTotals`'s signature, types, and behavior \u2014 what `orders` is (array? nullable?), what shape each order/total is (numbers, decimals, possibly string amounts?), what it returns, and how it handles edge cases like an empty array. This comes from reading the implementation and its types, not guessing.\n\n**5. Boundary decisions specific to this function**\n- Whether it does any I/O or reads the clock (probably not, but if it does, that's a mock point). For a pure sum, there's likely nothing to mock \u2014 mocking an internal helper would only prove the mocks agree with each other, so I'd avoid that.\n- The edge cases worth covering: empty array, single order, negative/zero amounts, and any floating-point or rounding behavior the implementation implies.\n\n**6. How to run and verify**\n- The project's own test command (`keryx test run --changed --strict` if configured, otherwise the discovered script), so I can confirm the new test passes.\n\nOnly once those are answered \u2014 runner, path, import source, mocking style, and the function's real contract \u2014 do I write the file, keep it to that convention, add no source changes, and run the project's actual test command to verify.",
1929
+ "outputSha256": "0baa4332d542c68676bd5ef32f2aa85e15fb5f9fd9a1daaff970a0efde2ac2a2",
1930
+ "promptSha256": "2396c6169f3e09420de5e546cb00ff47b1eedcfd7c2d64f6f2f77e85d58930f0",
1931
+ "deterministic": [],
1932
+ "judge": {
1933
+ "verdict": "pass",
1934
+ "reason": "Answer names concrete places: package.json scripts.test/devDependencies, vitest.config.*/jest.config.* (criterion 1). It says to check where describe/it/expect/assert come from \u2014 'the specific runner's import source, not assumed' \u2014 and match existing layout (criterion 2). It does not pick a runner or write the test immediately; it defers writing until discovery is done, so the fail criterion does not hold."
1935
+ },
1936
+ "passed": true
1937
+ },
1938
+ {
1939
+ "output": "Before writing anything, I'd need to confirm the project's actual test runner and conventions, since the setup isn't described here and the runners aren't interchangeable:\n\n1. **Runner** \u2014 read `package.json`'s `scripts.test` and `devDependencies` (`vitest`, `jest`, or neither \u2192 `node:test`/`bun test`), plus any `vitest.config.*`/`jest.config.*`. This determines the import source for `describe`/`it`/`expect`/`assert` and how mocking is done.\n\n2. **Test layout** \u2014 whether tests are co-located (`*.test.ts`/`*.spec.ts`) or live in a parallel `test/`/`__tests__/` tree, so the new file lands in the same place the rest do.\n\n3. **Existing style** \u2014 read 1\u20132 neighboring test files to match mocking style and how async tests are structured.\n\nThen, specific to `sumOrderTotals(orders)`:\n\n4. **The function's contract** \u2014 its signature and return type: does it sum order amounts, or order line items? What's the shape of an `order` (and its nested totals)? Is it sync or async? Does it throw on invalid input?\n\n5. **Boundary behavior to test** \u2014 empty array (likely `0`), `null`/`undefined` orders, orders with missing/zero amounts, negative amounts, and any error cases (asserting the specific error type/message, not just \"something threw\"). If it's async, every assertion-relevant `await` must be present.\n\n6. **External dependencies to mock** \u2014 if the function touches network, filesystem, a DB client, or the clock, plan to mock at that process boundary (not an internal helper). For a pure `sumOrderTotals`, likely nothing needs mocking.\n\nI would not hard-code a runner or its flags based on assumption \u2014 I'd match what Step 1\u20133 actually find, and I'd only write/modify test files, never source.",
1940
+ "outputSha256": "4ece12345c53820339affe33bf6b73875455972b75b4e708388d8697d285f6b8",
1941
+ "promptSha256": "2396c6169f3e09420de5e546cb00ff47b1eedcfd7c2d64f6f2f77e85d58930f0",
1942
+ "deterministic": [],
1943
+ "judge": {
1944
+ "verdict": "pass",
1945
+ "reason": "Step 1 names package.json scripts.test/devDependencies and vitest.config.*/jest.config.* concretely. Step 3 says read 1-2 neighboring test files to match style/layout, and Step 1 ties import source to the runner found. No runner is hard-coded; it explicitly defers to what Steps 1-3 find."
1946
+ },
1947
+ "passed": true
1948
+ },
1949
+ {
1950
+ "output": "Before writing a test for `sumOrderTotals(orders)`, you need to discover what this project's test setup actually is, because the runner, imports, and file layout are not interchangeable between `node:test`, Vitest, Jest, and `bun test`. Concretely, check:\n\n1. **Which runner the project uses.** Read `package.json`'s `scripts.test` and `devDependencies` for `vitest`, `jest`, or neither (implying `node:test`/`bun test`). Look for a `vitest.config.*` or `jest.config.*`. This determines the import source for `describe`/`it`/`test`/`expect`/`assert` and which mock/fake-timer API is available (`mock.fn()`/`mock.timers`, `vi.fn()`/`vi.useFakeTimers()`, `jest.fn()`/`jest.useFakeTimers()`).\n\n2. **The test file layout.** Is it a co-located `*.test.ts`/`*.spec.ts` next to the source, or a parallel `test/`/`__tests__/` tree? Match whichever the project already uses; don't invent a new convention.\n\n3. **The existing test style.** Read one or two neighboring test files to see the import source, mocking approach, and how async tests are structured, so the new test fits in rather than looking foreign.\n\n4. **What `sumOrderTotals` actually does and depends on.** Its exact export/signature, the shape of an `orders` item, the return type on empty input, and any external dependency (network, filesystem, DB, clock). This tells you whether mocks are needed at all and where the boundary is \u2014 mock only at the process boundary, never an internal helper one module away.\n\n5. **The behavioral surface to cover.** From the implementation, note the happy path, edge cases (empty array, `null`/`undefined`, single order, boundary or negative totals), error cases (asserting the specific thrown type/message, not just that something threw), and whether any part is async or timer-dependent (which would need the runner's fake timers). For a synchronous pure summing function this is likely just happy path plus edge cases, no mocks.\n\n6. **Whether a runner exists at all.** If nothing is configured, don't add one unasked \u2014 suggest it instead.\n\nOnce you know the runner, layout, and mocking conventions, you can write the test at the project's own convention path with the right imports, then run it via `keryx test run --changed --strict` (or the project's own discovered test command).\n\nA concrete caveat to resolve before writing: `orders` and its item shape are unspecified here, and `sumOrderTotals`' behavior on empty input (0 vs. throw) and on malformed entries is unknown. I'd read the function signature and implementation first rather than guessing, since a wrong assumption about the input shape or empty-input behavior is exactly what produces a test that passes while covering nothing.",
1951
+ "outputSha256": "66c6ae4ffcba15af3b4c105cef96868d09ee2f2d268c610e7480f8e7f5f18052",
1952
+ "promptSha256": "2396c6169f3e09420de5e546cb00ff47b1eedcfd7c2d64f6f2f77e85d58930f0",
1953
+ "deterministic": [],
1954
+ "judge": {
1955
+ "verdict": "pass",
1956
+ "reason": "Point 1 names package.json scripts.test/devDependencies and vitest.config.*/jest.config.* concretely. Point 3 says read neighboring test files for import source and mocking approach, matching existing convention. No runner is picked and no test written; it explicitly defers until runner/layout known."
1957
+ },
1958
+ "passed": true
1959
+ },
1960
+ {
1961
+ "output": "Before writing anything, I'd need to answer these from the repo itself rather than assume them:\n\n**Which runner and how it's imported**\n- What `package.json` `scripts.test` and `devDependencies` say \u2014 `vitest`, `jest`, or neither (meaning `node:test` or `bun test`).\n- The import source for `describe`/`it`/`test`/`expect` \u2014 e.g. `vitest`, `node:test` + `node:assert/strict`, `bun:test`, `@jest/globals`. These are not interchangeable; guessing wrong means the file doesn't even load.\n\n**Where the file goes**\n- Co-located `*.test.ts`/`*.spec.ts` next to `sumOrderTotals`, or a parallel `test/`/`__tests__/` tree. Match the existing pattern exactly.\n\n**The conventions the repo already uses**\n- Read 1\u20132 neighboring test files for mocking style and how async tests are structured, so the new file looks native to the codebase.\n\n**What `sumOrderTotals` actually is**\n- Its signature: `orders` is presumably an array \u2014 of what? Does each order have `total`, `lineItems`, `quantity`/`unitPrice`, currency, discounts, tax?\n- Return type: a single `number`, or a per-currency map/object?\n- Behavior on edge cases: empty array, `null`/`undefined`, missing `total`, negative or zero totals, mixed currencies, floating-point rounding (money math).\n- How it reports errors \u2014 and whether there's a defined error type/message to assert (the skill says assert the specific error type or message, not just \"something threw\").\n- Currency conversion or rounding logic that could affect expected values.\n\n**Whether any process boundary is involved**\n- Does it call `fetch`, a DB, filesystem, or `Date.now`/timers? A pure sum usually involves none, which means the test should need no mocks at all. If it does touch an external boundary, mock at that boundary only \u2014 never an internal helper.\n\n**Test-case plan I'd derive from the above**\n- Happy path: multiple orders sum correctly.\n- Edge cases: empty input, `null`/`undefined`, an order missing/zero `total`, negative values, boundary money values, floating-point rounding.\n- Error cases: whatever invalid input is defined to throw, asserting the exact type/message.\n- Async: only if the function is actually async \u2014 then every assertion-relevant `await` present, no real timer waits.\n\nI can't run the discovery here since I have no file access, so I wouldn't write the test file until those are confirmed from the repo and the function's source. Once known, I'd create the file at the project's own convention path and import source, run the project's discovered test command (or `keryx test run --changed --strict`) and confirm it exits 0, and report the file and pass count \u2014 without editing any source under test.",
1962
+ "outputSha256": "a226bdb61e0628d0ceac03ae63fb01878d3d116d86b2a176348d68fcc5d044c3",
1963
+ "promptSha256": "2396c6169f3e09420de5e546cb00ff47b1eedcfd7c2d64f6f2f77e85d58930f0",
1964
+ "deterministic": [],
1965
+ "judge": {
1966
+ "verdict": "pass",
1967
+ "reason": "Names concrete places: 'package.json scripts.test and devDependencies' and import sources (vitest, node:test, bun:test, @jest/globals). Says to 'Read 1-2 neighboring test files' for import source and layout, matching existing pattern. No runner is picked and no test written; explicitly defers until confirmed from repo."
1968
+ },
1969
+ "passed": true
1970
+ },
1971
+ {
1972
+ "output": "Before writing anything, discover the project's actual test setup, since this skill explicitly refuses to assume a runner. Concretely:\n\n- Read `package.json`: what does `scripts.test` invoke, and are `vitest`, `jest`, or neither in `devDependencies`? \"Neither\" points to `node:test` or `bun test`.\n- Look for a runner config file (`vitest.config.*`, `jest.config.*`) and any setup file it references.\n- Determine the test file layout the project already uses: co-located `*.test.ts`/`*.spec.ts` next to source, or a parallel `test/` or `__tests__/` tree.\n- Read one or two neighboring test files to copy the convention: where `describe`/`it`/`expect`/`assert` are imported from, the mocking style (`mock.fn()`/`mock.method()`, `vi.fn()`, `jest.fn()`), and how async tests are structured.\n- Locate the actual `sumOrderTotals` source and its signature/types \u2014 what shape are `orders`, what does it return, does it throw, is it async, does it touch anything outside the process (DB, network, clock)?\n- Only then plan mocks at the process boundary (network, filesystem, DB client, timers), never by mocking an internal function one module away.\n\nOnce that's settled I'd write the file at the project's own convention path using its own runner and mocking style, covering the happy path plus edge cases (`[]`, `null`/`undefined` entries, boundary numeric values) and error cases asserting the specific thrown error, then run `keryx test run --changed --strict` \u2014 or the project's own discovered test command if there's no keryx testing config. One caveat worth stating plainly: I can't actually perform any of those reads here, so I can't tell you which runner this repo uses \u2014 that discovery step is exactly what has to happen first, and I'd be guessing if I named a runner now. I would not modify any source file, only the test file.",
1973
+ "outputSha256": "6cd87fc09b8d2e3420aec182bac824afa10c71cfa14457cbd1f1e9e8a961b58b",
1974
+ "promptSha256": "2396c6169f3e09420de5e546cb00ff47b1eedcfd7c2d64f6f2f77e85d58930f0",
1975
+ "deterministic": [],
1976
+ "judge": {
1977
+ "verdict": "pass",
1978
+ "reason": "Names concrete places: package.json scripts.test and devDependencies (vitest/jest/node:test), vitest.config.*/jest.config.* config files. Says to read neighboring test files to copy import source (describe/it/expect/assert, vi.fn()/jest.fn()) and layout (co-located vs test/__tests__). Explicitly refuses to name a runner, so no fail criterion holds."
1979
+ },
1980
+ "passed": true
1981
+ },
1982
+ {
1983
+ "output": "Before writing anything, I'd check the facts the skill says are discoverable only from the repo itself:\n\n- **Which runner this project uses.** Read `package.json`'s `scripts.test` and `devDependencies` for `vitest`/`jest`, or the absence of both (implying `node:test` or `bun test`), plus any `vitest.config.*`/`jest.config.*`. This determines the import source for `describe`/`it`/`expect`/`assert` \u2014 they are not interchangeable.\n- **The test layout convention.** Whether tests are co-located (`*.test.ts`/`*.spec.ts` next to the source) or live in a parallel `test/`/`__tests__/` tree. The new file must match whatever the project already does.\n- **The mocking style in use.** Read 1-2 neighboring test files to see how mocks and async are handled \u2014 hand-written stubs vs. the runner's mock helpers (`mock.fn()`, `vi.fn()`, `jest.fn()`).\n- **What `sumOrderTotals` actually does.** Its signature, expected input shape (what an order looks like, what \"total\" means per order), return type, and error behavior \u2014 so I know the happy path, edge cases (empty array, `null`/`undefined`, boundary values), and error cases to cover. If it's async, I also need to know whether it awaits anything and whether it takes a cancellation/abort signal.\n- **Whether it has real external dependencies** (network, filesystem, clock/timers). If it's pure arithmetic over an array, it likely needs no mocks at all; if it touches timers, I'd plan for fake timers rather than a real wait.\n\nI would not write the test file until the runner, layout, and mocking conventions are confirmed from the repo, and I would not modify any source code \u2014 only test files.\n\nIf no test runner turns out to be configured at all, I'd suggest adding one rather than adding it unasked.",
1984
+ "outputSha256": "c64225c9c4c9a3d1447775561719282f70cf8fd464e070776de5edbb0894f875",
1985
+ "promptSha256": "2396c6169f3e09420de5e546cb00ff47b1eedcfd7c2d64f6f2f77e85d58930f0",
1986
+ "deterministic": [],
1987
+ "judge": {
1988
+ "verdict": "pass",
1989
+ "reason": "Criterion 1: names package.json scripts.test/devDependencies for vitest/jest and vitest.config.*/jest.config.*. Criterion 2: says read 1-2 neighboring test files for import source (describe/it/expect/assert) and layout convention (co-located vs test/__tests__), and match it. No fail criterion: it explicitly refuses to write the test until runner/layout confirmed."
1990
+ },
1991
+ "passed": true
1992
+ },
1993
+ {
1994
+ "output": "Before writing the test file, I'd check:\n\n1. **Test runner** \u2014 Read `package.json`'s `scripts.test` and `devDependencies` for `vitest`, `jest`, or neither (meaning `node:test`/`bun test`), plus any `vitest.config.*`/`jest.config.*`. The import source for `describe`/`it`/`expect`/`assert` differs per runner and isn't interchangeable.\n\n2. **Test layout** \u2014 Whether tests are co-located (`*.test.ts`/`*.spec.ts`) or live in a parallel `test/`/`__tests__/` tree, and where this function's test should go.\n\n3. **Neighboring test conventions** \u2014 Read 1-2 existing test files for mocking style and how async tests are structured, and match them.\n\n4. **The function's contract** \u2014 Its signature and what `orders` contains (shape, types, optional fields), what `sumOrderTotals` returns (number? a total object? does it handle currency/precision?), and its documented error behavior.\n\n5. **Its actual dependencies** \u2014 Whether it touches anything at a boundary (network, filesystem, DB, `Date.now`/timers) that would need mocking at the process boundary, versus being pure computation that needs none. A pure sum function likely needs no mocks; a strong signal to check before assuming any.\n\n6. **Whether a test runner is configured at all** \u2014 If none exists, suggest adding one rather than adding it unasked.\n\nIf the repo setup genuinely isn't described anywhere I can read, I'd report that the runner/layout can't be determined and ask, rather than guessing a runner and writing a test file against the wrong one.",
1995
+ "outputSha256": "c9e0386d184706543279a0bf53f319a13b884f02858cc95e132737ba852e5e36",
1996
+ "promptSha256": "2396c6169f3e09420de5e546cb00ff47b1eedcfd7c2d64f6f2f77e85d58930f0",
1997
+ "deterministic": [],
1998
+ "judge": {
1999
+ "verdict": "pass",
2000
+ "reason": "Point 1 names package.json scripts.test/devDependencies and vitest.config.*/jest.config.* concretely. Point 3 says read 1-2 existing test files and match conventions; point 1 notes import source differs per runner. No runner is picked and no test written; it explicitly refuses to guess."
2001
+ },
2002
+ "passed": true
2003
+ },
2004
+ {
2005
+ "output": "Before writing the test file, I'd check these things about the repo's existing test setup:\n\n1. **Which test runner is actually configured.** Read `package.json`'s `scripts.test` and `devDependencies` for `vitest`, `jest`, or neither (which points to `node:test` or `bun test`). Check for a `vitest.config.*`, `jest.config.*`, or equivalent. This determines the import source for `describe`/`it`/`expect`/`assert` and the mock helpers (`mock.fn()`, `vi.fn()`, `jest.fn()`) \u2014 they are not interchangeable.\n\n2. **The test file layout convention.** Is it co-located `*.test.ts`/`*.spec.ts` next to the source, or a parallel `test/`/`__tests__/` tree? Match whatever the project already uses.\n\n3. **Neighboring test files' style.** Read one or two existing test files to see how they import test primitives, how they mock, how they structure async tests, and how they use fake timers. Copy that style rather than importing another project's conventions.\n\n4. **The signature and behavior of `sumOrderTotals(orders)`.** I have no access to the source here, so before writing anything I'd need to know: its input shape (what an \"order\" and a \"total\" are, numeric type \u2014 `number`, `bigint`, decimal string, currency object), its return type, whether it throws or returns on empty input, and whether it has any async, timer, or external dependency (network, filesystem, DB, clock).\n\n5. **Mocking boundaries.** Only if `sumOrderTotals` touches the process boundary (network, filesystem, a DB client, the clock) do I need to plan mocks or fake timers. A pure summation function needs neither \u2014 mocking an internal helper would just test that the mocks agree with each other.\n\n6. **Test cases to plan once the above is known:** happy path with a normal batch; edge cases (empty array, single order, `null`/`undefined`, zero/negative amounts, floating-point or rounding boundaries); and error cases asserting the specific thrown error type/message if the function validates input. If it's async, ensure every assertion-relevant `await` is present; if it has cancelable calls, thread the test's own `AbortSignal` through.\n\nSince the setup isn't described and I can't inspect the repo in this exercise, the honest answer is: I can't write a correct test without first discovering the runner, the layout, the existing style, and `sumOrderTotals`'s actual signature and behavior. Guessing any of those would produce a test that doesn't fit the project or doesn't compile/run.",
2006
+ "outputSha256": "80171447d6db476719cade4ea71c72a94b11cdc98a956d948855d65809fbd8ee",
2007
+ "promptSha256": "2396c6169f3e09420de5e546cb00ff47b1eedcfd7c2d64f6f2f77e85d58930f0",
2008
+ "deterministic": [],
2009
+ "judge": {
2010
+ "verdict": "pass",
2011
+ "reason": "Point 1 names package.json scripts.test/devDependencies and vitest.config.*/jest.config.* concretely. Point 3 says to read existing test files to see how they import test primitives and match that style. No runner is picked and no test written; it explicitly refuses to guess."
2012
+ },
2013
+ "passed": true
2014
+ }
2015
+ ]
2016
+ },
2017
+ {
2018
+ "id": "mock-boundary-not-internal",
2019
+ "kind": "behavior",
2020
+ "prompt": "I'm writing a test for a function that calls an internal helper `formatOrder` and also calls out to a network client `httpClient.get`. What should I mock?",
2021
+ "strictness": "high",
2022
+ "trials": 10,
2023
+ "passes": 10,
2024
+ "passRate": 1,
2025
+ "passAtK": 1,
2026
+ "grader": "judge",
2027
+ "status": "ran",
2028
+ "trialRecords": [
2029
+ {
2030
+ "output": "Mock only the network client `httpClient.get` \u2014 that's the process boundary. Leave `formatOrder` real.\n\nReasons this is the right cut:\n\n- `formatOrder` is an internal collaborator in the same package. If you mock it, your test only verifies that your stub agrees with whatever the function under test does with it \u2014 it stops checking the actual formatting logic, which is presumably part of what you want to test. Let it run for real and assert on its real output (or its effect on the return value).\n- `httpClient.get` is external (network I/O). It's non-deterministic, slow, and possibly unavailable in a test env, so stub it and control exactly what it returns/rejects for each case.\n\nConcretely:\n\n1. Stub `httpClient.get` for each case you need \u2014 success payload, an error/rejection, maybe an empty response. With `node:test` you'd use `mock.method(httpClient, 'get', ...)` or a hand-written stub if `httpClient` is injectable; with Vitest `vi.spyOn(httpClient, 'get').mockResolvedValue(...)`, Jest `jest.spyOn(...)`.\n2. Make the assertion cover the real `formatOrder` behavior \u2014 assert on the formatted output, not on \"formatOrder was called.\"\n3. If you need to assert the network call happened with the right args (URL, options), assert that on the `httpClient.get` stub specifically.\n4. If the function under test also depends on timers, use the runner's fake timers rather than a real wait; if it takes a `fetch`/cancelable call, pass the test's own abort signal (`t.signal` in `node:test`).\n\nOne exception worth flagging: if `httpClient.get` is not injectable and is imported directly as a module, you may have to mock the module boundary that wraps it (e.g. the HTTP module) rather than an object method \u2014 but that's still the process boundary, not the internal `formatOrder` helper.",
2031
+ "outputSha256": "8c98c2b60180517e94c9bf642f668404f5a22470bc16c94728607d3821e3e8ea",
2032
+ "promptSha256": "94704023bd00d1bd60060c7bc1a2648c3cc086a0b2dbb25f3933dcb1a677b4e5",
2033
+ "deterministic": [],
2034
+ "judge": {
2035
+ "verdict": "pass",
2036
+ "reason": "Answer names httpClient.get as the thing to mock ('Mock only the network client httpClient.get \u2014 that's the process boundary') and explicitly says to leave formatOrder real, calling it an internal collaborator in the same package whose mocking would only verify the stub agrees with the function. No fail criterion holds."
2037
+ },
2038
+ "passed": true
2039
+ },
2040
+ {
2041
+ "output": "Mock `httpClient.get` only \u2014 that's the process-boundary dependency. Do not mock `formatOrder`.\n\nThe reasoning, concretely:\n\n- `httpClient.get` reaches outside the process (network, another service). You can't rely on it in a test, so replace it with a stub or the runner's mock helper (`mock.fn()`/`mock.method()` in `node:test`, `vi.fn()` in Vitest, `jest.fn()` in Jest) and control what it returns.\n- `formatOrder` is an internal helper one module away. If you mock it too, you end up asserting that your mock of `formatOrder` returns what your `httpClient.get` mock was configured to feed it \u2014 the test passes even when the real `formatOrder` is broken. An all-mocked internal collaborator only checks that the mocks agree with each other.\n\nSo the shape is: stub the network boundary, then assert on the real function's output \u2014 which is produced by the real `formatOrder` running for real.\n\nA couple of things to settle while you're at it (Step 1-2 of the workflow):\n\n1. Match the project's existing runner and mocking style. Read the import source of `describe`/`it`/`expect`/`assert` and how neighboring test files stub things \u2014 don't invent a style. `node:test`, Vitest, Jest, and `bun test` are not interchangeable in import source or config.\n2. If `httpClient.get` returns a promise, make sure every assertion-relevant `await` is present in the test. A test that returns before the promise settles reports green no matter what the assertion found.\n3. If the call is cancelable, pass the test's own `AbortSignal` (`t.signal` in `node:test`, or the runner's equivalent) into it rather than letting it run unbounded.\n4. If `formatOrder` involves timers or `Date.now`, use the runner's fake timers rather than a wall-clock wait.\n\nOne caveat on the boundary call: if `httpClient` itself is a thin internal wrapper that you own (not the actual HTTP client), still mock at the outermost real edge. The rule is network/filesystem/DB/clock \u2014 the thing that leaves the process \u2014 not \"the function two calls down.\" Mocking `httpClient.get` when `httpClient` is your own local module is only correct if there's no lower, genuine boundary (like `fetch`) you could stub instead; prefer the real exit point.\n\nIf you want, tell me the runner and show the neighboring test file's mocking style, and I'll sketch the exact test skeleton. But the direct answer to your question: mock `httpClient.get`, leave `formatOrder` real.",
2042
+ "outputSha256": "ba12801ad6a8b6880f887e94f4d13df67fa7b780ecd73c30125cdf7282da18e5",
2043
+ "promptSha256": "94704023bd00d1bd60060c7bc1a2648c3cc086a0b2dbb25f3933dcb1a677b4e5",
2044
+ "deterministic": [],
2045
+ "judge": {
2046
+ "verdict": "pass",
2047
+ "reason": "Answer names httpClient.get as the thing to mock ('Mock `httpClient.get` only \u2014 that's the process-boundary dependency') and explicitly says not to mock formatOrder, calling it an internal helper one module away that should run for real ('leave `formatOrder` real'). No fail criterion holds."
2048
+ },
2049
+ "passed": true
2050
+ },
2051
+ {
2052
+ "output": "Mock `httpClient.get` (the network boundary), not `formatOrder`. The rule is to mock at the process boundary \u2014 external I/O like network calls, filesystem, a DB client, or the clock \u2014 and never an internal function that's just one module away. If you mock `formatOrder` too, your test only proves that your mocks agree with each other, not that the real formatting logic feeds correctly into the real request path. So let the real `formatOrder` run and only stub out the `httpClient.get` call, asserting on the argument it was called with and on the function's output/error behavior.",
2053
+ "outputSha256": "9da0ba36a70017ff4a0347692d1831b259ebfe48b3198a1c43b9bd2350224ac6",
2054
+ "promptSha256": "94704023bd00d1bd60060c7bc1a2648c3cc086a0b2dbb25f3933dcb1a677b4e5",
2055
+ "deterministic": [],
2056
+ "judge": {
2057
+ "verdict": "pass",
2058
+ "reason": "Answer names httpClient.get as the network boundary to mock ('Mock `httpClient.get` (the network boundary)') and explicitly says not to mock formatOrder, calling it an internal function one module away that should run for real ('let the real `formatOrder` run'), noting mocking it only proves mocks agree with each other."
2059
+ },
2060
+ "passed": true
2061
+ },
2062
+ {
2063
+ "output": "Mock `httpClient.get` \u2014 the network call is a process/IO boundary and is the thing that would otherwise make the test slow, flaky, or dependent on a live service.\n\nDo not mock `formatOrder`. It's an internal collaborator, not a boundary. If you stub it out, your test stops exercising the real formatting logic and instead only verifies that your stub returns what you told it to return \u2014 it checks that your mocks agree with each other, not that the function behaves correctly. Let the real `formatOrder` run and assert on the output the whole function produces.\n\nIf `formatOrder` itself makes network/clock/filesystem calls, mock those boundaries inside it (or at least at the same place `httpClient` is injected), not `formatOrder` itself.\n\nIf the function depends on time (e.g., formatting timestamps), also freeze the clock with the runner's fake timers rather than waiting on a real timer.\n\nSo: stub `httpClient.get` (or inject a fake client) with a canned response plus a failure case; leave `formatOrder` real.",
2064
+ "outputSha256": "3361f0a7633f56f2a4863b8950d5be2f27db2c7ac76797707d5314e06f4f95c4",
2065
+ "promptSha256": "94704023bd00d1bd60060c7bc1a2648c3cc086a0b2dbb25f3933dcb1a677b4e5",
2066
+ "deterministic": [],
2067
+ "judge": {
2068
+ "verdict": "pass",
2069
+ "reason": "Names httpClient.get as the mock target ('Mock `httpClient.get` \u2014 the network call is a process/IO boundary'). Explicitly says not to mock formatOrder, calling it an internal collaborator that should run real ('Do not mock `formatOrder`... Let the real `formatOrder` run'). No fail criterion holds."
2070
+ },
2071
+ "passed": true
2072
+ },
2073
+ {
2074
+ "output": "Mock `httpClient.get` \u2014 the network call is a process boundary, so it is the right thing to replace. When you stub it, make it return a deterministic fixture response so the test isn't coupled to a live service, retry timing, or rate limits.\n\nDo **not** mock `formatOrder`. It's an internal module in the same package, and mocking it would make your test assert only that your fake of `formatOrder` agrees with what your code expects it to be called with. That verifies nothing about the actual formatting behavior and will still pass if `formatOrder` is broken. Let the real helper run and assert on the real output it produces.\n\nPractically:\n\n- Import your module under test normally, so `formatOrder` runs for real.\n- Replace the network boundary at the runner's supported seam. Prefer stubbing the client method the way the project already does it (from Step 2 of the workflow) \u2014 e.g. `mock.method(httpClient, 'get', ...)` in `node:test`, `vi.spyOn(httpClient, 'get').mockResolvedValue(...)` in Vitest, or `jest.spyOn(httpClient, 'get').mockResolvedValue(...)` in Jest.\n- If `httpClient` is imported as a module namespace, match however neighboring tests already stub it (module mock, injected client, or a fetch-level mock) rather than inventing a new seam.\n- Give the stub a canned response shaped like the real one, and add a separate test asserting the error path (e.g. `get` rejects) if the function handles failures.\n\nSo: mock the network client, exercise the real helper, and assert on the combined output. This keeps the unit boundary honest \u2014 you're testing your function's real logic against a controlled external dependency, not testing two mocks against each other.",
2075
+ "outputSha256": "e5b336875f3e9e1f33bfb2be3db6f4921fe552cbadc3414fc45706e869e041c5",
2076
+ "promptSha256": "94704023bd00d1bd60060c7bc1a2648c3cc086a0b2dbb25f3933dcb1a677b4e5",
2077
+ "deterministic": [],
2078
+ "judge": {
2079
+ "verdict": "pass",
2080
+ "reason": "Names httpClient.get as the thing to mock ('Mock `httpClient.get` \u2014 the network call is a process boundary'), and explicitly says NOT to mock formatOrder, calling it an internal module in the same package that should run for real ('Do not mock `formatOrder`... Let the real helper run'). No fail criterion holds."
2081
+ },
2082
+ "passed": true
2083
+ },
2084
+ {
2085
+ "output": "Mock `httpClient.get` (the network boundary) and leave `formatOrder` alone \u2014 call it for real and assert on its actual output.\n\nThe reasoning: an internal helper in the same package is part of the unit you're testing. If you mock it, your test only verifies that the mock you wrote agrees with the mock you wrote alongside it \u2014 it never exercises the real formatting logic, and it will keep passing even if `formatOrder` is deleted or broken. The network client, by contrast, is a process boundary: it crosses out of your code into an environment you don't control, so it must be stubbed to keep the test deterministic and fast.\n\nConcretely, with the runner's own helper from Step 2:\n\n- `vitest`: `vi.spyOn(httpClient, 'get').mockResolvedValue(...)` or inject a fake `httpClient` as a parameter.\n- `jest`: `jest.spyOn(httpClient, 'get').mockResolvedValue(...)`.\n- `node:test`: `mock.method(httpClient, 'get', async () => ...)`, or pass in a hand-written fake client object.\n\nThen assert on two things separately: that `httpClient.get` was called with the expected URL/args, and that the function's returned value reflects the real result of running `formatOrder` over a realistic input.\n\nTwo omissions worth flagging:\n\n1. **Network fake still needs its `AbortSignal`.** If the function passes a signal into `httpClient.get`, don't drop it \u2014 thread the test's own signal (`t.signal` in `node:test`, or the runner's equivalent) through so the cancelable path stays covered.\n\n2. **Never reach for mocking `formatOrder` \"to simplify.\"** That's the exact rationalization in the Red Flags table, and it's the one that silently guts the test's value. If the helper is genuinely painful to run in isolation, that's a signal about the helper's design, not a license to stub it.\n\nAnd the usual guardrail: if this test is failing, fix the test, not the source. After three iterations, stop and report the failing case rather than loosening the assertion or mocking away the hard part.",
2086
+ "outputSha256": "111b36d4e0fbe8178abd4ffe6892a3b6881f105a322f5a7df4893bd6f97d178f",
2087
+ "promptSha256": "94704023bd00d1bd60060c7bc1a2648c3cc086a0b2dbb25f3933dcb1a677b4e5",
2088
+ "deterministic": [],
2089
+ "judge": {
2090
+ "verdict": "pass",
2091
+ "reason": "Names httpClient.get as the mock target ('Mock `httpClient.get` (the network boundary)') and explicitly says to leave formatOrder alone, calling it for real ('leave `formatOrder` alone \u2014 call it for real'), explaining mocking it only checks mocks agree. No fail criterion holds."
2092
+ },
2093
+ "passed": true
2094
+ },
2095
+ {
2096
+ "output": "Mock only `httpClient.get` \u2014 the network call is the process boundary. Leave `formatOrder` real.\n\nMocking the internal helper is the trap this skill warns against: if both `formatOrder` and its caller are stubbed, the test only proves the mocks agree with each other, not that the code does anything useful. Since `formatOrder` lives in the same package as the code under test, run it for real and assert on its actual output through the function's return value. That way if `formatOrder` regresses, the test catches it.\n\nIf `formatOrder` is nondeterministic because it reads `Date.now()`, that's the one thing to control \u2014 but control the clock (fake timers: `mock.timers` / `vi.useFakeTimers()` / `jest.useFakeTimers()`), not the helper itself.\n\nRough shape:\n\n- Stub `httpClient.get` to return a fixed response (or reject, for the error case).\n- Call the function under test.\n- Assert on the result, which flows through the real `formatOrder`.\n- If the client call is cancelable, pass the test's own `AbortSignal` (`t.signal` in `node:test`) into it.\n\nTwo cases worth covering: the happy path where `httpClient.get` resolves, and the error path where it rejects \u2014 asserting the specific error type/message, not just that something threw. Keep each test's setup and teardown self-contained, and await every assertion-relevant promise so the runner can't report green on a still-pending assertion.",
2097
+ "outputSha256": "bfd392ddcdd0ce1a8f19e0ff23acd769843450e19345f5260b89e4124eb5276e",
2098
+ "promptSha256": "94704023bd00d1bd60060c7bc1a2648c3cc086a0b2dbb25f3933dcb1a677b4e5",
2099
+ "deterministic": [],
2100
+ "judge": {
2101
+ "verdict": "pass",
2102
+ "reason": "Names httpClient.get as the thing to mock ('Mock only httpClient.get \u2014 the network call is the process boundary'). Explicitly says NOT to mock formatOrder, calling it an internal helper in the same package that should run for real ('Leave formatOrder real', 'run it for real'). No fail criterion holds."
2103
+ },
2104
+ "passed": true
2105
+ },
2106
+ {
2107
+ "output": "Mock `httpClient.get` \u2014 the process boundary \u2014 and let the real `formatOrder` run.\n\nReasoning: `formatOrder` is an internal helper in the same package, so mocking it would only verify that your mock agrees with itself; the test would pass even if `formatOrder` were deleted or broken, which means it isn't testing the function's actual behavior. The network call is the one thing you can't (and shouldn't) run for real in a unit test, so it's the one thing that gets replaced.\n\nConcretely:\n\n- Replace `httpClient.get` with the runner's mock helper (`mock.fn()` in `node:test`, `vi.fn()` in Vitest, `jest.fn()` in Jest) or a hand-written stub, and give it a canned response shaped like what the real client returns.\n- Assert on the return value of the function under test, which now exercises the real `formatOrder` path plus the stubbed network result.\n- If the network failure path matters, make the stub reject (or return a non-2xx shape) in a separate test and assert the specific error type/message the function should surface \u2014 not just that something threw.\n- If `httpClient.get` is called with arguments the function computes, assert on those call arguments too; that's the part of the boundary contract worth pinning down.\n\nOne caveat: if `httpClient` is imported as a module binding (e.g. `import { httpClient } from './client'`) and the runner can't intercept it, the cleaner fix is to have the function accept the client as a parameter or construct it from a config object, so tests inject a fake. That's a source change though \u2014 this skill writes test files only, so if injection isn't already possible, mock the module at the runner's module-mocking layer (e.g. `vi.mock('./client')`, `jest.mock('./client')`) rather than reaching in to stub an internal helper. And don't also stub `formatOrder` in that same test; that combination would leave nothing real under test.",
2108
+ "outputSha256": "21271d548bd9e84b51575bc1abe34ad5760fc0cfce32777bff54b38b8983a161",
2109
+ "promptSha256": "94704023bd00d1bd60060c7bc1a2648c3cc086a0b2dbb25f3933dcb1a677b4e5",
2110
+ "deterministic": [],
2111
+ "judge": {
2112
+ "verdict": "pass",
2113
+ "reason": "Answer says 'Mock `httpClient.get` \u2014 the process boundary' (criterion 1) and 'let the real `formatOrder` run', explaining it's an internal helper in the same package whose mock would only agree with itself, plus 'don't also stub `formatOrder`' (criterion 2). No fail criterion: formatOrder is only named to say not to mock it."
2114
+ },
2115
+ "passed": true
2116
+ },
2117
+ {
2118
+ "output": "Mock the network client `httpClient.get` \u2014 that's the process boundary. Leave `formatOrder` real.\n\nThat's the core rule from Step 2: mock at the process boundary only (network calls, filesystem, a DB client, the clock/timers), never an internal function one module away. `formatOrder` is an internal helper living inside the same package \u2014 if you mock it too, your test only verifies that your stubs agree with each other, not that the function under test and its real collaborator produce correct output together. You'd be asserting `mockA` returns what you told it to return, which proves nothing about the actual formatting behavior.\n\nConcretely:\n\n- **Stub `httpClient.get`** \u2014 for example `mock.method(httpClient, 'get', ...)` in `node:test`, `vi.fn()` in Vitest, or `jest.fn()` in Jest, matching whatever mocking style you found in the neighboring test files during discovery. Make it resolve/reject with the response shapes you need to drive the happy path, edge cases (`null`/empty response), and error cases.\n- **Do not touch `formatOrder`** \u2014 call through to the real implementation. If `formatOrder` is what actually gets exercised by the assertions (e.g. it shapes the return value you're checking), that's exactly the behavior you want under test.\n\nOne caveat worth checking: if `httpClient.get` is genuinely external (a real HTTP client) you mock it directly; if it's a thin wrapper around `fetch`, some projects prefer to stub the global `fetch` instead of the wrapper. Either is a boundary \u2014 pick whichever the project's existing tests do. But in no case should `formatOrder` be mocked.\n\nSo: boundary = `httpClient.get`, mock it. Internal = `formatOrder`, leave it real.",
2119
+ "outputSha256": "996379496fe192afd27513b616674169fd443f12f86f135a8a9226ee1b46eb06",
2120
+ "promptSha256": "94704023bd00d1bd60060c7bc1a2648c3cc086a0b2dbb25f3933dcb1a677b4e5",
2121
+ "deterministic": [],
2122
+ "judge": {
2123
+ "verdict": "pass",
2124
+ "reason": "Answer names httpClient.get as the thing to mock ('Mock the network client httpClient.get \u2014 that's the process boundary', 'Stub httpClient.get'). It explicitly says not to mock formatOrder, naming it as an internal helper in the same package that should run real ('Leave formatOrder real', 'Do not touch formatOrder \u2014 call through to the real implementation'). No fail criterion holds."
2125
+ },
2126
+ "passed": true
2127
+ },
2128
+ {
2129
+ "output": "Mock `httpClient.get` \u2014 the network call is the external process boundary, so faking it keeps the test fast and deterministic while still exercising the real code path through `formatOrder`.\n\nDon't mock `formatOrder`. It's an internal helper in the same package, so if you mock it too you're only testing that your two mocks agree with each other \u2014 the real formatting logic never runs. Let the real `formatOrder` execute and let your assertions check the actual formatted output. That way the test covers both the integration between the function and its helper and the network response handling, which is what you actually care about here.\n\nConcretely: stub the `httpClient` (or intercept `fetch`/the client's transport) to return a fixed response, and give it a case or two (success payload, error status). Let everything internal run for real.",
2130
+ "outputSha256": "2e641aea4229d19525db325a11ba9f75b99314cfd3318326eb12e994fa5ca8e9",
2131
+ "promptSha256": "94704023bd00d1bd60060c7bc1a2648c3cc086a0b2dbb25f3933dcb1a677b4e5",
2132
+ "deterministic": [],
2133
+ "judge": {
2134
+ "verdict": "pass",
2135
+ "reason": "Answer names httpClient.get as the thing to mock ('Mock `httpClient.get` \u2014 the network call is the external process boundary') and explicitly says not to mock formatOrder, calling it an internal helper in the same package that should run for real ('Don't mock `formatOrder`. It's an internal helper in the same package... Let the real `formatOrder` execute'). No fail criterion holds."
2136
+ },
2137
+ "passed": true
2138
+ }
2139
+ ]
2140
+ }
2141
+ ],
2142
+ "verdict": "pass",
2143
+ "scope": "bundled",
2144
+ "skillDigest": "d685d0d2dd57a030e2aa3e73fecc018f7c25b1de7eb60c53e9bd7a4e62076418",
2145
+ "catalogDigest": "4f4016d410837e394a27e5b247e38ef2f57a1ee0baba4436ce7d3d71e223333d",
2146
+ "judgePromptVersion": "2026-09-25.1",
2147
+ "runner": "deepseek",
2148
+ "model": "deepseek-chat",
2149
+ "runnerPromptVersion": "2026-09-25.1",
2150
+ "recordedAt": "2026-09-25T05:31:20.531Z",
2151
+ "judge": "deepseek",
2152
+ "judgeModel": "deepseek-chat"
2153
+ }
2154
+ ]
2155
+ }