@levironexe/architect 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/CONTRIBUTING.md +55 -0
  3. package/README.md +341 -0
  4. package/dist/analyzers/ast-parser.d.ts +3 -0
  5. package/dist/analyzers/ast-parser.js +305 -0
  6. package/dist/analyzers/ast-parser.js.map +1 -0
  7. package/dist/analyzers/dependency-graph.d.ts +2 -0
  8. package/dist/analyzers/dependency-graph.js +67 -0
  9. package/dist/analyzers/dependency-graph.js.map +1 -0
  10. package/dist/analyzers/duplication.d.ts +2 -0
  11. package/dist/analyzers/duplication.js +56 -0
  12. package/dist/analyzers/duplication.js.map +1 -0
  13. package/dist/analyzers/file-walker.d.ts +3 -0
  14. package/dist/analyzers/file-walker.js +80 -0
  15. package/dist/analyzers/file-walker.js.map +1 -0
  16. package/dist/cli/context-runner.d.ts +1 -0
  17. package/dist/cli/context-runner.js +16 -0
  18. package/dist/cli/context-runner.js.map +1 -0
  19. package/dist/cli/index.d.ts +24 -0
  20. package/dist/cli/index.js +217 -0
  21. package/dist/cli/index.js.map +1 -0
  22. package/dist/cli/init-runner.d.ts +25 -0
  23. package/dist/cli/init-runner.js +152 -0
  24. package/dist/cli/init-runner.js.map +1 -0
  25. package/dist/cli/scan-runner.d.ts +8 -0
  26. package/dist/cli/scan-runner.js +133 -0
  27. package/dist/cli/scan-runner.js.map +1 -0
  28. package/dist/formatters/plan-json.d.ts +2 -0
  29. package/dist/formatters/plan-json.js +4 -0
  30. package/dist/formatters/plan-json.js.map +1 -0
  31. package/dist/formatters/plan-markdown.d.ts +2 -0
  32. package/dist/formatters/plan-markdown.js +42 -0
  33. package/dist/formatters/plan-markdown.js.map +1 -0
  34. package/dist/formatters/plan-prompt.d.ts +4 -0
  35. package/dist/formatters/plan-prompt.js +5 -0
  36. package/dist/formatters/plan-prompt.js.map +1 -0
  37. package/dist/formatters/plan-terminal.d.ts +5 -0
  38. package/dist/formatters/plan-terminal.js +62 -0
  39. package/dist/formatters/plan-terminal.js.map +1 -0
  40. package/dist/generators/blueprint-renderer.d.ts +3 -0
  41. package/dist/generators/blueprint-renderer.js +27 -0
  42. package/dist/generators/blueprint-renderer.js.map +1 -0
  43. package/dist/generators/claudeWriter.d.ts +3 -0
  44. package/dist/generators/claudeWriter.js +9 -0
  45. package/dist/generators/claudeWriter.js.map +1 -0
  46. package/dist/generators/copilotWriter.d.ts +3 -0
  47. package/dist/generators/copilotWriter.js +11 -0
  48. package/dist/generators/copilotWriter.js.map +1 -0
  49. package/dist/generators/cursorWriter.d.ts +3 -0
  50. package/dist/generators/cursorWriter.js +14 -0
  51. package/dist/generators/cursorWriter.js.map +1 -0
  52. package/dist/generators/genericWriter.d.ts +3 -0
  53. package/dist/generators/genericWriter.js +9 -0
  54. package/dist/generators/genericWriter.js.map +1 -0
  55. package/dist/generators/template-context.d.ts +18 -0
  56. package/dist/generators/template-context.js +126 -0
  57. package/dist/generators/template-context.js.map +1 -0
  58. package/dist/generators/templateRenderer.d.ts +2 -0
  59. package/dist/generators/templateRenderer.js +19 -0
  60. package/dist/generators/templateRenderer.js.map +1 -0
  61. package/dist/generators/windsurfWriter.d.ts +3 -0
  62. package/dist/generators/windsurfWriter.js +14 -0
  63. package/dist/generators/windsurfWriter.js.map +1 -0
  64. package/dist/generators/writer-types.d.ts +11 -0
  65. package/dist/generators/writer-types.js +40 -0
  66. package/dist/generators/writer-types.js.map +1 -0
  67. package/dist/llm/claude-provider.d.ts +8 -0
  68. package/dist/llm/claude-provider.js +22 -0
  69. package/dist/llm/claude-provider.js.map +1 -0
  70. package/dist/llm/concern-classifier.d.ts +15 -0
  71. package/dist/llm/concern-classifier.js +61 -0
  72. package/dist/llm/concern-classifier.js.map +1 -0
  73. package/dist/llm/config.d.ts +11 -0
  74. package/dist/llm/config.js +120 -0
  75. package/dist/llm/config.js.map +1 -0
  76. package/dist/llm/ollama-provider.d.ts +8 -0
  77. package/dist/llm/ollama-provider.js +27 -0
  78. package/dist/llm/ollama-provider.js.map +1 -0
  79. package/dist/llm/openai-provider.d.ts +8 -0
  80. package/dist/llm/openai-provider.js +19 -0
  81. package/dist/llm/openai-provider.js.map +1 -0
  82. package/dist/llm/prompt-builder.d.ts +12 -0
  83. package/dist/llm/prompt-builder.js +132 -0
  84. package/dist/llm/prompt-builder.js.map +1 -0
  85. package/dist/llm/provider.d.ts +17 -0
  86. package/dist/llm/provider.js +2 -0
  87. package/dist/llm/provider.js.map +1 -0
  88. package/dist/llm/response-parser.d.ts +6 -0
  89. package/dist/llm/response-parser.js +128 -0
  90. package/dist/llm/response-parser.js.map +1 -0
  91. package/dist/planner/plan-generator.d.ts +7 -0
  92. package/dist/planner/plan-generator.js +275 -0
  93. package/dist/planner/plan-generator.js.map +1 -0
  94. package/dist/planner/plan-prompt-builder.d.ts +9 -0
  95. package/dist/planner/plan-prompt-builder.js +92 -0
  96. package/dist/planner/plan-prompt-builder.js.map +1 -0
  97. package/dist/planner/plan-response-parser.d.ts +7 -0
  98. package/dist/planner/plan-response-parser.js +21 -0
  99. package/dist/planner/plan-response-parser.js.map +1 -0
  100. package/dist/planner/plan-validator.d.ts +3 -0
  101. package/dist/planner/plan-validator.js +49 -0
  102. package/dist/planner/plan-validator.js.map +1 -0
  103. package/dist/reporters/scan-json.d.ts +13 -0
  104. package/dist/reporters/scan-json.js +26 -0
  105. package/dist/reporters/scan-json.js.map +1 -0
  106. package/dist/reporters/terminal.d.ts +6 -0
  107. package/dist/reporters/terminal.js +224 -0
  108. package/dist/reporters/terminal.js.map +1 -0
  109. package/dist/scoring/consistency-score.d.ts +3 -0
  110. package/dist/scoring/consistency-score.js +23 -0
  111. package/dist/scoring/consistency-score.js.map +1 -0
  112. package/dist/scoring/duplication-score.d.ts +3 -0
  113. package/dist/scoring/duplication-score.js +16 -0
  114. package/dist/scoring/duplication-score.js.map +1 -0
  115. package/dist/scoring/health-score.d.ts +4 -0
  116. package/dist/scoring/health-score.js +20 -0
  117. package/dist/scoring/health-score.js.map +1 -0
  118. package/dist/scoring/issue-builder.d.ts +4 -0
  119. package/dist/scoring/issue-builder.js +62 -0
  120. package/dist/scoring/issue-builder.js.map +1 -0
  121. package/dist/scoring/modularity-score.d.ts +3 -0
  122. package/dist/scoring/modularity-score.js +56 -0
  123. package/dist/scoring/modularity-score.js.map +1 -0
  124. package/dist/scoring/pattern-analysis.d.ts +3 -0
  125. package/dist/scoring/pattern-analysis.js +74 -0
  126. package/dist/scoring/pattern-analysis.js.map +1 -0
  127. package/dist/scoring/separation-score.d.ts +3 -0
  128. package/dist/scoring/separation-score.js +35 -0
  129. package/dist/scoring/separation-score.js.map +1 -0
  130. package/dist/skills/detector.d.ts +4 -0
  131. package/dist/skills/detector.js +104 -0
  132. package/dist/skills/detector.js.map +1 -0
  133. package/dist/skills/lister.d.ts +9 -0
  134. package/dist/skills/lister.js +35 -0
  135. package/dist/skills/lister.js.map +1 -0
  136. package/dist/skills/loader.d.ts +6 -0
  137. package/dist/skills/loader.js +76 -0
  138. package/dist/skills/loader.js.map +1 -0
  139. package/dist/skills/structure-check.d.ts +2 -0
  140. package/dist/skills/structure-check.js +37 -0
  141. package/dist/skills/structure-check.js.map +1 -0
  142. package/dist/skills/validator.d.ts +6 -0
  143. package/dist/skills/validator.js +229 -0
  144. package/dist/skills/validator.js.map +1 -0
  145. package/dist/types/analysis.d.ts +130 -0
  146. package/dist/types/analysis.js +41 -0
  147. package/dist/types/analysis.js.map +1 -0
  148. package/dist/types/concern.d.ts +48 -0
  149. package/dist/types/concern.js +16 -0
  150. package/dist/types/concern.js.map +1 -0
  151. package/dist/types/generation.d.ts +32 -0
  152. package/dist/types/generation.js +2 -0
  153. package/dist/types/generation.js.map +1 -0
  154. package/dist/types/issue.d.ts +12 -0
  155. package/dist/types/issue.js +2 -0
  156. package/dist/types/issue.js.map +1 -0
  157. package/dist/types/pattern.d.ts +15 -0
  158. package/dist/types/pattern.js +2 -0
  159. package/dist/types/pattern.js.map +1 -0
  160. package/dist/types/plan.d.ts +56 -0
  161. package/dist/types/plan.js +2 -0
  162. package/dist/types/plan.js.map +1 -0
  163. package/dist/types/scan-output.d.ts +84 -0
  164. package/dist/types/scan-output.js +2 -0
  165. package/dist/types/scan-output.js.map +1 -0
  166. package/dist/types/scoring.d.ts +15 -0
  167. package/dist/types/scoring.js +2 -0
  168. package/dist/types/scoring.js.map +1 -0
  169. package/dist/types/skill.d.ts +97 -0
  170. package/dist/types/skill.js +2 -0
  171. package/dist/types/skill.js.map +1 -0
  172. package/dist/utils/agent-detector.d.ts +2 -0
  173. package/dist/utils/agent-detector.js +22 -0
  174. package/dist/utils/agent-detector.js.map +1 -0
  175. package/dist/utils/interactive.d.ts +6 -0
  176. package/dist/utils/interactive.js +15 -0
  177. package/dist/utils/interactive.js.map +1 -0
  178. package/dist/utils/path.d.ts +5 -0
  179. package/dist/utils/path.js +31 -0
  180. package/dist/utils/path.js.map +1 -0
  181. package/dist/utils/progress.d.ts +17 -0
  182. package/dist/utils/progress.js +48 -0
  183. package/dist/utils/progress.js.map +1 -0
  184. package/dist/utils/thresholds.d.ts +6 -0
  185. package/dist/utils/thresholds.js +48 -0
  186. package/dist/utils/thresholds.js.map +1 -0
  187. package/package.json +63 -0
  188. package/skills/meta/general-js.skill.yaml +131 -0
  189. package/skills/patterns/clerk-auth.skill.yaml +349 -0
  190. package/skills/patterns/docker-deploy.skill.yaml +214 -0
  191. package/skills/patterns/drizzle.skill.yaml +277 -0
  192. package/skills/patterns/mongoose.skill.yaml +290 -0
  193. package/skills/patterns/nextauth.skill.yaml +308 -0
  194. package/skills/patterns/playwright-e2e.skill.yaml +265 -0
  195. package/skills/patterns/prisma.skill.yaml +255 -0
  196. package/skills/patterns/s3-storage.skill.yaml +235 -0
  197. package/skills/patterns/selenium-e2e.skill.yaml +276 -0
  198. package/skills/patterns/supabase-auth.skill.yaml +298 -0
  199. package/skills/patterns/supabase.skill.yaml +304 -0
  200. package/skills/patterns/vercel-deploy.skill.yaml +219 -0
  201. package/skills/patterns/vitest-testing.skill.yaml +262 -0
  202. package/skills/stacks/express-api.skill.yaml +155 -0
  203. package/skills/stacks/fastify-api.skill.yaml +119 -0
  204. package/skills/stacks/hono-api.skill.yaml +130 -0
  205. package/skills/stacks/nestjs.skill.yaml +135 -0
  206. package/skills/stacks/nextjs-app-router.skill.yaml +176 -0
  207. package/skills/stacks/react-spa.skill.yaml +153 -0
  208. package/skills/stacks/vue-nuxt.skill.yaml +115 -0
  209. package/templates/architect-plan.md +139 -0
  210. package/templates/architect-refactor.md +119 -0
