@raftlabs/raftstack 1.6.3 → 1.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/asana/SKILL.md +378 -0
- package/.claude/skills/backend/SKILL.md +132 -1
- package/.claude/skills/code-quality/SKILL.md +53 -38
- package/.claude/skills/database/SKILL.md +55 -6
- package/.claude/skills/react/SKILL.md +112 -2
- package/.claude/skills/seo/SKILL.md +117 -5
- package/dist/cli.js +15 -6
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: asana
|
|
3
|
+
description: Use when working with Asana - creating tasks, updating tasks, adding comments, posting stories, searching tasks, managing projects, or any Asana MCP tool interaction. Essential for proper formatting and understanding MCP limitations.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Asana MCP Guide
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
This skill guides effective use of Asana MCP tools. **Critical limitation:** The Asana MCP server has restricted rich text support compared to the full Asana API.
|
|
11
|
+
|
|
12
|
+
## When to Use This Skill
|
|
13
|
+
|
|
14
|
+
Trigger on ANY Asana-related request:
|
|
15
|
+
- "Create a task in Asana"
|
|
16
|
+
- "Update the Asana task"
|
|
17
|
+
- "Add a comment to the task"
|
|
18
|
+
- "Post an update on Asana"
|
|
19
|
+
- "Leave a note on the Asana task"
|
|
20
|
+
- "Search for tasks in Asana"
|
|
21
|
+
- "Check my Asana tasks"
|
|
22
|
+
- "What's in my Asana project?"
|
|
23
|
+
- Any mention of Asana + task/project/comment/story
|
|
24
|
+
|
|
25
|
+
## ⚠️ Critical: MCP Rich Text Limitations
|
|
26
|
+
|
|
27
|
+
The Asana MCP server does **NOT** fully support rich text formatting:
|
|
28
|
+
|
|
29
|
+
| Tool | Rich Text Field | Plain Text Field | Rich Text Works? |
|
|
30
|
+
|------|-----------------|------------------|------------------|
|
|
31
|
+
| `asana_create_task` | `html_notes` ✅ | `notes` | **YES** |
|
|
32
|
+
| `asana_update_task` | ❌ None | `notes` | **NO** |
|
|
33
|
+
| `asana_create_task_story` | ❌ None | `text` | **NO** |
|
|
34
|
+
|
|
35
|
+
### What This Means
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
// ✅ WORKS: Creating a NEW task with rich text
|
|
39
|
+
asana_create_task({
|
|
40
|
+
name: "New feature",
|
|
41
|
+
project_id: "123",
|
|
42
|
+
html_notes: "<body><strong>Bold text</strong> works here!</body>"
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
// ❌ DOES NOT WORK: Updating existing task with rich text
|
|
46
|
+
// The MCP only exposes `notes` field (plain text)
|
|
47
|
+
asana_update_task({
|
|
48
|
+
task_id: "456",
|
|
49
|
+
notes: "Plain text only. <strong>Tags</strong> show as literal text."
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
// ❌ DOES NOT WORK: Comments with rich text
|
|
53
|
+
// The MCP only exposes `text` field (plain text)
|
|
54
|
+
asana_create_task_story({
|
|
55
|
+
task_id: "456",
|
|
56
|
+
text: "Plain text only. No formatting supported."
|
|
57
|
+
})
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## The Iron Rules
|
|
61
|
+
|
|
62
|
+
### 1. Use `html_notes` ONLY for `asana_create_task`
|
|
63
|
+
|
|
64
|
+
Rich text formatting **only works** when creating new tasks.
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
// ✅ CORRECT: Use html_notes for new tasks
|
|
68
|
+
asana_create_task({
|
|
69
|
+
name: "Implementation task",
|
|
70
|
+
project_id: "123456",
|
|
71
|
+
html_notes: `<body>
|
|
72
|
+
<h2>Requirements</h2>
|
|
73
|
+
<ul>
|
|
74
|
+
<li>Feature A</li>
|
|
75
|
+
<li>Feature B</li>
|
|
76
|
+
</ul>
|
|
77
|
+
<strong>Owner:</strong> <a data-asana-gid="789012"/>
|
|
78
|
+
</body>`
|
|
79
|
+
})
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 2. Accept Plain Text for Updates and Comments
|
|
83
|
+
|
|
84
|
+
For `asana_update_task` and `asana_create_task_story`, write naturally like you're messaging a colleague:
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
// ✅ CORRECT: Plain text for updates (only option available)
|
|
88
|
+
asana_update_task({
|
|
89
|
+
task_id: "456",
|
|
90
|
+
notes: `Quick update - finished features A and B. Waiting on John for review, should be good to merge after that.`
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
// ✅ CORRECT: Plain text for comments (only option available)
|
|
94
|
+
asana_create_task_story({
|
|
95
|
+
task_id: "456",
|
|
96
|
+
text: `Found the bug! It was in utils/parser.ts line 42 - missing a trim() call. Added .map(s => s.trim()) to fix it.
|
|
97
|
+
|
|
98
|
+
PR is up: https://github.com/org/repo/pull/123`
|
|
99
|
+
})
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 3. Wrap `html_notes` in `<body>` Tags
|
|
103
|
+
|
|
104
|
+
When using `html_notes` (only in `asana_create_task`), always wrap content in `<body>` tags:
|
|
105
|
+
|
|
106
|
+
```xml
|
|
107
|
+
<!-- ❌ BAD: No body wrapper -->
|
|
108
|
+
<strong>Important</strong> task details
|
|
109
|
+
|
|
110
|
+
<!-- ✅ GOOD: Properly wrapped -->
|
|
111
|
+
<body><strong>Important</strong> task details</body>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 4. Use `data-asana-gid` for @-mentions (Task Creation Only)
|
|
115
|
+
|
|
116
|
+
In `html_notes` for new tasks, reference users/tasks using `data-asana-gid`:
|
|
117
|
+
|
|
118
|
+
```xml
|
|
119
|
+
<body>
|
|
120
|
+
Assigned to <a data-asana-gid="USER_GID"/>. Please review.
|
|
121
|
+
See related: <a data-asana-gid="TASK_GID"/>
|
|
122
|
+
</body>
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Writing Natural Comments
|
|
126
|
+
|
|
127
|
+
Since most operations only support plain text, write like you're messaging a colleague - natural, simple, conversational. But also format so Asana renders it cleanly.
|
|
128
|
+
|
|
129
|
+
### The Goal
|
|
130
|
+
|
|
131
|
+
Sound like a human, not a report generator. Skip the `===` underlines and formal structure.
|
|
132
|
+
|
|
133
|
+
### Do This
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
Hey, finished the code review!
|
|
137
|
+
|
|
138
|
+
Found a few things:
|
|
139
|
+
• auth.ts line 42 needs error handling around the API call
|
|
140
|
+
• types.ts line 15 has an 'any' that should be a proper interface
|
|
141
|
+
• query.ts could use some caching for performance
|
|
142
|
+
|
|
143
|
+
Otherwise looks good, just minor fixes needed. Let me know if you have questions!
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Not This
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
Code Review Complete
|
|
150
|
+
--------------------
|
|
151
|
+
|
|
152
|
+
Found 3 issues:
|
|
153
|
+
|
|
154
|
+
1. Missing error handling in auth.ts:42
|
|
155
|
+
- Add try/catch around API call
|
|
156
|
+
|
|
157
|
+
2. Type safety issue in types.ts:15
|
|
158
|
+
- Change 'any' to proper interface
|
|
159
|
+
|
|
160
|
+
Overall: Good implementation, minor fixes needed.
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Natural Writing Tips
|
|
164
|
+
|
|
165
|
+
- **Write conversationally** - "Hey, quick update" or "Found the issue!" is fine
|
|
166
|
+
- **Keep it brief** - say what matters, then stop
|
|
167
|
+
- **Links work fine** - just paste URLs directly: https://github.com/org/repo/pull/123
|
|
168
|
+
- **Don't over-structure** - skip the headers and category labels
|
|
169
|
+
|
|
170
|
+
### Asana Plain Text Formatting
|
|
171
|
+
|
|
172
|
+
Asana's plain text renderer has quirks. Use these patterns for clean rendering:
|
|
173
|
+
|
|
174
|
+
| Use | Not | Why |
|
|
175
|
+
|-----|-----|-----|
|
|
176
|
+
| `•` (bullet character) | `-` (dash) | Bullets render as proper list items |
|
|
177
|
+
| Flat lists | Nested/indented lists | Indentation doesn't preserve well |
|
|
178
|
+
| One blank line between sections | Multiple blank lines | Keeps spacing consistent |
|
|
179
|
+
| Inline items (no indent) | Indented sub-items | Sub-indentation gets flattened |
|
|
180
|
+
|
|
181
|
+
**Good list format:**
|
|
182
|
+
```
|
|
183
|
+
Found a few things:
|
|
184
|
+
• First item here
|
|
185
|
+
• Second item here
|
|
186
|
+
• Third item here
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**Bad list format (indentation lost):**
|
|
190
|
+
```
|
|
191
|
+
Key Features:
|
|
192
|
+
|
|
193
|
+
- First item
|
|
194
|
+
- Sub-item (will flatten)
|
|
195
|
+
- Second item
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Anti-Patterns to Avoid
|
|
199
|
+
|
|
200
|
+
| Don't Do This | Why |
|
|
201
|
+
|---------------|-----|
|
|
202
|
+
| `===` or `---` underlines | ASCII art formatting looks robotic |
|
|
203
|
+
| ALL CAPS FOR EMPHASIS | Comes across as shouting |
|
|
204
|
+
| Category headers everywhere | "Completed:", "Blocked:", "Next:" feels like a form |
|
|
205
|
+
| Nested indentation | Asana flattens it, looks broken |
|
|
206
|
+
| Report-style formatting | You're messaging a colleague, not filing a TPS report |
|
|
207
|
+
|
|
208
|
+
## HTML Tags Reference (for `html_notes` only)
|
|
209
|
+
|
|
210
|
+
| Markdown | Asana XML |
|
|
211
|
+
|----------|-----------|
|
|
212
|
+
| `**bold**` | `<strong>bold</strong>` |
|
|
213
|
+
| `*italic*` | `<em>italic</em>` |
|
|
214
|
+
| `~~strike~~` | `<s>strike</s>` |
|
|
215
|
+
| `__underline__` | `<u>underline</u>` |
|
|
216
|
+
| `` `code` `` | `<code>code</code>` |
|
|
217
|
+
| `- item` | `<ul><li>item</li></ul>` |
|
|
218
|
+
| `1. item` | `<ol><li>item</li></ol>` |
|
|
219
|
+
| `> quote` | `<blockquote>quote</blockquote>` |
|
|
220
|
+
| ` ```block``` ` | `<pre>block</pre>` |
|
|
221
|
+
| `# H1` | `<h1>H1</h1>` |
|
|
222
|
+
| `## H2` | `<h2>H2</h2>` |
|
|
223
|
+
| `[text](url)` | `<a href="url">text</a>` |
|
|
224
|
+
| `@mention` | `<a data-asana-gid="GID"/>` |
|
|
225
|
+
|
|
226
|
+
## Complete Examples
|
|
227
|
+
|
|
228
|
+
### Example 1: Create Task with Rich Formatting
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
asana_create_task({
|
|
232
|
+
name: "Implement user authentication",
|
|
233
|
+
project_id: "111222333",
|
|
234
|
+
html_notes: `<body>
|
|
235
|
+
<h1>User Authentication Feature</h1>
|
|
236
|
+
|
|
237
|
+
<h2>Requirements</h2>
|
|
238
|
+
<ul>
|
|
239
|
+
<li>OAuth 2.0 with Google</li>
|
|
240
|
+
<li>Session management</li>
|
|
241
|
+
<li>Password reset flow</li>
|
|
242
|
+
</ul>
|
|
243
|
+
|
|
244
|
+
<h2>Technical Notes</h2>
|
|
245
|
+
<blockquote>Must comply with security policy SEC-2024-001</blockquote>
|
|
246
|
+
|
|
247
|
+
<strong>Owner:</strong> <a data-asana-gid="12345678901234"/>
|
|
248
|
+
</body>`,
|
|
249
|
+
due_on: "2024-03-15"
|
|
250
|
+
})
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Example 2: Update Task (Plain Text Only)
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
asana_update_task({
|
|
257
|
+
task_id: "1234567890",
|
|
258
|
+
notes: `Quick update on this - API endpoints are done and tests are passing (95% coverage). Documentation draft is ready too.
|
|
259
|
+
|
|
260
|
+
Currently working on integration testing and addressing review feedback.
|
|
261
|
+
|
|
262
|
+
One blocker: still waiting on design approval for the UI changes. Pinged Sarah about it yesterday.
|
|
263
|
+
|
|
264
|
+
Once that's sorted, just need to finish integration tests and we can deploy to staging.`
|
|
265
|
+
})
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Example 3: Add Comment (Plain Text Only)
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
asana_create_task_story({
|
|
272
|
+
task_id: "1234567890",
|
|
273
|
+
text: `Hey, finished the code review!
|
|
274
|
+
|
|
275
|
+
Found a few things:
|
|
276
|
+
• auth.ts line 42 needs error handling around the API call
|
|
277
|
+
• types.ts line 15 has an 'any' that should be a proper interface
|
|
278
|
+
• query.ts line 88 could use some caching for performance
|
|
279
|
+
|
|
280
|
+
Otherwise looks good - solid implementation, just minor fixes. Let me know if you have questions!
|
|
281
|
+
|
|
282
|
+
PR: https://github.com/org/repo/pull/456`
|
|
283
|
+
})
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
## Available Asana MCP Tools
|
|
287
|
+
|
|
288
|
+
### Task Operations
|
|
289
|
+
| Tool | Purpose | Key Parameters |
|
|
290
|
+
|------|---------|----------------|
|
|
291
|
+
| `asana_create_task` | Create new task | `name`, `project_id`, `html_notes`, `assignee`, `due_on` |
|
|
292
|
+
| `asana_update_task` | Update existing task | `task_id`, `notes` (plain text), `completed`, `assignee` |
|
|
293
|
+
| `asana_get_task` | Get task details | `task_id`, `opt_fields` |
|
|
294
|
+
| `asana_delete_task` | Delete a task | `task_id` |
|
|
295
|
+
| `asana_search_tasks` | Search tasks | `workspace`, `text`, `assignee_any`, `completed` |
|
|
296
|
+
|
|
297
|
+
### Comments/Stories
|
|
298
|
+
| Tool | Purpose | Key Parameters |
|
|
299
|
+
|------|---------|----------------|
|
|
300
|
+
| `asana_create_task_story` | Add comment | `task_id`, `text` (plain text only) |
|
|
301
|
+
| `asana_get_stories_for_task` | Get task comments | `task_id` |
|
|
302
|
+
|
|
303
|
+
### Project Operations
|
|
304
|
+
| Tool | Purpose | Key Parameters |
|
|
305
|
+
|------|---------|----------------|
|
|
306
|
+
| `asana_get_project` | Get project details | `project_id` |
|
|
307
|
+
| `asana_get_projects` | List projects | `workspace` |
|
|
308
|
+
| `asana_get_project_sections` | Get sections | `project_id` |
|
|
309
|
+
| `asana_create_project` | Create project | `name`, `workspace`, `team` |
|
|
310
|
+
|
|
311
|
+
### Search & Discovery
|
|
312
|
+
| Tool | Purpose | Key Parameters |
|
|
313
|
+
|------|---------|----------------|
|
|
314
|
+
| `asana_typeahead_search` | Quick search | `workspace_gid`, `resource_type`, `query` |
|
|
315
|
+
| `asana_list_workspaces` | Get workspaces | (none required) |
|
|
316
|
+
| `asana_get_user` | Get user info | `user_id` (default: "me") |
|
|
317
|
+
|
|
318
|
+
## Common Workflows
|
|
319
|
+
|
|
320
|
+
### Find and Update a Task
|
|
321
|
+
```typescript
|
|
322
|
+
// 1. Search for the task
|
|
323
|
+
asana_typeahead_search({
|
|
324
|
+
workspace_gid: "WORKSPACE_ID",
|
|
325
|
+
resource_type: "task",
|
|
326
|
+
query: "authentication feature"
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
// 2. Get task details
|
|
330
|
+
asana_get_task({
|
|
331
|
+
task_id: "FOUND_TASK_ID",
|
|
332
|
+
opt_fields: "name,notes,assignee,due_on,completed"
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
// 3. Update the task (plain text only)
|
|
336
|
+
asana_update_task({
|
|
337
|
+
task_id: "FOUND_TASK_ID",
|
|
338
|
+
notes: "Updated description here (plain text)"
|
|
339
|
+
})
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### Add Status Update Comment
|
|
343
|
+
```typescript
|
|
344
|
+
// Get current user first (for context)
|
|
345
|
+
asana_get_user({}) // Returns current user info
|
|
346
|
+
|
|
347
|
+
// Add comment to task
|
|
348
|
+
asana_create_task_story({
|
|
349
|
+
task_id: "TASK_ID",
|
|
350
|
+
text: `Making good progress here! Got the core functionality working, moving on to testing and docs now. No blockers.`
|
|
351
|
+
})
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
## Common Mistakes
|
|
355
|
+
|
|
356
|
+
| Mistake | Impact | Fix |
|
|
357
|
+
|---------|--------|-----|
|
|
358
|
+
| Using `html_notes` in `asana_update_task` | Parameter doesn't exist, ignored | Use `notes` with plain text |
|
|
359
|
+
| Using `html_text` in `asana_create_task_story` | Parameter doesn't exist, ignored | Use `text` with plain text |
|
|
360
|
+
| Expecting markdown to render | Shows as literal `**text**` | Write naturally in plain text |
|
|
361
|
+
| Missing `<body>` tags in `html_notes` | May fail or render incorrectly | Always wrap in `<body>` tags |
|
|
362
|
+
| Not closing XML tags | Invalid XML error | Close all tags: `<li></li>` |
|
|
363
|
+
|
|
364
|
+
## Red Flags - STOP and Check
|
|
365
|
+
|
|
366
|
+
| Thought | Reality |
|
|
367
|
+
|---------|---------|
|
|
368
|
+
| "I'll use html_notes to update the task" | `asana_update_task` doesn't have `html_notes`. Use `notes`. |
|
|
369
|
+
| "I'll format the comment with HTML" | `asana_create_task_story` only has `text`. Plain text only. |
|
|
370
|
+
| "Markdown will render in Asana" | No. Write naturally in plain text, or use `html_notes` for new tasks only. |
|
|
371
|
+
| "I need the workspace ID" | Call `asana_list_workspaces` first to get it. |
|
|
372
|
+
| "I'll @mention with @username" | Use `<a data-asana-gid="GID"/>` in `html_notes` only. |
|
|
373
|
+
|
|
374
|
+
## References
|
|
375
|
+
|
|
376
|
+
- [Asana Rich Text Documentation](https://developers.asana.com/docs/rich-text) - Full API rich text (note: MCP has limited support)
|
|
377
|
+
- Asana MCP exposes subset of Asana API functionality
|
|
378
|
+
- Rich text via `html_notes` only available in `asana_create_task`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: backend
|
|
3
|
-
description: Use when writing
|
|
3
|
+
description: Use when writing Lambda functions, API routes, Hono handlers, Express routes, serverless endpoints, or backend services. Use when creating API validation with Zod, implementing service layers, or structuring handler code.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Backend Development
|
|
@@ -578,6 +578,135 @@ export const handler = compose(
|
|
|
578
578
|
)(baseHandler);
|
|
579
579
|
```
|
|
580
580
|
|
|
581
|
+
## Hono.js Patterns
|
|
582
|
+
|
|
583
|
+
Hono is a fast, lightweight framework for serverless and edge. Same patterns apply - layer separation, DI, Zod validation.
|
|
584
|
+
|
|
585
|
+
### Basic Hono Handler with Zod
|
|
586
|
+
|
|
587
|
+
```typescript
|
|
588
|
+
import { Hono } from 'hono';
|
|
589
|
+
import { zValidator } from '@hono/zod-validator';
|
|
590
|
+
import { z } from 'zod';
|
|
591
|
+
|
|
592
|
+
const app = new Hono();
|
|
593
|
+
|
|
594
|
+
const CreateUserSchema = z.object({
|
|
595
|
+
email: z.string().email(),
|
|
596
|
+
name: z.string().min(1),
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
// ✅ GOOD: Validation middleware + typed body
|
|
600
|
+
app.post(
|
|
601
|
+
'/users',
|
|
602
|
+
zValidator('json', CreateUserSchema),
|
|
603
|
+
async (c) => {
|
|
604
|
+
const body = c.req.valid('json'); // Typed as { email: string; name: string }
|
|
605
|
+
const user = await userService.createUser(body);
|
|
606
|
+
return c.json(user, 201);
|
|
607
|
+
}
|
|
608
|
+
);
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
### Hono with Dependency Injection
|
|
612
|
+
|
|
613
|
+
```typescript
|
|
614
|
+
// ✅ GOOD: Factory pattern for testable Hono apps
|
|
615
|
+
import { Hono } from 'hono';
|
|
616
|
+
|
|
617
|
+
export function createApp(deps: {
|
|
618
|
+
userService: UserService;
|
|
619
|
+
authService: AuthService;
|
|
620
|
+
}) {
|
|
621
|
+
const app = new Hono();
|
|
622
|
+
|
|
623
|
+
app.post('/users', async (c) => {
|
|
624
|
+
const body = await c.req.json();
|
|
625
|
+
const user = await deps.userService.createUser(body);
|
|
626
|
+
return c.json(user, 201);
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
app.post('/login', async (c) => {
|
|
630
|
+
const { email, password } = await c.req.json();
|
|
631
|
+
const token = await deps.authService.login(email, password);
|
|
632
|
+
return c.json({ token });
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
return app;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// Production: inject real services
|
|
639
|
+
const app = createApp({
|
|
640
|
+
userService: createUserService(db),
|
|
641
|
+
authService: createAuthService(db),
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
export default app;
|
|
645
|
+
|
|
646
|
+
// Test: inject mocks
|
|
647
|
+
const testApp = createApp({
|
|
648
|
+
userService: { createUser: vi.fn() },
|
|
649
|
+
authService: { login: vi.fn() },
|
|
650
|
+
});
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
### Hono Error Handling Middleware
|
|
654
|
+
|
|
655
|
+
```typescript
|
|
656
|
+
import { Hono } from 'hono';
|
|
657
|
+
import { HTTPException } from 'hono/http-exception';
|
|
658
|
+
|
|
659
|
+
const app = new Hono();
|
|
660
|
+
|
|
661
|
+
// Global error handler
|
|
662
|
+
app.onError((err, c) => {
|
|
663
|
+
if (err instanceof AppError) {
|
|
664
|
+
return c.json(
|
|
665
|
+
{ error: err.message, code: err.code },
|
|
666
|
+
err.statusCode
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
if (err instanceof HTTPException) {
|
|
671
|
+
return c.json({ error: err.message }, err.status);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
console.error('Unexpected error:', err);
|
|
675
|
+
return c.json({ error: 'Internal server error' }, 500);
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
// Route handlers throw AppError
|
|
679
|
+
app.get('/users/:id', async (c) => {
|
|
680
|
+
const user = await userService.getUser(c.req.param('id'));
|
|
681
|
+
if (!user) {
|
|
682
|
+
throw new NotFoundError('User not found');
|
|
683
|
+
}
|
|
684
|
+
return c.json(user);
|
|
685
|
+
});
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
### Hono Middleware Composition
|
|
689
|
+
|
|
690
|
+
```typescript
|
|
691
|
+
import { Hono } from 'hono';
|
|
692
|
+
import { cors } from 'hono/cors';
|
|
693
|
+
import { logger } from 'hono/logger';
|
|
694
|
+
import { jwt } from 'hono/jwt';
|
|
695
|
+
|
|
696
|
+
const app = new Hono();
|
|
697
|
+
|
|
698
|
+
// Apply middleware in order
|
|
699
|
+
app.use('*', logger());
|
|
700
|
+
app.use('*', cors());
|
|
701
|
+
app.use('/api/*', jwt({ secret: process.env.JWT_SECRET }));
|
|
702
|
+
|
|
703
|
+
// Protected routes
|
|
704
|
+
app.get('/api/me', (c) => {
|
|
705
|
+
const payload = c.get('jwtPayload');
|
|
706
|
+
return c.json({ userId: payload.sub });
|
|
707
|
+
});
|
|
708
|
+
```
|
|
709
|
+
|
|
581
710
|
## Testing Strategy
|
|
582
711
|
|
|
583
712
|
| What to Test | How |
|
|
@@ -735,6 +864,7 @@ describe('Handler', () => {
|
|
|
735
864
|
## References
|
|
736
865
|
|
|
737
866
|
- [Zod Documentation](https://zod.dev) - Validation, transforms, error formatting, branded types
|
|
867
|
+
- [Hono Documentation](https://hono.dev) - Lightweight framework for serverless and edge
|
|
738
868
|
- [Vitest Documentation](https://vitest.dev) - Testing, mocking, vi.fn(), vi.spyOn()
|
|
739
869
|
- [AWS Lambda Cold Starts](https://aws.amazon.com/blogs/compute/understanding-and-remediating-cold-starts-an-aws-lambda-perspective/) - Official optimization guide
|
|
740
870
|
- [AWS Lambda Performance](https://aws.amazon.com/blogs/compute/operating-lambda-performance-optimization-part-1/) - Best practices
|
|
@@ -742,6 +872,7 @@ describe('Handler', () => {
|
|
|
742
872
|
**Version Notes:**
|
|
743
873
|
- Zod v3.24+: Improved error formatting, discriminated unions, branded types
|
|
744
874
|
- Zod v4.0+: prefault(), enhanced pipe(), performance improvements
|
|
875
|
+
- Hono v4+: Stable, edge-ready, built-in middleware
|
|
745
876
|
- Vitest v3+: mockResolvedValue, mockRejectedValue patterns
|
|
746
877
|
- AWS Lambda: Node.js 20.x has faster cold starts than 18.x
|
|
747
878
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: code-quality
|
|
3
|
-
description: Use when
|
|
3
|
+
description: Use when refactoring functions, extracting helpers, splitting large files, improving naming conventions, or reducing complexity. Use when functions exceed 30 lines, have too many parameters, or contain magic numbers. NOT for React/backend/database-specific patterns.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Code Quality
|
|
@@ -141,46 +141,59 @@ if (user.role === 'admin') { }
|
|
|
141
141
|
|
|
142
142
|
## Automated Enforcement
|
|
143
143
|
|
|
144
|
-
### ESLint Configuration
|
|
144
|
+
### ESLint Configuration (Flat Config - v9+)
|
|
145
145
|
|
|
146
|
-
Enforce code quality
|
|
146
|
+
ESLint 9+ uses flat config (`eslint.config.js`). Enforce code quality automatically:
|
|
147
147
|
|
|
148
148
|
```javascript
|
|
149
|
-
// .
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
149
|
+
// eslint.config.js (ESLint v9+ flat config)
|
|
150
|
+
import js from '@eslint/js';
|
|
151
|
+
import tseslint from 'typescript-eslint';
|
|
152
|
+
|
|
153
|
+
export default tseslint.config(
|
|
154
|
+
js.configs.recommended,
|
|
155
|
+
...tseslint.configs.recommended,
|
|
156
|
+
{
|
|
157
|
+
rules: {
|
|
158
|
+
// Max function length
|
|
159
|
+
'max-lines-per-function': ['error', {
|
|
160
|
+
max: 30,
|
|
161
|
+
skipBlankLines: true,
|
|
162
|
+
skipComments: true,
|
|
163
|
+
}],
|
|
164
|
+
|
|
165
|
+
// Cyclomatic complexity
|
|
166
|
+
'complexity': ['error', { max: 10 }],
|
|
167
|
+
|
|
168
|
+
// Max file length
|
|
169
|
+
'max-lines': ['error', {
|
|
170
|
+
max: 300,
|
|
171
|
+
skipBlankLines: true,
|
|
172
|
+
skipComments: true,
|
|
173
|
+
}],
|
|
174
|
+
|
|
175
|
+
// Max function params
|
|
176
|
+
'max-params': ['error', 3],
|
|
177
|
+
|
|
178
|
+
// Max nested callbacks
|
|
179
|
+
'max-nested-callbacks': ['error', 2],
|
|
180
|
+
|
|
181
|
+
// No magic numbers
|
|
182
|
+
'no-magic-numbers': ['error', {
|
|
183
|
+
ignore: [0, 1, -1],
|
|
184
|
+
ignoreArrayIndexes: true,
|
|
185
|
+
}],
|
|
186
|
+
},
|
|
180
187
|
},
|
|
181
|
-
|
|
188
|
+
{
|
|
189
|
+
// Ignore patterns (replaces .eslintignore)
|
|
190
|
+
ignores: ['node_modules/', 'dist/', '*.config.js'],
|
|
191
|
+
}
|
|
192
|
+
);
|
|
182
193
|
```
|
|
183
194
|
|
|
195
|
+
**Legacy config?** ESLint 9+ still supports `.eslintrc.js` via `ESLINT_USE_FLAT_CONFIG=false`, but flat config is the future.
|
|
196
|
+
|
|
184
197
|
### TypeScript-Specific Patterns
|
|
185
198
|
|
|
186
199
|
```typescript
|
|
@@ -294,14 +307,16 @@ When someone says "just make it work fast":
|
|
|
294
307
|
|
|
295
308
|
## References
|
|
296
309
|
|
|
310
|
+
- [ESLint Flat Config](https://eslint.org/docs/latest/use/configure/configuration-files-new) - ESLint 9+ configuration
|
|
297
311
|
- [ESLint Complexity Rule](https://eslint.org/docs/latest/rules/complexity) - Cyclomatic complexity enforcement
|
|
298
|
-
- [
|
|
312
|
+
- [typescript-eslint](https://typescript-eslint.io/) - TypeScript ESLint integration
|
|
299
313
|
- [TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/) - Advanced type patterns
|
|
300
314
|
- [Clean Code (Martin)](https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882) - Function length rationale
|
|
301
315
|
|
|
302
316
|
**Version Notes:**
|
|
303
|
-
- ESLint 9+: Flat config
|
|
304
|
-
- TypeScript 5+: Improved discriminated union narrowing
|
|
317
|
+
- ESLint 9+: Flat config (`eslint.config.js`), replaces `.eslintrc.*`
|
|
318
|
+
- TypeScript 5+: Improved discriminated union narrowing, const type parameters
|
|
319
|
+
- typescript-eslint 8+: Native flat config support
|
|
305
320
|
- Cyclomatic complexity: Default threshold 20, recommended 10
|
|
306
321
|
|
|
307
322
|
## Common Mistakes
|