@mars-stack/core 0.4.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 (192) hide show
  1. package/README.md +32 -0
  2. package/cursor/manifest.json +304 -0
  3. package/cursor/rules/mars-composition-patterns.mdc +186 -0
  4. package/cursor/rules/mars-data-access.mdc +26 -0
  5. package/cursor/rules/mars-project-structure.mdc +34 -0
  6. package/cursor/rules/mars-security.mdc +25 -0
  7. package/cursor/rules/mars-testing.mdc +24 -0
  8. package/cursor/rules/mars-ui-conventions.mdc +29 -0
  9. package/cursor/skills/mars-add-api-route/SKILL.md +120 -0
  10. package/cursor/skills/mars-add-audit-log/SKILL.md +373 -0
  11. package/cursor/skills/mars-add-blog/SKILL.md +447 -0
  12. package/cursor/skills/mars-add-command-palette/SKILL.md +438 -0
  13. package/cursor/skills/mars-add-component/SKILL.md +158 -0
  14. package/cursor/skills/mars-add-crud-routes/SKILL.md +221 -0
  15. package/cursor/skills/mars-add-e2e-test/SKILL.md +227 -0
  16. package/cursor/skills/mars-add-error-boundary/SKILL.md +472 -0
  17. package/cursor/skills/mars-add-feature/SKILL.md +174 -0
  18. package/cursor/skills/mars-add-middleware/SKILL.md +135 -0
  19. package/cursor/skills/mars-add-page/SKILL.md +153 -0
  20. package/cursor/skills/mars-add-prisma-model/SKILL.md +148 -0
  21. package/cursor/skills/mars-add-protected-resource/SKILL.md +192 -0
  22. package/cursor/skills/mars-add-role/SKILL.md +156 -0
  23. package/cursor/skills/mars-add-server-action/SKILL.md +167 -0
  24. package/cursor/skills/mars-add-webhook/SKILL.md +192 -0
  25. package/cursor/skills/mars-build-complete-feature/SKILL.md +228 -0
  26. package/cursor/skills/mars-build-dashboard/SKILL.md +211 -0
  27. package/cursor/skills/mars-build-data-table/SKILL.md +284 -0
  28. package/cursor/skills/mars-build-form/SKILL.md +229 -0
  29. package/cursor/skills/mars-build-landing-page/SKILL.md +248 -0
  30. package/cursor/skills/mars-capture-conversation-context/SKILL.md +119 -0
  31. package/cursor/skills/mars-configure-ai/SKILL.md +617 -0
  32. package/cursor/skills/mars-configure-analytics/SKILL.md +413 -0
  33. package/cursor/skills/mars-configure-dark-mode/SKILL.md +309 -0
  34. package/cursor/skills/mars-configure-email/SKILL.md +170 -0
  35. package/cursor/skills/mars-configure-email-verification/SKILL.md +333 -0
  36. package/cursor/skills/mars-configure-feature-flags/SKILL.md +361 -0
  37. package/cursor/skills/mars-configure-i18n/SKILL.md +518 -0
  38. package/cursor/skills/mars-configure-jobs/SKILL.md +500 -0
  39. package/cursor/skills/mars-configure-magic-links/SKILL.md +385 -0
  40. package/cursor/skills/mars-configure-multi-tenancy/SKILL.md +611 -0
  41. package/cursor/skills/mars-configure-notifications/SKILL.md +569 -0
  42. package/cursor/skills/mars-configure-oauth/SKILL.md +217 -0
  43. package/cursor/skills/mars-configure-onboarding/SKILL.md +483 -0
  44. package/cursor/skills/mars-configure-payments/SKILL.md +243 -0
  45. package/cursor/skills/mars-configure-realtime/SKILL.md +733 -0
  46. package/cursor/skills/mars-configure-search/SKILL.md +581 -0
  47. package/cursor/skills/mars-configure-storage/SKILL.md +273 -0
  48. package/cursor/skills/mars-configure-two-factor/SKILL.md +518 -0
  49. package/cursor/skills/mars-create-execution-plan/SKILL.md +204 -0
  50. package/cursor/skills/mars-create-seed/SKILL.md +191 -0
  51. package/cursor/skills/mars-deploy-to-vercel/SKILL.md +300 -0
  52. package/cursor/skills/mars-design-tokens/SKILL.md +138 -0
  53. package/cursor/skills/mars-setup-billing/SKILL.md +322 -0
  54. package/cursor/skills/mars-setup-project/SKILL.md +104 -0
  55. package/cursor/skills/mars-setup-teams/SKILL.md +688 -0
  56. package/cursor/skills/mars-test-api-route/SKILL.md +219 -0
  57. package/cursor/skills/mars-update-architecture-docs/SKILL.md +189 -0
  58. package/dist/api-error/index.d.ts +27 -0
  59. package/dist/api-error/index.d.ts.map +1 -0
  60. package/dist/api-error/index.js +2 -0
  61. package/dist/auth/credential-tag.d.ts +5 -0
  62. package/dist/auth/credential-tag.d.ts.map +1 -0
  63. package/dist/auth/credential-tag.js +2 -0
  64. package/dist/auth/crypto-utils.d.ts +43 -0
  65. package/dist/auth/crypto-utils.d.ts.map +1 -0
  66. package/dist/auth/crypto-utils.js +1 -0
  67. package/dist/auth/csrf.d.ts +32 -0
  68. package/dist/auth/csrf.d.ts.map +1 -0
  69. package/dist/auth/csrf.js +2 -0
  70. package/dist/auth/hooks/index.d.ts +4 -0
  71. package/dist/auth/hooks/index.d.ts.map +1 -0
  72. package/dist/auth/hooks/index.js +68 -0
  73. package/dist/auth/hooks/useCSRF.d.ts +7 -0
  74. package/dist/auth/hooks/useCSRF.d.ts.map +1 -0
  75. package/dist/auth/hooks/usePasswordStrength.d.ts +17 -0
  76. package/dist/auth/hooks/usePasswordStrength.d.ts.map +1 -0
  77. package/dist/auth/internal-api-key.d.ts +5 -0
  78. package/dist/auth/internal-api-key.d.ts.map +1 -0
  79. package/dist/auth/internal-api-key.js +30 -0
  80. package/dist/auth/link-utils.d.ts +13 -0
  81. package/dist/auth/link-utils.d.ts.map +1 -0
  82. package/dist/auth/link-utils.js +1 -0
  83. package/dist/auth/middleware.d.ts +56 -0
  84. package/dist/auth/middleware.d.ts.map +1 -0
  85. package/dist/auth/middleware.js +3 -0
  86. package/dist/auth/password.d.ts +28 -0
  87. package/dist/auth/password.d.ts.map +1 -0
  88. package/dist/auth/password.js +1 -0
  89. package/dist/auth/reset-token.d.ts +3 -0
  90. package/dist/auth/reset-token.d.ts.map +1 -0
  91. package/dist/auth/reset-token.js +9 -0
  92. package/dist/auth/responses.d.ts +15 -0
  93. package/dist/auth/responses.d.ts.map +1 -0
  94. package/dist/auth/responses.js +2 -0
  95. package/dist/auth/session.d.ts +79 -0
  96. package/dist/auth/session.d.ts.map +1 -0
  97. package/dist/auth/session.js +1 -0
  98. package/dist/auth/types.d.ts +18 -0
  99. package/dist/auth/types.d.ts.map +1 -0
  100. package/dist/auth/types.js +10 -0
  101. package/dist/auth/validation.d.ts +146 -0
  102. package/dist/auth/validation.d.ts.map +1 -0
  103. package/dist/auth/validation.js +116 -0
  104. package/dist/auth/validators.d.ts +4 -0
  105. package/dist/auth/validators.d.ts.map +1 -0
  106. package/dist/auth/validators.js +27 -0
  107. package/dist/auth/verification.d.ts +54 -0
  108. package/dist/auth/verification.d.ts.map +1 -0
  109. package/dist/auth/verification.js +39 -0
  110. package/dist/chunk-4LS3QDD5.js +162 -0
  111. package/dist/chunk-ABBUHT5Z.js +110 -0
  112. package/dist/chunk-CTYAVMOF.js +15 -0
  113. package/dist/chunk-GVLH2GQP.js +14 -0
  114. package/dist/chunk-HOSMMQMA.js +109 -0
  115. package/dist/chunk-MXQ66RUN.js +28 -0
  116. package/dist/chunk-PZE3JGXO.js +149 -0
  117. package/dist/chunk-QAH2Y5WK.js +93 -0
  118. package/dist/chunk-QWMN5UJC.js +76 -0
  119. package/dist/chunk-ROQV54MU.js +117 -0
  120. package/dist/chunk-U4NZQ366.js +46 -0
  121. package/dist/chunk-WBJOIENS.js +22 -0
  122. package/dist/chunk-WO6FHJHG.js +29 -0
  123. package/dist/chunk-Z5BEKPJI.js +96 -0
  124. package/dist/chunk-ZA46T6GX.js +24 -0
  125. package/dist/configure-mars.d.ts +104 -0
  126. package/dist/configure-mars.d.ts.map +1 -0
  127. package/dist/database/index.d.ts +8 -0
  128. package/dist/database/index.d.ts.map +1 -0
  129. package/dist/database/index.js +1 -0
  130. package/dist/email/index.d.ts +25 -0
  131. package/dist/email/index.d.ts.map +1 -0
  132. package/dist/email/index.js +2 -0
  133. package/dist/email/types.d.ts +18 -0
  134. package/dist/email/types.d.ts.map +1 -0
  135. package/dist/env/index.d.ts +36 -0
  136. package/dist/env/index.d.ts.map +1 -0
  137. package/dist/env/index.js +1 -0
  138. package/dist/index.d.ts +6 -0
  139. package/dist/index.d.ts.map +1 -0
  140. package/dist/index.js +163 -0
  141. package/dist/logger/index.d.ts +80 -0
  142. package/dist/logger/index.d.ts.map +1 -0
  143. package/dist/logger/index.js +1 -0
  144. package/dist/payments/index.d.ts +53 -0
  145. package/dist/payments/index.d.ts.map +1 -0
  146. package/dist/payments/index.js +72 -0
  147. package/dist/plugin/builtin/email-plugins.d.ts +10 -0
  148. package/dist/plugin/builtin/email-plugins.d.ts.map +1 -0
  149. package/dist/plugin/builtin/index.d.ts +4 -0
  150. package/dist/plugin/builtin/index.d.ts.map +1 -0
  151. package/dist/plugin/builtin/index.js +324 -0
  152. package/dist/plugin/builtin/payment-plugins.d.ts +4 -0
  153. package/dist/plugin/builtin/payment-plugins.d.ts.map +1 -0
  154. package/dist/plugin/builtin/storage-plugins.d.ts +5 -0
  155. package/dist/plugin/builtin/storage-plugins.d.ts.map +1 -0
  156. package/dist/plugin/index.d.ts +21 -0
  157. package/dist/plugin/index.d.ts.map +1 -0
  158. package/dist/plugin/index.js +30 -0
  159. package/dist/rate-limit/index.d.ts +89 -0
  160. package/dist/rate-limit/index.d.ts.map +1 -0
  161. package/dist/rate-limit/index.js +166 -0
  162. package/dist/seo/faq.d.ts +37 -0
  163. package/dist/seo/faq.d.ts.map +1 -0
  164. package/dist/seo/index.d.ts +75 -0
  165. package/dist/seo/index.d.ts.map +1 -0
  166. package/dist/seo/index.js +1 -0
  167. package/dist/storage/index.d.ts +50 -0
  168. package/dist/storage/index.d.ts.map +1 -0
  169. package/dist/storage/index.js +211 -0
  170. package/dist/test-utils/factories.d.ts +38 -0
  171. package/dist/test-utils/factories.d.ts.map +1 -0
  172. package/dist/test-utils/index.d.ts +6 -0
  173. package/dist/test-utils/index.d.ts.map +1 -0
  174. package/dist/test-utils/index.js +117 -0
  175. package/dist/test-utils/mock-auth.d.ts +25 -0
  176. package/dist/test-utils/mock-auth.d.ts.map +1 -0
  177. package/dist/test-utils/mock-prisma.d.ts +55 -0
  178. package/dist/test-utils/mock-prisma.d.ts.map +1 -0
  179. package/dist/test-utils/render.d.ts +4 -0
  180. package/dist/test-utils/render.d.ts.map +1 -0
  181. package/dist/test-utils/request-helpers.d.ts +6 -0
  182. package/dist/test-utils/request-helpers.d.ts.map +1 -0
  183. package/dist/types.d.ts +53 -0
  184. package/dist/types.d.ts.map +1 -0
  185. package/dist/utils/math.d.ts +2 -0
  186. package/dist/utils/math.d.ts.map +1 -0
  187. package/dist/utils/math.js +7 -0
  188. package/dist/utils/optional-import.d.ts +14 -0
  189. package/dist/utils/optional-import.d.ts.map +1 -0
  190. package/package.json +205 -0
  191. package/scripts/generate-skill-adapters.ts +146 -0
  192. package/scripts/postinstall.mjs +146 -0