@@ -0,0 +1,262 @@
1
+ schema_version: "2.0.0"
2
+ id: vitest-testing
3
+ name: "Vitest"
4
+ version: "2.0.0"
5
+ description: "Unit and integration testing with Vitest — native ESM, vi.mock() module replacement, coverage thresholds, async assertion patterns, and test behavior verification over implementation details."
6
+ category: pattern
7
+ language: javascript
8
+ frameworks:
9
+ - vitest
10
+ dependencies:
11
+ none:
12
+ - jest
13
+ - "@jest/globals"
14
+ detection:
15
+ dependencies:
16
+ any:
17
+ - vitest
18
+ source_indicators:
19
+ - "from 'vitest'"
20
+ - "vi.fn("
21
+ - "vi.mock("
22
+ - "describe("
23
+ - "vi.spyOn("
24
+ structure:
25
+ required_dirs:
26
+ - path: src/__tests__
27
+ purpose: "Unit tests co-located near source — mirrors the src/ structure. Test files are named [filename].test.ts where [filename] matches the source file they cover. E.g., src/services/user.service.ts → src/__tests__/services/user.service.test.ts. This makes it immediately obvious which tests cover which source file."
28
+ recommended_dirs:
29
+ - path: tests/integration
30
+ purpose: "Integration tests that span multiple modules or require a real database connection. Use a test database (test schema or separate DB) isolated from development data. Do not mock the database in integration tests — the point is to test the full stack together."
31
+ - path: tests/fixtures
32
+ purpose: "Shared test factories, mock data objects, and test utilities imported by multiple test files. Examples: createTestUser() factory for Prisma, mockHttpServer using MSW for API mocking, shared vitest setup files that configure global mocks."
33
+ separation:
34
+ rules:
35
+ - concern: unit_test_location
36
+ belongs_in: src/__tests__
37
+ rule_text: "Unit tests live in src/__tests__/ and mirror the source structure. Test files are named [source-filename].test.ts. Each test file imports exactly the module it tests — not its entire dependency tree. The goal is to test the public API of each module in isolation."
38
+ example: |
39
+ // src/__tests__/services/user.service.test.ts
40
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
41
+ import { UserService } from '@/services/user.service';
42
+
43
+ // Mock the repository — unit tests don't hit the database
44
+ vi.mock('@/repositories/user.repository', () => ({
45
+ findUserByEmail: vi.fn(),
46
+ createUser: vi.fn(),
47
+ }));
48
+
49
+ describe('UserService', () => {
50
+ beforeEach(() => vi.clearAllMocks());
51
+
52
+ it('should throw if email already exists', async () => {
53
+ const { findUserByEmail } = await import('@/repositories/user.repository');
54
+ vi.mocked(findUserByEmail).mockResolvedValue({ id: '1', email: 'a@b.com' } as any);
55
+ await expect(UserService.register('a@b.com', 'password')).rejects.toThrow('Email already in use');
56
+ });
57
+ });
58
+ indicators:
59
+ - ".test.ts"
60
+ - ".spec.ts"
61
+ - "vi.mock("
62
+ - concern: mocking
63
+ belongs_in: src/__tests__
64
+ rule_text: "Use vi.mock() to mock modules at module load time — call it at the top level (hoisted). Use vi.fn() for function mocks with mockReturnValue/mockResolvedValue to control behavior. Reset all mocks in beforeEach with vi.clearAllMocks() to prevent state leaking between tests."
65
+ example: |
66
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
67
+ import { sendEmail } from '@/lib/email';
68
+ import { notifyUser } from '@/services/notification.service';
69
+
70
+ // vi.mock() is hoisted — runs before imports
71
+ vi.mock('@/lib/email', () => ({
72
+ sendEmail: vi.fn().mockResolvedValue({ messageId: 'test-123' }),
73
+ }));
74
+
75
+ describe('notifyUser', () => {
76
+ beforeEach(() => vi.clearAllMocks()); // reset call counts and return values
77
+
78
+ it('sends an email to the user', async () => {
79
+ await notifyUser('user@example.com', 'Welcome!');
80
+ expect(sendEmail).toHaveBeenCalledOnce();
81
+ expect(sendEmail).toHaveBeenCalledWith('user@example.com', expect.objectContaining({ subject: 'Welcome!' }));
82
+ });
83
+
84
+ it('does not send if user has opted out', async () => {
85
+ await notifyUser('optout@example.com', 'Welcome!', { optedOut: true });
86
+ expect(sendEmail).not.toHaveBeenCalled();
87
+ });
88
+ });
89
+ indicators:
90
+ - "vi.mock("
91
+ - "vi.fn("
92
+ - "vi.spyOn("
93
+ - concern: coverage
94
+ belongs_in: vitest.config.ts
95
+ rule_text: "Set coverage thresholds in vitest.config.ts to enforce minimum coverage. Use @vitest/coverage-v8 as the provider (faster, native). Include/exclude patterns control which files are measured. CI fails if thresholds are not met — protecting against coverage regression."
96
+ example: |
97
+ // vitest.config.ts
98
+ import { defineConfig } from 'vitest/config';
99
+ import path from 'path';
100
+
101
+ export default defineConfig({
102
+ test: {
103
+ globals: true,
104
+ environment: 'node',
105
+ setupFiles: ['./tests/setup.ts'],
106
+ coverage: {
107
+ provider: 'v8',
108
+ reporter: ['text', 'json', 'html'],
109
+ include: ['src/**/*.ts'],
110
+ exclude: ['src/**/*.d.ts', 'src/**/index.ts', 'src/types/**'],
111
+ thresholds: {
112
+ lines: 80,
113
+ functions: 80,
114
+ branches: 70,
115
+ statements: 80,
116
+ },
117
+ },
118
+ },
119
+ resolve: {
120
+ alias: { '@': path.resolve(__dirname, './src') },
121
+ },
122
+ });
123
+ indicators:
124
+ - "vitest.config.ts"
125
+ - "coverage:"
126
+ - "thresholds:"
127
+ - concern: async_testing
128
+ belongs_in: src/__tests__
129
+ rule_text: "Always await async assertions and use the correct Vitest assertion for async operations. Use expect(...).rejects.toThrow() for expected errors. Always return or await the assertion chain — unawaited assertions pass even when they should fail."
130
+ example: |
131
+ import { it, expect, vi } from 'vitest';
132
+ import { getUser } from '@/services/user.service';
133
+
134
+ it('returns null for unknown user', async () => {
135
+ const user = await getUser('nonexistent-id');
136
+ expect(user).toBeNull();
137
+ });
138
+
139
+ it('throws for invalid ID format', async () => {
140
+ // ✓ Must await rejects assertions
141
+ await expect(getUser('not-a-uuid')).rejects.toThrow('Invalid user ID');
142
+ });
143
+
144
+ it('resolves with user data', async () => {
145
+ const user = await getUser('valid-uuid-123');
146
+ expect(user).toMatchObject({ id: 'valid-uuid-123', email: expect.any(String) });
147
+ });
148
+ indicators:
149
+ - "await expect("
150
+ - ".rejects."
151
+ - ".resolves."
152
+ patterns:
153
+ data_flow:
154
+ direction: "Test → vi.mock() (module replacement) → Module Under Test → Assertion"
155
+ rules:
156
+ - "vi.mock() replaces modules at load time — mock the dependencies, not the module under test."
157
+ - "vi.clearAllMocks() in beforeEach ensures each test starts with clean mock state."
158
+ - "Test observable behavior (return values, thrown errors, side effects) not internal state."
159
+ - "Integration tests in tests/integration/ use real dependencies — no mocking."
160
+ - "Coverage thresholds in vitest.config.ts — CI fails if coverage drops below threshold."
161
+ error_handling:
162
+ recommended: "For expected errors, use await expect(fn()).rejects.toThrow(). Always await the assertion — an unawaited .rejects check never runs the assertion and passes silently."
163
+ naming:
164
+ unit_tests: "src/__tests__/[path]/[module].test.ts — mirrors source path"
165
+ integration_tests: "tests/integration/[feature].integration.test.ts"
166
+ fixtures: "tests/fixtures/[resource].factory.ts — factory functions returning test data"
167
+ setup: "tests/setup.ts — global beforeAll/afterAll hooks (DB connection, etc.)"
168
+ anti_patterns:
169
+ - id: jest_api_in_vitest
170
+ severity: warning
171
+ description: "Using Jest APIs (jest.fn(), jest.mock(), @jest/globals) in a Vitest project. Jest and Vitest have similar but distinct APIs — jest.mock() does not work in Vitest, and jest.fn() creates an incompatible mock object."
172
+ bad_example: |
173
+ // ❌ Jest API in a Vitest project — jest is undefined at runtime
174
+ import { jest } from '@jest/globals';
175
+ const fn = jest.fn();
176
+ jest.mock('../lib/db');
177
+ jest.spyOn(console, 'error');
178
+ good_example: |
179
+ // ✓ Vitest API — same patterns, correct module
180
+ import { vi } from 'vitest';
181
+ const fn = vi.fn();
182
+ vi.mock('../lib/db');
183
+ vi.spyOn(console, 'error');
184
+ - id: no_coverage_threshold
185
+ severity: warning
186
+ description: "Configuring coverage reporting without thresholds. Coverage numbers are displayed but no CI failure is triggered when they drop. Coverage silently regresses to 0% over time without developer awareness."
187
+ bad_example: |
188
+ // vitest.config.ts — coverage runs but thresholds not enforced
189
+ coverage: {
190
+ provider: 'v8',
191
+ reporter: ['text'],
192
+ // Missing: thresholds
193
+ }
194
+ good_example: |
195
+ coverage: {
196
+ provider: 'v8',
197
+ reporter: ['text', 'html'],
198
+ thresholds: { lines: 80, functions: 80, branches: 70 },
199
+ }
200
+ - id: shared_mock_state
201
+ severity: warning
202
+ description: "Not calling vi.clearAllMocks() between tests — mock call counts and return values from one test persist into the next test. A mock that returns a value in test 1 will return the same value in test 2 even if the test doesn't configure it, causing false passes."
203
+ bad_example: |
204
+ // ❌ Mock state leaks between tests — no clearAllMocks
205
+ it('test 1', () => { mockFn.mockReturnValue('hello'); mockFn(); });
206
+ it('test 2', () => {
207
+ // mockFn still has call count = 1 and return value 'hello' from test 1
208
+ expect(mockFn).not.toHaveBeenCalled(); // FALSE POSITIVE — passes incorrectly
209
+ });
210
+ good_example: |
211
+ // ✓ Clean mock state for every test
212
+ beforeEach(() => vi.clearAllMocks()); // resets all mock state before each test
213
+ - id: testing_implementation_details
214
+ severity: warning
215
+ description: "Testing internal state, private methods, or how a function does its work instead of what it returns. Tests tied to implementation break on every refactor even when behavior is correct — they add maintenance burden without adding safety."
216
+ bad_example: |
217
+ // ❌ Testing internal implementation — breaks on refactor
218
+ it('should call _formatEmail before sending', () => {
219
+ const spy = vi.spyOn(service, '_formatEmail' as any);
220
+ service.sendWelcomeEmail('user@example.com');
221
+ expect(spy).toHaveBeenCalled(); // testing HOW, not WHAT
222
+ });
223
+ good_example: |
224
+ // ✓ Test observable behavior — what the function produces
225
+ it('sends a welcome email with the correct subject', async () => {
226
+ await service.sendWelcomeEmail('user@example.com');
227
+ expect(sendEmail).toHaveBeenCalledWith('user@example.com', expect.objectContaining({
228
+ subject: 'Welcome to the platform',
229
+ }));
230
+ });
231
+ - id: async_test_without_await
232
+ severity: critical
233
+ description: "Not awaiting async assertions in tests. Without await, expect(...).rejects.toThrow() returns a Promise that is ignored — the test passes even if the function throws synchronously or doesn't throw at all."
234
+ bad_example: |
235
+ // ❌ Unawaited assertion — always passes regardless of behavior
236
+ it('should throw for invalid input', () => {
237
+ // No await — this Promise is created and immediately discarded
238
+ expect(getUser('invalid')).rejects.toThrow(); // always passes!
239
+ });
240
+ good_example: |
241
+ // ✓ Await the assertion — test fails if the function doesn't throw
242
+ it('should throw for invalid input', async () => {
243
+ await expect(getUser('invalid')).rejects.toThrow('Invalid user ID');
244
+ });
245
+ - id: snapshot_overuse
246
+ severity: warning
247
+ description: "Using toMatchSnapshot() for business logic assertions — snapshot tests hide intent (the snapshot is not readable), break on irrelevant UI/format changes, and make it impossible to understand what a test is verifying without looking up the snapshot file."
248
+ bad_example: |
249
+ // ❌ Snapshot for business logic — what does this test?
250
+ it('should calculate order total', () => {
251
+ const order = calculateOrder([{ price: 10, qty: 2 }, { price: 5, qty: 1 }]);
252
+ expect(order).toMatchSnapshot(); // snapshot: { total: 25, items: [...] }
253
+ // When the snapshot fails, is it a bug or an intentional change?
254
+ });
255
+ good_example: |
256
+ // ✓ Explicit assertions — intent is immediately clear
257
+ it('should calculate order total with correct subtotals', () => {
258
+ const order = calculateOrder([{ price: 10, qty: 2 }, { price: 5, qty: 1 }]);
259
+ expect(order.total).toBe(25);
260
+ expect(order.items).toHaveLength(2);
261
+ expect(order.items[0].subtotal).toBe(20);
262
+ });
@@ -0,0 +1,155 @@
1
+ schema_version: "2.0.0"
2
+ id: express-api
3
+ name: "Express.js REST API"
4
+ version: "1.1.0"
5
+ description: "Layered Express REST API with routing, request handling, business logic, data access, middleware, configuration, and utilities separated."
6
+ category: stack
7
+ language: javascript
8
+ frameworks:
9
+ - express
10
+ detection:
11
+ dependencies:
12
+ any:
13
+ - express
14
+ none:
15
+ - next
16
+ source_indicators:
17
+ - "express()"
18
+ - "app.listen"
19
+ - "router.get"
20
+ - "router.post"
21
+ structure:
22
+ required_dirs:
23
+ - path: src/routes
24
+ purpose: "Route definitions organized by resource."
25
+ - path: src/controllers
26
+ purpose: "Request handlers that receive HTTP input, call services, and return responses."
27
+ - path: src/services
28
+ purpose: "Business logic with no HTTP request or response awareness."
29
+ - path: src/models
30
+ purpose: "Data models and database interactions."
31
+ - path: src/middleware
32
+ purpose: "Cross-cutting request behavior such as auth, validation, and error handling."
33
+ recommended_dirs:
34
+ - path: src/config
35
+ purpose: "Environment and application configuration."
36
+ - path: src/utils
37
+ purpose: "Small shared helpers with no framework coupling."
38
+ - path: src/validators
39
+ purpose: "Request and domain validation schemas."
40
+ separation:
41
+ rules:
42
+ - concern: routing
43
+ belongs_in: src/routes
44
+ rule_text: "Route files define HTTP endpoints and delegate request flow to controllers. They should not contain persistence or business rules."
45
+ example: |
46
+ router.get('/users', UserController.list);
47
+ router.post('/users', UserController.create);
48
+ indicators:
49
+ - "router.get"
50
+ - "router.post"
51
+ - "app.get"
52
+ - concern: request_handling
53
+ belongs_in: src/controllers
54
+ rule_text: "Controllers translate req/res objects into plain inputs for services and shape the HTTP response from service results."
55
+ example: |
56
+ export async function create(req, res) {
57
+ const user = await userService.create(req.body);
58
+ res.status(201).json(user);
59
+ }
60
+ indicators:
61
+ - "req.body"
62
+ - "res.json"
63
+ - "res.status"
64
+ - concern: business_logic
65
+ belongs_in: src/services
66
+ rule_text: "Services contain business rules and orchestration. They accept plain data and return plain data without touching HTTP objects."
67
+ example: |
68
+ export async function createUser(input) {
69
+ await validateUser(input);
70
+ return userModel.create(input);
71
+ }
72
+ anti_indicators:
73
+ - "req."
74
+ - "res."
75
+ - "next("
76
+ - concern: data_access
77
+ belongs_in: src/models
78
+ rule_text: "Models and repositories own database queries and persistence details so higher layers stay storage-agnostic."
79
+ example: |
80
+ export async function createUserRecord(data) {
81
+ return prisma.user.create({ data });
82
+ }
83
+ indicators:
84
+ - "prisma."
85
+ - ".findOne"
86
+ - ".create("
87
+ - "SELECT"
88
+ patterns:
89
+ error_handling:
90
+ recommended: "Use centralized error-handling middleware."
91
+ data_flow:
92
+ direction: "Route -> Controller -> Service -> Model"
93
+ rules:
94
+ - "Routes call controllers."
95
+ - "Controllers call services."
96
+ - "Services do not import request or response objects."
97
+ naming:
98
+ files: "Use kebab-case for all file names. Suffix by layer: users.route.ts, users.controller.ts, users.service.ts, users.model.ts, auth.middleware.ts."
99
+ controllers: "[resource].controller.ts"
100
+ services: "[resource].service.ts"
101
+ anti_patterns:
102
+ - id: god_file
103
+ severity: critical
104
+ description: "Single file mixes routes, data access, validation, and business logic."
105
+ bad_example: |
106
+ app.post('/users', async (req, res) => {
107
+ const hash = await bcrypt.hash(req.body.password, 10);
108
+ const user = await db.query('INSERT INTO users ...');
109
+ res.status(201).json(user);
110
+ });
111
+ good_example: |
112
+ // src/routes/users.ts — route only delegates
113
+ router.post('/users', UserController.create);
114
+
115
+ // src/controllers/users.controller.ts — translates HTTP to plain data
116
+ export async function create(req: Request, res: Response) {
117
+ const user = await userService.createUser(req.body);
118
+ res.status(201).json(user);
119
+ }
120
+
121
+ // src/services/users.service.ts — business rules, no HTTP awareness
122
+ export async function createUser(input: CreateUserDTO) {
123
+ const hash = await bcrypt.hash(input.password, 10);
124
+ return userModel.create({ ...input, password: hash });
125
+ }
126
+ - id: raw_sql_in_routes
127
+ severity: warning
128
+ description: "SQL queries appear directly in route handlers."
129
+ bad_example: |
130
+ router.get('/users', async (req, res) => {
131
+ const rows = await db.query('SELECT * FROM users');
132
+ res.json(rows);
133
+ });
134
+ good_example: |
135
+ router.get('/users', UserController.list);
136
+ - id: hardcoded_secrets
137
+ severity: critical
138
+ description: "Secrets or credentials are hardcoded in source files."
139
+ bad_example: |
140
+ const jwtSecret = 'super-secret-key';
141
+ good_example: |
142
+ // src/config/index.ts — validated at startup, never scattered across the app
143
+ import { z } from 'zod';
144
+ const env = z.object({
145
+ JWT_SECRET: z.string().min(32),
146
+ DATABASE_URL: z.string().url(),
147
+ }).parse(process.env);
148
+ export const config = {
149
+ auth: { jwtSecret: env.JWT_SECRET },
150
+ db: { url: env.DATABASE_URL },
151
+ };
152
+
153
+ // In middleware — reads from config, not from process.env
154
+ import { config } from '@/config';
155
+ const jwtSecret = config.auth.jwtSecret;
@@ -0,0 +1,119 @@
1
+ schema_version: "2.0.0"
2
+ id: fastify-api
3
+ name: "Fastify API"
4
+ version: "1.1.0"
5
+ description: "High-performance Fastify API with plugin architecture, TypeBox schema validation, and decorator patterns."
6
+ category: stack
7
+ language: javascript
8
+ frameworks:
9
+ - fastify
10
+ detection:
11
+ dependencies:
12
+ any:
13
+ - fastify
14
+ none:
15
+ - express
16
+ - next
17
+ - hono
18
+ source_indicators:
19
+ - "fastify()"
20
+ - "Fastify()"
21
+ - "fastify.register"
22
+ structure:
23
+ required_dirs:
24
+ - path: src/routes
25
+ purpose: "Route definitions registered as Fastify plugins — one file per resource."
26
+ - path: src/services
27
+ purpose: "Business logic with no Fastify imports — receives plain data, returns plain data."
28
+ - path: src/schemas
29
+ purpose: "TypeBox JSON schemas for request/response validation shared between routes and types."
30
+ - path: src/plugins
31
+ purpose: "Fastify plugins — auth, DB connections, decorators, and shared utilities."
32
+ recommended_dirs:
33
+ - path: src/types
34
+ purpose: "Shared TypeScript interfaces and custom error types."
35
+ - path: src/lib
36
+ purpose: "Client singletons such as database connections."
37
+ separation:
38
+ rules:
39
+ - concern: schema_validation
40
+ belongs_in: src/schemas
41
+ rule_text: "Define all request/response schemas using TypeBox in src/schemas/. Register them on routes using the schema option. Never skip schema validation on public endpoints."
42
+ example: |
43
+ // src/schemas/user.schema.ts
44
+ import { Type } from '@sinclair/typebox';
45
+ export const CreateUserBody = Type.Object({
46
+ email: Type.String({ format: 'email' }),
47
+ password: Type.String({ minLength: 8 }),
48
+ });
49
+ indicators:
50
+ - "Type.Object"
51
+ - "@sinclair/typebox"
52
+ - concern: plugins
53
+ belongs_in: src/plugins
54
+ rule_text: "Encapsulate cross-cutting concerns as Fastify plugins. Register DB clients, auth decorators, and shared utilities as plugins — not inline in route handlers. Use fastify-plugin (fp) to avoid scope encapsulation so decorators are visible across the entire app."
55
+ example: |
56
+ // src/plugins/db.ts
57
+ import fp from 'fastify-plugin';
58
+ export default fp(async (fastify) => {
59
+ fastify.decorate('db', drizzle(connection));
60
+ });
61
+ indicators:
62
+ - "fastify-plugin"
63
+ - "fp("
64
+ - "fastify.decorate"
65
+ - "fastify.register"
66
+ - concern: services
67
+ belongs_in: src/services
68
+ rule_text: "Services must not import Fastify types (FastifyRequest, FastifyReply). They receive plain objects and return plain data."
69
+ example: |
70
+ // src/services/users.service.ts
71
+ export async function createUser(data: CreateUserDTO) {
72
+ return db.insert(users).values(data).returning();
73
+ }
74
+ anti_indicators:
75
+ - "FastifyRequest"
76
+ - "FastifyReply"
77
+ patterns:
78
+ data_flow:
79
+ direction: "Route Plugin → Service → Data Access"
80
+ rules:
81
+ - "Route plugins define schemas and delegate to services."
82
+ - "Services contain business logic with no Fastify imports."
83
+ - "Plugins manage cross-cutting concerns like auth and DB connections."
84
+ error_handling:
85
+ recommended: "Use Fastify's setErrorHandler for centralized error handling."
86
+ naming:
87
+ routes: "[resource].route.ts"
88
+ services: "[resource].service.ts"
89
+ schemas: "[resource].schema.ts"
90
+ anti_patterns:
91
+ - id: no_schema
92
+ severity: critical
93
+ description: "Defining Fastify routes without a schema object loses type safety, automatic serialization, and OpenAPI doc generation."
94
+ bad_example: |
95
+ // ❌ No schema — no type safety or serialization
96
+ fastify.post('/users', async (req, reply) => { ... });
97
+ good_example: |
98
+ // ✓ Schema-validated route
99
+ fastify.post('/users', { schema: { body: CreateUserBody } }, handler);
100
+ - id: business_logic_in_handler
101
+ severity: critical
102
+ description: "Putting business rules and DB queries directly in route handlers instead of services."
103
+ bad_example: |
104
+ fastify.post('/users', async (req) => {
105
+ const hash = await bcrypt.hash(req.body.password, 10);
106
+ return fastify.db.users.create({ ...req.body, password: hash });
107
+ });
108
+ good_example: |
109
+ fastify.post('/users', { schema: { body: CreateUserBody } }, async (req) => {
110
+ return userService.createUser(req.body);
111
+ });
112
+ - id: fastify_type_in_service
113
+ severity: warning
114
+ description: "Importing FastifyRequest or FastifyReply in service files couples business logic to the HTTP framework."
115
+ bad_example: |
116
+ import type { FastifyRequest } from 'fastify';
117
+ export function handleUser(req: FastifyRequest) { ... }
118
+ good_example: |
119
+ export function handleUser(data: CreateUserDTO) { ... }
@@ -0,0 +1,130 @@
1
+ schema_version: "2.0.0"
2
+ id: hono-api
3
+ name: "Hono API"
4
+ version: "1.1.0"
5
+ description: "Lightweight, edge-first Hono API with Zod validation, platform-agnostic handlers, and middleware chaining."
6
+ category: stack
7
+ language: javascript
8
+ frameworks:
9
+ - hono
10
+ detection:
11
+ dependencies:
12
+ any:
13
+ - hono
14
+ none:
15
+ - express
16
+ - fastify
17
+ - "@nestjs/core"
18
+ source_indicators:
19
+ - "new Hono()"
20
+ - "from 'hono'"
21
+ - "from \"hono\""
22
+ structure:
23
+ required_dirs:
24
+ - path: src/routes
25
+ purpose: "Route definitions grouped by resource — registered on the Hono app instance."
26
+ - path: src/services
27
+ purpose: "Business logic with no Hono or platform-specific imports."
28
+ - path: src/middleware
29
+ purpose: "Reusable Hono middleware — auth, logging, CORS, error handling."
30
+ recommended_dirs:
31
+ - path: src/schemas
32
+ purpose: "Zod schemas for request/response validation."
33
+ - path: src/types
34
+ purpose: "Shared TypeScript interfaces and error types."
35
+ - path: src/lib
36
+ purpose: "Singleton instances (database connections, KV clients) initialized once and reused across the app. Environment parsing and config validation live here. No route definitions or business logic."
37
+ separation:
38
+ rules:
39
+ - concern: routing
40
+ belongs_in: src/routes
41
+ rule_text: "Route files define HTTP endpoints and call services. Use Hono's zValidator middleware for input validation. No business logic or DB queries in route handlers."
42
+ indicators:
43
+ - "new Hono()"
44
+ - ".get("
45
+ - ".post("
46
+ - "zValidator"
47
+ example: |
48
+ // src/routes/users.ts
49
+ import { Hono } from 'hono';
50
+ import { zValidator } from '@hono/zod-validator';
51
+ import { createUserSchema } from '../schemas/user.schema';
52
+ import { userService } from '../services/users.service';
53
+ const users = new Hono();
54
+ users.post('/', zValidator('json', createUserSchema), async (c) => {
55
+ const data = c.req.valid('json');
56
+ return c.json(await userService.create(data), 201);
57
+ });
58
+ - concern: platform_agnostic_handlers
59
+ belongs_in: src/services
60
+ rule_text: "Services must not import Hono context types (Context, HonoRequest). They receive plain data and return plain data — this keeps them portable across Cloudflare Workers, Node.js, and Bun."
61
+ example: |
62
+ // src/services/users.service.ts
63
+ export async function createUser(data: CreateUserDTO) {
64
+ return db.insert(users).values(data);
65
+ }
66
+ anti_indicators:
67
+ - "Context"
68
+ - "HonoRequest"
69
+ - "c.req"
70
+ - concern: validation
71
+ belongs_in: src/schemas
72
+ rule_text: "Define Zod schemas in src/schemas/ and use @hono/zod-validator to validate request bodies. Never manually parse and validate req.json() in handlers."
73
+ example: |
74
+ // src/schemas/user.schema.ts
75
+ import { z } from 'zod';
76
+ export const createUserSchema = z.object({
77
+ email: z.string().email(),
78
+ password: z.string().min(8),
79
+ });
80
+ patterns:
81
+ data_flow:
82
+ direction: "Route → Service → Data Access"
83
+ rules:
84
+ - "Routes validate input with Zod and delegate to services."
85
+ - "Services are platform-agnostic — no Hono or env-specific imports."
86
+ - "Middleware handles cross-cutting concerns like auth and CORS."
87
+ error_handling:
88
+ recommended: "Use app.onError() for centralized error handling."
89
+ naming:
90
+ routes: "[resource].ts"
91
+ services: "[resource].service.ts"
92
+ schemas: "[resource].schema.ts"
93
+ anti_patterns:
94
+ - id: node_api_in_handler
95
+ severity: critical
96
+ description: "Using Node.js-specific APIs (fs, process, Buffer) in route handlers breaks Cloudflare Workers and Bun compatibility."
97
+ bad_example: |
98
+ // ❌ Node.js API breaks edge compatibility
99
+ app.get('/file', (c) => {
100
+ const data = require('fs').readFileSync('./data.json');
101
+ return c.json(JSON.parse(data));
102
+ });
103
+ good_example: |
104
+ // ✓ Use env bindings or fetch — platform-agnostic
105
+ app.get('/file', async (c) => {
106
+ const data = await c.env.KV.get('data');
107
+ return c.json(JSON.parse(data));
108
+ });
109
+ - id: business_logic_in_handler
110
+ severity: critical
111
+ description: "Placing business rules and DB queries directly in route handlers instead of services."
112
+ bad_example: |
113
+ app.post('/users', async (c) => {
114
+ const body = await c.req.json();
115
+ const hash = await bcrypt.hash(body.password, 10);
116
+ const user = await db.insert(users).values({ ...body, password: hash });
117
+ return c.json(user);
118
+ });
119
+ good_example: |
120
+ app.post('/users', zValidator('json', createUserSchema), async (c) => {
121
+ return c.json(await userService.create(c.req.valid('json')), 201);
122
+ });
123
+ - id: no_zod_validation
124
+ severity: warning
125
+ description: "Parsing request body with c.req.json() without schema validation — trusts unvalidated user input."
126
+ bad_example: |
127
+ const body = await c.req.json(); // unvalidated
128
+ good_example: |
129
+ // Use zValidator middleware — body is typed and validated
130
+ zValidator('json', createUserSchema)