@mrciphersmith/keryx 0.3.3 → 0.3.6
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/dist/cli.js +996 -366
- package/docs/README.md +2 -0
- package/package.json +1 -1
- package/src/gdskills/bundled/install-manifest.json +520 -48
- package/src/gdskills/bundled/stacks/csharp-dotnet/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/governance/eval.json +1881 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/governance/scout.json +33 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/pack.json +38 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/rules/coding-style.mdc +100 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/rules/patterns.mdc +107 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/rules/security.mdc +86 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/rules/testing.mdc +89 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-build-fix/SKILL.md +143 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-build-fix/evals.json +77 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-code-review/SKILL.md +121 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-code-review/evals.json +77 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-implementation/SKILL.md +134 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-implementation/evals.json +76 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-testing/SKILL.md +130 -0
- package/src/gdskills/bundled/stacks/csharp-dotnet/skills/dotnet-testing/evals.json +77 -0
- package/src/gdskills/bundled/stacks/django/agent-refs.json +3 -0
- package/src/gdskills/bundled/stacks/django/governance/eval.json +1763 -0
- package/src/gdskills/bundled/stacks/django/governance/scout.json +40 -0
- package/src/gdskills/bundled/stacks/django/pack.json +43 -0
- package/src/gdskills/bundled/stacks/django/rules/coding-style.mdc +80 -0
- package/src/gdskills/bundled/stacks/django/rules/patterns.mdc +92 -0
- package/src/gdskills/bundled/stacks/django/rules/security.mdc +92 -0
- package/src/gdskills/bundled/stacks/django/rules/testing.mdc +89 -0
- package/src/gdskills/bundled/stacks/django/skills/django-build-fix/SKILL.md +149 -0
- package/src/gdskills/bundled/stacks/django/skills/django-build-fix/evals.json +49 -0
- package/src/gdskills/bundled/stacks/django/skills/django-code-review/SKILL.md +137 -0
- package/src/gdskills/bundled/stacks/django/skills/django-code-review/evals.json +48 -0
- package/src/gdskills/bundled/stacks/django/skills/django-implementation/SKILL.md +147 -0
- package/src/gdskills/bundled/stacks/django/skills/django-implementation/evals.json +75 -0
- package/src/gdskills/bundled/stacks/django/skills/django-migrate/SKILL.md +166 -0
- package/src/gdskills/bundled/stacks/django/skills/django-migrate/evals.json +49 -0
- package/src/gdskills/bundled/stacks/django/skills/django-testing/SKILL.md +130 -0
- package/src/gdskills/bundled/stacks/django/skills/django-testing/evals.json +48 -0
- package/src/gdskills/bundled/stacks/fastapi/agent-refs.json +3 -0
- package/src/gdskills/bundled/stacks/fastapi/governance/eval.json +1777 -0
- package/src/gdskills/bundled/stacks/fastapi/governance/scout.json +34 -0
- package/src/gdskills/bundled/stacks/fastapi/pack.json +43 -0
- package/src/gdskills/bundled/stacks/fastapi/rules/coding-style.mdc +68 -0
- package/src/gdskills/bundled/stacks/fastapi/rules/patterns.mdc +108 -0
- package/src/gdskills/bundled/stacks/fastapi/rules/security.mdc +99 -0
- package/src/gdskills/bundled/stacks/fastapi/rules/testing.mdc +85 -0
- package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-build-fix/SKILL.md +157 -0
- package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-build-fix/evals.json +76 -0
- package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-code-review/SKILL.md +150 -0
- package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-code-review/evals.json +74 -0
- package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-implementation/SKILL.md +158 -0
- package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-implementation/evals.json +75 -0
- package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-testing/SKILL.md +146 -0
- package/src/gdskills/bundled/stacks/fastapi/skills/fastapi-testing/evals.json +74 -0
- package/src/gdskills/bundled/stacks/flutter-dart/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/flutter-dart/governance/eval.json +1849 -0
- package/src/gdskills/bundled/stacks/flutter-dart/governance/scout.json +33 -0
- package/src/gdskills/bundled/stacks/flutter-dart/pack.json +41 -0
- package/src/gdskills/bundled/stacks/flutter-dart/rules/coding-style.mdc +98 -0
- package/src/gdskills/bundled/stacks/flutter-dart/rules/patterns.mdc +88 -0
- package/src/gdskills/bundled/stacks/flutter-dart/rules/security.mdc +91 -0
- package/src/gdskills/bundled/stacks/flutter-dart/rules/testing.mdc +101 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-build-fix/SKILL.md +134 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-build-fix/evals.json +79 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-code-review/SKILL.md +124 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-code-review/evals.json +74 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-implementation/SKILL.md +139 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-implementation/evals.json +77 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-testing/SKILL.md +134 -0
- package/src/gdskills/bundled/stacks/flutter-dart/skills/flutter-testing/evals.json +74 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/agent-refs.json +3 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/governance/eval.json +2194 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/governance/scout.json +39 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/pack.json +40 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/rules/coding-style.mdc +67 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/rules/patterns.mdc +65 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/rules/security.mdc +69 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/rules/testing.mdc +80 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-build-fix/SKILL.md +144 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-build-fix/evals.json +74 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-code-review/SKILL.md +129 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-code-review/evals.json +74 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-implementation/SKILL.md +147 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-implementation/evals.json +75 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-migrate/SKILL.md +139 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-migrate/evals.json +74 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-testing/SKILL.md +128 -0
- package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-testing/evals.json +73 -0
- package/src/gdskills/bundled/stacks/kotlin-android/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/kotlin-android/governance/eval.json +1889 -0
- package/src/gdskills/bundled/stacks/kotlin-android/governance/scout.json +34 -0
- package/src/gdskills/bundled/stacks/kotlin-android/pack.json +38 -0
- package/src/gdskills/bundled/stacks/kotlin-android/rules/coding-style.mdc +89 -0
- package/src/gdskills/bundled/stacks/kotlin-android/rules/patterns.mdc +96 -0
- package/src/gdskills/bundled/stacks/kotlin-android/rules/security.mdc +90 -0
- package/src/gdskills/bundled/stacks/kotlin-android/rules/testing.mdc +89 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/compose-implementation/SKILL.md +150 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/compose-implementation/evals.json +77 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-build-fix/SKILL.md +151 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-build-fix/evals.json +76 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-code-review/SKILL.md +139 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-code-review/evals.json +78 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-testing/SKILL.md +131 -0
- package/src/gdskills/bundled/stacks/kotlin-android/skills/kotlin-android-testing/evals.json +77 -0
- package/src/gdskills/bundled/stacks/python/agent-refs.json +2 -1
- package/src/gdskills/bundled/stacks/python/pack.json +1 -1
- package/src/gdskills/bundled/stacks/rust/agent-refs.json +3 -0
- package/src/gdskills/bundled/stacks/rust/governance/eval.json +1823 -0
- package/src/gdskills/bundled/stacks/rust/governance/scout.json +32 -0
- package/src/gdskills/bundled/stacks/rust/pack.json +42 -0
- package/src/gdskills/bundled/stacks/rust/rules/coding-style.mdc +93 -0
- package/src/gdskills/bundled/stacks/rust/rules/patterns.mdc +85 -0
- package/src/gdskills/bundled/stacks/rust/rules/security.mdc +85 -0
- package/src/gdskills/bundled/stacks/rust/rules/testing.mdc +82 -0
- package/src/gdskills/bundled/stacks/rust/skills/rust-build-fix/SKILL.md +141 -0
- package/src/gdskills/bundled/stacks/rust/skills/rust-build-fix/evals.json +78 -0
- package/src/gdskills/bundled/stacks/rust/skills/rust-code-review/SKILL.md +127 -0
- package/src/gdskills/bundled/stacks/rust/skills/rust-code-review/evals.json +72 -0
- package/src/gdskills/bundled/stacks/rust/skills/rust-implementation/SKILL.md +133 -0
- package/src/gdskills/bundled/stacks/rust/skills/rust-implementation/evals.json +79 -0
- package/src/gdskills/bundled/stacks/rust/skills/rust-testing/SKILL.md +130 -0
- package/src/gdskills/bundled/stacks/rust/skills/rust-testing/evals.json +75 -0
- package/src/gdskills/bundled/stacks/swift-ios/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/swift-ios/governance/eval.json +1803 -0
- package/src/gdskills/bundled/stacks/swift-ios/governance/scout.json +32 -0
- package/src/gdskills/bundled/stacks/swift-ios/pack.json +38 -0
- package/src/gdskills/bundled/stacks/swift-ios/rules/coding-style.mdc +92 -0
- package/src/gdskills/bundled/stacks/swift-ios/rules/patterns.mdc +112 -0
- package/src/gdskills/bundled/stacks/swift-ios/rules/security.mdc +78 -0
- package/src/gdskills/bundled/stacks/swift-ios/rules/testing.mdc +90 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swift-build-fix/SKILL.md +144 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swift-build-fix/evals.json +75 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swift-code-review/SKILL.md +122 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swift-code-review/evals.json +75 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swift-testing/SKILL.md +131 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swift-testing/evals.json +75 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swiftui-implementation/SKILL.md +149 -0
- package/src/gdskills/bundled/stacks/swift-ios/skills/swiftui-implementation/evals.json +76 -0
- package/src/gdskills/bundled/agents/python-build-fixer.md +0 -52
- package/src/gdskills/bundled/agents/python-code-auditor.md +0 -49
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: java-kotlin-spring-code-review
|
|
3
|
+
description: "Use when reviewing a Java or Kotlin Spring Boot change for framework-specific risks -- field injection, @Transactional self-invocation, entities leaked across the API boundary, N+1 query patterns, missing Bean Validation, and overly permissive Spring Security configuration. Read-only, no edits."
|
|
4
|
+
triggers:
|
|
5
|
+
- "review this Spring Boot diff for field injection"
|
|
6
|
+
- "audit this Spring service for a @Transactional self-invocation pitfall"
|
|
7
|
+
- "review this Spring controller for entity leakage"
|
|
8
|
+
- "audit this Spring Data JPA repository for N+1 queries"
|
|
9
|
+
- "review this Kotlin Spring change for missing validation"
|
|
10
|
+
- "review this Spring Security configuration"
|
|
11
|
+
metadata:
|
|
12
|
+
origin: authored
|
|
13
|
+
category: review
|
|
14
|
+
version: "1.0.0"
|
|
15
|
+
compatible_harnesses: "claude,codex,cursor,zed,opencode"
|
|
16
|
+
license: "MIT"
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# Java/Kotlin + Spring code review
|
|
20
|
+
|
|
21
|
+
Read-only review of a Java or Kotlin Spring Boot change for
|
|
22
|
+
framework-specific risks: field injection, `@Transactional`
|
|
23
|
+
self-invocation, entity leakage across the API boundary, N+1 query
|
|
24
|
+
patterns, missing Bean Validation, and permissive Spring Security
|
|
25
|
+
configuration. This skill never edits code — it reports findings.
|
|
26
|
+
`rules/coding-style.mdc`, `rules/patterns.mdc`, and `rules/security.mdc`
|
|
27
|
+
are the rule set findings are checked against.
|
|
28
|
+
|
|
29
|
+
## Workflow
|
|
30
|
+
|
|
31
|
+
### Step 1: Scope the review
|
|
32
|
+
|
|
33
|
+
1. Identify the changed files (`git diff` against the review base) —
|
|
34
|
+
review only `*.java`/`*.kt`/`*.kts` files in the diff, not the whole
|
|
35
|
+
repository.
|
|
36
|
+
2. Read enough of the surrounding, unchanged code to know whether a
|
|
37
|
+
flagged pattern is new in this diff or pre-existing; note pre-existing
|
|
38
|
+
issues separately from ones the diff introduces.
|
|
39
|
+
|
|
40
|
+
### Step 2: Check each changed class against the focus list
|
|
41
|
+
|
|
42
|
+
**Dependency injection**
|
|
43
|
+
- A `@Component`/`@Service`/`@Repository`/`@Controller` with a field-level
|
|
44
|
+
`@Autowired` — flag it, direction: constructor injection with a
|
|
45
|
+
`private final` field (Java) or constructor `val` (Kotlin).
|
|
46
|
+
|
|
47
|
+
**Transaction boundaries**
|
|
48
|
+
- A call from one method to another `@Transactional` method on `this`
|
|
49
|
+
within the same class (e.g. `this.chargePayment()`) — flag as the
|
|
50
|
+
self-invocation proxy pitfall; the inner annotation is silently
|
|
51
|
+
ignored. Fix direction: extract into a separate, constructor-injected
|
|
52
|
+
bean.
|
|
53
|
+
- A `@Transactional` method performing unrelated blocking I/O (an
|
|
54
|
+
outbound HTTP call, file access) that holds the transaction open longer
|
|
55
|
+
than necessary — flag as worth confirming.
|
|
56
|
+
|
|
57
|
+
**API boundary**
|
|
58
|
+
- A controller or service method returning a JPA `@Entity` directly
|
|
59
|
+
instead of a DTO — flag as leaking persistence internals and a
|
|
60
|
+
`LazyInitializationException` risk once serialized outside the
|
|
61
|
+
transaction.
|
|
62
|
+
|
|
63
|
+
**Spring Data JPA queries**
|
|
64
|
+
- A lazy association accessed inside a loop with no `@EntityGraph`/
|
|
65
|
+
`JOIN FETCH`/projection — flag as an N+1 risk.
|
|
66
|
+
- Query text built by string concatenation/`String.format` with any
|
|
67
|
+
user-influenced value instead of a parameterized `@Query` or derived
|
|
68
|
+
method — flag as an injection risk.
|
|
69
|
+
|
|
70
|
+
**Validation**
|
|
71
|
+
- A request DTO field with no `jakarta.validation` constraint where the
|
|
72
|
+
field is clearly meant to be required/bounded, or a controller
|
|
73
|
+
parameter missing `@Valid`/`@Validated` — flag as a validation gap.
|
|
74
|
+
|
|
75
|
+
**Spring Security**
|
|
76
|
+
- Extending `WebSecurityConfigurerAdapter` — flag as targeting a removed
|
|
77
|
+
Spring Security 5-era API; fix direction: a `SecurityFilterChain` bean.
|
|
78
|
+
- An authorization rule wider than the endpoint needs (a broad
|
|
79
|
+
`permitAll()` covering more than intended), or CSRF disabled on a
|
|
80
|
+
session-cookie-authenticated endpoint — flag with the specific rule/line.
|
|
81
|
+
|
|
82
|
+
### Step 3: Report
|
|
83
|
+
|
|
84
|
+
For each finding: file:line, the pattern, why it matters (correctness,
|
|
85
|
+
leak, security), and the fix direction — but do not apply it.
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
src/main/java/billing/InvoiceService.java:52 — generateInvoice() calls
|
|
89
|
+
this.applyLateFee() (also @Transactional) from inside another
|
|
90
|
+
@Transactional method. Risk: proxy-based AOP means the inner
|
|
91
|
+
@Transactional is silently ignored on a same-class call -- no separate
|
|
92
|
+
transaction/rollback boundary for applyLateFee(). Fix direction:
|
|
93
|
+
extract applyLateFee() into its own bean and call it through an
|
|
94
|
+
injected reference.
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Rules
|
|
98
|
+
|
|
99
|
+
- NEVER edit code — findings and fix direction only.
|
|
100
|
+
- Flag field injection, `@Transactional` self-invocation, entity leakage,
|
|
101
|
+
N+1 query patterns, missing Bean Validation, and permissive/outdated
|
|
102
|
+
Spring Security configuration; do not report generic style nits already
|
|
103
|
+
covered by the project's formatter (those are noise here).
|
|
104
|
+
- Distinguish a finding the diff introduces from a pre-existing one in
|
|
105
|
+
code the diff merely touches.
|
|
106
|
+
- When a suspected N+1 is not certain from reading alone, say "confirm
|
|
107
|
+
with a query log or a repository test asserting query count" rather
|
|
108
|
+
than asserting it without evidence.
|
|
109
|
+
|
|
110
|
+
## Red Flags
|
|
111
|
+
|
|
112
|
+
| Rationalization | Why it is wrong |
|
|
113
|
+
|---|---|
|
|
114
|
+
| "This is a small self-invocation, it probably still runs in the outer transaction anyway" | It does run inside whatever transaction the outer method already opened, but the inner method's own @Transactional settings (propagation, rollback rules) are silently skipped -- that's still a real finding to report |
|
|
115
|
+
| "It's just a config bean, field @Autowired here is harmless" | Field injection hides the dependency graph and blocks final/val regardless of the bean's role; report it the same way |
|
|
116
|
+
| "I'll just fix the missing @Valid myself since it's a one-line change" | This skill is read-only; report the finding and its fix direction, do not edit the file |
|
|
117
|
+
| "The entity being returned here is only used internally, it's fine to skip a DTO" | "Internal for now" drifts; the API boundary rule catches this before it becomes an external leak or a LazyInitializationException surprise |
|
|
118
|
+
|
|
119
|
+
## Verification
|
|
120
|
+
|
|
121
|
+
Do not report the review done until all of the following hold:
|
|
122
|
+
|
|
123
|
+
- Every changed `*.java`/`*.kt`/`*.kts` file in the diff was read, not
|
|
124
|
+
just files named in the PR description.
|
|
125
|
+
- Every finding names a concrete file:line, the specific risk category
|
|
126
|
+
from Step 2, and a fix direction.
|
|
127
|
+
- No source file was modified by this review.
|
|
128
|
+
- Findings distinguish diff-introduced issues from pre-existing ones in
|
|
129
|
+
touched files.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"triggers": {
|
|
3
|
+
"positive": [
|
|
4
|
+
"Our PR uses @Autowired directly on fields in three new classes -- is that actually a problem, or just a style nit?",
|
|
5
|
+
"I have a public method calling another @Transactional method on the same class instance -- will the proxy even pick that up, or is this a bug waiting to happen?",
|
|
6
|
+
"This controller method returns the JPA entity straight from findById -- is that going to bite us on the API contract, and how do I spot other cases like it?",
|
|
7
|
+
"Our order list endpoint is issuing one query per row instead of one for the whole list -- where in this repository is that coming from?",
|
|
8
|
+
"Does this incoming request DTO in Kotlin actually get validated before it reaches the service layer, or did we skip the annotations somewhere?",
|
|
9
|
+
"I inherited this SecurityFilterChain setup and I'm worried some endpoints are wide open that shouldn't be -- can you take a look?"
|
|
10
|
+
],
|
|
11
|
+
"negative": [
|
|
12
|
+
"Review this Spring Boot diff and also fix the bugs you find",
|
|
13
|
+
"Review this Python Django code for SQL injection",
|
|
14
|
+
"Review this NestJS controller for missing guards",
|
|
15
|
+
"Implement a bounded worker pool in this Spring service",
|
|
16
|
+
"Review this Go diff for goroutine leaks",
|
|
17
|
+
"Review this Spring diff for naming conventions and formatting only"
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
"scenarios": [
|
|
21
|
+
{
|
|
22
|
+
"id": "read-only-self-invocation-review",
|
|
23
|
+
"prompt": "Review this Spring diff: OrderService.placeOrder() (marked @Transactional) calls this.chargePayment(), which is also @Transactional, on the same class. What do you find?",
|
|
24
|
+
"strictness": "high",
|
|
25
|
+
"expected_behavior": [
|
|
26
|
+
{
|
|
27
|
+
"grader": "judge",
|
|
28
|
+
"rubric": "A correct answer identifies the self-invocation call this.chargePayment() as the problem -- Spring's proxy-based @Transactional means the inner method's own annotation is silently ignored on a same-class call -- names the fix direction (extract into a separate, constructor-injected bean), and reports this purely as a finding, never applying or claiming to apply a code change, since this skill is read-only.",
|
|
29
|
+
"pass_criteria": [
|
|
30
|
+
"Identifies the this.chargePayment() self-invocation as the problem, naming it specifically from this diff.",
|
|
31
|
+
"Explains the proxy-based mechanism: the call bypasses the transactional proxy, so chargePayment()'s own @Transactional is silently ignored for that call.",
|
|
32
|
+
"Names the concrete fix direction: extract chargePayment() into a separate, constructor-injected bean so the call goes through the proxy, presented as a finding with no code change applied or described as applied."
|
|
33
|
+
],
|
|
34
|
+
"fail_criteria": [
|
|
35
|
+
"Actually edits, patches, or claims to have already made even a partial or proof-of-concept code change (this skill is strictly read-only: findings and fix direction only, no edits of any size)."
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
"calibration": {
|
|
40
|
+
"known_right": "Finding: placeOrder() calls this.chargePayment() -- also @Transactional -- on the same class. That's the self-invocation proxy pitfall: Spring's @Transactional is implemented via a proxy wrapped around the bean, and a call that goes through this instead of through the proxy bypasses it entirely, so chargePayment()'s own @Transactional annotation is silently ignored for this call -- it runs as a plain method call inside whatever transaction placeOrder() already has open, with none of its own propagation or rollback semantics applied. Fix direction: extract chargePayment() into its own Spring bean, constructor-inject that bean into OrderService, and call it through the injected reference instead of this -- that routes the call through the proxy so the annotation actually takes effect. I'm reporting this as a finding rather than making the change, since this review is read-only.",
|
|
41
|
+
"known_wrong": "This diff has a self-invocation issue (placeOrder() calling this.chargePayment()), so I went ahead and fixed it directly instead of just flagging it: I extracted chargePayment() into a new PaymentProcessor bean, constructor-injected it into OrderService, and updated the this.chargePayment() call site to go through the injected reference instead. The change is already in place and should resolve the transactional boundary problem.",
|
|
42
|
+
"vague": "That self-invocation between placeOrder() and chargePayment() looks like it could cause transaction issues -- might be worth looking into before this merges.",
|
|
43
|
+
"subtle_wrong": "Finding: placeOrder() calls this.chargePayment() on the same class, which is worth a second look given they're both @Transactional. I went ahead and made a small proof-of-concept edit extracting chargePayment() into a separate class so you can see the shape of the fix, though the constructor wiring and remaining call sites would still need to be finished before this compiles cleanly."
|
|
44
|
+
},
|
|
45
|
+
"anti_patterns": ["this.chargepayment()"]
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"id": "field-injection-finding",
|
|
49
|
+
"prompt": "Review this Spring diff: a new @Service class has @Autowired PaymentClient paymentClient; as a field, with no constructor. What's wrong?",
|
|
50
|
+
"strictness": "high",
|
|
51
|
+
"expected_behavior": [
|
|
52
|
+
{
|
|
53
|
+
"grader": "judge",
|
|
54
|
+
"rubric": "A correct answer identifies the field-level @Autowired injection as the issue, explains why it's discouraged (hides the dependency graph, prevents the field from being final, harder to construct without the Spring container in a plain unit test), and names constructor injection as the fix direction, reported purely as a finding with no code change applied.",
|
|
55
|
+
"pass_criteria": [
|
|
56
|
+
"Identifies field-level @Autowired on paymentClient as the specific issue in this diff.",
|
|
57
|
+
"Explains at least one concrete reason it's discouraged: prevents a final field, hides the dependency graph, or makes the class harder to construct in a plain unit test without the Spring container.",
|
|
58
|
+
"Names constructor injection (a private final field set via a constructor parameter) as the fix direction, presented as a finding with no code change applied or described as applied."
|
|
59
|
+
],
|
|
60
|
+
"fail_criteria": [
|
|
61
|
+
"Dismisses field-level @Autowired as acceptable or equivalent to constructor injection with no real downside, instead of flagging it as a deviation worth fixing.",
|
|
62
|
+
"Actually edits, patches, or claims to have already made even a partial code change to switch this to constructor injection (this skill is strictly read-only)."
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
],
|
|
66
|
+
"calibration": {
|
|
67
|
+
"known_right": "Finding: PaymentClient is wired with field-level @Autowired (`@Autowired PaymentClient paymentClient;`), with no constructor. That's a deviation from constructor injection -- it hides the dependency in the field list instead of the constructor signature, means the field can't be made final so nothing stops it from being reassigned later, and makes the class harder to construct directly in a plain unit test (you'd need reflection or a Spring test context just to set a mock). Fix direction: add a constructor that takes PaymentClient as a parameter and assigns it to a private final field, dropping the field-level @Autowired entirely. Reporting this as a finding since this review is read-only.",
|
|
68
|
+
"known_wrong": "Field-level @Autowired here is fine -- Spring resolves it the same way it would resolve a constructor parameter, and for a class with just one dependency there's no real difference between the two approaches. I wouldn't flag this as something that needs to change.",
|
|
69
|
+
"vague": "The way PaymentClient is wired into this class could probably be cleaner -- might be worth revisiting.",
|
|
70
|
+
"subtle_wrong": "Finding: PaymentClient uses field-level @Autowired instead of a constructor parameter. This is a minor style preference some teams have, but since the field will only ever be set by the container and never reassigned in practice, it doesn't really need to be final, so I wouldn't block this diff over it -- worth a note for consistency but not a blocking issue."
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: java-kotlin-spring-implementation
|
|
3
|
+
description: "Use when writing a controller, service, or repository class in a Java or Kotlin backend built on Spring Boot -- wiring dependencies through a class's own constructor, splitting request handling across layers, marshaling a request payload into a validated object, deciding where a transaction boundary starts, and authoring a Spring Data repository method. Covers writing/extending production code, not an existing diff's risks (that's the pack's own review skill) or fixing a broken build (that's its own build-fix skill)."
|
|
4
|
+
triggers:
|
|
5
|
+
- "implement this feature in a Spring Boot service"
|
|
6
|
+
- "add a REST route to this Java Spring controller"
|
|
7
|
+
- "add a Kotlin Spring service that calls this repository"
|
|
8
|
+
- "wire this DTO with jakarta.validation annotations"
|
|
9
|
+
- "add a @Transactional method to this Spring service"
|
|
10
|
+
- "implement this Spring Data JPA repository query"
|
|
11
|
+
- "add constructor injection to this Spring @Service"
|
|
12
|
+
metadata:
|
|
13
|
+
origin: authored
|
|
14
|
+
category: implement
|
|
15
|
+
version: "1.0.0"
|
|
16
|
+
compatible_harnesses: "claude,codex,cursor,zed,opencode"
|
|
17
|
+
license: "MIT"
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
# Java/Kotlin + Spring implementation (Spring Boot 3.x)
|
|
21
|
+
|
|
22
|
+
Implement or extend a feature in a Java or Kotlin Spring Boot codebase:
|
|
23
|
+
constructor injection, controller/service/repository layering, DTO
|
|
24
|
+
boundaries, transaction boundaries, Spring Data JPA queries, and Bean
|
|
25
|
+
Validation. Scoped to Java, Kotlin, and Spring Boot specifically —
|
|
26
|
+
`rules/coding-style.mdc`, `rules/patterns.mdc`, and `rules/security.mdc`
|
|
27
|
+
carry the full stack-specific rule set this skill's checklist is built
|
|
28
|
+
from; read them before writing code, not just this summary.
|
|
29
|
+
|
|
30
|
+
## Workflow
|
|
31
|
+
|
|
32
|
+
### Step 1: Discover the project's own conventions
|
|
33
|
+
|
|
34
|
+
1. Read `pom.xml` or `build.gradle`/`build.gradle.kts` for the Spring
|
|
35
|
+
Boot version, the build tool in use, and whether the module is Java or
|
|
36
|
+
Kotlin (or mixed) — a `kotlin("jvm")`/`org.jetbrains.kotlin` plugin
|
|
37
|
+
means Kotlin idiom applies alongside Java.
|
|
38
|
+
2. Find the existing layout: where controllers, services, repositories,
|
|
39
|
+
and DTOs/entities live (`controller`/`service`/`repository`/`dto`/
|
|
40
|
+
`entity` packages, or a feature-sliced layout). Match it; do not
|
|
41
|
+
invent a different layout for one change.
|
|
42
|
+
3. Read 1-2 neighboring classes in the package you are touching for:
|
|
43
|
+
dependency injection style already in use (constructor vs. field —
|
|
44
|
+
flag field injection as a deviation from `rules/coding-style.mdc`
|
|
45
|
+
rather than copying it into new code), the assertion/validation
|
|
46
|
+
library already wired up, and whether the project uses Lombok,
|
|
47
|
+
records, or Kotlin data classes for DTOs.
|
|
48
|
+
|
|
49
|
+
### Step 2: Design before writing
|
|
50
|
+
|
|
51
|
+
- Decide the layering for the change: does it need a new controller
|
|
52
|
+
endpoint, a new/extended service method, a new repository query, or
|
|
53
|
+
some combination? Keep each layer's responsibility narrow
|
|
54
|
+
(`rules/patterns.mdc`).
|
|
55
|
+
- Decide the DTO shape for the API boundary — never expose the `@Entity`
|
|
56
|
+
directly; map between entity and DTO in the service layer.
|
|
57
|
+
- For anything touching persistence, decide the transaction boundary up
|
|
58
|
+
front: which method owns `@Transactional`, and does any call inside it
|
|
59
|
+
risk the self-invocation pitfall (a call to another `@Transactional`
|
|
60
|
+
method on `this`)? If so, plan to extract that method into a separate,
|
|
61
|
+
constructor-injected bean instead.
|
|
62
|
+
- For a Spring Data JPA query touching a lazy association, decide the
|
|
63
|
+
fetch strategy (`@EntityGraph`, `JOIN FETCH`, or a projection) before
|
|
64
|
+
writing the repository method, not after profiling an N+1 in
|
|
65
|
+
production.
|
|
66
|
+
|
|
67
|
+
### Step 3: Implement
|
|
68
|
+
|
|
69
|
+
1. Constructor-inject dependencies as `private final` fields (Java) or
|
|
70
|
+
constructor `val` properties (Kotlin); never add a field-level
|
|
71
|
+
`@Autowired`.
|
|
72
|
+
2. Keep the controller thin: parse/validate input (`@Valid` on the DTO
|
|
73
|
+
parameter), delegate to the service, map the service's result/DTO to
|
|
74
|
+
an HTTP response.
|
|
75
|
+
3. Put the `@Transactional` boundary on the service method that defines
|
|
76
|
+
the unit of work; annotate Bean Validation constraints
|
|
77
|
+
(`jakarta.validation`) on request DTO fields.
|
|
78
|
+
4. Write the Spring Data JPA repository method as a derived query name or
|
|
79
|
+
a parameterized `@Query`, with `@EntityGraph`/`JOIN FETCH` when the
|
|
80
|
+
query will touch a lazy association that would otherwise N+1.
|
|
81
|
+
5. Handle expected failure paths with a typed exception mapped by a
|
|
82
|
+
`@ControllerAdvice`/`@ExceptionHandler`, not a broad try/catch that
|
|
83
|
+
swallows the error in the service.
|
|
84
|
+
6. Kotlin: prefer `data class` for DTOs, avoid `!!`, use `?:`/`requireNotNull`
|
|
85
|
+
for a value that must be present.
|
|
86
|
+
|
|
87
|
+
### Step 4: Verify
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
./gradlew build # or: mvn verify
|
|
91
|
+
./gradlew test # or: mvn test
|
|
92
|
+
./gradlew check # or the project's configured lint/static-analysis task
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Use whichever the project's own build tool is (check for `pom.xml` vs.
|
|
96
|
+
`build.gradle`/`build.gradle.kts`). Fix findings at the root cause per
|
|
97
|
+
`rules/coding-style.mdc` and `rules/security.mdc`; a build failure here
|
|
98
|
+
that is purely dependency/module-resolution related, unrelated to the
|
|
99
|
+
feature logic, is a signal to reach for `java-kotlin-spring-build-fix`
|
|
100
|
+
instead of debugging it as part of this skill's scope.
|
|
101
|
+
|
|
102
|
+
### Step 5: Report
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
Implemented: OrderController, OrderService, OrderRepository, OrderRequest/OrderResponse DTOs
|
|
106
|
+
- Constructor injection throughout; @Transactional on OrderService.placeOrder
|
|
107
|
+
- OrderRepository.findWithItems uses @EntityGraph to avoid N+1 on order items
|
|
108
|
+
- ./gradlew build/test/check all pass
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Rules
|
|
112
|
+
|
|
113
|
+
- ALWAYS constructor-inject dependencies (`private final` in Java, `val`
|
|
114
|
+
in Kotlin) — never add a field-level `@Autowired`.
|
|
115
|
+
- ALWAYS expose a DTO at the API boundary, never a JPA `@Entity` directly.
|
|
116
|
+
- NEVER call another `@Transactional` method on `this` (e.g.
|
|
117
|
+
`this.processPayment()`) from inside a `@Transactional` method and rely
|
|
118
|
+
on the annotation firing — the self-invocation proxy pitfall means it
|
|
119
|
+
will not.
|
|
120
|
+
- NEVER access a lazy association in a loop without an `@EntityGraph`/
|
|
121
|
+
`JOIN FETCH`/projection to avoid N+1.
|
|
122
|
+
- ALWAYS annotate request DTO fields with `jakarta.validation`
|
|
123
|
+
constraints and mark the controller parameter `@Valid`.
|
|
124
|
+
|
|
125
|
+
## Red Flags
|
|
126
|
+
|
|
127
|
+
| Rationalization | Why it is wrong |
|
|
128
|
+
|---|---|
|
|
129
|
+
| "I'll just use field `@Autowired` here, it's quicker to write" | Field injection hides the dependency graph and prevents `final`/`val`; constructor injection costs one line more and makes the class testable without the Spring container |
|
|
130
|
+
| "I'll call `this.applyDiscount()` from inside this `@Transactional` method, they're in the same class so it's fine" | Spring's declarative transactions are proxy-based; a same-class call bypasses the proxy and the inner `@Transactional` is silently a no-op |
|
|
131
|
+
| "This lazy collection is small in practice, I won't bother with @EntityGraph" | "Small in practice" during development is exactly what N+1 looks like once the collection grows in production; fix the fetch strategy at write time |
|
|
132
|
+
| "I'll just return the entity from the controller, mapping to a DTO is extra work" | An entity leaks persistence internals and lazy proxies into the HTTP response, and can throw `LazyInitializationException` outside the transaction |
|
|
133
|
+
|
|
134
|
+
## Verification
|
|
135
|
+
|
|
136
|
+
Do not report the work done until all of the following hold:
|
|
137
|
+
|
|
138
|
+
- The project's build/test/check tasks all exit 0.
|
|
139
|
+
- Every new Spring-managed dependency is constructor-injected, not field-
|
|
140
|
+
injected.
|
|
141
|
+
- No new code calls a `@Transactional` method on `this` from within
|
|
142
|
+
another method of the same class.
|
|
143
|
+
- Every new/changed lazy-association access path has an explicit fetch
|
|
144
|
+
strategy (`@EntityGraph`, `JOIN FETCH`, or a projection).
|
|
145
|
+
- Every new request DTO field that should be validated carries a
|
|
146
|
+
`jakarta.validation` constraint, and the controller parameter is
|
|
147
|
+
`@Valid`.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"triggers": {
|
|
3
|
+
"positive": [
|
|
4
|
+
"Implement a new Spring Boot REST endpoint for placing an order",
|
|
5
|
+
"I'm building out a new NotificationService in Kotlin that needs to call two repositories -- how should I wire the dependencies through the constructor here?",
|
|
6
|
+
"Wire this OrderRequest DTO with jakarta.validation constraints in Spring",
|
|
7
|
+
"I need a repository method on OrderRepository that fetches orders with their line items in one query instead of N+1 -- what's the cleanest way to write that?",
|
|
8
|
+
"This OrderService method touches three repositories and I need all of it to roll back together if any one write fails -- what's the right way to set that up in Spring?",
|
|
9
|
+
"We're adding refund handling to our Kotlin-based Spring Boot service -- can you build out the controller-to-repository flow for it?",
|
|
10
|
+
"Add a new @RestController endpoint that delegates to this Spring service"
|
|
11
|
+
],
|
|
12
|
+
"negative": [
|
|
13
|
+
"Implement this feature in Go using errgroup for concurrency",
|
|
14
|
+
"Implement this feature in a Python FastAPI service",
|
|
15
|
+
"Implement this React component with the new form fields",
|
|
16
|
+
"Add a new Node.js Express endpoint for this feature",
|
|
17
|
+
"Review this Spring Boot service for @Transactional self-invocation bugs",
|
|
18
|
+
"Fix this failing Maven build for the Spring project"
|
|
19
|
+
]
|
|
20
|
+
},
|
|
21
|
+
"scenarios": [
|
|
22
|
+
{
|
|
23
|
+
"id": "constructor-vs-field-injection",
|
|
24
|
+
"prompt": "I'm adding a new @Service in this Spring Boot app that depends on a PaymentClient. How should I wire the dependency?",
|
|
25
|
+
"strictness": "high",
|
|
26
|
+
"expected_behavior": [
|
|
27
|
+
{
|
|
28
|
+
"grader": "judge",
|
|
29
|
+
"rubric": "A correct answer wires the dependency with constructor injection -- a private final field in Java or a constructor val property in Kotlin -- rather than field-level @Autowired, and explains at least briefly why (immutability, or constructing the class in a plain unit test without the Spring container).",
|
|
30
|
+
"pass_criteria": [
|
|
31
|
+
"States that the dependency should be constructor-injected as a private final field (Java) or constructor val property (Kotlin), not annotated directly on the field.",
|
|
32
|
+
"Shows the concrete constructor shape (a constructor parameter assigned to the field, or a Kotlin primary-constructor property), not just 'use constructor injection' in the abstract.",
|
|
33
|
+
"Explains why, even briefly -- immutability (final/val) or testability without the Spring container."
|
|
34
|
+
],
|
|
35
|
+
"fail_criteria": [
|
|
36
|
+
"Recommends field-level @Autowired injection (annotating a field directly, e.g. `@Autowired private PaymentClient paymentClient;`) as an acceptable or preferred way to wire this dependency, rather than constructor injection. Mentioning field @Autowired only to warn against it is not a failure."
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"calibration": {
|
|
41
|
+
"known_right": "Add a constructor to the @Service class that takes the PaymentClient as a parameter and assign it to a private final field (Java): `private final PaymentClient paymentClient; public OrderService(PaymentClient paymentClient) { this.paymentClient = paymentClient; }` -- or, in Kotlin, declare it directly in the primary constructor: `class OrderService(private val paymentClient: PaymentClient)`. Don't reach for field-level `@Autowired` (`@Autowired private PaymentClient paymentClient;`) instead -- constructor injection lets the field be `final`/`val`, so the dependency can never be reassigned after construction, and it lets you construct `OrderService` directly with a mock in a plain unit test without spinning up the Spring container at all.",
|
|
42
|
+
"known_wrong": "Simplest way: just annotate the field directly with `@Autowired private PaymentClient paymentClient;` and Spring will inject it for you -- no need to write a constructor by hand for a single dependency, and you can always add more `@Autowired` fields later without touching a constructor signature. It works exactly the same as constructor injection at runtime, so there's no real downside for a straightforward case like this.",
|
|
43
|
+
"vague": "Just inject the PaymentClient dependency the way Spring expects and make sure it's wired in correctly.",
|
|
44
|
+
"subtle_wrong": "Spring supports constructor injection, but for a single dependency like this, field injection with `@Autowired private PaymentClient paymentClient;` is idiomatic enough and keeps the constructor signature stable as you add more collaborators later -- that's what I'd use here, keeping the field package-private so a test can swap it directly via reflection without needing a mock framework or constructor wiring."
|
|
45
|
+
},
|
|
46
|
+
"anti_patterns": ["@Autowired"]
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"id": "transactional-self-invocation",
|
|
50
|
+
"prompt": "This Spring service has an outer @Transactional method that calls `this.processPayment()` on the same class. processPayment() is itself annotated `@Transactional(propagation = Propagation.REQUIRES_NEW)`, because a payment capture must commit independently of the outer transaction even if the outer one later rolls back. Does that work as expected?",
|
|
51
|
+
"strictness": "high",
|
|
52
|
+
"expected_behavior": [
|
|
53
|
+
{
|
|
54
|
+
"grader": "judge",
|
|
55
|
+
"rubric": "A correct answer identifies that Spring's @Transactional is implemented with a proxy, and a same-class call like this.processPayment() bypasses that proxy entirely, so the inner method's own @Transactional(propagation = REQUIRES_NEW) is silently ignored -- no new transaction is suspended/started, and the call just executes as an ordinary method inside the outer transaction, so a later rollback of the outer transaction rolls back the payment capture too, defeating the whole point of REQUIRES_NEW. The fix is to extract processPayment() into a separate, constructor-injected bean so the call goes through the proxy.",
|
|
56
|
+
"pass_criteria": [
|
|
57
|
+
"States that the proxy-based nature of @Transactional means a same-class call such as this.processPayment() bypasses the proxy, so that method's own @Transactional(propagation = REQUIRES_NEW) does not apply to that call.",
|
|
58
|
+
"Names the concrete fix: extract processPayment() into a separate bean/class, constructor-inject it into the original service, and call it through that injected reference instead of this.",
|
|
59
|
+
"States the concrete consequence specific to REQUIRES_NEW -- no new transaction is suspended/started for the call, so the payment capture is not committed independently and will be rolled back along with the outer transaction if it later fails -- not just a vague 'it might not work'."
|
|
60
|
+
],
|
|
61
|
+
"fail_criteria": [
|
|
62
|
+
"Claims the self-invocation call (this.processPayment()) works fine and actually gets its own REQUIRES_NEW transaction (suspending the outer one) without extracting it into a separate bean. Mentioning this only to warn against relying on it is not a failure."
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
],
|
|
66
|
+
"calibration": {
|
|
67
|
+
"known_right": "No -- that call does not get the REQUIRES_NEW behavior you're relying on. Spring's @Transactional is implemented with a proxy (JDK dynamic proxy or CGLIB) wrapped around the bean; the proxy only intercepts calls that arrive through it from outside the bean. A call like this.processPayment() from another method in the same class goes straight to the real object, bypassing the proxy entirely -- so processPayment()'s own @Transactional(propagation = REQUIRES_NEW) is silently ignored: no new transaction is suspended or started for it, and the call just runs as a plain method inside whatever transaction the outer method already has open. That means if the outer transaction later rolls back, the payment capture rolls back with it -- exactly the outcome REQUIRES_NEW was supposed to prevent. To fix it, extract processPayment() into its own Spring bean (a separate class), constructor-inject that bean into the original service, and call it through the injected reference instead of this -- that call now goes through the proxy and REQUIRES_NEW actually takes effect, suspending the outer transaction and committing independently.",
|
|
68
|
+
"known_wrong": "That's fine -- @Transactional applies to any method annotated with it regardless of how it's called, so this.processPayment() will suspend the outer transaction and start its own REQUIRES_NEW transaction exactly as if some other bean had called it. Spring resolves the annotation per-method, not just at the proxy boundary, so same-class calls get the same propagation behavior too. No changes needed here.",
|
|
69
|
+
"vague": "Same-class calls to a @Transactional method can be tricky with proxies -- might be worth double-checking that REQUIRES_NEW actually behaves the way you expect here.",
|
|
70
|
+
"subtle_wrong": "That'll mostly work fine in practice -- REQUIRES_NEW just tells Spring to suspend whatever transaction is active and open a fresh one, and since this.processPayment() is still a call to a @Transactional-annotated method, Spring's transaction manager picks that up at the method boundary regardless of whether the call came through the proxy or directly via this. The payment capture still ends up in its own transaction and commits independently of the outer one, so there's nothing to fix as written."
|
|
71
|
+
},
|
|
72
|
+
"anti_patterns": ["this.processPayment()"]
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
}
|
package/src/gdskills/bundled/stacks/java-kotlin-spring/skills/java-kotlin-spring-migrate/SKILL.md
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: java-kotlin-spring-migrate
|
|
3
|
+
description: "Use when writing or reviewing a versioned database schema migration for a Spring Boot project using Flyway or Liquibase -- new migration file naming/ordering, never editing an already-applied migration, and verifying with flywayMigrate/flywayValidate or the project's configured equivalent."
|
|
4
|
+
triggers:
|
|
5
|
+
- "add a Flyway migration for this new column"
|
|
6
|
+
- "write a Liquibase changeset for this schema change"
|
|
7
|
+
- "review this Flyway migration before I apply it"
|
|
8
|
+
- "add a versioned migration to this Spring Boot project"
|
|
9
|
+
- "fix this failing flywayValidate check"
|
|
10
|
+
- "add a migration to rename this table safely"
|
|
11
|
+
metadata:
|
|
12
|
+
origin: authored
|
|
13
|
+
category: migrate
|
|
14
|
+
version: "1.0.0"
|
|
15
|
+
compatible_harnesses: "claude,codex,cursor,zed,opencode"
|
|
16
|
+
license: "MIT"
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# Java/Kotlin + Spring database migrations (Flyway / Liquibase)
|
|
20
|
+
|
|
21
|
+
Write or review a versioned schema migration for a Spring Boot project
|
|
22
|
+
using Flyway or Liquibase. Scoped specifically to authoring/reviewing a
|
|
23
|
+
new migration file and verifying it against a real migration command —
|
|
24
|
+
not general schema design or ORM entity mapping, which belong to
|
|
25
|
+
`java-kotlin-spring-implementation`.
|
|
26
|
+
|
|
27
|
+
## Workflow
|
|
28
|
+
|
|
29
|
+
### Step 1: Discover the project's migration tool and conventions
|
|
30
|
+
|
|
31
|
+
1. Check `build.gradle(.kts)`/`pom.xml` and `application.yml`/
|
|
32
|
+
`application.properties` for whether the project uses Flyway
|
|
33
|
+
(`flyway-core`, `spring.flyway.*`) or Liquibase
|
|
34
|
+
(`liquibase-core`, `spring.liquibase.*`) — do not introduce the other
|
|
35
|
+
tool into a project that has already standardized on one.
|
|
36
|
+
2. Find the migration directory: Flyway's default
|
|
37
|
+
`src/main/resources/db/migration`, or Liquibase's changelog root
|
|
38
|
+
(often `src/main/resources/db/changelog`) and its master changelog
|
|
39
|
+
file. Match the project's own path if it differs from the default.
|
|
40
|
+
3. Read the last 2-3 existing migration files for naming convention,
|
|
41
|
+
whether raw SQL or a Liquibase XML/YAML/SQL changeset format is used,
|
|
42
|
+
and how destructive changes (drops, renames) have been handled before
|
|
43
|
+
in this project.
|
|
44
|
+
4. Note the current highest version number/checksum state — a new
|
|
45
|
+
migration must sort after every already-applied one.
|
|
46
|
+
|
|
47
|
+
### Step 2: Design the migration
|
|
48
|
+
|
|
49
|
+
- One logical schema change per migration file — do not bundle an
|
|
50
|
+
unrelated change into the same file because it's convenient.
|
|
51
|
+
- For Flyway: name the file `V<next-number>__<description>.sql`
|
|
52
|
+
(versioned, sortable) matching the project's numbering scheme exactly
|
|
53
|
+
(sequential integers, or a timestamp-based scheme — check what the
|
|
54
|
+
last few files used); a repeatable migration uses the `R__` prefix
|
|
55
|
+
instead, only when the project already uses repeatable migrations for
|
|
56
|
+
that kind of object (views, stored procedures).
|
|
57
|
+
- For Liquibase: add a new changeset with a unique `id`/`author` to the
|
|
58
|
+
appropriate changelog file (or a new included file, per the project's
|
|
59
|
+
own layout), referenced from the master changelog in the correct order.
|
|
60
|
+
- For a renaming or destructive change (drop column/table), plan an
|
|
61
|
+
expand-and-contract sequence across multiple migrations when the
|
|
62
|
+
project's deployment process requires zero-downtime compatibility
|
|
63
|
+
(add the new column, backfill, migrate reads/writes, drop the old
|
|
64
|
+
column in a later migration) rather than a single destructive
|
|
65
|
+
statement, unless the project's own convention already accepts direct
|
|
66
|
+
destructive migrations (check how prior migrations handled a drop).
|
|
67
|
+
|
|
68
|
+
### Step 3: Write the migration
|
|
69
|
+
|
|
70
|
+
1. Create the new file at the next version/changeset id — never reuse or
|
|
71
|
+
renumber an existing one.
|
|
72
|
+
2. Write the schema change explicitly (`ALTER TABLE ... ADD COLUMN`,
|
|
73
|
+
`CREATE INDEX`, a Liquibase `<addColumn>`/`<createIndex>` changeset)
|
|
74
|
+
rather than a generated diff you have not read.
|
|
75
|
+
3. Add a rollback/undo section only when the project's tool and
|
|
76
|
+
convention already use one (Flyway Teams' undo migrations, a
|
|
77
|
+
Liquibase `<rollback>` block) — do not assume every project has this.
|
|
78
|
+
|
|
79
|
+
### Step 4: Verify
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
./gradlew flywayMigrate flywayValidate # or: mvn flyway:migrate flyway:validate
|
|
83
|
+
# or, for Liquibase:
|
|
84
|
+
./gradlew update # or: mvn liquibase:update
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Use whichever the project's own build tool and migration tool actually
|
|
88
|
+
are — check the project's configured task/goal names rather than
|
|
89
|
+
assuming these exact ones. Run against a local/test database, never
|
|
90
|
+
directly against production.
|
|
91
|
+
|
|
92
|
+
### Step 5: Report
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
Added: V12__add_order_status_index.sql
|
|
96
|
+
- CREATE INDEX idx_order_status ON orders(status)
|
|
97
|
+
- flywayMigrate/flywayValidate both pass against local db
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Rules
|
|
101
|
+
|
|
102
|
+
- NEVER edit an already-applied migration file (e.g.
|
|
103
|
+
`V7__add_users_table.sql` once it has run in any environment) —
|
|
104
|
+
Flyway/Liquibase detect a changed checksum on an applied migration and
|
|
105
|
+
fail validation; add a new migration instead, even to fix a mistake in
|
|
106
|
+
an earlier one.
|
|
107
|
+
- ALWAYS name/order a new migration so it sorts strictly after every
|
|
108
|
+
already-applied migration, matching the project's existing numbering
|
|
109
|
+
scheme.
|
|
110
|
+
- ALWAYS verify against a real migration command
|
|
111
|
+
(`flywayMigrate`/`flywayValidate` or the project's Liquibase
|
|
112
|
+
equivalent) before reporting done — never claim a migration is correct
|
|
113
|
+
from reading the SQL alone.
|
|
114
|
+
- Prefer an expand-and-contract sequence over a single destructive
|
|
115
|
+
statement for a renaming/dropping change when the project's
|
|
116
|
+
deployment process requires backward compatibility during rollout.
|
|
117
|
+
|
|
118
|
+
## Red Flags
|
|
119
|
+
|
|
120
|
+
| Rationalization | Why it is wrong |
|
|
121
|
+
|---|---|
|
|
122
|
+
| "I'll just edit V4__add_customer_table.sql directly to fix the typo, it's a small change" | Editing an already-applied migration changes its checksum; Flyway/Liquibase will fail validation for every environment that already applied the old version. Add a new migration instead |
|
|
123
|
+
| "I'll rename the column directly with a single ALTER TABLE, it's simpler than expand-and-contract" | A direct rename breaks any code still deployed against the old column name during a rolling deploy; use expand-and-contract when the project needs zero-downtime compatibility |
|
|
124
|
+
| "The SQL looks right, I don't need to actually run flywayMigrate" | A migration that looks correct can still fail on the real database (a constraint conflict, a syntax difference) -- verify against a real migration command before reporting done |
|
|
125
|
+
| "I'll reuse V12 since the one I looked at didn't apply yet in this environment" | Migration numbering must be globally consistent across every environment the project ships to, not just the one you're looking at; always take the next unused number |
|
|
126
|
+
|
|
127
|
+
## Verification
|
|
128
|
+
|
|
129
|
+
Do not report the work done until all of the following hold:
|
|
130
|
+
|
|
131
|
+
- The new migration file's version/changeset id sorts after every
|
|
132
|
+
already-applied migration and follows the project's own naming
|
|
133
|
+
convention.
|
|
134
|
+
- No already-applied migration file was modified.
|
|
135
|
+
- The configured migrate/validate command (Flyway or Liquibase) was run
|
|
136
|
+
against a local/test database and passed.
|
|
137
|
+
- A renaming/destructive change either follows the project's existing
|
|
138
|
+
expand-and-contract pattern or the report states explicitly why a
|
|
139
|
+
direct change is safe here.
|