@@ -0,0 +1,219 @@
1
+ # Skill: Test an API Route
2
+
3
+ Write unit tests for a MARS API route using Vitest, mock-auth, and Prisma mocking.
4
+
5
+ ## When to Use
6
+
7
+ Use this skill when the user asks to test an API endpoint, write backend tests, or add test coverage for a route handler.
8
+
9
+ ## Architecture
10
+
11
+ MARS API route tests:
12
+ - Import and call the exported handler function directly (no HTTP server needed)
13
+ - Mock Prisma, auth session, and external services
14
+ - Assert response status and body
15
+ - Test all paths: success, validation errors, auth errors, database errors
16
+
17
+ ## Template: Full API Route Test
18
+
19
+ ```typescript
20
+ // src/app/api/protected/projects/route.test.ts
21
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
22
+ import { GET, POST } from './route';
23
+ import { mockAuth, createTestRequest, createTestRequestWithBody } from '@mars-stack/core/test-utils';
24
+
25
+ // Mock database
26
+ const mockFindMany = vi.fn();
27
+ const mockCreate = vi.fn();
28
+
29
+ vi.mock('@/lib/prisma', () => ({
30
+ prisma: {
31
+ project: {
32
+ findMany: (...args: unknown[]) => mockFindMany(...args),
33
+ create: (...args: unknown[]) => mockCreate(...args),
34
+ },
35
+ },
36
+ isDatabaseError: vi.fn(() => false),
37
+ formatDatabaseError: vi.fn(),
38
+ }));
39
+
40
+ // Mock auth -- return a valid session
41
+ vi.mock('@/lib/mars', () => ({
42
+ verifySessionForAPI: vi.fn(() => Promise.resolve(mockAuth)),
43
+ }));
44
+
45
+ describe('GET /api/protected/projects', () => {
46
+ beforeEach(() => {
47
+ vi.clearAllMocks();
48
+ });
49
+
50
+ it('returns projects for the authenticated user', async () => {
51
+ const projects = [
52
+ { id: '1', name: 'Project A', userId: mockAuth.userId },
53
+ { id: '2', name: 'Project B', userId: mockAuth.userId },
54
+ ];
55
+ mockFindMany.mockResolvedValue(projects);
56
+
57
+ const request = createTestRequest('GET', '/api/protected/projects');
58
+ const response = await GET(request);
59
+ const body = await response.json();
60
+
61
+ expect(response.status).toBe(200);
62
+ expect(body).toHaveLength(2);
63
+ expect(mockFindMany).toHaveBeenCalledWith(
64
+ expect.objectContaining({
65
+ where: { userId: mockAuth.userId },
66
+ }),
67
+ );
68
+ });
69
+
70
+ it('returns 401 when not authenticated', async () => {
71
+ const { verifySessionForAPI } = await import('@/lib/mars');
72
+ vi.mocked(verifySessionForAPI).mockResolvedValueOnce(null);
73
+
74
+ const request = createTestRequest('GET', '/api/protected/projects');
75
+ const response = await GET(request);
76
+
77
+ expect(response.status).toBe(401);
78
+ });
79
+ });
80
+
81
+ describe('POST /api/protected/projects', () => {
82
+ beforeEach(() => {
83
+ vi.clearAllMocks();
84
+ });
85
+
86
+ it('creates a project with valid data', async () => {
87
+ const newProject = { id: '3', name: 'New Project', userId: mockAuth.userId };
88
+ mockCreate.mockResolvedValue(newProject);
89
+
90
+ const request = createTestRequestWithBody('POST', '/api/protected/projects', {
91
+ name: 'New Project',
92
+ });
93
+ const response = await POST(request);
94
+ const body = await response.json();
95
+
96
+ expect(response.status).toBe(201);
97
+ expect(body.name).toBe('New Project');
98
+ expect(mockCreate).toHaveBeenCalledWith(
99
+ expect.objectContaining({
100
+ data: expect.objectContaining({
101
+ name: 'New Project',
102
+ userId: mockAuth.userId,
103
+ }),
104
+ }),
105
+ );
106
+ });
107
+
108
+ it('returns 400 for invalid input', async () => {
109
+ const request = createTestRequestWithBody('POST', '/api/protected/projects', {
110
+ name: '', // Empty name should fail validation
111
+ });
112
+ const response = await POST(request);
113
+
114
+ expect(response.status).toBe(400);
115
+ });
116
+
117
+ it('returns 503 when database is unavailable', async () => {
118
+ const dbError = new Error("Can't reach database server");
119
+ Object.defineProperty(dbError, 'name', { value: 'PrismaClientInitializationError' });
120
+ mockCreate.mockRejectedValue(dbError);
121
+
122
+ const { isDatabaseError } = await import('@/lib/prisma');
123
+ vi.mocked(isDatabaseError).mockReturnValueOnce(true);
124
+
125
+ const request = createTestRequestWithBody('POST', '/api/protected/projects', {
126
+ name: 'Test Project',
127
+ });
128
+ const response = await POST(request);
129
+
130
+ expect(response.status).toBe(503);
131
+ });
132
+ });
133
+ ```
134
+
135
+ ## Mock Auth Utilities
136
+
137
+ MARS provides mock auth utilities via `@mars-stack/core/test-utils`:
138
+
139
+ ```typescript
140
+ import { mockAuth, mockAuthAsAdmin, mockAuthAsUser } from '@mars-stack/core/test-utils';
141
+ import { createTestRequest, createTestRequestWithBody } from '@mars-stack/core/test-utils';
142
+
143
+ // mockAuth — default mock session object
144
+ // mockAuthAsAdmin / mockAuthAsUser — role-specific variants
145
+ // createTestRequest(method, path) — build a NextRequest without a body
146
+ // createTestRequestWithBody(method, path, body) — build a NextRequest with JSON body
147
+ ```
148
+
149
+ ## Testing Patterns
150
+
151
+ ### Test Auth Required
152
+
153
+ ```typescript
154
+ it('returns 401 when not authenticated', async () => {
155
+ const { verifySessionForAPI } = await import('@/lib/mars');
156
+ vi.mocked(verifySessionForAPI).mockResolvedValueOnce(null);
157
+ // ...assert 401
158
+ });
159
+ ```
160
+
161
+ ### Test Role Requirement
162
+
163
+ ```typescript
164
+ it('returns 403 for non-admin users', async () => {
165
+ const { verifySessionForAPI } = await import('@/lib/mars');
166
+ vi.mocked(verifySessionForAPI).mockResolvedValueOnce({ ...mockAuth, role: 'user' });
167
+
168
+ // For withRole, also mock the DB role check
169
+ mockFindUnique.mockResolvedValueOnce({ role: 'user' });
170
+
171
+ // ...assert 403
172
+ });
173
+ ```
174
+
175
+ ### Test Validation Errors
176
+
177
+ ```typescript
178
+ it('returns 400 with validation errors', async () => {
179
+ const request = createTestRequestWithBody('POST', '/api/protected/resource', {
180
+ email: 'not-an-email',
181
+ });
182
+ const response = await handler(request);
183
+ const body = await response.json();
184
+
185
+ expect(response.status).toBe(400);
186
+ expect(body.error).toBeDefined();
187
+ });
188
+ ```
189
+
190
+ ### Test Database Errors
191
+
192
+ ```typescript
193
+ it('returns 503 when database is down', async () => {
194
+ mockFindMany.mockRejectedValueOnce(new Error("Can't reach database"));
195
+ // ... assert 503
196
+ });
197
+ ```
198
+
199
+ ## Running Tests
200
+
201
+ ```bash
202
+ yarn test # Run all tests
203
+ yarn test src/app/api/ # Run only API tests
204
+ yarn test:watch # Watch mode
205
+ yarn test:coverage # With coverage report
206
+ ```
207
+
208
+ ## Checklist
209
+
210
+ - [ ] Test file created next to route (`route.test.ts`)
211
+ - [ ] Prisma mocked with `vi.mock('@/lib/prisma')`
212
+ - [ ] Auth session mocked with `vi.mock('@/lib/mars')`
213
+ - [ ] `vi.clearAllMocks()` in `beforeEach`
214
+ - [ ] Success path tested
215
+ - [ ] 401 unauthorized tested
216
+ - [ ] 400 validation error tested
217
+ - [ ] 403 forbidden tested (if role-gated)
218
+ - [ ] 503 database error tested
219
+ - [ ] Response status AND body assertions
@@ -0,0 +1,189 @@
1
+ # Skill: Update Architecture Docs
2
+
3
+ Keep the Mars project documentation in sync with code changes. Covers when and how to update ARCHITECTURE.md, AGENTS.md, QUALITY_SCORE.md, and generated docs.
4
+
5
+ ## When to Use
6
+
7
+ Use this skill when:
8
+ - You have just added a new feature, service, or package
9
+ - You have changed the database schema
10
+ - You have added or removed a skill or rule
11
+ - You have completed an execution plan
12
+ - The user asks to update docs, sync docs, or refresh documentation
13
+ - Another skill's checklist includes "update docs"
14
+
15
+ ## Prerequisites
16
+
17
+ - Read `ARCHITECTURE.md` to understand the current documented state.
18
+ - Read `AGENTS.md` for the project-level agent guide.
19
+ - Read `docs/QUALITY_SCORE.md` for current quality grades.
20
+
21
+ ## Document Index
22
+
23
+ | Document | Location | Purpose | Update frequency |
24
+ |----------|----------|---------|------------------|
25
+ | `AGENTS.md` | Repo root | First file any agent reads. Overview + pointers | On structure changes |
26
+ | `ARCHITECTURE.md` | Repo root | Detailed technical architecture | On arch changes |
27
+ | `docs/QUALITY_SCORE.md` | Docs | Quality grades by domain | After every improvement |
28
+ | `docs/generated/package-map.md` | Docs | Auto-generated package structure | Regenerate on package changes |
29
+ | `docs/generated/skill-inventory.md` | Docs | Auto-generated skill list | Regenerate on skill changes |
30
+ | `template/AGENTS.md` | Template | Consumer-facing project guide | On template structure changes |
31
+ | `packages/core/cursor/manifest.json` | Core | Skill index with triggers | On skill add/remove |
32
+
33
+ ## When to Update Each Document
34
+
35
+ ### ARCHITECTURE.md
36
+
37
+ Update when:
38
+ - A new package is added to the monorepo
39
+ - The build pipeline changes (new build step, tool change)
40
+ - A new export path is added to a package
41
+ - The security model changes (new auth pattern, new middleware)
42
+ - The skill/rule system changes
43
+
44
+ What to update:
45
+ 1. Package layering diagram (if dependency graph changed)
46
+ 2. Package export table (if new exports added)
47
+ 3. Build pipeline section (if build process changed)
48
+ 4. Security architecture section (if auth patterns changed)
49
+ 5. Skills and rules section (if skill system changed)
50
+
51
+ ### AGENTS.md (Root)
52
+
53
+ Update when:
54
+ - A new top-level directory is added
55
+ - A new CLI command is added
56
+ - The "How to Run" commands change
57
+ - A new "How to" section is needed (e.g., "How to Add a Plugin")
58
+
59
+ Keep it concise — AGENTS.md should fit in a single context window.
60
+
61
+ ### template/AGENTS.md
62
+
63
+ Update when:
64
+ - The template directory structure changes
65
+ - New route groups are added
66
+ - New feature modules are added to the template
67
+ - The config structure changes
68
+ - New "How to Run" commands are added
69
+
70
+ ### docs/QUALITY_SCORE.md
71
+
72
+ Update when:
73
+ - A quality grade changes (feature implemented, bug fixed, test added)
74
+ - A new domain is added to the grading table
75
+ - An execution plan is completed
76
+
77
+ How to update:
78
+ 1. Find the relevant row in the appropriate table
79
+ 2. Update the grade (A/B/C/D/F)
80
+ 3. Update the notes to reflect current state
81
+ 4. Add date reference if significant change
82
+
83
+ ```markdown
84
+ | Dashboard | B | Basic layout with stat cards. Missing: chart widgets, date range filter |
85
+ ```
86
+
87
+ ### Generated Docs
88
+
89
+ Regenerate `docs/generated/package-map.md` when:
90
+ - A package's exports change
91
+ - A new package is added
92
+ - A package is removed
93
+
94
+ Regenerate `docs/generated/skill-inventory.md` when:
95
+ - A skill is added, removed, or renamed
96
+ - Skill triggers or dependencies change
97
+
98
+ ### manifest.json
99
+
100
+ Update when:
101
+ - A new skill is added (add entry with triggers, dependencies, capabilities)
102
+ - A skill is removed (remove entry)
103
+ - Skill triggers change (update triggers array)
104
+ - Skill dependencies change (update dependencies array)
105
+
106
+ ## Step-by-Step: After Adding a Feature
107
+
108
+ 1. **QUALITY_SCORE.md** — Update the grade for the relevant domain:
109
+
110
+ ```markdown
111
+ | Billing | B | Stripe checkout, portal, webhook. Missing: usage-based billing |
112
+ ```
113
+
114
+ 2. **ARCHITECTURE.md** — If the feature introduces a new architectural pattern:
115
+
116
+ ```markdown
117
+ ### Billing Architecture
118
+
119
+ The billing system uses Stripe as the payment provider...
120
+ ```
121
+
122
+ 3. **template/AGENTS.md** — If the feature adds new directories or conventions:
123
+
124
+ ```markdown
125
+ ├── features/
126
+ │ └── billing/ # Subscription management
127
+ ```
128
+
129
+ 4. **Execution plan** — Mark tasks complete, update verification:
130
+
131
+ ```markdown
132
+ - [x] **3.1 Billing page** — Done. `src/app/(protected)/billing/page.tsx`
133
+ ```
134
+
135
+ ## Step-by-Step: After Adding a Skill
136
+
137
+ 1. **manifest.json** — Add the skill entry:
138
+
139
+ ```json
140
+ "new-skill-name": {
141
+ "file": "skills/mars-new-skill-name/SKILL.md",
142
+ "triggers": ["trigger phrase 1", "trigger phrase 2"],
143
+ "dependencies": ["dependency-skill"],
144
+ "capabilities": ["file-edit", "terminal"]
145
+ }
146
+ ```
147
+
148
+ 2. **skill-inventory.md** — Regenerate or manually add:
149
+
150
+ ```markdown
151
+ | new-skill-name | Add a new thing | file-edit, terminal |
152
+ ```
153
+
154
+ 3. **AGENTS.md** — If the skill represents a new "How to" workflow:
155
+
156
+ ```markdown
157
+ ## How to Add a New Thing
158
+
159
+ 1. Create the skill file
160
+ 2. Add to manifest
161
+ 3. ...
162
+ ```
163
+
164
+ ## Step-by-Step: After a Schema Change
165
+
166
+ 1. **ARCHITECTURE.md** — Update if the change affects the data model section
167
+ 2. **template/AGENTS.md** — Update the schema file listing if new files added
168
+ 3. **QUALITY_SCORE.md** — Update database-related grades
169
+
170
+ ## Validation
171
+
172
+ After updating docs, verify:
173
+
174
+ 1. **No broken links** — Check that referenced files exist
175
+ 2. **Consistent terminology** — Use the same names as the code
176
+ 3. **Up-to-date grades** — Quality scores reflect actual state
177
+ 4. **AGENTS.md fits in context** — Keep it under ~200 lines
178
+
179
+ ## Checklist
180
+
181
+ - [ ] Identified which documents need updating
182
+ - [ ] ARCHITECTURE.md updated (if architectural change)
183
+ - [ ] AGENTS.md updated (if structure or workflow change)
184
+ - [ ] template/AGENTS.md updated (if template structure change)
185
+ - [ ] QUALITY_SCORE.md grades updated
186
+ - [ ] manifest.json updated (if skill change)
187
+ - [ ] Generated docs regenerated (if package or skill change)
188
+ - [ ] No broken internal links
189
+ - [ ] Execution plan marked as complete (if applicable)
@@ -0,0 +1,27 @@
1
+ import { NextResponse } from 'next/server';
2
+ interface ApiErrorHandlerConfig {
3
+ logger: {
4
+ error: (...args: unknown[]) => void;
5
+ };
6
+ }
7
+ export interface ApiErrorOptions {
8
+ endpoint: string;
9
+ fallbackMessage?: string;
10
+ }
11
+ /**
12
+ * Creates an API error handler that maps Zod validation errors, database errors,
13
+ * and unhandled exceptions to appropriate JSON responses with status codes.
14
+ *
15
+ * @param config - Configuration with a structured logger
16
+ * @returns An object containing the `handleApiError` function
17
+ * @example
18
+ * const { handleApiError } = createApiErrorHandler({ logger: console });
19
+ * try { ... } catch (error) {
20
+ * return handleApiError(error, { endpoint: '/api/users' });
21
+ * }
22
+ */
23
+ export declare function createApiErrorHandler(config: ApiErrorHandlerConfig): {
24
+ handleApiError: (error: unknown, options: ApiErrorOptions) => NextResponse;
25
+ };
26
+ export {};
27
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/api-error/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAI3C,UAAU,qBAAqB;IAC7B,MAAM,EAAE;QAAE,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAA;KAAE,CAAC;CACjD;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AA0BD;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,qBAAqB;4BAClC,OAAO,WAAW,eAAe,KAAG,YAAY;EAyBhF"}
@@ -0,0 +1,2 @@
1
+ export { createApiErrorHandler } from '../chunk-U4NZQ366.js';
2
+ import '../chunk-QAH2Y5WK.js';
@@ -0,0 +1,5 @@
1
+ import 'server-only';
2
+ /** Tag for users without a password (e.g. OAuth-only). Sessions cannot be individually revoked. */
3
+ export declare const NO_PASSWORD_TAG = "no-password";
4
+ export declare function buildCredentialTag(passwordHash: string | null | undefined, _revocationSeed?: string): Promise<string>;
5
+ //# sourceMappingURL=credential-tag.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credential-tag.d.ts","sourceRoot":"","sources":["../../src/auth/credential-tag.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAC;AAMrB,mGAAmG;AACnG,eAAO,MAAM,eAAe,gBAAgB,CAAC;AAE7C,wBAAsB,kBAAkB,CACtC,YAAY,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACvC,eAAe,CAAC,EAAE,MAAM,GACvB,OAAO,CAAC,MAAM,CAAC,CAOjB"}
@@ -0,0 +1,2 @@
1
+ export { NO_PASSWORD_TAG, buildCredentialTag } from '../chunk-GVLH2GQP.js';
2
+ import '../chunk-MXQ66RUN.js';
@@ -0,0 +1,43 @@
1
+ import 'server-only';
2
+ /**
3
+ * Constant-time string comparison to prevent timing attacks.
4
+ * Pads comparison to max length so length mismatch does not leak information.
5
+ *
6
+ * @param a - First string to compare
7
+ * @param b - Second string to compare
8
+ * @returns True if the strings are identical
9
+ * @example
10
+ * if (constantTimeEqual(suppliedToken, expectedToken)) { /* valid *\/ }
11
+ */
12
+ export declare function constantTimeEqual(a: string, b: string): boolean;
13
+ /**
14
+ * Converts a byte array to a lowercase hex-encoded string.
15
+ *
16
+ * @param bytes - The byte array to convert
17
+ * @returns The hex-encoded string
18
+ * @example
19
+ * const hex = bytesToHex(new Uint8Array([0xde, 0xad])); // 'dead'
20
+ */
21
+ export declare function bytesToHex(bytes: Uint8Array): string;
22
+ /**
23
+ * Escapes HTML special characters to prevent XSS in email templates.
24
+ *
25
+ * @param str - The raw string to escape
26
+ * @returns The HTML-safe string with &, <, >, ", ' escaped
27
+ * @example
28
+ * const safe = escapeHtml('<script>alert("xss")</script>');
29
+ * // '&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;'
30
+ */
31
+ export declare function escapeHtml(str: string): string;
32
+ /**
33
+ * Hashes a token with a domain separator for secure storage.
34
+ * Uses SHA-256(domain + ":" + token) to prevent cross-protocol hash collisions.
35
+ *
36
+ * @param token - The raw token to hash
37
+ * @param domain - A domain separator string (e.g. 'email-verification')
38
+ * @returns The hex-encoded SHA-256 hash
39
+ * @example
40
+ * const hashed = await hashToken(rawToken, 'password-reset');
41
+ */
42
+ export declare function hashToken(token: string, domain: string): Promise<string>;
43
+ //# sourceMappingURL=crypto-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto-utils.d.ts","sourceRoot":"","sources":["../../src/auth/crypto-utils.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAC;AAErB;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAU/D;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAIpD;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAO9C;AAED;;;;;;;;;GASG;AACH,wBAAsB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAK9E"}
@@ -0,0 +1 @@
1
+ export { bytesToHex, constantTimeEqual, escapeHtml, hashToken } from '../chunk-MXQ66RUN.js';
@@ -0,0 +1,32 @@
1
+ import 'server-only';
2
+ import { NextRequest } from 'next/server';
3
+ interface CSRFProtectionConfig {
4
+ getJWTSecret: () => string;
5
+ logViolation: (event: {
6
+ ipAddress?: string;
7
+ userAgent?: string;
8
+ endpoint?: string;
9
+ }) => void;
10
+ }
11
+ /**
12
+ * Creates a CSRF protection module with token generation, verification, and
13
+ * request validation. Derives a separate HMAC key from the JWT secret to
14
+ * prevent key reuse across protocols.
15
+ *
16
+ * @param config - Configuration with JWT secret accessor and violation logger
17
+ * @returns An object with CSRF token lifecycle and request validation methods
18
+ * @example
19
+ * const csrf = createCSRFProtection({
20
+ * getJWTSecret: () => env.JWT_SECRET,
21
+ * logViolation: (event) => logger.warn('CSRF violation', event),
22
+ * });
23
+ * const token = await csrf.generateCSRFToken(session.fingerprint);
24
+ */
25
+ export declare function createCSRFProtection(config: CSRFProtectionConfig): {
26
+ generateCSRFToken: (sessionFingerprint?: string) => Promise<string>;
27
+ verifyCSRFToken: (token: string, expectedFingerprint?: string) => Promise<boolean>;
28
+ requireCSRFToken: (sessionFingerprint?: string) => Promise<string>;
29
+ validateCSRFRequest: (request: NextRequest, sessionFingerprint?: string) => Promise<boolean>;
30
+ };
31
+ export {};
32
+ //# sourceMappingURL=csrf.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"csrf.d.ts","sourceRoot":"","sources":["../../src/auth/csrf.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAC;AAGrB,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAiC1C,UAAU,oBAAoB;IAC5B,YAAY,EAAE,MAAM,MAAM,CAAC;IAC3B,YAAY,EAAE,CAAC,KAAK,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CAC9F;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,oBAAoB;6CAUT,MAAM,KAAG,OAAO,CAAC,MAAM,CAAC;6BAYrE,MAAM,wBACS,MAAM,KAC3B,OAAO,CAAC,OAAO,CAAC;4CAkCkC,MAAM,KAAG,OAAO,CAAC,MAAM,CAAC;mCA2ClE,WAAW,uBACC,MAAM,KAC1B,OAAO,CAAC,OAAO,CAAC;EA4CpB"}
@@ -0,0 +1,2 @@
1
+ export { createCSRFProtection } from '../chunk-PZE3JGXO.js';
2
+ import '../chunk-MXQ66RUN.js';
@@ -0,0 +1,4 @@
1
+ export { useCSRF } from './useCSRF';
2
+ export { usePasswordStrength } from './usePasswordStrength';
3
+ export type { PasswordRequirements, PasswordStrength, UsePasswordStrengthResult } from './usePasswordStrength';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/auth/hooks/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,YAAY,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC"}
@@ -0,0 +1,68 @@
1
+ import { useState, useEffect, useMemo } from 'react';
2
+
3
+ // src/auth/hooks/useCSRF.ts
4
+ function useCSRF() {
5
+ const [csrfToken, setCSRFToken] = useState(null);
6
+ const [isLoading, setIsLoading] = useState(true);
7
+ useEffect(() => {
8
+ const fetchCSRFToken = async () => {
9
+ try {
10
+ const response = await fetch("/api/csrf", {
11
+ method: "GET",
12
+ credentials: "include"
13
+ });
14
+ if (response.ok) {
15
+ const data = await response.json();
16
+ setCSRFToken(data.token);
17
+ }
18
+ } catch (error) {
19
+ console.error("Failed to fetch CSRF token:", error);
20
+ } finally {
21
+ setIsLoading(false);
22
+ }
23
+ };
24
+ fetchCSRFToken();
25
+ }, []);
26
+ function getCSRFHeaders() {
27
+ if (!csrfToken) return {};
28
+ return { "X-CSRF-Token": csrfToken };
29
+ }
30
+ function getCSRFFormData() {
31
+ if (!csrfToken) return {};
32
+ return { csrfToken };
33
+ }
34
+ return { csrfToken, isLoading, getCSRFHeaders, getCSRFFormData };
35
+ }
36
+ function usePasswordStrength(password) {
37
+ return useMemo(() => {
38
+ if (!password) {
39
+ return {
40
+ strength: "Weak",
41
+ requirements: {
42
+ length: false,
43
+ lowercase: false,
44
+ uppercase: false,
45
+ number: false,
46
+ special: false,
47
+ noPassword: false,
48
+ noSequential: false
49
+ },
50
+ score: 0
51
+ };
52
+ }
53
+ const requirements = {
54
+ length: password.length >= 8 && password.length <= 100,
55
+ lowercase: /[a-z]/.test(password),
56
+ uppercase: /[A-Z]/.test(password),
57
+ number: /[0-9]/.test(password),
58
+ special: /[^A-Za-z0-9]/.test(password),
59
+ noPassword: !password.toLowerCase().includes("password"),
60
+ noSequential: !password.includes("123")
61
+ };
62
+ const score = Object.values(requirements).filter(Boolean).length;
63
+ const strength = score >= 6 ? "Strong" : score >= 4 ? "Medium" : "Weak";
64
+ return { strength, requirements, score };
65
+ }, [password]);
66
+ }
67
+
68
+ export { useCSRF, usePasswordStrength };
@@ -0,0 +1,7 @@
1
+ export declare function useCSRF(): {
2
+ csrfToken: string | null;
3
+ isLoading: boolean;
4
+ getCSRFHeaders: () => Record<string, string>;
5
+ getCSRFFormData: () => Record<string, string>;
6
+ };
7
+ //# sourceMappingURL=useCSRF.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useCSRF.d.ts","sourceRoot":"","sources":["../../../src/auth/hooks/useCSRF.ts"],"names":[],"mappings":"AAIA,wBAAgB,OAAO;;;0BA0BM,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;2BAKrB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;EAMnD"}
@@ -0,0 +1,17 @@
1
+ export interface PasswordRequirements {
2
+ length: boolean;
3
+ lowercase: boolean;
4
+ uppercase: boolean;
5
+ number: boolean;
6
+ special: boolean;
7
+ noPassword: boolean;
8
+ noSequential: boolean;
9
+ }
10
+ export type PasswordStrength = 'Weak' | 'Medium' | 'Strong';
11
+ export interface UsePasswordStrengthResult {
12
+ strength: PasswordStrength;
13
+ requirements: PasswordRequirements;
14
+ score: number;
15
+ }
16
+ export declare function usePasswordStrength(password: string): UsePasswordStrengthResult;
17
+ //# sourceMappingURL=usePasswordStrength.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePasswordStrength.d.ts","sourceRoot":"","sources":["../../../src/auth/hooks/usePasswordStrength.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,OAAO,CAAC;IACpB,YAAY,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE5D,MAAM,WAAW,yBAAyB;IACxC,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,YAAY,EAAE,oBAAoB,CAAC;IACnC,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,yBAAyB,CAiC/E"}
@@ -0,0 +1,5 @@
1
+ import 'server-only';
2
+ export declare function getRequiredInternalApiKey(): string;
3
+ export declare function getInternalApiKeys(): string[];
4
+ export declare function isInternalApiRequestAuthorized(authHeader: string | null, expectedApiKeys: string | string[]): boolean;
5
+ //# sourceMappingURL=internal-api-key.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"internal-api-key.d.ts","sourceRoot":"","sources":["../../src/auth/internal-api-key.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAC;AAerB,wBAAgB,yBAAyB,IAAI,MAAM,CAMlD;AAED,wBAAgB,kBAAkB,IAAI,MAAM,EAAE,CAE7C;AAED,wBAAgB,8BAA8B,CAC5C,UAAU,EAAE,MAAM,GAAG,IAAI,EACzB,eAAe,EAAE,MAAM,GAAG,MAAM,EAAE,GACjC,OAAO,CAST"}