@c0x12c/ai-toolkit 1.15.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 +16 -0
- package/.claude-plugin/plugin.json +12 -0
- package/README.md +439 -0
- package/VERSION +1 -0
- package/agents/design-critic.md +127 -0
- package/agents/idea-killer.md +72 -0
- package/agents/infrastructure-expert.md +49 -0
- package/agents/micronaut-backend-expert.md +45 -0
- package/agents/phase-reviewer.md +150 -0
- package/agents/research-planner.md +70 -0
- package/agents/solution-architect-cto.md +49 -0
- package/agents/sre-architect.md +49 -0
- package/agents/team-coordinator.md +111 -0
- package/bin/cli.js +780 -0
- package/claude-md/00-header.md +39 -0
- package/claude-md/01-core.md +105 -0
- package/claude-md/05-database.md +20 -0
- package/claude-md/11-backend-micronaut.md +19 -0
- package/claude-md/20-frontend-react.md +44 -0
- package/claude-md/25-ux-design.md +56 -0
- package/claude-md/30-infrastructure.md +24 -0
- package/claude-md/30-project-mgmt.md +119 -0
- package/claude-md/40-product.md +39 -0
- package/claude-md/50-ops.md +34 -0
- package/claude-md/60-research.md +27 -0
- package/claude-md/90-footer.md +21 -0
- package/commands/spartan/brainstorm.md +134 -0
- package/commands/spartan/brownfield.md +157 -0
- package/commands/spartan/build.md +435 -0
- package/commands/spartan/careful.md +94 -0
- package/commands/spartan/commit-message.md +112 -0
- package/commands/spartan/content.md +17 -0
- package/commands/spartan/context-save.md +161 -0
- package/commands/spartan/contribute.md +140 -0
- package/commands/spartan/daily.md +42 -0
- package/commands/spartan/debug.md +308 -0
- package/commands/spartan/deep-dive.md +55 -0
- package/commands/spartan/deploy.md +207 -0
- package/commands/spartan/e2e.md +264 -0
- package/commands/spartan/env-setup.md +166 -0
- package/commands/spartan/epic.md +199 -0
- package/commands/spartan/fe-review.md +181 -0
- package/commands/spartan/figma-to-code.md +260 -0
- package/commands/spartan/forensics.md +46 -0
- package/commands/spartan/freeze.md +84 -0
- package/commands/spartan/fundraise.md +53 -0
- package/commands/spartan/gate-review.md +229 -0
- package/commands/spartan/gsd-upgrade.md +376 -0
- package/commands/spartan/guard.md +42 -0
- package/commands/spartan/init-project.md +178 -0
- package/commands/spartan/init-rules.md +298 -0
- package/commands/spartan/interview.md +154 -0
- package/commands/spartan/kickoff.md +73 -0
- package/commands/spartan/kotlin-service.md +109 -0
- package/commands/spartan/lean-canvas.md +222 -0
- package/commands/spartan/lint-rules.md +122 -0
- package/commands/spartan/map-codebase.md +124 -0
- package/commands/spartan/migration.md +82 -0
- package/commands/spartan/next-app.md +317 -0
- package/commands/spartan/next-feature.md +212 -0
- package/commands/spartan/onboard.md +326 -0
- package/commands/spartan/outreach.md +16 -0
- package/commands/spartan/phase.md +142 -0
- package/commands/spartan/pitch.md +18 -0
- package/commands/spartan/plan.md +210 -0
- package/commands/spartan/pr-ready.md +202 -0
- package/commands/spartan/project.md +106 -0
- package/commands/spartan/qa.md +222 -0
- package/commands/spartan/research.md +254 -0
- package/commands/spartan/review.md +132 -0
- package/commands/spartan/scan-rules.md +173 -0
- package/commands/spartan/sessions.md +143 -0
- package/commands/spartan/spec.md +131 -0
- package/commands/spartan/startup.md +257 -0
- package/commands/spartan/team.md +570 -0
- package/commands/spartan/teardown.md +161 -0
- package/commands/spartan/testcontainer.md +97 -0
- package/commands/spartan/tf-cost.md +123 -0
- package/commands/spartan/tf-deploy.md +116 -0
- package/commands/spartan/tf-drift.md +100 -0
- package/commands/spartan/tf-import.md +107 -0
- package/commands/spartan/tf-module.md +121 -0
- package/commands/spartan/tf-plan.md +100 -0
- package/commands/spartan/tf-review.md +106 -0
- package/commands/spartan/tf-scaffold.md +109 -0
- package/commands/spartan/tf-security.md +147 -0
- package/commands/spartan/think.md +221 -0
- package/commands/spartan/unfreeze.md +13 -0
- package/commands/spartan/update.md +134 -0
- package/commands/spartan/ux.md +1233 -0
- package/commands/spartan/validate.md +193 -0
- package/commands/spartan/web-to-prd.md +706 -0
- package/commands/spartan/workstreams.md +109 -0
- package/commands/spartan/write.md +16 -0
- package/commands/spartan.md +386 -0
- package/frameworks/00-framework-comparison-guide.md +317 -0
- package/frameworks/01-lean-canvas.md +196 -0
- package/frameworks/02-design-sprint.md +304 -0
- package/frameworks/03-foundation-sprint.md +337 -0
- package/frameworks/04-business-model-canvas.md +391 -0
- package/frameworks/05-customer-development.md +426 -0
- package/frameworks/06-jobs-to-be-done.md +358 -0
- package/frameworks/07-mom-test.md +392 -0
- package/frameworks/08-value-proposition-canvas.md +488 -0
- package/frameworks/09-javelin-board.md +428 -0
- package/frameworks/10-build-measure-learn.md +467 -0
- package/frameworks/11-mvp-approaches.md +533 -0
- package/frameworks/think-before-build.md +593 -0
- package/lib/assembler.js +197 -0
- package/lib/assembler.test.js +159 -0
- package/lib/detector.js +166 -0
- package/lib/detector.test.js +221 -0
- package/lib/packs.js +16 -0
- package/lib/resolver.js +272 -0
- package/lib/resolver.test.js +298 -0
- package/lib/worktree.sh +104 -0
- package/package.json +50 -0
- package/packs/backend-micronaut.yaml +35 -0
- package/packs/backend-nodejs.yaml +15 -0
- package/packs/backend-python.yaml +15 -0
- package/packs/core.yaml +37 -0
- package/packs/database.yaml +21 -0
- package/packs/frontend-react.yaml +24 -0
- package/packs/infrastructure.yaml +40 -0
- package/packs/ops.yaml +16 -0
- package/packs/packs.compiled.json +371 -0
- package/packs/product.yaml +22 -0
- package/packs/project-mgmt.yaml +24 -0
- package/packs/research.yaml +39 -0
- package/packs/shared-backend.yaml +14 -0
- package/packs/ux-design.yaml +21 -0
- package/rules/backend-micronaut/API_DESIGN.md +313 -0
- package/rules/backend-micronaut/BATCH_PROCESSING.md +92 -0
- package/rules/backend-micronaut/CONTROLLERS.md +388 -0
- package/rules/backend-micronaut/KOTLIN.md +414 -0
- package/rules/backend-micronaut/RETROFIT_PLACEMENT.md +290 -0
- package/rules/backend-micronaut/SERVICES_AND_BEANS.md +325 -0
- package/rules/core/NAMING_CONVENTIONS.md +208 -0
- package/rules/core/SKILL_AUTHORING.md +174 -0
- package/rules/core/TIMEZONE.md +316 -0
- package/rules/database/ORM_AND_REPO.md +289 -0
- package/rules/database/SCHEMA.md +146 -0
- package/rules/database/TRANSACTIONS.md +311 -0
- package/rules/frontend-react/FRONTEND.md +344 -0
- package/rules/infrastructure/MODULES.md +260 -0
- package/rules/infrastructure/NAMING.md +196 -0
- package/rules/infrastructure/PROVIDERS.md +309 -0
- package/rules/infrastructure/SECURITY.md +310 -0
- package/rules/infrastructure/STATE_AND_BACKEND.md +237 -0
- package/rules/infrastructure/STRUCTURE.md +234 -0
- package/rules/infrastructure/VARIABLES.md +285 -0
- package/rules/shared-backend/ARCHITECTURE.md +46 -0
- package/rules/ux-design/DESIGN_PROCESS.md +176 -0
- package/skills/api-endpoint-creator/SKILL.md +455 -0
- package/skills/api-endpoint-creator/error-handling-guide.md +244 -0
- package/skills/api-endpoint-creator/examples.md +522 -0
- package/skills/api-endpoint-creator/testing-patterns.md +302 -0
- package/skills/article-writing/SKILL.md +109 -0
- package/skills/article-writing/examples.md +59 -0
- package/skills/backend-api-design/SKILL.md +84 -0
- package/skills/backend-api-design/code-patterns.md +138 -0
- package/skills/brainstorm/SKILL.md +95 -0
- package/skills/browser-qa/SKILL.md +87 -0
- 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 +93 -0
- package/skills/competitive-teardown/example-analysis.md +50 -0
- package/skills/content-engine/SKILL.md +131 -0
- package/skills/content-engine/examples.md +72 -0
- package/skills/database-patterns/SKILL.md +72 -0
- package/skills/database-patterns/code-templates.md +114 -0
- package/skills/database-table-creator/SKILL.md +141 -0
- package/skills/database-table-creator/examples.md +552 -0
- package/skills/database-table-creator/kotlin-templates.md +400 -0
- package/skills/database-table-creator/migration-template.sql +68 -0
- package/skills/database-table-creator/validation-checklist.md +337 -0
- package/skills/deep-research/SKILL.md +80 -0
- package/skills/design-intelligence/SKILL.md +268 -0
- package/skills/design-workflow/SKILL.md +127 -0
- package/skills/design-workflow/checklists.md +45 -0
- package/skills/idea-validation/SKILL.md +129 -0
- package/skills/idea-validation/example-report.md +50 -0
- package/skills/investor-materials/SKILL.md +122 -0
- package/skills/investor-materials/example-outline.md +70 -0
- package/skills/investor-outreach/SKILL.md +112 -0
- package/skills/investor-outreach/examples.md +76 -0
- package/skills/kotlin-best-practices/SKILL.md +58 -0
- package/skills/kotlin-best-practices/code-patterns.md +132 -0
- package/skills/market-research/SKILL.md +99 -0
- package/skills/security-checklist/SKILL.md +65 -0
- 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 +152 -0
- package/skills/terraform-best-practices/SKILL.md +244 -0
- package/skills/terraform-module-creator/SKILL.md +284 -0
- package/skills/terraform-review/SKILL.md +222 -0
- package/skills/terraform-security-audit/SKILL.md +280 -0
- package/skills/terraform-service-scaffold/SKILL.md +574 -0
- package/skills/testing-strategies/SKILL.md +116 -0
- 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 +238 -0
- package/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/skills/ui-ux-pro-max/data/colors.csv +97 -0
- package/skills/ui-ux-pro-max/data/icons.csv +101 -0
- package/skills/ui-ux-pro-max/data/landing.csv +31 -0
- package/skills/ui-ux-pro-max/data/products.csv +97 -0
- package/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
- package/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/skills/ui-ux-pro-max/data/styles.csv +68 -0
- package/skills/ui-ux-pro-max/data/typography.csv +58 -0
- package/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/skills/ui-ux-pro-max/python-setup.md +146 -0
- package/skills/ui-ux-pro-max/scripts/core.py +253 -0
- package/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
- package/skills/ui-ux-pro-max/scripts/search.py +114 -0
- package/skills/web-to-prd/SKILL.md +478 -0
- package/templates/build-config.yaml +44 -0
- package/templates/commands-config.yaml +55 -0
- package/templates/competitor-analysis.md +60 -0
- package/templates/content/AGENT_TEMPLATE.md +47 -0
- package/templates/content/COMMAND_TEMPLATE.md +27 -0
- package/templates/content/RULE_TEMPLATE.md +40 -0
- package/templates/content/SKILL_TEMPLATE.md +41 -0
- package/templates/design-config.md +105 -0
- package/templates/design-doc.md +207 -0
- package/templates/epic.md +100 -0
- package/templates/feature-spec.md +181 -0
- package/templates/idea-canvas.md +47 -0
- package/templates/implementation-plan.md +159 -0
- package/templates/prd-template.md +86 -0
- package/templates/preamble.md +89 -0
- package/templates/project-readme.md +35 -0
- package/templates/quality-gates.md +230 -0
- package/templates/spartan-config.yaml +164 -0
- package/templates/user-interview.md +69 -0
- package/templates/validation-checklist.md +108 -0
- package/templates/workflow-backend-micronaut.md +409 -0
- package/templates/workflow-frontend-react.md +233 -0
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
# Controller Rules
|
|
2
|
+
|
|
3
|
+
> Full guide: use `/api-endpoint-creator` skill
|
|
4
|
+
|
|
5
|
+
## Required Annotations
|
|
6
|
+
|
|
7
|
+
Every controller class MUST have these annotations:
|
|
8
|
+
|
|
9
|
+
```kotlin
|
|
10
|
+
@ExecuteOn(TaskExecutors.IO) // REQUIRED: Enables coroutine suspension
|
|
11
|
+
@Validated // Input validation
|
|
12
|
+
@Controller("/api/v1/...") // Endpoint path
|
|
13
|
+
@Secured(...) // Security rule
|
|
14
|
+
class MyController(
|
|
15
|
+
private val myManager: MyManager // ONLY managers, never repositories
|
|
16
|
+
) {
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Why @ExecuteOn(TaskExecutors.IO) is Required
|
|
20
|
+
|
|
21
|
+
- Controllers use `suspend` functions for async operations
|
|
22
|
+
- Without this annotation, suspend functions may not run right
|
|
23
|
+
- It offloads blocking operations to the IO thread pool
|
|
24
|
+
- Stops blocking the main event loop
|
|
25
|
+
|
|
26
|
+
## Dependency Rules
|
|
27
|
+
|
|
28
|
+
| Allowed | Not Allowed |
|
|
29
|
+
|---------|-------------|
|
|
30
|
+
| Managers (`*Manager`) | Repositories (`*Repository`) |
|
|
31
|
+
| Detectors (`*Detector`) | Database context |
|
|
32
|
+
| - | Direct entity access |
|
|
33
|
+
| - | External API clients |
|
|
34
|
+
|
|
35
|
+
### Example: Correct Controller
|
|
36
|
+
|
|
37
|
+
```kotlin
|
|
38
|
+
@ExecuteOn(TaskExecutors.IO)
|
|
39
|
+
@Controller("/api/v1/admin")
|
|
40
|
+
@Secured(OAuthSecurityRule.ADMIN)
|
|
41
|
+
class ProjectHealthController(
|
|
42
|
+
private val projectHealthManager: ProjectHealthManager, // ✓ Manager
|
|
43
|
+
private val projectRiskDetector: ProjectRiskDetector // ✓ Detector/Manager
|
|
44
|
+
) {
|
|
45
|
+
@Get("/project/health")
|
|
46
|
+
suspend fun getProjectHealth(@QueryValue id: UUID): ProjectHealthResponse {
|
|
47
|
+
return projectHealthManager.computeProjectHealth(id, ...).throwOrValue()
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Example: Incorrect Controller (VIOLATION)
|
|
53
|
+
|
|
54
|
+
```kotlin
|
|
55
|
+
// ❌ WRONG - Controller directly uses repositories
|
|
56
|
+
@Controller("/api/v1/admin")
|
|
57
|
+
class ProjectHealthController(
|
|
58
|
+
private val projectHealthManager: ProjectHealthManager,
|
|
59
|
+
private val projectAlertRepository: ProjectAlertRepository, // ❌ NO!
|
|
60
|
+
private val projectRepository: ProjectRepository // ❌ NO!
|
|
61
|
+
) {
|
|
62
|
+
@Get("/project/alerts")
|
|
63
|
+
suspend fun getProjectAlerts(@QueryValue id: UUID): List<ProjectAlertSummary> {
|
|
64
|
+
val alerts = projectAlertRepository.byProjectId(id) // ❌ Direct repo access
|
|
65
|
+
return alerts.map { ProjectAlertSummary.from(it) }
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Thin Controller Pattern
|
|
71
|
+
|
|
72
|
+
Controllers should ONLY:
|
|
73
|
+
|
|
74
|
+
1. **Parse and validate HTTP input** (query params, body, headers)
|
|
75
|
+
2. **Delegate to managers** for business logic
|
|
76
|
+
3. **Transform manager results** to HTTP responses
|
|
77
|
+
4. **Handle authentication context** (get current user, etc.)
|
|
78
|
+
|
|
79
|
+
Controllers should NEVER:
|
|
80
|
+
|
|
81
|
+
1. Access repositories directly
|
|
82
|
+
2. Contain business logic
|
|
83
|
+
3. Make database queries
|
|
84
|
+
4. Call external APIs
|
|
85
|
+
5. Manage transactions
|
|
86
|
+
6. **Define inline data classes** (see below)
|
|
87
|
+
|
|
88
|
+
```kotlin
|
|
89
|
+
@Get("/project/health")
|
|
90
|
+
suspend fun getProjectHealth(
|
|
91
|
+
@QueryValue id: UUID,
|
|
92
|
+
@QueryValue date: String?,
|
|
93
|
+
@QueryValue window: Int?
|
|
94
|
+
): ProjectHealthResponse {
|
|
95
|
+
// 1. Parse input
|
|
96
|
+
val targetDate = date?.let { LocalDate.parse(it) } ?: LocalDate.now()
|
|
97
|
+
val windowDays = window ?: 7
|
|
98
|
+
|
|
99
|
+
// 2. Delegate to manager and return
|
|
100
|
+
return projectHealthManager.computeProjectHealth(id, targetDate, windowDays).throwOrValue()
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Error Handling Pattern
|
|
105
|
+
|
|
106
|
+
```kotlin
|
|
107
|
+
@Post("/project/alert/acknowledge")
|
|
108
|
+
suspend fun acknowledgeAlert(
|
|
109
|
+
@QueryValue id: UUID,
|
|
110
|
+
@QueryValue acknowledgedBy: UUID
|
|
111
|
+
): ProjectAlertSummary {
|
|
112
|
+
// Manager returns Either<ClientException, T>
|
|
113
|
+
// .throwOrValue() unwraps or throws the exception
|
|
114
|
+
return projectHealthManager.acknowledgeAlert(id, acknowledgedBy).throwOrValue()
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## No Inline Data Classes
|
|
119
|
+
|
|
120
|
+
**NEVER define `data class` declarations inside or at the bottom of controller files.**
|
|
121
|
+
|
|
122
|
+
All request/response models MUST live in `module-client`:
|
|
123
|
+
- Requests: `module-client/src/main/kotlin/com/yourcompany/client/request/`
|
|
124
|
+
- Responses: `module-client/src/main/kotlin/com/yourcompany/client/response/`
|
|
125
|
+
|
|
126
|
+
### Bad - Inline Data Classes (VIOLATION)
|
|
127
|
+
|
|
128
|
+
```kotlin
|
|
129
|
+
// ❌ WRONG - data classes at bottom of controller file
|
|
130
|
+
@Controller("/api/v1/admin/github")
|
|
131
|
+
class GitHubController(private val manager: GitHubManager) {
|
|
132
|
+
|
|
133
|
+
@Post("/project-sources/orgs/set")
|
|
134
|
+
suspend fun setProjectOrgs(
|
|
135
|
+
@QueryValue projectId: UUID,
|
|
136
|
+
@Body request: SetProjectOrgsRequest
|
|
137
|
+
): List<ProjectOrgAssignment> {
|
|
138
|
+
return manager.setProjectOrgs(projectId, request.organizations).throwOrValue()
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ❌ NEVER DO THIS - data classes defined in controller file
|
|
143
|
+
data class SetProjectOrgsRequest(
|
|
144
|
+
val organizations: List<OrgAssignmentInput>
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
data class OrgAssignmentInput(
|
|
148
|
+
val orgLogin: String,
|
|
149
|
+
val includeAllRepos: Boolean = true
|
|
150
|
+
)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Good - Separate Request File in module-client
|
|
154
|
+
|
|
155
|
+
```kotlin
|
|
156
|
+
// ✓ CORRECT - In module-client/request/GitHubProjectSourcesRequest.kt
|
|
157
|
+
package com.yourcompany.client.request
|
|
158
|
+
|
|
159
|
+
import io.micronaut.serde.annotation.Serdeable
|
|
160
|
+
import java.util.UUID
|
|
161
|
+
|
|
162
|
+
@Serdeable
|
|
163
|
+
data class SetProjectOrgsRequest(
|
|
164
|
+
val organizations: List<OrgAssignmentInput>
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
@Serdeable
|
|
168
|
+
data class OrgAssignmentInput(
|
|
169
|
+
val orgLogin: String,
|
|
170
|
+
val includeAllRepos: Boolean = true
|
|
171
|
+
)
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
```kotlin
|
|
175
|
+
// ✓ CORRECT - Controller imports from module-client
|
|
176
|
+
import com.yourcompany.client.request.SetProjectOrgsRequest
|
|
177
|
+
import com.yourcompany.client.request.OrgAssignmentInput
|
|
178
|
+
|
|
179
|
+
@Controller("/api/v1/admin/github")
|
|
180
|
+
class GitHubController(private val manager: GitHubManager) {
|
|
181
|
+
// Uses imported request classes
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Why This Rule Exists
|
|
186
|
+
|
|
187
|
+
1. **Single source of truth** - Models defined once, used everywhere
|
|
188
|
+
2. **Client generation** - External clients can import from module-client
|
|
189
|
+
3. **Consistency** - All teams know where to find request/response models
|
|
190
|
+
4. **Maintainability** - Changes to models are tracked in one place
|
|
191
|
+
5. **Testing** - Retrofit clients in tests use the same models
|
|
192
|
+
|
|
193
|
+
### How to Fix Violations
|
|
194
|
+
|
|
195
|
+
1. Create the right file in `module-client/request/` or `module-client/response/`
|
|
196
|
+
2. Move data class definitions there with `@Serdeable` annotation
|
|
197
|
+
3. Add proper imports in controller
|
|
198
|
+
4. Delete inline definitions from controller file
|
|
199
|
+
|
|
200
|
+
## No Private Converter Functions
|
|
201
|
+
|
|
202
|
+
**NEVER define private extension functions like `.toResponse()` in controller files.**
|
|
203
|
+
|
|
204
|
+
### Bad - Private Converter Functions (VIOLATION)
|
|
205
|
+
|
|
206
|
+
```kotlin
|
|
207
|
+
// ❌ WRONG - private converter functions in controller
|
|
208
|
+
class DataSyncController(private val manager: DataSyncManager) {
|
|
209
|
+
|
|
210
|
+
@Post("/sync")
|
|
211
|
+
suspend fun sync(): List<SyncResultResponse> {
|
|
212
|
+
return manager.sync().throwOrValue().map { it.toResponse() }
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ❌ NEVER DO THIS
|
|
216
|
+
private fun SyncResult.toResponse() = SyncResultResponse(
|
|
217
|
+
success = success,
|
|
218
|
+
resourceType = resourceType,
|
|
219
|
+
// ...
|
|
220
|
+
)
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Preferred: Manager Returns Response DTOs
|
|
225
|
+
|
|
226
|
+
The best pattern is for managers to return Response DTOs directly:
|
|
227
|
+
|
|
228
|
+
```kotlin
|
|
229
|
+
// ✓ BEST - Manager returns Response DTOs
|
|
230
|
+
class DataSyncController(private val manager: DataSyncManager) {
|
|
231
|
+
|
|
232
|
+
@Post("/sync")
|
|
233
|
+
suspend fun sync(): List<SyncResultResponse> {
|
|
234
|
+
return manager.sync().throwOrValue() // Manager already returns Response type
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Acceptable: Inline Mapping in Controller
|
|
240
|
+
|
|
241
|
+
When the manager returns domain models, use inline mapping:
|
|
242
|
+
|
|
243
|
+
```kotlin
|
|
244
|
+
// ✓ ACCEPTABLE - Inline mapping (no private functions)
|
|
245
|
+
class DataSyncController(private val manager: DataSyncManager) {
|
|
246
|
+
|
|
247
|
+
@Post("/sync")
|
|
248
|
+
suspend fun sync(): List<SyncResultResponse> {
|
|
249
|
+
return manager.sync()
|
|
250
|
+
.throwOrValue()
|
|
251
|
+
.map {
|
|
252
|
+
SyncResultResponse(
|
|
253
|
+
success = it.success,
|
|
254
|
+
resourceType = it.resourceType,
|
|
255
|
+
itemsSynced = it.itemsSynced,
|
|
256
|
+
errors = it.errors
|
|
257
|
+
)
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### When Companion Objects Work
|
|
264
|
+
|
|
265
|
+
If `module-client` can depend on the model's module without circular dependency:
|
|
266
|
+
|
|
267
|
+
```kotlin
|
|
268
|
+
// In module-client/response/...
|
|
269
|
+
data class UserResponse(...) {
|
|
270
|
+
companion object {
|
|
271
|
+
fun from(entity: UserEntity) = UserResponse(...)
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Then use: `users.map { UserResponse.from(it) }`
|
|
277
|
+
|
|
278
|
+
When circular dependencies stop companion objects from working, use inline mapping or have the manager return Response DTOs directly.
|
|
279
|
+
|
|
280
|
+
## How to Fix Controller Violations
|
|
281
|
+
|
|
282
|
+
When a controller directly uses repositories, follow these steps:
|
|
283
|
+
|
|
284
|
+
### 1. Identify the Operations
|
|
285
|
+
|
|
286
|
+
Look at what repository methods the controller is calling:
|
|
287
|
+
- `repository.byId(id)`
|
|
288
|
+
- `repository.byStatus(status)`
|
|
289
|
+
- `repository.update(...)`
|
|
290
|
+
|
|
291
|
+
### 2. Add Methods to the Manager Interface
|
|
292
|
+
|
|
293
|
+
```kotlin
|
|
294
|
+
// In the Manager interface (e.g., ProjectHealthManager.kt)
|
|
295
|
+
interface ProjectHealthManager {
|
|
296
|
+
// ... existing methods ...
|
|
297
|
+
|
|
298
|
+
// Add new methods for operations that were in the controller
|
|
299
|
+
suspend fun getProjectAlerts(projectId: UUID): Either<ClientException, List<ProjectAlertSummary>>
|
|
300
|
+
suspend fun listAlerts(status: String?): Either<ClientException, List<ProjectAlertSummary>>
|
|
301
|
+
suspend fun acknowledgeAlert(alertId: UUID, acknowledgedBy: UUID): Either<ClientException, ProjectAlertSummary>
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### 3. Implement in the Manager
|
|
306
|
+
|
|
307
|
+
```kotlin
|
|
308
|
+
// In DefaultProjectHealthManager.kt
|
|
309
|
+
class DefaultProjectHealthManager(
|
|
310
|
+
// ... existing dependencies ...
|
|
311
|
+
private val projectAlertRepository: ProjectAlertRepository // Move repo here
|
|
312
|
+
) : ProjectHealthManager {
|
|
313
|
+
|
|
314
|
+
override suspend fun getProjectAlerts(projectId: UUID): Either<ClientException, List<ProjectAlertSummary>> {
|
|
315
|
+
val alerts = projectAlertRepository.byProjectId(projectId)
|
|
316
|
+
return alerts.map { ProjectAlertSummary.from(it) }.right()
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### 4. Update the Controller
|
|
322
|
+
|
|
323
|
+
```kotlin
|
|
324
|
+
// Remove repository dependency, use manager instead
|
|
325
|
+
class ProjectHealthController(
|
|
326
|
+
private val projectHealthManager: ProjectHealthManager
|
|
327
|
+
// Repository dependency REMOVED
|
|
328
|
+
) {
|
|
329
|
+
@Get("/project/alerts")
|
|
330
|
+
suspend fun getProjectAlerts(@QueryValue id: UUID): List<ProjectAlertSummary> {
|
|
331
|
+
return projectHealthManager.getProjectAlerts(id).throwOrValue()
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### 5. Update the Factory
|
|
337
|
+
|
|
338
|
+
```kotlin
|
|
339
|
+
// In EvaluationManagerFactory.kt
|
|
340
|
+
@Singleton
|
|
341
|
+
fun provideProjectHealthManager(
|
|
342
|
+
// ... existing params ...
|
|
343
|
+
projectAlertRepository: ProjectAlertRepository // Add new dependency
|
|
344
|
+
): ProjectHealthManager {
|
|
345
|
+
return DefaultProjectHealthManager(
|
|
346
|
+
// ... existing args ...
|
|
347
|
+
projectAlertRepository = projectAlertRepository
|
|
348
|
+
)
|
|
349
|
+
}
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
## Either Extension for Controllers
|
|
353
|
+
|
|
354
|
+
```kotlin
|
|
355
|
+
// Extension to convert Either to HTTP response
|
|
356
|
+
fun <T> Either<ClientException, T>.getOrThrow(): T =
|
|
357
|
+
fold({ throw it }, { it })
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
---
|
|
361
|
+
|
|
362
|
+
## Controller Testing
|
|
363
|
+
|
|
364
|
+
> Full testing guide: use `/testing-strategies` skill
|
|
365
|
+
|
|
366
|
+
**Key rules:**
|
|
367
|
+
- Extend `AbstractControllerTest`, never standalone tests
|
|
368
|
+
- Use Retrofit clients from `module-client` — never raw `HttpRequest`
|
|
369
|
+
- Generate real JWT tokens via `accessToken()`
|
|
370
|
+
- Test full HTTP stack end-to-end, no mocking managers
|
|
371
|
+
- Every endpoint needs: happy path, auth (401), ownership (403/404), validation (400), not found (404)
|
|
372
|
+
- Clean up test data in `@BeforeAll` / `@AfterAll`
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
## Enforcement Checklist
|
|
377
|
+
|
|
378
|
+
Before committing controller changes:
|
|
379
|
+
|
|
380
|
+
- [ ] Controller class has `@ExecuteOn(TaskExecutors.IO)` annotation
|
|
381
|
+
- [ ] Controller only injects Managers/Detectors (no Repositories)
|
|
382
|
+
- [ ] All database operations are delegated to managers
|
|
383
|
+
- [ ] Controller methods are thin (just input parsing and delegation)
|
|
384
|
+
- [ ] Business logic is in managers, not controllers
|
|
385
|
+
- [ ] **NO inline data classes** - all models in `module-client`
|
|
386
|
+
- [ ] **NO private converter functions** - use inline mapping or manager returns Response DTOs
|
|
387
|
+
- [ ] **NO @Put, @Patch, @Delete** — only @Get and @Post (RPC-style, see API_DESIGN.md)
|
|
388
|
+
- [ ] **NO @PathVariable** — only @QueryValue for all IDs
|