@c0x12c/spartan-ai-toolkit 1.5.0 → 1.6.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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +2 -2
- package/VERSION +1 -1
- package/commands/spartan/build.md +59 -4
- package/package.json +1 -1
- package/packs/core.yaml +1 -0
- package/packs/packs.compiled.json +2 -1
- package/rules/core/SKILL_AUTHORING.md +174 -0
- package/skills/article-writing/SKILL.md +14 -0
- package/skills/article-writing/examples.md +59 -0
- package/skills/backend-api-design/SKILL.md +22 -125
- package/skills/backend-api-design/code-patterns.md +138 -0
- package/skills/brainstorm/SKILL.md +10 -0
- package/skills/browser-qa/SKILL.md +12 -105
- package/skills/browser-qa/playwright-snippets.md +110 -0
- package/skills/ci-cd-patterns/SKILL.md +108 -0
- package/skills/ci-cd-patterns/workflows.md +149 -0
- package/skills/competitive-teardown/SKILL.md +14 -0
- package/skills/competitive-teardown/example-analysis.md +50 -0
- package/skills/content-engine/SKILL.md +30 -0
- package/skills/content-engine/examples.md +72 -0
- package/skills/database-patterns/SKILL.md +29 -102
- package/skills/database-patterns/code-templates.md +114 -0
- package/skills/deep-research/SKILL.md +12 -0
- package/skills/design-workflow/SKILL.md +5 -45
- package/skills/design-workflow/checklists.md +45 -0
- package/skills/idea-validation/SKILL.md +14 -0
- package/skills/idea-validation/example-report.md +50 -0
- package/skills/investor-materials/SKILL.md +13 -6
- package/skills/investor-materials/example-outline.md +70 -0
- package/skills/investor-outreach/SKILL.md +14 -0
- package/skills/investor-outreach/examples.md +76 -0
- package/skills/kotlin-best-practices/SKILL.md +27 -114
- package/skills/kotlin-best-practices/code-patterns.md +132 -0
- package/skills/market-research/SKILL.md +12 -0
- package/skills/security-checklist/SKILL.md +12 -97
- package/skills/security-checklist/audit-reference.md +95 -0
- package/skills/service-debugging/SKILL.md +116 -0
- package/skills/service-debugging/common-issues.md +65 -0
- package/skills/startup-pipeline/SKILL.md +28 -1
- package/skills/testing-strategies/SKILL.md +27 -153
- package/skills/testing-strategies/examples.md +103 -0
- package/skills/testing-strategies/integration-test-setup.md +71 -0
- package/skills/ui-ux-pro-max/SKILL.md +8 -1
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
"plugins": [
|
|
9
9
|
{
|
|
10
10
|
"name": "spartan-ai-toolkit",
|
|
11
|
-
"description": "5 workflows,
|
|
11
|
+
"description": "5 workflows, 56 commands, 12 rules, 22 skills, 7 agents — organized in 11 packs with dependencies",
|
|
12
12
|
"source": "./toolkit",
|
|
13
|
-
"version": "1.
|
|
13
|
+
"version": "1.6.0"
|
|
14
14
|
}
|
|
15
15
|
]
|
|
16
16
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spartan-ai-toolkit",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Engineering discipline layer for Claude Code — 5 workflows,
|
|
3
|
+
"version": "1.6.0",
|
|
4
|
+
"description": "Engineering discipline layer for Claude Code — 5 workflows, 56 commands, 12 rules, 22 skills, 7 agents organized in 11 packs",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Khoa Tran",
|
|
7
7
|
"url": "https://github.com/spartan-stratos"
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.
|
|
1
|
+
1.6.0
|
|
@@ -224,6 +224,8 @@ Uses skills: `ui-ux-pro-max`, frontend rules
|
|
|
224
224
|
|
|
225
225
|
**Full-stack mode** — backend tasks first, then frontend tasks. Mark the integration point clearly (where frontend starts depending on backend API).
|
|
226
226
|
|
|
227
|
+
**CRITICAL: Full-stack means BOTH layers must complete.** Don't move to Gate 3 after finishing backend only. The plan must include frontend tasks and ALL tasks must be done before review. If the spec mentions UI changes, API responses shown to users, or any user-facing behavior — frontend tasks are mandatory.
|
|
228
|
+
|
|
227
229
|
### Create branch
|
|
228
230
|
|
|
229
231
|
```bash
|
|
@@ -299,7 +301,24 @@ Call the right skills based on what you're doing:
|
|
|
299
301
|
| React components | `ui-ux-pro-max` |
|
|
300
302
|
| Security-sensitive code | `security-checklist` |
|
|
301
303
|
|
|
302
|
-
### After all tasks
|
|
304
|
+
### After all tasks — Verify Definition of Done
|
|
305
|
+
|
|
306
|
+
**Before moving to Gate 3, verify ALL layers are complete:**
|
|
307
|
+
|
|
308
|
+
| Mode | Must be done before review |
|
|
309
|
+
|------|---------------------------|
|
|
310
|
+
| **Backend** | Migration applied, entity/table/repo created, manager with business logic, controller with endpoints, all tests passing (`./gradlew test`) |
|
|
311
|
+
| **Frontend** | Types defined, API client updated, components built, page/routing wired, build passes (`yarn build` or `npm run build`) |
|
|
312
|
+
| **Full-stack** | ALL backend items above + ALL frontend items above + frontend calls the new backend API correctly |
|
|
313
|
+
|
|
314
|
+
**Full-stack verification (MANDATORY if mode is full-stack):**
|
|
315
|
+
1. Backend tests pass: `./gradlew test`
|
|
316
|
+
2. Frontend builds: `yarn build` or `npm run build`
|
|
317
|
+
3. Frontend types match backend response DTOs
|
|
318
|
+
4. API client has methods for all new endpoints
|
|
319
|
+
5. UI shows the data from the new endpoints
|
|
320
|
+
|
|
321
|
+
**If any layer is incomplete**, go back and finish it. Do NOT proceed to Gate 3 with only backend done.
|
|
303
322
|
|
|
304
323
|
Run the full test suite:
|
|
305
324
|
```bash
|
|
@@ -307,15 +326,17 @@ Run the full test suite:
|
|
|
307
326
|
./gradlew test
|
|
308
327
|
|
|
309
328
|
# Frontend
|
|
310
|
-
npm test
|
|
329
|
+
npm test && npm run build
|
|
311
330
|
|
|
312
|
-
# Both
|
|
313
|
-
./gradlew test && npm test
|
|
331
|
+
# Both (full-stack)
|
|
332
|
+
./gradlew test && npm test && npm run build
|
|
314
333
|
```
|
|
315
334
|
|
|
316
335
|
**GATE 3 — STOP and ask:**
|
|
317
336
|
> "All [N] tasks done. [X] tests passing. Ready for review?"
|
|
318
337
|
>
|
|
338
|
+
> **Full-stack:** "Backend: [N] tasks done, tests passing. Frontend: [N] tasks done, build passing. Ready for review?"
|
|
339
|
+
>
|
|
319
340
|
> If 3+ tasks were completed: "I'll run a self-review now. For a deeper dual-agent review, say 'gate review'."
|
|
320
341
|
>
|
|
321
342
|
> **Auto mode on?** → Continue to Review immediately.
|
|
@@ -401,3 +422,37 @@ If a previous session was interrupted (context overflow, user stopped, etc.), th
|
|
|
401
422
|
- **Frontend checks for backend needs.** If a new page needs data that doesn't exist yet, say so at Stage 2 and add backend tasks first.
|
|
402
423
|
- **Don't over-plan.** If the whole thing is 1-2 files and 30 minutes of work, don't force it into this workflow. Just do it. This workflow is for features that need structure — at least 2-3 tasks.
|
|
403
424
|
- **Save state for big work.** If 5+ tasks, save artifacts to `.planning/` so future sessions can resume.
|
|
425
|
+
- **Full-stack = both layers done.** If the feature touches both backend and frontend, you MUST implement both before creating the PR. Backend-only completion is NOT "done" for a full-stack feature.
|
|
426
|
+
|
|
427
|
+
---
|
|
428
|
+
|
|
429
|
+
## Definition of Done
|
|
430
|
+
|
|
431
|
+
A feature is NOT done until every applicable item is checked:
|
|
432
|
+
|
|
433
|
+
### Backend
|
|
434
|
+
- [ ] Database migration (if new/changed table)
|
|
435
|
+
- [ ] Kotlin entity, table object, repository with tests
|
|
436
|
+
- [ ] Manager with business logic and Either error handling
|
|
437
|
+
- [ ] Controller with proper annotations (@ExecuteOn, @Secured, @Validated)
|
|
438
|
+
- [ ] Integration tests for all endpoints (happy path, 404, 401)
|
|
439
|
+
- [ ] `./gradlew test` passes
|
|
440
|
+
|
|
441
|
+
### Frontend
|
|
442
|
+
- [ ] TypeScript types/interfaces match backend DTOs
|
|
443
|
+
- [ ] API client methods for all new/changed endpoints
|
|
444
|
+
- [ ] Components built and connected to data
|
|
445
|
+
- [ ] Page routing and navigation working
|
|
446
|
+
- [ ] `yarn build` (or `npm run build`) passes with no errors
|
|
447
|
+
- [ ] Loading, empty, and error states handled
|
|
448
|
+
|
|
449
|
+
### Full-Stack (ALL of the above, plus)
|
|
450
|
+
- [ ] Frontend calls backend API correctly (snake_case conversion, auth headers)
|
|
451
|
+
- [ ] Response data renders in the UI
|
|
452
|
+
- [ ] End-to-end flow works: user action → API call → backend processing → response → UI update
|
|
453
|
+
- [ ] Both `./gradlew test` AND `yarn build` pass
|
|
454
|
+
|
|
455
|
+
### Always
|
|
456
|
+
- [ ] Atomic commits (one per task)
|
|
457
|
+
- [ ] Self-review passed (backend review + frontend review if applicable)
|
|
458
|
+
- [ ] PR created with summary and test plan
|
package/package.json
CHANGED
package/packs/core.yaml
CHANGED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# Skill Authoring Rules
|
|
2
|
+
|
|
3
|
+
Rules for creating and modifying skills in the Spartan AI Toolkit. Follow these when writing new skills or improving existing ones.
|
|
4
|
+
|
|
5
|
+
## Frontmatter (REQUIRED)
|
|
6
|
+
|
|
7
|
+
Every SKILL.md must have these fields:
|
|
8
|
+
|
|
9
|
+
```yaml
|
|
10
|
+
---
|
|
11
|
+
name: skill-name
|
|
12
|
+
description: "What it does. Use when [trigger conditions]."
|
|
13
|
+
allowed_tools:
|
|
14
|
+
- Read
|
|
15
|
+
- Write
|
|
16
|
+
# ... tools the skill needs
|
|
17
|
+
---
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Description Must Be a Trigger
|
|
21
|
+
|
|
22
|
+
The description tells the model WHEN to activate the skill, not WHAT the skill is.
|
|
23
|
+
|
|
24
|
+
| Bad (summary) | Good (trigger) |
|
|
25
|
+
|----------------|----------------|
|
|
26
|
+
| "Database design patterns including schemas and migrations" | "Database design patterns. Use when creating tables, writing migrations, or implementing repositories." |
|
|
27
|
+
| "The full startup pipeline from brainstorm to outreach" | "Coordinates the full startup pipeline. Use when the user starts a new idea project or references stages/gates." |
|
|
28
|
+
|
|
29
|
+
**Rule:** Every description must contain "Use when" followed by specific trigger conditions.
|
|
30
|
+
|
|
31
|
+
### allowed_tools Must Match the Skill's Needs
|
|
32
|
+
|
|
33
|
+
| Skill type | Typical tools |
|
|
34
|
+
|------------|--------------|
|
|
35
|
+
| Code/backend (writes files) | Read, Write, Edit, Glob, Grep, Bash |
|
|
36
|
+
| Research/analysis (web searches) | WebSearch, WebFetch, Read |
|
|
37
|
+
| Content/writing (creates + researches) | Read, Write, WebSearch |
|
|
38
|
+
| Review/audit (reads only) | Read, Glob, Grep |
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Folder Structure (Skills Are Folders, Not Files)
|
|
43
|
+
|
|
44
|
+
A skill is a directory, not just a markdown file. Use the file system for progressive disclosure.
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
toolkit/skills/my-skill/
|
|
48
|
+
SKILL.md # Main definition — short, high-level (required)
|
|
49
|
+
code-patterns.md # Code examples (if code-heavy skill)
|
|
50
|
+
examples.md # Good/bad examples (if teaching a style)
|
|
51
|
+
checklists.md # Review checklists (if audit/review skill)
|
|
52
|
+
workflows.md # Ready-to-use templates (if scaffolding skill)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### When to Split Into Multiple Files
|
|
56
|
+
|
|
57
|
+
| SKILL.md is... | Action |
|
|
58
|
+
|-----------------|--------|
|
|
59
|
+
| Under 100 lines | One file is fine |
|
|
60
|
+
| 100-150 lines with code blocks | Split code into a reference file |
|
|
61
|
+
| 150+ lines | Must split — too much for one read |
|
|
62
|
+
|
|
63
|
+
### SKILL.md Should Be the Summary
|
|
64
|
+
|
|
65
|
+
Keep SKILL.md short (60-120 lines). It should have:
|
|
66
|
+
- Frontmatter
|
|
67
|
+
- "When to Use" section
|
|
68
|
+
- Key rules and principles (without detailed code)
|
|
69
|
+
- Gotchas section
|
|
70
|
+
- References to supporting files
|
|
71
|
+
|
|
72
|
+
Move into supporting files:
|
|
73
|
+
- Detailed code templates and examples
|
|
74
|
+
- Long checklists
|
|
75
|
+
- Good/bad comparisons
|
|
76
|
+
- Ready-to-use templates
|
|
77
|
+
|
|
78
|
+
Reference with: `> See code-patterns.md for complete implementation templates.`
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Gotchas Section (REQUIRED)
|
|
83
|
+
|
|
84
|
+
Every skill must have a `## Gotchas` section. This is the highest-value content in any skill.
|
|
85
|
+
|
|
86
|
+
### Format
|
|
87
|
+
|
|
88
|
+
```markdown
|
|
89
|
+
## Gotchas
|
|
90
|
+
|
|
91
|
+
- **Bold lead-in sentence.** Explanation of why this matters and what to do instead.
|
|
92
|
+
- **Another gotcha.** Details.
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### What Makes a Good Gotcha
|
|
96
|
+
|
|
97
|
+
- Specific failure patterns Claude hits when using this skill
|
|
98
|
+
- Things that look right but are wrong
|
|
99
|
+
- Common mistakes users make in this domain
|
|
100
|
+
- Counter-intuitive rules that violate defaults
|
|
101
|
+
|
|
102
|
+
### What is NOT a Gotcha
|
|
103
|
+
|
|
104
|
+
- General best practices (put those in Rules)
|
|
105
|
+
- Obvious things Claude already knows
|
|
106
|
+
- Restating the instructions in negative form
|
|
107
|
+
|
|
108
|
+
**Minimum 3 gotchas per skill. Build this section over time as you find new failure patterns.**
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Content Rules
|
|
113
|
+
|
|
114
|
+
### Don't State the Obvious
|
|
115
|
+
|
|
116
|
+
Claude already knows how to code, research, and write. Focus on information that pushes Claude OUT of its normal patterns.
|
|
117
|
+
|
|
118
|
+
| Bad (obvious) | Good (non-obvious) |
|
|
119
|
+
|----------------|---------------------|
|
|
120
|
+
| "Use proper error handling" | "`!!` is banned — use `?.`, `?:`, or null check" |
|
|
121
|
+
| "Write clean code" | "Don't add docstrings to code you didn't change" |
|
|
122
|
+
| "Research thoroughly" | "Press releases aren't research — cross-check with third-party sources" |
|
|
123
|
+
|
|
124
|
+
### Give Claude Flexibility
|
|
125
|
+
|
|
126
|
+
Tell Claude WHAT to check and WHY, not exact steps for every situation. Skills are reused across many contexts — being too specific makes them brittle.
|
|
127
|
+
|
|
128
|
+
| Bad (railroading) | Good (flexible) |
|
|
129
|
+
|---------------------|------------------|
|
|
130
|
+
| "Step 1: Open file X. Step 2: Find line Y. Step 3: Change to Z." | "Check the controller for @ExecuteOn annotation. If missing, add it." |
|
|
131
|
+
|
|
132
|
+
### Use Examples Over Instructions
|
|
133
|
+
|
|
134
|
+
A good/bad example teaches more than a paragraph of rules. When possible, show rather than tell.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Config Pattern (for Stateful Skills)
|
|
139
|
+
|
|
140
|
+
Skills that run repeatedly for the same user should store preferences:
|
|
141
|
+
|
|
142
|
+
```json
|
|
143
|
+
// content-config.json in the project root
|
|
144
|
+
{
|
|
145
|
+
"defaultPlatforms": ["x", "linkedin"],
|
|
146
|
+
"brandVoice": "direct and technical",
|
|
147
|
+
"audience": "developers"
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Read config at skill start. Skip setup questions for configured fields.
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Naming
|
|
156
|
+
|
|
157
|
+
| Type | Convention | Example |
|
|
158
|
+
|------|-----------|---------|
|
|
159
|
+
| Skill directory | `kebab-case` | `ci-cd-patterns/` |
|
|
160
|
+
| Main file | `SKILL.md` (always) | `SKILL.md` |
|
|
161
|
+
| Supporting files | `kebab-case.md` | `code-patterns.md` |
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Checklist: Before Shipping a Skill
|
|
166
|
+
|
|
167
|
+
- [ ] Frontmatter has `name`, `description` (with trigger), and `allowed_tools`
|
|
168
|
+
- [ ] Description says "Use when..." with specific conditions
|
|
169
|
+
- [ ] SKILL.md is under 120 lines (split if longer)
|
|
170
|
+
- [ ] Has a `## Gotchas` section with 3+ items
|
|
171
|
+
- [ ] Code-heavy content is in supporting files, not inline
|
|
172
|
+
- [ ] Examples show good AND bad patterns where applicable
|
|
173
|
+
- [ ] Doesn't restate things Claude already knows
|
|
174
|
+
- [ ] Gives Claude flexibility — principles over exact steps
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: article-writing
|
|
3
3
|
description: Write blog posts, guides, tutorials, and long-form content. Sounds like a real person, not AI. Use when the user wants polished written content.
|
|
4
|
+
allowed_tools:
|
|
5
|
+
- Read
|
|
6
|
+
- Write
|
|
7
|
+
- WebSearch
|
|
4
8
|
---
|
|
5
9
|
|
|
6
10
|
# Article Writing
|
|
@@ -14,6 +18,8 @@ Write long-form content that sounds like a person wrote it.
|
|
|
14
18
|
- Matching an existing voice from examples
|
|
15
19
|
- Cleaning up and tightening existing writing
|
|
16
20
|
|
|
21
|
+
> See `examples.md` for good vs bad writing examples that show what "sounds human" actually means.
|
|
22
|
+
|
|
17
23
|
## Rules
|
|
18
24
|
|
|
19
25
|
1. Start with something concrete: example, number, story, or code block.
|
|
@@ -82,6 +88,14 @@ This is a two-way talk:
|
|
|
82
88
|
- Skip the "this part is weak" feedback
|
|
83
89
|
- Write AI-sounding content and call it done
|
|
84
90
|
|
|
91
|
+
## Gotchas
|
|
92
|
+
|
|
93
|
+
- **The intro is where most articles die.** If the first paragraph starts with "In this article, we'll explore..." — delete it and start with a story, stat, or code block.
|
|
94
|
+
- **AI-written articles all sound the same.** They hedge ("it's important to note"), use transition words nobody says out loud ("Moreover"), and avoid strong opinions. Cut all of that.
|
|
95
|
+
- **Don't explain the obvious.** If your audience is developers, don't explain what an API is. Write for the person, not the lowest common denominator.
|
|
96
|
+
- **Long ≠ thorough.** A 3,000-word article with 1,500 words of filler is worse than a 1,500-word article where every sentence earns its spot.
|
|
97
|
+
- **Every section needs evidence.** A claim without a number, example, or code block is just an opinion. Back it up or cut it.
|
|
98
|
+
|
|
85
99
|
## Before You Deliver
|
|
86
100
|
|
|
87
101
|
- Facts match sources
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Article Writing — Good vs Bad Examples
|
|
2
|
+
|
|
3
|
+
> Read these examples to calibrate your writing style. The "bad" versions are typical AI output. The "good" versions sound like a person wrote them.
|
|
4
|
+
|
|
5
|
+
## Intro Paragraphs
|
|
6
|
+
|
|
7
|
+
### Bad (AI-style)
|
|
8
|
+
> In today's rapidly evolving technological landscape, the importance of effective error handling cannot be overstated. This comprehensive guide will walk you through the various aspects of implementing robust error handling strategies in your Kotlin applications, enabling you to build more resilient and maintainable software systems.
|
|
9
|
+
|
|
10
|
+
### Good (human-style)
|
|
11
|
+
> Last Tuesday, our payment service threw 4,000 unhandled exceptions in 20 minutes. The fix was 3 lines of code. Here's what went wrong and how to make sure it doesn't happen to you.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Explaining a Concept
|
|
16
|
+
|
|
17
|
+
### Bad (AI-style)
|
|
18
|
+
> Dependency injection is a crucial software design pattern that facilitates the development of loosely coupled, maintainable, and testable code. By leveraging this paradigm, developers can effectively manage the complex dependencies between various components of their application architecture.
|
|
19
|
+
|
|
20
|
+
### Good (human-style)
|
|
21
|
+
> Dependency injection means your class doesn't create its own dependencies — someone hands them in. Instead of `val db = Database()` inside your class, you write `class UserRepo(val db: Database)` and let the framework wire it up. That's it. The rest is details.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Technical Guide Section
|
|
26
|
+
|
|
27
|
+
### Bad (AI-style)
|
|
28
|
+
> To implement this feature, you'll want to start by carefully considering the architectural implications. First, create a new service class that will handle the business logic. Then, implement the necessary repository methods. Finally, wire everything together in the controller layer. This approach ensures a clean separation of concerns.
|
|
29
|
+
|
|
30
|
+
### Good (human-style)
|
|
31
|
+
> Three files to create:
|
|
32
|
+
> 1. `PaymentManager.kt` — validates the amount, calls Stripe, saves the record
|
|
33
|
+
> 2. `PaymentRepository.kt` — insert and soft-delete methods
|
|
34
|
+
> 3. `PaymentController.kt` — one POST endpoint, delegates to the manager
|
|
35
|
+
>
|
|
36
|
+
> Start with the manager. The other two are boilerplate.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Transitions Between Sections
|
|
41
|
+
|
|
42
|
+
### Bad (AI-style)
|
|
43
|
+
> Now that we've explored the fundamentals of error handling, let's delve into the more advanced aspects of this topic. In the following section, we'll examine how to implement custom error types that can provide more granular control over your application's error handling mechanisms.
|
|
44
|
+
|
|
45
|
+
### Good (human-style)
|
|
46
|
+
> The basics work for 80% of cases. But when you need different error responses for different callers (API vs webhook vs internal), you need custom error types.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Conclusions
|
|
51
|
+
|
|
52
|
+
### Bad (AI-style)
|
|
53
|
+
> In conclusion, we've covered a comprehensive overview of error handling patterns in Kotlin. By implementing these strategies, you'll be well-equipped to build robust, maintainable applications. Remember that error handling is not just about catching exceptions — it's about creating a resilient system that gracefully handles the unexpected.
|
|
54
|
+
|
|
55
|
+
### Good (human-style)
|
|
56
|
+
> Three things to remember:
|
|
57
|
+
> 1. Return `Either`, don't throw. Your callers will thank you.
|
|
58
|
+
> 2. Log the actual error, not a generic message.
|
|
59
|
+
> 3. Test the error paths. They run more often than you think.
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: backend-api-design
|
|
3
3
|
description: Design RPC-style APIs with layered architecture (Controller → Manager → Repository). Use when creating new API endpoints, designing API contracts, or reviewing API patterns.
|
|
4
|
+
allowed_tools:
|
|
5
|
+
- Read
|
|
6
|
+
- Write
|
|
7
|
+
- Edit
|
|
8
|
+
- Glob
|
|
9
|
+
- Grep
|
|
4
10
|
---
|
|
5
11
|
|
|
6
12
|
# Backend API Design — Quick Reference
|
|
@@ -24,36 +30,6 @@ POST /api/v1/sync/employees # Action
|
|
|
24
30
|
- **Plural for collections** — `/employees`
|
|
25
31
|
- **Verb sub-paths for actions** — `/delete`, `/restore`, `/sync`
|
|
26
32
|
|
|
27
|
-
## Controller Template
|
|
28
|
-
|
|
29
|
-
```kotlin
|
|
30
|
-
@ExecuteOn(TaskExecutors.IO) // REQUIRED for suspend
|
|
31
|
-
@Validated
|
|
32
|
-
@Controller("/api/v1/admin")
|
|
33
|
-
@Secured(OAuthSecurityRule.ADMIN)
|
|
34
|
-
class EmployeeController(
|
|
35
|
-
private val employeeManager: EmployeeManager // Managers ONLY
|
|
36
|
-
) {
|
|
37
|
-
@Get("/employee")
|
|
38
|
-
suspend fun getEmployee(@QueryValue id: UUID): EmployeeResponse {
|
|
39
|
-
return employeeManager.findById(id).throwOrValue()
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
@Get("/employees")
|
|
43
|
-
suspend fun listEmployees(
|
|
44
|
-
@QueryValue page: Int?,
|
|
45
|
-
@QueryValue limit: Int?,
|
|
46
|
-
@QueryValue status: String?
|
|
47
|
-
): EmployeeListResponse {
|
|
48
|
-
return employeeManager.list(
|
|
49
|
-
page = page ?: 1,
|
|
50
|
-
limit = (limit ?: 20).coerceAtMost(100),
|
|
51
|
-
status = status
|
|
52
|
-
).throwOrValue()
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
```
|
|
56
|
-
|
|
57
33
|
## Layered Architecture
|
|
58
34
|
|
|
59
35
|
```
|
|
@@ -81,107 +57,28 @@ Repository → data access only, no business logic
|
|
|
81
57
|
- `db.replica` for reads, `db.primary` for writes
|
|
82
58
|
- Always checks `deletedAt.isNull()`
|
|
83
59
|
|
|
84
|
-
##
|
|
60
|
+
## Quick Code Reference
|
|
85
61
|
|
|
86
|
-
|
|
62
|
+
The core controller delegation pattern:
|
|
87
63
|
|
|
88
64
|
```kotlin
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
val email: String,
|
|
93
|
-
val status: String,
|
|
94
|
-
val createdAt: Instant
|
|
95
|
-
) {
|
|
96
|
-
companion object {
|
|
97
|
-
fun from(entity: EmployeeEntity) = EmployeeResponse(
|
|
98
|
-
id = entity.id,
|
|
99
|
-
name = entity.name,
|
|
100
|
-
email = entity.email,
|
|
101
|
-
status = entity.status,
|
|
102
|
-
createdAt = entity.createdAt
|
|
103
|
-
)
|
|
104
|
-
}
|
|
65
|
+
@Get("/employee")
|
|
66
|
+
suspend fun getEmployee(@QueryValue id: UUID): EmployeeResponse {
|
|
67
|
+
return employeeManager.findById(id).throwOrValue()
|
|
105
68
|
}
|
|
106
|
-
|
|
107
|
-
data class EmployeeListResponse(
|
|
108
|
-
val items: List<EmployeeResponse>,
|
|
109
|
-
val total: Int,
|
|
110
|
-
val page: Int,
|
|
111
|
-
val limit: Int,
|
|
112
|
-
val hasMore: Boolean
|
|
113
|
-
)
|
|
114
69
|
```
|
|
115
70
|
|
|
116
|
-
|
|
71
|
+
- **Response models** — `companion object { fun from(entity) }` in `module-client/response/{domain}/`
|
|
72
|
+
- **Pagination** — offset-based, manager returns `EmployeeListResponse` with `items`, `total`, `page`, `limit`, `hasMore`
|
|
73
|
+
- **Errors** — return `ClientError.NOT_FOUND.asException().left()` from managers, never throw
|
|
74
|
+
- **Factory beans** — `@Factory` class with `@Singleton` method, wire repos + db into manager
|
|
117
75
|
|
|
118
|
-
|
|
119
|
-
override suspend fun list(
|
|
120
|
-
page: Int,
|
|
121
|
-
limit: Int,
|
|
122
|
-
status: String?
|
|
123
|
-
): Either<ClientException, EmployeeListResponse> {
|
|
124
|
-
val offset = (page - 1) * limit
|
|
125
|
-
|
|
126
|
-
val (items, total) = transaction(db.replica) {
|
|
127
|
-
val query = EmployeesTable
|
|
128
|
-
.selectAll()
|
|
129
|
-
.where { EmployeesTable.deletedAt.isNull() }
|
|
130
|
-
|
|
131
|
-
if (status != null) {
|
|
132
|
-
query.andWhere { EmployeesTable.status eq status }
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
val total = query.count().toInt()
|
|
136
|
-
val items = query
|
|
137
|
-
.orderBy(EmployeesTable.createdAt to SortOrder.DESC)
|
|
138
|
-
.limit(limit)
|
|
139
|
-
.offset(offset.toLong())
|
|
140
|
-
.map { convert(it) }
|
|
141
|
-
|
|
142
|
-
items to total
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return EmployeeListResponse(
|
|
146
|
-
items = items.map { EmployeeResponse.from(it) },
|
|
147
|
-
total = total,
|
|
148
|
-
page = page,
|
|
149
|
-
limit = limit,
|
|
150
|
-
hasMore = (page * limit) < total
|
|
151
|
-
).right()
|
|
152
|
-
}
|
|
153
|
-
```
|
|
76
|
+
> See code-patterns.md for complete controller, response model, pagination, error handling, and factory bean templates.
|
|
154
77
|
|
|
155
|
-
##
|
|
78
|
+
## Gotchas
|
|
156
79
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
val
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
// Already exists
|
|
163
|
-
val existing = repository.byEmail(email)
|
|
164
|
-
if (existing != null) {
|
|
165
|
-
return ClientError.ALREADY_EXISTS.asException().left()
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Validation
|
|
169
|
-
if (request.name.isBlank()) {
|
|
170
|
-
return ClientError.INVALID_INPUT.asException("Name is required").left()
|
|
171
|
-
}
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
## Factory Bean
|
|
175
|
-
|
|
176
|
-
```kotlin
|
|
177
|
-
@Factory
|
|
178
|
-
class EmployeeManagerFactory {
|
|
179
|
-
@Singleton
|
|
180
|
-
fun provideEmployeeManager(
|
|
181
|
-
employeeRepository: EmployeeRepository,
|
|
182
|
-
db: DatabaseContext
|
|
183
|
-
): EmployeeManager {
|
|
184
|
-
return DefaultEmployeeManager(employeeRepository, db)
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
```
|
|
80
|
+
- **Multi-word `@QueryValue` params MUST have explicit snake_case names.** The frontend axios interceptor sends `project_id` but Micronaut matches the literal param name. Write `@QueryValue("project_id") projectId: UUID`, not bare `@QueryValue projectId: UUID`.
|
|
81
|
+
- **Don't use `@Put`, `@Delete`, or `@Patch`.** This is RPC-style — all mutations are `@Post`. The only `@Get` is for reads.
|
|
82
|
+
- **Controllers that inject repositories are a code smell.** If you see `private val fooRepository: FooRepository` in a controller, move it to the manager.
|
|
83
|
+
- **`andWhere {}` not second `.where {}`.** Calling `.where {}` twice replaces the first condition. Use `.andWhere {}` to chain.
|
|
84
|
+
- **Don't forget `@ExecuteOn(TaskExecutors.IO)`.** Without it, suspend functions may hang or run on the wrong thread pool. Every controller needs it.
|