backend-claude-code 1.0.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/settings.json +42 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +35 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +33 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +32 -0
- package/.mcp.json +19 -0
- package/CLAUDE.md +126 -0
- package/README.md +142 -0
- package/agents/code-reviewer.md +84 -0
- package/agents/database-reviewer.md +91 -0
- package/agents/java-build-resolver.md +127 -0
- package/agents/java-performance-reviewer.md +262 -0
- package/agents/planner.md +99 -0
- package/agents/security-reviewer.md +119 -0
- package/agents/tdd-guide.md +189 -0
- package/bin/cli.js +144 -0
- package/commands/db-migrate.md +134 -0
- package/commands/dev-build.md +72 -0
- package/commands/dev-coverage.md +73 -0
- package/commands/dev-fix.md +75 -0
- package/commands/dev-plan.md +501 -0
- package/commands/dev-review.md +144 -0
- package/commands/dev-run.md +385 -0
- package/commands/dev-test.md +89 -0
- package/commands/dev-verify.md +95 -0
- package/commands/dev.md +45 -0
- package/commands/git-commit.md +112 -0
- package/commands/git-issue.md +74 -0
- package/commands/git-pr.md +184 -0
- package/commands/git-push.md +28 -0
- package/package.json +24 -0
- package/rules/architecture.md +33 -0
- package/rules/coding-style.md +113 -0
- package/rules/controller-patterns.md +63 -0
- package/rules/dto-patterns.md +76 -0
- package/rules/entity-patterns.md +70 -0
- package/rules/error-handling.md +56 -0
- package/rules/hooks.md +73 -0
- package/rules/repository-patterns.md +75 -0
- package/rules/security.md +101 -0
- package/rules/service-patterns.md +70 -0
- package/rules/testing.md +174 -0
- package/skills/api-design/SKILL.md +523 -0
- package/skills/architecture-decision-records/SKILL.md +179 -0
- package/skills/database-migrations/SKILL.md +429 -0
- package/skills/hexagonal-architecture/SKILL.md +276 -0
- package/skills/java-coding-standards/SKILL.md +236 -0
- package/skills/jpa-patterns/SKILL.md +218 -0
- package/skills/postgres-patterns/SKILL.md +147 -0
- package/skills/springboot-patterns/SKILL.md +255 -0
- package/skills/springboot-security/SKILL.md +241 -0
- package/skills/springboot-tdd/SKILL.md +236 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tdd-guide
|
|
3
|
+
description: Test-Driven Development specialist for Java/Spring Boot. Enforces write-tests-first methodology with JUnit 5, Mockito, MockMvc, and Testcontainers. Use PROACTIVELY for new features, bug fixes, and refactoring. Ensures 80%+ test coverage.
|
|
4
|
+
tools: ["Read", "Write", "Edit", "Bash", "Grep", "Glob"]
|
|
5
|
+
model: sonnet
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# TDD Guide — Java/Spring Boot
|
|
9
|
+
|
|
10
|
+
Java/Spring Boot TDD 워크플로우를 안내하는 전문 에이전트.
|
|
11
|
+
|
|
12
|
+
**핵심 원칙**: 테스트를 먼저 작성한다. 실패하는 테스트 없이 구현 코드를 작성하지 않는다.
|
|
13
|
+
|
|
14
|
+
## TDD Cycle
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
RED → 실패하는 테스트 작성
|
|
18
|
+
GREEN → 테스트를 통과하는 최소 구현
|
|
19
|
+
REFACTOR → 테스트 통과 상태에서 코드 개선
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Phase 1 — UNDERSTAND
|
|
23
|
+
|
|
24
|
+
구현 전 다음을 파악:
|
|
25
|
+
1. 어떤 동작이 필요한가?
|
|
26
|
+
2. 경계 조건은 무엇인가?
|
|
27
|
+
3. 실패 케이스는 무엇인가?
|
|
28
|
+
4. 어떤 계층을 테스트하는가? (서비스/컨트롤러/레포지토리)
|
|
29
|
+
|
|
30
|
+
## Phase 2 — RED (테스트 작성)
|
|
31
|
+
|
|
32
|
+
### 서비스 단위 테스트
|
|
33
|
+
|
|
34
|
+
```java
|
|
35
|
+
@ExtendWith(MockitoExtension.class)
|
|
36
|
+
class OrderServiceTest {
|
|
37
|
+
@Mock OrderRepository orderRepository;
|
|
38
|
+
private OrderService orderService;
|
|
39
|
+
|
|
40
|
+
@BeforeEach
|
|
41
|
+
void setUp() { orderService = new OrderService(orderRepository); }
|
|
42
|
+
|
|
43
|
+
@Test
|
|
44
|
+
@DisplayName("create — 유효한 요청으로 주문 생성")
|
|
45
|
+
void create_validRequest_returnsCreatedOrder() {
|
|
46
|
+
// Arrange
|
|
47
|
+
var request = new CreateOrderRequest("Alice", BigDecimal.TEN);
|
|
48
|
+
when(orderRepository.save(any())).thenAnswer(inv -> {
|
|
49
|
+
Order o = inv.getArgument(0);
|
|
50
|
+
return new Order(1L, o.getCustomerName(), o.getAmount());
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Act
|
|
54
|
+
var result = orderService.create(request);
|
|
55
|
+
|
|
56
|
+
// Assert
|
|
57
|
+
assertThat(result.id()).isNotNull();
|
|
58
|
+
assertThat(result.customerName()).isEqualTo("Alice");
|
|
59
|
+
verify(orderRepository).save(any());
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@Test
|
|
63
|
+
@DisplayName("create — 고객명 공백 시 예외 발생")
|
|
64
|
+
void create_blankCustomerName_throwsException() {
|
|
65
|
+
var request = new CreateOrderRequest("", BigDecimal.TEN);
|
|
66
|
+
assertThatThrownBy(() -> orderService.create(request))
|
|
67
|
+
.isInstanceOf(IllegalArgumentException.class)
|
|
68
|
+
.hasMessageContaining("customer");
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 컨트롤러 테스트
|
|
74
|
+
|
|
75
|
+
```java
|
|
76
|
+
@WebMvcTest(OrderController.class)
|
|
77
|
+
class OrderControllerTest {
|
|
78
|
+
@Autowired MockMvc mockMvc;
|
|
79
|
+
@MockBean OrderService orderService;
|
|
80
|
+
|
|
81
|
+
@Test
|
|
82
|
+
@DisplayName("POST /orders — 201 반환")
|
|
83
|
+
void createOrder_returns201() throws Exception {
|
|
84
|
+
when(orderService.create(any())).thenReturn(sampleResponse());
|
|
85
|
+
|
|
86
|
+
mockMvc.perform(post("/api/orders")
|
|
87
|
+
.contentType(MediaType.APPLICATION_JSON)
|
|
88
|
+
.content("""{"customerName":"Alice","amount":100}"""))
|
|
89
|
+
.andExpect(status().isCreated())
|
|
90
|
+
.andExpect(jsonPath("$.data.customerName").value("Alice"));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
@Test
|
|
94
|
+
@DisplayName("POST /orders — 검증 실패 시 400 반환")
|
|
95
|
+
void createOrder_invalidInput_returns400() throws Exception {
|
|
96
|
+
mockMvc.perform(post("/api/orders")
|
|
97
|
+
.contentType(MediaType.APPLICATION_JSON)
|
|
98
|
+
.content("""{"customerName":"","amount":-1}"""))
|
|
99
|
+
.andExpect(status().isBadRequest());
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 레포지토리 테스트
|
|
105
|
+
|
|
106
|
+
```java
|
|
107
|
+
@DataJpaTest
|
|
108
|
+
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
|
|
109
|
+
@Import(TestContainersConfig.class)
|
|
110
|
+
class OrderRepositoryTest {
|
|
111
|
+
@Autowired OrderRepository repository;
|
|
112
|
+
|
|
113
|
+
@Test
|
|
114
|
+
void findByCustomerName_existingName_returnsOrders() {
|
|
115
|
+
repository.save(new OrderEntity("Alice", BigDecimal.TEN));
|
|
116
|
+
var results = repository.findByCustomerName("Alice");
|
|
117
|
+
assertThat(results).hasSize(1);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Phase 3 — GREEN (최소 구현)
|
|
123
|
+
|
|
124
|
+
테스트를 통과하는 최소한의 코드만 작성. 과도한 구현 금지.
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
# 테스트 실행
|
|
128
|
+
./mvnw test -Dtest=OrderServiceTest -q
|
|
129
|
+
./gradlew test --tests "com.example.service.OrderServiceTest"
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
모든 테스트 통과 확인 후 다음 단계.
|
|
133
|
+
|
|
134
|
+
## Phase 4 — REFACTOR
|
|
135
|
+
|
|
136
|
+
테스트가 통과한 상태에서:
|
|
137
|
+
- 중복 제거
|
|
138
|
+
- 명명 개선
|
|
139
|
+
- 메서드 추출
|
|
140
|
+
- 매 변경 후 테스트 재실행
|
|
141
|
+
|
|
142
|
+
## Phase 5 — COVERAGE
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
./mvnw test jacoco:report -q
|
|
146
|
+
./gradlew test jacocoTestReport
|
|
147
|
+
# 리포트: target/site/jacoco/index.html
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
80% 미만이면 누락된 경로에 대한 테스트 추가.
|
|
151
|
+
|
|
152
|
+
## 파라미터화 테스트
|
|
153
|
+
|
|
154
|
+
```java
|
|
155
|
+
@ParameterizedTest
|
|
156
|
+
@CsvSource({
|
|
157
|
+
"100.00, 10, 90.00",
|
|
158
|
+
"50.00, 0, 50.00",
|
|
159
|
+
"200.00, 25, 150.00"
|
|
160
|
+
})
|
|
161
|
+
@DisplayName("할인 계산 정확성")
|
|
162
|
+
void applyDiscount(BigDecimal price, int pct, BigDecimal expected) {
|
|
163
|
+
assertThat(PricingUtils.discount(price, pct))
|
|
164
|
+
.isEqualByComparingTo(expected);
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## 테스트 데이터 빌더
|
|
169
|
+
|
|
170
|
+
```java
|
|
171
|
+
class OrderBuilder {
|
|
172
|
+
private String customerName = "Test Customer";
|
|
173
|
+
private BigDecimal amount = BigDecimal.TEN;
|
|
174
|
+
|
|
175
|
+
OrderBuilder withCustomerName(String name) {
|
|
176
|
+
this.customerName = name;
|
|
177
|
+
return this;
|
|
178
|
+
}
|
|
179
|
+
OrderBuilder withAmount(BigDecimal amount) {
|
|
180
|
+
this.amount = amount;
|
|
181
|
+
return this;
|
|
182
|
+
}
|
|
183
|
+
Order build() {
|
|
184
|
+
return new Order(null, customerName, amount);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
For detailed TDD patterns, see `skill: springboot-tdd`.
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// npx backend-claude-code [target-dir] 진입점
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
const PKG_DIR = path.join(__dirname, '..');
|
|
9
|
+
const TARGET = process.argv[2] ? path.resolve(process.argv[2]) : process.cwd();
|
|
10
|
+
|
|
11
|
+
if (!fs.existsSync(TARGET)) {
|
|
12
|
+
console.error(`Error: 대상 디렉토리가 존재하지 않습니다: ${TARGET}`);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
console.log(`backend-claude-code → ${TARGET}`);
|
|
17
|
+
|
|
18
|
+
const CLAUDE_DIR = path.join(TARGET, '.claude');
|
|
19
|
+
const TRACKING = path.join(CLAUDE_DIR, '.installed-files');
|
|
20
|
+
const IGNORE_FILE = path.join(CLAUDE_DIR, '.claude-ignore');
|
|
21
|
+
|
|
22
|
+
// 디렉토리 초기화
|
|
23
|
+
for (const d of ['rules', 'agents', 'commands', 'skills']) {
|
|
24
|
+
fs.mkdirSync(path.join(CLAUDE_DIR, d), { recursive: true });
|
|
25
|
+
}
|
|
26
|
+
if (!fs.existsSync(TRACKING)) fs.writeFileSync(TRACKING, '');
|
|
27
|
+
|
|
28
|
+
// .claude-ignore 파싱
|
|
29
|
+
const ignored = new Set(
|
|
30
|
+
fs.existsSync(IGNORE_FILE)
|
|
31
|
+
? fs.readFileSync(IGNORE_FILE, 'utf8')
|
|
32
|
+
.split('\n')
|
|
33
|
+
.map(l => l.trim())
|
|
34
|
+
.filter(l => l && !l.startsWith('#'))
|
|
35
|
+
: []
|
|
36
|
+
);
|
|
37
|
+
const isIgnored = rel => ignored.has(rel);
|
|
38
|
+
|
|
39
|
+
// 트래킹 파일 읽기/쓰기
|
|
40
|
+
const readTracked = () => new Set(fs.readFileSync(TRACKING, 'utf8').split('\n').filter(Boolean));
|
|
41
|
+
const writeTracked = set => fs.writeFileSync(TRACKING, [...set].join('\n') + (set.size ? '\n' : ''));
|
|
42
|
+
|
|
43
|
+
const track = dest => { const s = readTracked(); s.add(dest); writeTracked(s); };
|
|
44
|
+
const untrack = dest => { const s = readTracked(); s.delete(dest); writeTracked(s); };
|
|
45
|
+
|
|
46
|
+
// 파일 동기화
|
|
47
|
+
const syncFile = (src, dest, rel) => {
|
|
48
|
+
if (isIgnored(rel)) { console.log(` skip (ignored): ${rel}`); return; }
|
|
49
|
+
fs.copyFileSync(src, dest);
|
|
50
|
+
track(dest);
|
|
51
|
+
console.log(` sync: ${rel}`);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// dest → { rel, src } 매핑
|
|
55
|
+
const resolve = dest => {
|
|
56
|
+
const settingsDest = path.join(CLAUDE_DIR, 'settings.json');
|
|
57
|
+
if (dest === settingsDest)
|
|
58
|
+
return { rel: 'settings.json', src: path.join(PKG_DIR, '.claude', 'settings.json') };
|
|
59
|
+
if (dest.startsWith(CLAUDE_DIR + path.sep)) {
|
|
60
|
+
const rel = dest.slice(CLAUDE_DIR.length + 1);
|
|
61
|
+
return { rel, src: path.join(PKG_DIR, rel) };
|
|
62
|
+
}
|
|
63
|
+
if (dest === path.join(TARGET, 'CLAUDE.md'))
|
|
64
|
+
return { rel: 'CLAUDE.md', src: path.join(PKG_DIR, 'CLAUDE.md') };
|
|
65
|
+
if (dest === path.join(TARGET, '.mcp.json'))
|
|
66
|
+
return { rel: '.mcp.json', src: path.join(PKG_DIR, '.mcp.json') };
|
|
67
|
+
return null;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// --- 소스에서 삭제된 파일 정리 ---
|
|
71
|
+
console.log('\n정리 중...');
|
|
72
|
+
for (const dest of readTracked()) {
|
|
73
|
+
const r = resolve(dest);
|
|
74
|
+
if (!r) continue;
|
|
75
|
+
if (fs.existsSync(r.src)) continue;
|
|
76
|
+
|
|
77
|
+
if (isIgnored(r.rel)) {
|
|
78
|
+
console.log(` keep (ignored, source removed): ${r.rel}`);
|
|
79
|
+
} else {
|
|
80
|
+
fs.rmSync(dest, { force: true });
|
|
81
|
+
untrack(dest);
|
|
82
|
+
console.log(` remove: ${r.rel}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// --- 동기화 ---
|
|
87
|
+
console.log('\n동기화 중...');
|
|
88
|
+
|
|
89
|
+
for (const dir of ['rules', 'agents', 'commands']) {
|
|
90
|
+
const srcDir = path.join(PKG_DIR, dir);
|
|
91
|
+
if (!fs.existsSync(srcDir)) continue;
|
|
92
|
+
for (const fname of fs.readdirSync(srcDir).filter(f => f.endsWith('.md'))) {
|
|
93
|
+
syncFile(path.join(srcDir, fname), path.join(CLAUDE_DIR, dir, fname), `${dir}/${fname}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const skillsDir = path.join(PKG_DIR, 'skills');
|
|
98
|
+
if (fs.existsSync(skillsDir)) {
|
|
99
|
+
for (const name of fs.readdirSync(skillsDir)) {
|
|
100
|
+
const src = path.join(skillsDir, name, 'SKILL.md');
|
|
101
|
+
if (!fs.existsSync(src)) continue;
|
|
102
|
+
const destDir = path.join(CLAUDE_DIR, 'skills', name);
|
|
103
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
104
|
+
syncFile(src, path.join(destDir, 'SKILL.md'), `skills/${name}/SKILL.md`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const settingsSrc = path.join(PKG_DIR, '.claude', 'settings.json');
|
|
109
|
+
if (fs.existsSync(settingsSrc)) {
|
|
110
|
+
syncFile(settingsSrc, path.join(CLAUDE_DIR, 'settings.json'), 'settings.json');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const mcpSrc = path.join(PKG_DIR, '.mcp.json');
|
|
114
|
+
if (fs.existsSync(mcpSrc)) {
|
|
115
|
+
syncFile(mcpSrc, path.join(TARGET, '.mcp.json'), '.mcp.json');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const claudeMdSrc = path.join(PKG_DIR, 'CLAUDE.md');
|
|
119
|
+
if (fs.existsSync(claudeMdSrc)) {
|
|
120
|
+
syncFile(claudeMdSrc, path.join(TARGET, 'CLAUDE.md'), 'CLAUDE.md');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// GitHub 템플릿 (항상 덮어쓰기)
|
|
124
|
+
const githubSrc = path.join(PKG_DIR, '.github');
|
|
125
|
+
if (fs.existsSync(githubSrc)) {
|
|
126
|
+
const prSrc = path.join(githubSrc, 'PULL_REQUEST_TEMPLATE.md');
|
|
127
|
+
if (fs.existsSync(prSrc)) {
|
|
128
|
+
fs.mkdirSync(path.join(TARGET, '.github'), { recursive: true });
|
|
129
|
+
fs.copyFileSync(prSrc, path.join(TARGET, '.github', 'PULL_REQUEST_TEMPLATE.md'));
|
|
130
|
+
}
|
|
131
|
+
const issueSrc = path.join(githubSrc, 'ISSUE_TEMPLATE');
|
|
132
|
+
if (fs.existsSync(issueSrc)) {
|
|
133
|
+
const issueDest = path.join(TARGET, '.github', 'ISSUE_TEMPLATE');
|
|
134
|
+
fs.mkdirSync(issueDest, { recursive: true });
|
|
135
|
+
for (const fname of fs.readdirSync(issueSrc).filter(f => f.endsWith('.md'))) {
|
|
136
|
+
fs.copyFileSync(path.join(issueSrc, fname), path.join(issueDest, fname));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
console.log('\n완료!');
|
|
142
|
+
console.log('\n특정 파일을 업데이트에서 제외하려면:');
|
|
143
|
+
console.log(` ${IGNORE_FILE}`);
|
|
144
|
+
console.log(' 예시: rules/repository-patterns.md');
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: DB 마이그레이션 실행·상태 확인·롤백 — Flyway 또는 Liquibase 자동 감지
|
|
3
|
+
argument-hint: [info | migrate | rollback | repair | blank for status]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# DB Migrate
|
|
7
|
+
|
|
8
|
+
**Action**: $ARGUMENTS (기본값: `info`)
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Phase 1 — DETECT
|
|
13
|
+
|
|
14
|
+
빌드 도구와 마이그레이션 도구 감지:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# 빌드 도구
|
|
18
|
+
[ -f "pom.xml" ] && echo "Maven" || echo "Gradle"
|
|
19
|
+
|
|
20
|
+
# 마이그레이션 도구
|
|
21
|
+
grep -q "flyway" pom.xml build.gradle build.gradle.kts 2>/dev/null && echo "Flyway"
|
|
22
|
+
grep -q "liquibase" pom.xml build.gradle build.gradle.kts 2>/dev/null && echo "Liquibase"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
마이그레이션 도구를 찾을 수 없으면 종료: "pom.xml 또는 build.gradle에서 Flyway/Liquibase 의존성을 찾을 수 없습니다."
|
|
26
|
+
|
|
27
|
+
마이그레이션 파일 위치 확인:
|
|
28
|
+
```bash
|
|
29
|
+
find src/main/resources -name "*.sql" -path "*/migration*" | head -10
|
|
30
|
+
find src/main/resources -name "*.sql" -path "*/db*" | head -10
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Phase 2 — INFO (현재 상태 확인)
|
|
34
|
+
|
|
35
|
+
`$ARGUMENTS`가 비어 있거나 `info`인 경우:
|
|
36
|
+
|
|
37
|
+
**Flyway (Maven)**:
|
|
38
|
+
```bash
|
|
39
|
+
./mvnw flyway:info -q 2>&1
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Flyway (Gradle)**:
|
|
43
|
+
```bash
|
|
44
|
+
./gradlew flywayInfo 2>&1
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Liquibase (Maven)**:
|
|
48
|
+
```bash
|
|
49
|
+
./mvnw liquibase:status -q 2>&1
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Liquibase (Gradle)**:
|
|
53
|
+
```bash
|
|
54
|
+
./gradlew liquibaseStatus 2>&1
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
현재 버전, 적용된 마이그레이션, 대기 중인 마이그레이션을 보고.
|
|
58
|
+
|
|
59
|
+
## Phase 3 — MIGRATE
|
|
60
|
+
|
|
61
|
+
`$ARGUMENTS`가 `migrate`인 경우:
|
|
62
|
+
|
|
63
|
+
### 사전 확인
|
|
64
|
+
```bash
|
|
65
|
+
# 대기 중인 마이그레이션 목록 확인
|
|
66
|
+
./mvnw flyway:info -q 2>&1 | grep "Pending"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
대기 중인 마이그레이션이 있으면 목록을 보여주고 확인 요청.
|
|
70
|
+
|
|
71
|
+
### 실행
|
|
72
|
+
**Flyway (Maven)**:
|
|
73
|
+
```bash
|
|
74
|
+
./mvnw flyway:migrate 2>&1
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Flyway (Gradle)**:
|
|
78
|
+
```bash
|
|
79
|
+
./gradlew flywayMigrate 2>&1
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Liquibase (Maven)**:
|
|
83
|
+
```bash
|
|
84
|
+
./mvnw liquibase:update 2>&1
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### 실행 후 검증
|
|
88
|
+
```bash
|
|
89
|
+
./mvnw flyway:info -q 2>&1 | tail -10
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Phase 4 — ROLLBACK
|
|
93
|
+
|
|
94
|
+
`$ARGUMENTS`가 `rollback`인 경우:
|
|
95
|
+
|
|
96
|
+
> **주의**: Flyway 무료 버전은 undo를 지원하지 않음. Liquibase는 rollbackCount 사용.
|
|
97
|
+
|
|
98
|
+
**Flyway Pro (Maven)**:
|
|
99
|
+
```bash
|
|
100
|
+
./mvnw flyway:undo 2>&1
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**Liquibase (Maven)**:
|
|
104
|
+
```bash
|
|
105
|
+
./mvnw liquibase:rollback -Dliquibase.rollbackCount=1 2>&1
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
롤백 후 현재 상태 재확인.
|
|
109
|
+
|
|
110
|
+
## Phase 5 — REPAIR
|
|
111
|
+
|
|
112
|
+
`$ARGUMENTS`가 `repair`인 경우 (체크섬 불일치 또는 실패한 마이그레이션 수정):
|
|
113
|
+
|
|
114
|
+
**Flyway (Maven)**:
|
|
115
|
+
```bash
|
|
116
|
+
./mvnw flyway:repair 2>&1
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Output
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
DB Migration — <날짜>
|
|
123
|
+
Tool: Flyway | Liquibase
|
|
124
|
+
Build: Maven | Gradle
|
|
125
|
+
Action: info | migrate | rollback | repair
|
|
126
|
+
|
|
127
|
+
Current version: <version>
|
|
128
|
+
Applied: N
|
|
129
|
+
Pending: N
|
|
130
|
+
|
|
131
|
+
Status: SUCCESS | FAILED | NO_CHANGES
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
> 마이그레이션 파일 작성 시 `skill: database-migrations` 참조.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Java 빌드 오류 진단 및 수정 — Maven/Gradle 컴파일/테스트 실패 자동 해결
|
|
3
|
+
argument-hint: [error message | blank to auto-detect]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Java Build Fix
|
|
7
|
+
|
|
8
|
+
**Input**: $ARGUMENTS
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Phase 1 — DETECT BUILD TOOL
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
[ -f "pom.xml" ] && echo "Maven" || \
|
|
16
|
+
[ -f "build.gradle.kts" ] && echo "Gradle Kotlin DSL" || \
|
|
17
|
+
[ -f "build.gradle" ] && echo "Gradle" || \
|
|
18
|
+
echo "Unknown — 빌드 파일을 찾을 수 없습니다"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Phase 2 — RUN BUILD
|
|
22
|
+
|
|
23
|
+
Maven:
|
|
24
|
+
```bash
|
|
25
|
+
./mvnw compile -q --no-transfer-progress 2>&1
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Gradle:
|
|
29
|
+
```bash
|
|
30
|
+
./gradlew compileJava -q 2>&1
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
오류 없으면: "빌드 성공. 오류가 없습니다."
|
|
34
|
+
|
|
35
|
+
## Phase 3 — PARSE ERROR
|
|
36
|
+
|
|
37
|
+
오류 메시지 분석:
|
|
38
|
+
1. 오류 타입 식별 (컴파일, 의존성, 어노테이션 프로세서)
|
|
39
|
+
2. 영향 받는 파일과 라인 특정
|
|
40
|
+
3. 근본 원인 파악
|
|
41
|
+
|
|
42
|
+
## Phase 4 — DELEGATE TO java-build-resolver
|
|
43
|
+
|
|
44
|
+
`java-build-resolver` 에이전트에 위임:
|
|
45
|
+
- 오류 메시지 전달
|
|
46
|
+
- 영향 받는 파일 목록 포함
|
|
47
|
+
- 빌드 도구 (Maven/Gradle) 명시
|
|
48
|
+
|
|
49
|
+
## Phase 5 — VERIFY
|
|
50
|
+
|
|
51
|
+
수정 후 빌드 재실행:
|
|
52
|
+
```bash
|
|
53
|
+
./mvnw compile -q --no-transfer-progress 2>&1 || \
|
|
54
|
+
./gradlew compileJava -q 2>&1
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
성공 시:
|
|
58
|
+
```bash
|
|
59
|
+
./mvnw test -q --no-transfer-progress 2>&1 | tail -10 || \
|
|
60
|
+
./gradlew test -q 2>&1 | tail -10
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Output
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
Build Fix — <날짜>
|
|
67
|
+
Build Tool: Maven | Gradle
|
|
68
|
+
Initial Status: FAILED
|
|
69
|
+
Errors Fixed: N
|
|
70
|
+
Files Modified: <파일 목록>
|
|
71
|
+
Final Status: SUCCESS | FAILED
|
|
72
|
+
```
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Analyze coverage, identify gaps, and generate missing tests toward the target threshold.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Test Coverage
|
|
6
|
+
|
|
7
|
+
Analyze test coverage, identify gaps, and generate missing tests to reach 80%+ coverage.
|
|
8
|
+
|
|
9
|
+
## Step 1: Detect Test Framework
|
|
10
|
+
|
|
11
|
+
| Indicator | Coverage Command |
|
|
12
|
+
|-----------|-----------------|
|
|
13
|
+
| `jest.config.*` or `package.json` jest | `npx jest --coverage --coverageReporters=json-summary` |
|
|
14
|
+
| `vitest.config.*` | `npx vitest run --coverage` |
|
|
15
|
+
| `pytest.ini` / `pyproject.toml` pytest | `pytest --cov=src --cov-report=json` |
|
|
16
|
+
| `Cargo.toml` | `cargo llvm-cov --json` |
|
|
17
|
+
| `pom.xml` with JaCoCo | `mvn test jacoco:report` |
|
|
18
|
+
| `go.mod` | `go test -coverprofile=coverage.out ./...` |
|
|
19
|
+
|
|
20
|
+
## Step 2: Analyze Coverage Report
|
|
21
|
+
|
|
22
|
+
1. Run the coverage command
|
|
23
|
+
2. Parse the output (JSON summary or terminal output)
|
|
24
|
+
3. List files **below 80% coverage**, sorted worst-first
|
|
25
|
+
4. For each under-covered file, identify:
|
|
26
|
+
- Untested functions or methods
|
|
27
|
+
- Missing branch coverage (if/else, switch, error paths)
|
|
28
|
+
- Dead code that inflates the denominator
|
|
29
|
+
|
|
30
|
+
## Step 3: Generate Missing Tests
|
|
31
|
+
|
|
32
|
+
For each under-covered file, generate tests following this priority:
|
|
33
|
+
|
|
34
|
+
1. **Happy path** — Core functionality with valid inputs
|
|
35
|
+
2. **Error handling** — Invalid inputs, missing data, network failures
|
|
36
|
+
3. **Edge cases** — Empty arrays, null/undefined, boundary values (0, -1, MAX_INT)
|
|
37
|
+
4. **Branch coverage** — Each if/else, switch case, ternary
|
|
38
|
+
|
|
39
|
+
### Test Generation Rules
|
|
40
|
+
|
|
41
|
+
- Place tests adjacent to source: `foo.ts` → `foo.test.ts` (or project convention)
|
|
42
|
+
- Use existing test patterns from the project (import style, assertion library, mocking approach)
|
|
43
|
+
- Mock external dependencies (database, APIs, file system)
|
|
44
|
+
- Each test should be independent — no shared mutable state between tests
|
|
45
|
+
- Name tests descriptively: `test_create_user_with_duplicate_email_returns_409`
|
|
46
|
+
|
|
47
|
+
## Step 4: Verify
|
|
48
|
+
|
|
49
|
+
1. Run the full test suite — all tests must pass
|
|
50
|
+
2. Re-run coverage — verify improvement
|
|
51
|
+
3. If still below 80%, repeat Step 3 for remaining gaps
|
|
52
|
+
|
|
53
|
+
## Step 5: Report
|
|
54
|
+
|
|
55
|
+
Show before/after comparison:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
Coverage Report
|
|
59
|
+
──────────────────────────────
|
|
60
|
+
File Before After
|
|
61
|
+
src/services/auth.ts 45% 88%
|
|
62
|
+
src/utils/validation.ts 32% 82%
|
|
63
|
+
──────────────────────────────
|
|
64
|
+
Overall: 67% 84% PASS:
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Focus Areas
|
|
68
|
+
|
|
69
|
+
- Functions with complex branching (high cyclomatic complexity)
|
|
70
|
+
- Error handlers and catch blocks
|
|
71
|
+
- Utility functions used across the codebase
|
|
72
|
+
- API endpoint handlers (request → response flow)
|
|
73
|
+
- Edge cases: null, undefined, empty string, empty array, zero, negative numbers
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: 빌드 에러 자동 수정 — 빌드 도구를 감지하고 컴파일/테스트 오류를 수정합니다
|
|
3
|
+
argument-hint: [blank to auto-detect build errors]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Build Fix
|
|
7
|
+
|
|
8
|
+
**Input**: $ARGUMENTS
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Step 1 — DETECT
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
[ -f "pom.xml" ] && echo "Maven" || \
|
|
16
|
+
[ -f "build.gradle.kts" ] && echo "Gradle KTS" || \
|
|
17
|
+
[ -f "build.gradle" ] && echo "Gradle" || \
|
|
18
|
+
echo "No build file found"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Java 버전 확인:
|
|
22
|
+
```bash
|
|
23
|
+
java -version 2>&1 | head -1
|
|
24
|
+
./mvnw --version 2>&1 | head -1 || ./gradlew --version 2>&1 | head -3
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Step 2 — BUILD
|
|
28
|
+
|
|
29
|
+
Maven:
|
|
30
|
+
```bash
|
|
31
|
+
./mvnw clean compile -q --no-transfer-progress 2>&1
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Gradle:
|
|
35
|
+
```bash
|
|
36
|
+
./gradlew clean compileJava -q 2>&1
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Step 3 — ANALYZE ERRORS
|
|
40
|
+
|
|
41
|
+
오류 없으면: "빌드 성공. 수정할 오류가 없습니다."
|
|
42
|
+
|
|
43
|
+
오류 있으면 분류:
|
|
44
|
+
- **컴파일 오류** → `java-build-resolver` 에이전트
|
|
45
|
+
- **의존성 오류** → `java-build-resolver` 에이전트
|
|
46
|
+
- **테스트 실패** → 별도 분석 후 `java-build-resolver`
|
|
47
|
+
|
|
48
|
+
## Step 4 — FIX
|
|
49
|
+
|
|
50
|
+
`java-build-resolver` 에이전트에 위임:
|
|
51
|
+
- 오류 메시지 전달
|
|
52
|
+
- 빌드 도구 명시
|
|
53
|
+
- 영향 파일 목록 포함
|
|
54
|
+
|
|
55
|
+
**원칙**: 증상을 억제하지 않고 근본 원인을 수정. `@SuppressWarnings` 금지.
|
|
56
|
+
|
|
57
|
+
## Step 5 — VERIFY
|
|
58
|
+
|
|
59
|
+
수정 후:
|
|
60
|
+
```bash
|
|
61
|
+
# 컴파일 확인
|
|
62
|
+
./mvnw compile -q --no-transfer-progress 2>&1 | tail -3 || \
|
|
63
|
+
./gradlew compileJava -q 2>&1 | tail -3
|
|
64
|
+
|
|
65
|
+
# 테스트 확인
|
|
66
|
+
./mvnw test -q --no-transfer-progress 2>&1 | tail -5 || \
|
|
67
|
+
./gradlew test -q 2>&1 | tail -5
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Stop Condition
|
|
71
|
+
|
|
72
|
+
3회 시도 후에도 실패 시:
|
|
73
|
+
- 오류 상세 내용 보고
|
|
74
|
+
- 수동 개입 요청
|
|
75
|
+
- 아키텍처 변경이 필요한 경우 `planner` 에이전트 권장
|