From bf31dfc3f28ee94790af83d9ad8fca634b8abb1f Mon Sep 17 00:00:00 2001 From: Whale0928 Date: Sat, 25 Jul 2026 18:30:25 +0900 Subject: [PATCH 1/9] =?UTF-8?q?docs:=20GSL=20=EA=B0=9C=ED=8E=B8=20define?= =?UTF-8?q?=20=EB=AC=B8=EC=84=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- plan/gsl-overhaul.md | 59 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 plan/gsl-overhaul.md diff --git a/plan/gsl-overhaul.md b/plan/gsl-overhaul.md new file mode 100644 index 000000000..e3bd584fa --- /dev/null +++ b/plan/gsl-overhaul.md @@ -0,0 +1,59 @@ +# Plan: GSL 스킬셋 개편 + +## Overview + +GSL(Guided Software Lifecycle) 9개 스킬을 이 저장소 전용으로 대폭 개편한다. 범용 프레임워크의 무게를 걷어내고, 두 가지 설계 철학을 중심축으로 재작성한다. + +**철학 1 — WHAT/HOW 분리.** `/define`(무엇을)과 `/plan`(어떻게)은 합치지 않는다. WHAT은 사람의 의도를 끌어내야 해서 재생성이 비싸고, HOW는 에이전트가 다시 짜면 되므로 재생성이 싸다. 사람의 협의 비용은 WHAT에 집중 투자한다. WHAT이 명확히 협의되면 이후 실패는 HOW의 실패로 격리되어 회복 가능해진다. + +**철학 2 — Task/Slice 이원화 (정육 모델).** Task는 문제의 자연 관절(의존성·모듈 경계·리뷰 단위)을 따라 자르는 부위 분해로, 올바른 답이 적고 틀리면 비싸므로 사용자 승인 대상이다. Slice는 그 부위를 어떻게 썰지에 대한 실행 선택으로, 정답이 여럿이고 틀려도 싸므로 에이전트 전권이다. 순서는 분해 → 의존성 정렬 → 크기 검증이며, 크기는 절단 기준이 아니라 검산이다. + +**신규 축 — Execution Mode (define 후 위임 계약).** define 승인 게이트를 계약 서명으로 격상한다. WHAT 승인 시 이후 단계의 위임 수준을 함께 선언하고 plan 문서에 기록한다. 기본값은 step-by-step(현행 단계별 승인)이며, delegated 선언 시 plan→implement→test→verify→commit(→push→PR·체인지로그, scope에 따라)을 자율 진행하되 stop-conditions(가정 붕괴, verify 반복 실패, 선언 범위 밖 파괴적 행동)에서는 무조건 멈춘다. + +### Assumptions + +1. GSL은 이 저장소 전용이다. 범용성(타 언어 references, Polyglot Mode, Superpowers 연동)은 제거한다. 다른 프로젝트에 쓰려면 그때 복사해 변형한다. — 사용자 확정 +2. Execution Mode 기본값은 step-by-step이다. delegated는 define 승인 시 명시적으로 선언한 경우에만 적용된다. — 사용자 확정 +3. `/next-flow`는 삭제한다. 위임 모드가 마찰 문제를 근본 해결하고, 다음 명령 제안은 각 스킬 종료 보고에 이미 있다. — 사용자 확정 +4. `/scan-conventions`는 완전 은퇴한다. 스킬 삭제, `plan/conventions.md` 정리. 관습의 유일 소유자는 CLAUDE.md/AGENTS.md다. `/define`의 하드 게이트는 conventions.md 존재 확인 대신 지침 존재로 대체한다. — 사용자 확정 +5. `.claude/skills` ↔ `.agents/skills` 바이트 동일 미러 규칙은 유지한다. 모든 변경은 양쪽에 짝으로 적용한다. +6. 지침(CLAUDE.md/AGENTS.md)의 "GSL Runtime Boundary Rules" 섹션은 개편 후 Execution Mode 규칙으로 대체된다. HARD STOP 보일러플레이트(15줄×9회)는 지침의 단일 섹션으로 통합하고 스킬에는 한 줄 참조만 남긴다. +7. plan 문서 생명주기(`plan/` → `complete/`/`stale/`)와 "기능 하나 = 문서 하나" 원칙은 유지한다. +8. 유지 대상: Common Rationalizations 표, Fake/InMemory-first 정책, verify L1/L2/L3, 수직 슬라이스 원칙. +9. 이 개편 작업 자체는 현 브랜치(`chore/sync-agent`)가 아닌 별도 브랜치·별도 PR로 진행한다. PR #672는 이미 11커밋 규모라 더 얹지 않는다. — 승인 시 확정 필요 +10. 이 개편 작업 자체의 Execution Mode는 step-by-step이다. — 승인 시 확정 필요 + +### Success Criteria + +- 스킬 수 9 → 7 (`next-flow`, `scan-conventions` 삭제, 양쪽 미러) +- references에서 `python.md`, `go.md`, `cli.md`, `library.md`, `worker-daemon.md` 제거. 잔존은 `java-spring.md`, `bottlenote-patterns.md`, `web-api.md`, `batch.md`, 테스트 `java.md`, verify `java-gradle.md` +- 전체 스킬에서 "Superpowers" 언급 0건, "Polyglot" 언급 0건 +- HARD STOP 보일러플레이트가 스킬 본문에서 제거되고 지침의 단일 섹션 참조로 대체됨 +- `/define`에 Execution Mode 계약 정의: 선언 형식(mode/scope/push/pr/stop-conditions), plan 문서 기록 위치, 승인 게이트 문구 +- `/define`에 재개봉 프로토콜 명시: 구현 중 가정이 깨지면 STOP → define 수정 → 재승인 (WHAT 변경 시) +- `/plan`이 분해 → 의존성 정렬 → 크기 검증 순서로 재작성됨. Task 계약은 수용 기준이며 `Files:`는 참고 추정(advisory)으로 강등 +- `/implement`에 delegated 모드 분기: HARD STOP 대신 체크포인트 보고(Progress Log 기록 + 진행 보고), stop-conditions 준수 +- PR 오픈·체인지로그 작성이 생명주기 공식 단계로 정의됨 (이 저장소의 "PR 본문 = 체인지로그" 관례 명시, delegated scope 항목) +- 각 스킬 description에 발화 트리거 외 산출물·상태 기반 트리거 추가 +- 검증: `diff -rq .agents/skills .claude/skills` 일치, 지침 정규화 diff 일치, 지침 Skills 표가 7개 스킬과 정합 +- 스킬 전체 용량이 현행 약 197KB에서 절반 이하로 감소 + +### Impact Scope + +- `.claude/skills/` + `.agents/skills/` (미러 양쪽): 스킬 삭제 2종, 재작성 7종(define/plan/implement/test/verify/debug/self-review), references 삭제 약 10파일 +- `CLAUDE.md` / `AGENTS.md`: GSL Runtime Boundary Rules 섹션 → Execution Mode 규칙으로 대체, Skills 표 9→7 갱신, `/define` 게이트 서술 변경 +- `plan/conventions.md`: 삭제 또는 `stale/` 이동 +- 운영 코드(Java/Kotlin) 변경 없음. Gradle 빌드·테스트 영향 없음 +- 브랜치: 별도 브랜치·별도 PR (Assumption 9) + +## Execution Mode + +- mode: step-by-step (Assumption 10, 승인 시 확정) + +## Tasks + +(/plan에서 작성) + +## Progress Log + +(비어 있음 — /implement에서 기록) From 88a7f006be9a0ea3a6a4aa3348ffcfe9e6180f31 Mon Sep 17 00:00:00 2001 From: Whale0928 Date: Sat, 25 Jul 2026 18:32:50 +0900 Subject: [PATCH 2/9] =?UTF-8?q?docs:=20GSL=20=EA=B0=9C=ED=8E=B8=20?= =?UTF-8?q?=ED=83=9C=EC=8A=A4=ED=81=AC=20=EB=B6=84=ED=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- plan/gsl-overhaul.md | 69 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 2 deletions(-) diff --git a/plan/gsl-overhaul.md b/plan/gsl-overhaul.md index e3bd584fa..720fd266e 100644 --- a/plan/gsl-overhaul.md +++ b/plan/gsl-overhaul.md @@ -48,11 +48,76 @@ GSL(Guided Software Lifecycle) 9개 스킬을 이 저장소 전용으로 대폭 ## Execution Mode -- mode: step-by-step (Assumption 10, 승인 시 확정) +- mode: step-by-step (Assumption 10, 확정) +- 검증 방법론: 서브에이전트 시나리오 검증 (사용자 지시로 추가) + +## 검증 방법론 — 서브에이전트 시나리오 검증 + +재작성된 스킬 텍스트가 실제로 의도한 행동을 유도하는지, 합성 시나리오를 만들어 서브에이전트로 롤플레이 검증한다. + +- **방식**: 서브에이전트에게 재작성된 SKILL.md 전문과 가상의 기능 요청(예: "시음 노트에 온도 기록 추가")을 주고, 그 스킬을 따르는 에이전트로서 어떻게 행동할지 서술하게 한다. 지시가 모호한 곳, 상충하는 곳, 폭주(승인 없이 진행)를 허용하는 구멍을 보고받는다. +- **함정 시나리오 포함**: 정상 경로만이 아니라 위반 유도 시나리오를 섞는다 — 수평 분해를 유도하는 요청, 애매한 "계속해줘", 가정이 깨지는 중간 발견, delegated 모드에서 stop-condition 상황. +- **수용 기준**: 치명 결함(승인 게이트 우회 가능, stop-condition 무시 가능, 지시 상충) 0건. 경미한 모호함은 기록 후 수정 여부 판단. +- 서브에이전트는 스킬 텍스트만 보고 판단한다 (이 대화의 맥락 없이) — 신규 세션이 읽었을 때의 실효성을 검증하는 것이 목적이다. ## Tasks -(/plan에서 작성) +### Task 1: 삭제 — 스킬 2종·미사용 references·conventions 은퇴 +- Acceptance: `next-flow`/`scan-conventions` 스킬이 미러 양쪽에서 삭제됨. references에서 python/go/cli/library/worker-daemon 삭제. `plan/conventions.md` 은퇴(stale 이동). 잔존 스킬 7종은 아직 수정 전이어도 무방 +- Verification: `diff -rq .agents/skills .claude/skills` 일치, 삭제 대상 파일 0건 확인, 잔존 스킬에서 삭제된 스킬 참조가 깨지는 지점 목록화(다음 Task 입력) +- Files (advisory): 미러 양쪽 약 24파일 삭제 + plan/conventions.md 이동. 브랜치 분리(`chore/gsl-overhaul` 생성, define 커밋 이관) 포함 +- Size: M +- Status: [ ] not done + +### Task 2: Execution Mode 계약 + 지침 갱신 +- Acceptance: 지침(CLAUDE.md/AGENTS.md)의 "GSL Runtime Boundary Rules" 섹션이 Execution Mode 규칙(기본 step-by-step, delegated 선언 형식, stop-conditions, HARD STOP 통합 규정)으로 대체됨. Skills 표 9→7. PR 본문=체인지로그 관례가 delegated scope 정의에 포함됨 +- Verification: 지침 정규화 diff 일치, 표의 7개 커맨드가 실제 스킬 디렉터리와 정합 +- Files (advisory): CLAUDE.md, AGENTS.md +- Size: S +- Status: [ ] not done + +### Checkpoint: after Tasks 1-2 +- [ ] 미러 diff 일치, 지침 diff 일치 +- [ ] 잔존 스킬의 깨진 참조 목록 확보 + +### Task 3: /define 재작성 +- Acceptance: WHAT/HOW 분리 철학 명문화, 하드 게이트를 conventions.md → 지침 존재로 교체, 승인 게이트에 Execution Mode 선언 절차 추가, 재개봉 프로토콜(가정 붕괴 시 STOP→수정→재승인) 명시, HARD STOP 보일러플레이트 제거(지침 참조 한 줄), 트리거에 상태 기반 조건 추가 +- Verification: 서브에이전트 시나리오 2건 — (a) 정상: 신규 기능 요청 → 가정·성공기준 도출 및 모드 선언까지, (b) 함정: 모호한 요청에 가정 확인 없이 진행하도록 유도 → 게이트가 막는지 +- Files (advisory): define/SKILL.md × 미러 2 +- Size: S +- Status: [ ] not done + +### Task 4: /plan 재작성 +- Acceptance: 분해(자연 관절)→의존성 정렬→크기 검증 순서로 재구성, Task=승인 대상/Slice=에이전트 전권 구분 명문화, `Files:`를 advisory로 강등, Superpowers 블록 제거, HARD STOP 제거 +- Verification: 서브에이전트 시나리오 2건 — (a) 정상: Task 3 시나리오의 define 산출물로 분해, (b) 함정: 수평 분해가 자연스러워 보이는 요청 → 수직 강제가 작동하는지 +- Files (advisory): plan/SKILL.md × 미러 2 +- Size: S +- Status: [ ] not done + +### Task 5: /implement 재작성 +- Acceptance: Execution Mode 분기 구현 — step-by-step은 Task별 HARD STOP 유지, delegated는 체크포인트 보고(Progress Log 기록+진행 보고)로 대체하되 stop-conditions 준수. Polyglot Mode·언어 fallback 제거, java-spring 고정. PR·체인지로그 단계를 Phase 4 이후 공식 꼬리로 정의 +- Verification: 서브에이전트 시나리오 3건 — (a) step 모드 Task 완료 후 정지 확인, (b) delegated 모드 연속 진행+보고 확인, (c) 함정: delegated 중 define 가정이 깨지는 발견 → stop-condition 발동 확인 +- Files (advisory): implement/SKILL.md × 미러 2 +- Size: M +- Status: [ ] not done + +### Checkpoint: after Tasks 3-5 +- [ ] 미러 diff 일치 +- [ ] 시나리오 치명 결함 0건 (발견 시 해당 Task로 되돌아가 수정) + +### Task 6: test/verify/debug/self-review 정리 +- Acceptance: 4개 스킬에서 HARD STOP 보일러플레이트 제거→지침 참조, Superpowers·타 언어 분기 제거, java 고정, 트리거에 상태 기반 조건 추가. 워크플로 본문은 유지(이미 유효) +- Verification: 4파일×미러 grep — "Superpowers" 0건, "HARD STOP" 상세 블록 0건, python/go 분기 0건 +- Files (advisory): 4 SKILL.md × 미러 2 = 8파일 +- Size: M +- Status: [ ] not done + +### Task 7: 종합 검증 및 마감 +- Acceptance: Success Criteria 13개 전 항목 체크리스트 대조 통과. E2E 시나리오 2건(step-by-step 전체 사이클 1건, delegated 전체 사이클 1건) 치명 결함 0건. 용량 측정 기록 +- Verification: `diff -rq` 미러 일치, 지침 정규화 diff 일치, `du -sk` 전후 비교, E2E 서브에이전트 보고서 +- Files (advisory): 수정 없음(검증 전용), 필요 시 발견 결함 수정 +- Size: S +- Status: [ ] not done ## Progress Log From 14b3afc9d9ae83736b59f7395acf9967cffe8276 Mon Sep 17 00:00:00 2001 From: Whale0928 Date: Sat, 25 Jul 2026 18:35:58 +0900 Subject: [PATCH 3/9] =?UTF-8?q?chore:=20GSL=20=EB=AF=B8=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=20=EC=8A=A4=ED=82=AC=20=EB=B0=8F=20references=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- .../implement/references/languages/go.md | 488 -------------- .../implement/references/languages/python.md | 605 ------------------ .../skills/implement/references/types/cli.md | 215 ------- .../implement/references/types/library.md | 237 ------- .../references/types/worker-daemon.md | 149 ----- .agents/skills/next-flow/SKILL.md | 147 ----- .agents/skills/scan-conventions/SKILL.md | 229 ------- .agents/skills/test/references/testing/go.md | 383 ----------- .../skills/test/references/testing/python.md | 477 -------------- .agents/skills/verify/references/verify/go.md | 169 ----- .../skills/verify/references/verify/python.md | 174 ----- .../implement/references/languages/go.md | 488 -------------- .../implement/references/languages/python.md | 605 ------------------ .../skills/implement/references/types/cli.md | 215 ------- .../implement/references/types/library.md | 237 ------- .../references/types/worker-daemon.md | 149 ----- .claude/skills/next-flow/SKILL.md | 147 ----- .claude/skills/scan-conventions/SKILL.md | 229 ------- .claude/skills/test/references/testing/go.md | 383 ----------- .../skills/test/references/testing/python.md | 477 -------------- .claude/skills/verify/references/verify/go.md | 169 ----- .../skills/verify/references/verify/python.md | 174 ----- plan/gsl-overhaul.md | 4 +- plan/{ => stale}/conventions.md | 0 24 files changed, 3 insertions(+), 6547 deletions(-) delete mode 100644 .agents/skills/implement/references/languages/go.md delete mode 100644 .agents/skills/implement/references/languages/python.md delete mode 100644 .agents/skills/implement/references/types/cli.md delete mode 100644 .agents/skills/implement/references/types/library.md delete mode 100644 .agents/skills/implement/references/types/worker-daemon.md delete mode 100644 .agents/skills/next-flow/SKILL.md delete mode 100644 .agents/skills/scan-conventions/SKILL.md delete mode 100644 .agents/skills/test/references/testing/go.md delete mode 100644 .agents/skills/test/references/testing/python.md delete mode 100644 .agents/skills/verify/references/verify/go.md delete mode 100644 .agents/skills/verify/references/verify/python.md delete mode 100644 .claude/skills/implement/references/languages/go.md delete mode 100644 .claude/skills/implement/references/languages/python.md delete mode 100644 .claude/skills/implement/references/types/cli.md delete mode 100644 .claude/skills/implement/references/types/library.md delete mode 100644 .claude/skills/implement/references/types/worker-daemon.md delete mode 100644 .claude/skills/next-flow/SKILL.md delete mode 100644 .claude/skills/scan-conventions/SKILL.md delete mode 100644 .claude/skills/test/references/testing/go.md delete mode 100644 .claude/skills/test/references/testing/python.md delete mode 100644 .claude/skills/verify/references/verify/go.md delete mode 100644 .claude/skills/verify/references/verify/python.md rename plan/{ => stale}/conventions.md (100%) diff --git a/.agents/skills/implement/references/languages/go.md b/.agents/skills/implement/references/languages/go.md deleted file mode 100644 index eeb3f5cc6..000000000 --- a/.agents/skills/implement/references/languages/go.md +++ /dev/null @@ -1,488 +0,0 @@ -# Language: go - -Battle-tested patterns for Go 1.22+ services. Use this with matching `types/*.md`, `testing/go.md`, and `verify/go.md`. - -> Default assumption: HTTP API or worker service with explicit layers, standard `context.Context`, and tests built around fakes or testcontainers. - -## Module Layout - -Recommended single-module layout: - -```text -. -├── cmd/ -│ └── api/ -│ └── main.go # process entrypoint only -├── internal/ -│ └── user/ -│ ├── handler/ # transport adapters: HTTP, gRPC, queue -│ ├── usecase/ # business logic and consumer-owned interfaces -│ └── repository/ # DB/cache implementations -├── pkg/ # public library packages only when truly reusable -├── sql/ # migrations or sqlc query files -├── go.mod -└── go.sum -``` - -Rules: -- `cmd/{app}/main.go` wires configuration, logger, DB, repositories, usecases, and server. -- `internal/` is the default for product code. Go enforces import privacy: packages outside the parent tree cannot import `internal/...`. -- `pkg/` is not a dumping ground. Use it only for stable, reusable libraries that are safe for external import. -- Keep domain slices under `internal/{domain}/...` unless the project already uses technical slices. - -**Existing `pkg/`-centric projects.** Some established Go projects place product code under `pkg/` instead of `internal/`. This may predate the modern `internal/` convention or be a deliberate choice (e.g., exposing internal packages for tooling, a library that doubles as a service). Treat this as a **REFINEMENT** of the convention, NOT a CONFLICT: - -- Respect the existing layout. Add new code where the project's peers already live. -- Do NOT migrate `pkg/X` → `internal/X` as part of a feature change. Migration is a separate refactor requiring explicit user approval and a dedicated plan. -- The Phase 0 hard gate of `/implement` still applies. Just resolve `{package-root}` to the project's actual choice (`internal/` OR `pkg/`). - -## Layered Structure - -Default flow: - -```text -handler -> usecase -> repository implementation -HTTP business sqlc/sqlx/GORM/Redis/external client -``` - -Responsibilities: -- `handler`: decode transport input, validate DTO, call usecase, map errors to transport response. -- `usecase`: own business rules, transactions, idempotency, authorization decisions, and repository interfaces. -- `repository`: implement persistence details. No HTTP DTOs and no transport-specific errors. - -Do not let repository code decide API status codes. Do not let handlers reach into SQL helpers directly. - -## Interface Boundary - -Go interface ownership is opposite to many Java/Spring designs. - -Rule: define the interface in the consumer package, usually `usecase`, not in the producer package. - -```text -[OK] internal/user/usecase.UserRepository interface -[OK] internal/user/repository.PostgresRepository implements usecase.UserRepository -[NO] internal/user/repository.Repository exported by producer and imported everywhere -``` - -Why: -- Consumers declare only the methods they need. -- Tests can provide small fakes without implementing producer-wide interfaces. -- Producer packages stay concrete and simple. - -## DTO Pattern - -Use plain structs with tags. Keep DTOs separate from domain entities. - -```go -package handler - -type RegisterUserRequest struct { - Email string `json:"email" validate:"required,email"` - Name string `json:"name" validate:"required,min=1,max=80"` -} - -type RegisterUserResponse struct { - ID string `json:"id"` - Email string `json:"email"` - Name string `json:"name"` -} -``` - -Validation: -- Use `github.com/go-playground/validator/v10` at the transport boundary. -- Convert valid DTOs into usecase input structs. -- Do not pass HTTP request DTOs into repository packages. - -```go -package usecase - -type RegisterUserInput struct { - Email string - Name string -} -``` - -## Error Handling - -Use explicit errors. Libraries return errors; applications decide how to present them. - -Recommended layers: -- Sentinel errors for stable categories: `ErrUserNotFound`, `ErrEmailAlreadyUsed`. -- Domain error type when the caller needs structured details. -- `errors.Is` for category checks. -- `errors.As` for extracting typed errors. - -```go -package usecase - -import "errors" - -var ( - ErrUserNotFound = errors.New("user not found") - ErrEmailAlreadyUsed = errors.New("email already used") -) - -type ValidationError struct { - Field string - Message string -} - -func (e *ValidationError) Error() string { - return e.Field + ": " + e.Message -} -``` - -Wrap lower-level errors with context: - -```go -if err := r.queries.CreateUser(ctx, params); err != nil { - return User{}, fmt.Errorf("create user: %w", err) -} -``` - -Map errors at the edge: - -```go -switch { -case errors.Is(err, usecase.ErrEmailAlreadyUsed): - http.Error(w, "email already used", http.StatusConflict) -default: - http.Error(w, "internal server error", http.StatusInternalServerError) -} -``` - -Never `panic` for normal business errors. - -## Context Propagation - -Every I/O-capable method receives `context.Context` as the first argument. - -```go -func (s *Service) Register(ctx context.Context, in RegisterUserInput) (User, error) -func (r *PostgresRepository) Save(ctx context.Context, user User) error -``` - -Rules: -- First parameter: `ctx context.Context`. -- Do not store `context.Context` in structs. -- Do not pass `nil`; use `context.Background()` only at process boundaries or tests. -- Propagate request cancellation to DB, Redis, HTTP clients, and queue clients. -- Avoid global state; inject dependencies through constructors. - -## Repository Pattern - -Define the repository interface in the usecase package: - -```go -package usecase - -import "context" - -type UserRepository interface { - Save(ctx context.Context, user User) error - FindByEmail(ctx context.Context, email string) (User, error) - FindByID(ctx context.Context, id string) (User, error) -} -``` - -Implement it in the repository package: - -```go -package repository - -type PostgresRepository struct { - db *sql.DB - queries *dbgen.Queries // sqlc generated package -} - -func NewPostgresRepository(db *sql.DB) *PostgresRepository { - return &PostgresRepository{db: db, queries: dbgen.New(db)} -} -``` - -Implementation options: -- `sqlc`: preferred when SQL shape matters and compile-time query types are valuable. -- `sqlx`: good for hand-written SQL with lightweight scanning helpers. -- `GORM`: acceptable when the project already standardizes on ORM behavior; keep it behind repository interfaces. - -Transaction handling belongs in usecase or a small transaction manager abstraction, not hidden deep inside random repository methods. - -## Dependency Injection - -Default: manual constructor injection. - -```go -userRepo := repository.NewPostgresRepository(db) -userSvc := usecase.NewService(userRepo, clock, ids) -userHandler := handler.NewHandler(userSvc, validator.New()) -``` - -Constructor rules: -- Accept interfaces only when the package consumes behavior. -- Return concrete structs unless there is a strong reason to hide them. -- Keep constructors boring and side-effect free. - -Use `wire`, `fx`, or another DI container only when the project is large enough that manual wiring is demonstrably noisy. Do not introduce a DI framework for a few constructors. - -## Folder Convention - -Preferred domain-first layout: - -```text -internal/user/ -├── handler/ -│ ├── dto.go -│ └── http.go -├── usecase/ -│ ├── errors.go -│ ├── repository.go -│ ├── service.go -│ └── user.go -└── repository/ - ├── postgres.go - └── mapper.go -``` - -Keep generated packages separate when using sqlc: - -```text -internal/user/repository/dbgen/ # generated by sqlc -sql/queries/user.sql -sql/schema/001_create_users.sql -``` - -## Code Example: User Registration - -### Entity - -```go -package usecase - -import ( - "strings" - "time" -) - -type User struct { - ID string - Email string - Name string - CreatedAt time.Time -} - -func NewUser(id, email, name string, now time.Time) (User, error) { - email = strings.TrimSpace(strings.ToLower(email)) - name = strings.TrimSpace(name) - - if email == "" { - return User{}, &ValidationError{Field: "email", Message: "email is required"} - } - if name == "" { - return User{}, &ValidationError{Field: "name", Message: "name is required"} - } - - return User{ID: id, Email: email, Name: name, CreatedAt: now}, nil -} -``` - -### Repository Interface - -```go -package usecase - -import "context" - -type UserRepository interface { - Save(ctx context.Context, user User) error - FindByEmail(ctx context.Context, email string) (User, error) -} -``` - -### Repository Implementation - -```go -package repository - -import ( - "context" - "database/sql" - "errors" - "fmt" - - "example.com/app/internal/user/usecase" -) - -type PostgresRepository struct { - db *sql.DB -} - -func NewPostgresRepository(db *sql.DB) *PostgresRepository { - return &PostgresRepository{db: db} -} - -func (r *PostgresRepository) Save(ctx context.Context, user usecase.User) error { - const q = `insert into users (id, email, name, created_at) values ($1, $2, $3, $4)` - if _, err := r.db.ExecContext(ctx, q, user.ID, user.Email, user.Name, user.CreatedAt); err != nil { - if isUniqueViolation(err) { - return usecase.ErrEmailAlreadyUsed - } - return fmt.Errorf("save user: %w", err) - } - return nil -} - -func (r *PostgresRepository) FindByEmail(ctx context.Context, email string) (usecase.User, error) { - const q = `select id, email, name, created_at from users where email = $1` - var user usecase.User - err := r.db.QueryRowContext(ctx, q, email).Scan(&user.ID, &user.Email, &user.Name, &user.CreatedAt) - if errors.Is(err, sql.ErrNoRows) { - return usecase.User{}, usecase.ErrUserNotFound - } - if err != nil { - return usecase.User{}, fmt.Errorf("find user by email: %w", err) - } - return user, nil -} -``` - -### Usecase - -```go -package usecase - -import ( - "context" - "errors" - "fmt" - "time" -) - -type IDGenerator interface { - NewID() string -} - -type Clock interface { - Now() time.Time -} - -type Service struct { - repo UserRepository - ids IDGenerator - clk Clock -} - -func NewService(repo UserRepository, ids IDGenerator, clk Clock) *Service { - return &Service{repo: repo, ids: ids, clk: clk} -} - -func (s *Service) Register(ctx context.Context, in RegisterUserInput) (User, error) { - if _, err := s.repo.FindByEmail(ctx, in.Email); err == nil { - return User{}, ErrEmailAlreadyUsed - } else if !errors.Is(err, ErrUserNotFound) { - return User{}, fmt.Errorf("check duplicate email: %w", err) - } - - user, err := NewUser(s.ids.NewID(), in.Email, in.Name, s.clk.Now()) - if err != nil { - return User{}, err - } - if err := s.repo.Save(ctx, user); err != nil { - return User{}, err - } - return user, nil -} -``` - -### HTTP Handler - -```go -package handler - -import ( - "encoding/json" - "errors" - "net/http" - - "github.com/go-playground/validator/v10" - - "example.com/app/internal/user/usecase" -) - -type Handler struct { - svc *usecase.Service - validate *validator.Validate -} - -func NewHandler(svc *usecase.Service, validate *validator.Validate) *Handler { - return &Handler{svc: svc, validate: validate} -} - -func (h *Handler) Register(w http.ResponseWriter, r *http.Request) { - var req RegisterUserRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - http.Error(w, "invalid json", http.StatusBadRequest) - return - } - if err := h.validate.Struct(req); err != nil { - http.Error(w, "invalid request", http.StatusBadRequest) - return - } - - user, err := h.svc.Register(r.Context(), usecase.RegisterUserInput{ - Email: req.Email, - Name: req.Name, - }) - if err != nil { - writeRegisterError(w, err) - return - } - - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusCreated) - _ = json.NewEncoder(w).Encode(RegisterUserResponse{ - ID: user.ID, - Email: user.Email, - Name: user.Name, - }) -} - -func writeRegisterError(w http.ResponseWriter, err error) { - switch { - case errors.Is(err, usecase.ErrEmailAlreadyUsed): - http.Error(w, "email already used", http.StatusConflict) - default: - http.Error(w, "internal server error", http.StatusInternalServerError) - } -} -``` - -## Build and Tooling - -Common commands: - -```bash -go test ./... -go test ./... -tags=integration -go test -race ./... -go vet ./... -golangci-lint run ./... -``` - -Project defaults: -- Run commands from module root. -- Prefer `./...` scope for verification. -- Keep `go.mod` minimal; unused dependencies are a smell. -- Run `gofmt` and `goimports` before review. - -## Anti-Patterns - -Avoid: -- Global mutable variables for DB, logger, config, or clients. -- `panic` in libraries or business logic for recoverable errors. -- Missing `context.Context` on I/O paths. -- Defining broad interfaces in producer packages. -- Passing HTTP DTOs into usecase or repository packages. -- Creating `pkg/` packages before there is a real external reuse need. -- Hiding transactions inside unrelated helper methods. -- Returning raw SQL driver errors across usecase boundaries. - diff --git a/.agents/skills/implement/references/languages/python.md b/.agents/skills/implement/references/languages/python.md deleted file mode 100644 index af0e68c03..000000000 --- a/.agents/skills/implement/references/languages/python.md +++ /dev/null @@ -1,605 +0,0 @@ -# Language: python - -Battle-tested patterns for Python 3.12+ web-api / service code. Default recommendation: FastAPI + Pydantic v2 + SQLAlchemy 2.x async + pytest. - -> Use this with the matching `types/*.md` (typically `web-api.md`) and `testing/python.md`. - -## Framework Choice - -### Default — FastAPI - -Use FastAPI for new Python web-api work when the project needs: -- async I/O support -- automatic OpenAPI generation -- explicit request / response DTOs -- lightweight application structure -- clear dependency injection via `Depends()` - -FastAPI keeps API boundaries explicit. It fits service-oriented code where domain logic lives outside route handlers. - -### Alternative — Django - -Use Django when the project needs: -- admin UI out of the box -- full-stack server-rendered features -- Django ORM and ecosystem conventions -- batteries-included auth / sessions / forms - -Do not mix Django ORM patterns into FastAPI projects. If Django is selected, follow Django app conventions instead of the folder layout below. - -## Module Layout - -Prefer `src-layout` for installable packages and CI consistency. - -``` -project-root/ -├── pyproject.toml -├── src/ -│ └── app/ -│ ├── __init__.py -│ ├── main.py -│ ├── core/ -│ │ ├── __init__.py -│ │ ├── config.py -│ │ ├── database.py -│ │ └── errors.py -│ └── users/ -│ ├── __init__.py -│ ├── models.py -│ ├── schemas.py -│ ├── repository.py -│ ├── service.py -│ └── router.py -└── tests/ -``` - -### `src-layout` vs flat layout - -Use `src/` when: -- the code is packaged -- imports must behave the same locally and in CI -- the project has multiple modules or app entrypoints - -Flat layout is acceptable only for small scripts or throwaway prototypes. - -### Package vs flat modules - -Use packages when a domain has multiple files: -- `users/models.py` -- `users/schemas.py` -- `users/repository.py` -- `users/service.py` -- `users/router.py` - -Avoid one huge `users.py` once the domain has API, persistence, and business logic. - -### `__init__.py` convention - -Keep `__init__.py` small. -- OK: package marker, public re-export for stable API -- Avoid: side effects, database connection, logger setup, app creation - -```python -# src/app/users/__init__.py -from app.users.service import UserService - -__all__ = ["UserService"] -``` - -## Folder Convention - -``` -src/{root}/{domain}/ -├── models.py # SQLAlchemy ORM model -├── schemas.py # Pydantic request / response DTOs -├── repository.py # thin DB wrapper + Protocol boundary -├── service.py # business use cases -└── router.py # FastAPI routes, thin HTTP adapter -``` - -Shared infrastructure: - -``` -src/{root}/core/ -├── config.py # settings -├── database.py # engine, sessionmaker, session dependency -└── errors.py # common error response shape -``` - -## Application Entry - -`main.py` wires routers and exception handlers. It should not contain business logic. - -```python -# src/app/main.py -from fastapi import FastAPI - -from app.core.errors import register_exception_handlers -from app.users.router import router as users_router - -app = FastAPI(title="Example API") - -app.include_router(users_router, prefix="/users", tags=["users"]) -register_exception_handlers(app) -``` - -## DTO Pattern — Pydantic v2 - -Use Pydantic v2 `BaseModel` for request / response DTOs. Keep DTOs separate from ORM models. - -```python -# src/app/users/schemas.py -from __future__ import annotations - -from datetime import datetime - -from pydantic import BaseModel, ConfigDict, EmailStr, Field, field_validator - - -class UserRegisterRequest(BaseModel): - email: EmailStr - name: str = Field(min_length=1, max_length=80) - password: str = Field(min_length=12, max_length=128) - - @field_validator("name") - @classmethod - def normalize_name(cls, value: str) -> str: - normalized = value.strip() - if not normalized: - raise ValueError("name must not be blank") - return normalized - - -class UserResponse(BaseModel): - model_config = ConfigDict(from_attributes=True) - - id: int - email: EmailStr - name: str - created_at: datetime -``` - -### DTO rules - -- Request DTO validates external input. -- Response DTO defines the public API contract. -- ORM model should not be returned directly from a route. -- Use `model_validate(entity)` only at the route boundary or service return mapping boundary. -- Do not mix Pydantic v1 `Config.orm_mode = True` with v2 code. Use `ConfigDict(from_attributes=True)`. - -## ORM Pattern — SQLAlchemy 2.x Async - -Use SQLAlchemy 2.x typed mappings and `AsyncSession`. - -```python -# src/app/users/models.py -from __future__ import annotations - -from datetime import datetime - -from sqlalchemy import DateTime, String, func -from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column - - -class Base(DeclarativeBase): - pass - - -class User(Base): - __tablename__ = "users" - - id: Mapped[int] = mapped_column(primary_key=True) - email: Mapped[str] = mapped_column(String(320), unique=True, index=True) - name: Mapped[str] = mapped_column(String(80)) - password_hash: Mapped[str] = mapped_column(String(255)) - created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) -``` - -### Async session boundary - -Create the engine and sessionmaker once. Create `AsyncSession` per request or per transaction boundary. - -```python -# src/app/core/database.py -from __future__ import annotations - -from collections.abc import AsyncIterator - -from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine - -from app.core.config import settings - -engine = create_async_engine(settings.database_url, pool_pre_ping=True) -AsyncSessionMaker = async_sessionmaker(engine, expire_on_commit=False) - - -async def get_session() -> AsyncIterator[AsyncSession]: - async with AsyncSessionMaker() as session: - yield session -``` - -## Dependency Injection - -Use FastAPI `Depends()` for request-scoped dependencies. Use factory functions for services. - -```python -# src/app/users/router.py -from __future__ import annotations - -from typing import Annotated - -from fastapi import Depends -from sqlalchemy.ext.asyncio import AsyncSession - -from app.core.database import get_session -from app.users.repository import SqlAlchemyUserRepository, UserRepository -from app.users.service import UserService - - -def get_user_repository( - session: Annotated[AsyncSession, Depends(get_session)], -) -> UserRepository: - return SqlAlchemyUserRepository(session) - - -def get_user_service( - repository: Annotated[UserRepository, Depends(get_user_repository)], - session: Annotated[AsyncSession, Depends(get_session)], -) -> UserService: - return UserService(repository=repository, session=session) -``` - -### DI rules - -- Prefer explicit factory functions over global singletons. -- Dependencies should be easy to override in tests. -- Route handlers receive services, not repositories, unless the endpoint is truly infrastructure-only. -- Do not create database sessions at import time. - -## Repository Pattern - -Repository should be a thin wrapper around persistence. It should not own business decisions. - -Use `Protocol` for interface boundaries. This keeps services testable without `unittest.mock.Mock`. - -```python -# src/app/users/repository.py -from __future__ import annotations - -from typing import Protocol - -from sqlalchemy import select -from sqlalchemy.ext.asyncio import AsyncSession - -from app.users.models import User - - -class UserRepository(Protocol): - async def save(self, user: User) -> User: - ... - - async def find_by_email(self, email: str) -> User | None: - ... - - -class SqlAlchemyUserRepository: - def __init__(self, session: AsyncSession) -> None: - self._session = session - - async def save(self, user: User) -> User: - self._session.add(user) - await self._session.flush() - await self._session.refresh(user) - return user - - async def find_by_email(self, email: str) -> User | None: - result = await self._session.execute(select(User).where(User.email == email)) - return result.scalar_one_or_none() -``` - -### Repository rules - -- OK: CRUD, simple query composition, persistence details. -- Avoid: password hashing, permission checks, domain decisions. -- Keep methods named by domain intent when useful: `find_by_email`, `exists_by_email`. -- Do not expose raw SQLAlchemy query objects to services. - -## Service Pattern - -Services contain use cases. They may be classes or pure functions. - -Use a class when: -- multiple dependencies are needed -- route-level DI should construct the use case -- the domain has several related operations - -Use pure functions when: -- dependencies are passed explicitly -- the operation is small and stateless - -```python -# src/app/users/service.py -from __future__ import annotations - -from sqlalchemy.ext.asyncio import AsyncSession - -from app.users.models import User -from app.users.repository import UserRepository -from app.users.schemas import UserRegisterRequest - - -class UserAlreadyExistsError(Exception): - def __init__(self, email: str) -> None: - super().__init__(f"user already exists: {email}") - self.email = email - - -class UserService: - def __init__(self, repository: UserRepository, session: AsyncSession) -> None: - self._repository = repository - self._session = session - - async def register(self, request: UserRegisterRequest) -> User: - existing = await self._repository.find_by_email(str(request.email)) - if existing is not None: - raise UserAlreadyExistsError(str(request.email)) - - user = User( - email=str(request.email), - name=request.name, - password_hash=hash_password(request.password), - ) - - async with self._session.begin(): - return await self._repository.save(user) - - -def hash_password(raw_password: str) -> str: - # 실제 프로젝트에서는 argon2/bcrypt 같은 검증된 해시를 사용한다. - return f"hashed:{raw_password}" -``` - -### Transaction rule - -Prefer one transaction boundary per use case. - -```python -async with session.begin(): - ... -``` - -Do not commit inside repository methods. Repository methods may `flush()` when generated IDs are needed. - -## Router Pattern - -Routers are HTTP adapters. They validate input, call service, and map output. - -```python -# src/app/users/router.py -from __future__ import annotations - -from typing import Annotated - -from fastapi import APIRouter, Depends, status - -from app.users.schemas import UserRegisterRequest, UserResponse -from app.users.service import UserService - -router = APIRouter() - - -@router.post("", response_model=UserResponse, status_code=status.HTTP_201_CREATED) -async def register_user( - request: UserRegisterRequest, - service: Annotated[UserService, Depends(get_user_service)], -) -> UserResponse: - user = await service.register(request) - return UserResponse.model_validate(user) -``` - -### Router rules - -- Keep route handlers thin. -- Do not open transactions in route handlers when service owns the use case. -- Do not put SQLAlchemy queries in route handlers. -- Always declare `response_model` for public API routes. - -## Error Model - -Use domain exception subclasses and map them through FastAPI exception handlers. - -```python -# src/app/core/errors.py -from __future__ import annotations - -from fastapi import FastAPI, Request, status -from fastapi.responses import JSONResponse - -from app.users.service import UserAlreadyExistsError - - -class ErrorResponse(dict[str, object]): - pass - - -def register_exception_handlers(app: FastAPI) -> None: - @app.exception_handler(UserAlreadyExistsError) - async def handle_user_already_exists( - request: Request, - exc: UserAlreadyExistsError, - ) -> JSONResponse: - return JSONResponse( - status_code=status.HTTP_409_CONFLICT, - content={ - "code": "USER_ALREADY_EXISTS", - "message": "user already exists", - "details": {"email": exc.email}, - }, - ) -``` - -### Error rules - -- Raise domain exceptions from services. -- Map HTTP status in API layer, not deep in domain code. -- Do not raise `HTTPException` from repositories or domain services. -- Keep error response shape stable. - -## Async Decision Rule - -Use `async def` when the function awaits I/O: -- database calls -- HTTP calls -- Redis / queue calls -- file I/O through async libraries - -Use `def` when the work is CPU-bound or purely in-memory: -- validation helpers -- DTO mapping -- password policy checks -- small calculations - -In FastAPI: -- `async def` route is preferred when calling async dependencies. -- `def` route is acceptable for sync-only work. -- Never call blocking I/O from `async def` without moving it to a threadpool or using an async client. - -## Typing Defaults - -Use modern typing as a design constraint. - -```python -from __future__ import annotations - -from collections.abc import Sequence -from typing import Protocol -``` - -Recommended: -- Python 3.12+ -- `mypy --strict` or `pyright` -- `from __future__ import annotations` -- `Protocol` for service/repository boundaries -- concrete return types for public functions -- `Sequence[T]` for read-only collection inputs -- `list[T]` when mutation or concrete list output is required - -Avoid: -- implicit `Any` -- untyped decorators -- `dict` without key/value types -- `# type: ignore` without a specific error code and reason - -## Packaging - -Use `pyproject.toml` as the single source of project metadata. - -Prefer `uv` for new projects when fast reproducible dependency management is desired. Poetry is acceptable when the project already standardizes on it. - -```toml -[project] -name = "example-api" -version = "0.1.0" -requires-python = ">=3.12" -dependencies = [ - "fastapi>=0.110", - "pydantic[email]>=2.6", - "sqlalchemy[asyncio]>=2.0", - "asyncpg>=0.29", - "uvicorn[standard]>=0.27", -] - -[dependency-groups] -dev = [ - "mypy>=1.8", - "ruff>=0.3", - "pytest>=8", - "pytest-asyncio>=0.23", - "pytest-cov>=5", -] - -[tool.mypy] -python_version = "3.12" -strict = true -mypy_path = "src" - -[tool.ruff] -line-length = 100 -target-version = "py312" -src = ["src", "tests"] -``` - -## End-to-End Example — User Registration - -Minimal flow: - -``` -router -> service -> repository -> SQLAlchemy model -``` - -```python -# schemas.py -class UserRegisterRequest(BaseModel): - email: EmailStr - name: str = Field(min_length=1) - password: str = Field(min_length=12) - - -class UserResponse(BaseModel): - model_config = ConfigDict(from_attributes=True) - - id: int - email: EmailStr - name: str -``` - -```python -# service.py -class UserService: - def __init__(self, repository: UserRepository, session: AsyncSession) -> None: - self._repository = repository - self._session = session - - async def register(self, request: UserRegisterRequest) -> User: - if await self._repository.find_by_email(str(request.email)): - raise UserAlreadyExistsError(str(request.email)) - - async with self._session.begin(): - return await self._repository.save( - User( - email=str(request.email), - name=request.name, - password_hash=hash_password(request.password), - ) - ) -``` - -```python -# router.py -@router.post("", response_model=UserResponse, status_code=201) -async def register_user( - request: UserRegisterRequest, - service: Annotated[UserService, Depends(get_user_service)], -) -> UserResponse: - user = await service.register(request) - return UserResponse.model_validate(user) -``` - -## Common Anti-patterns - -- Global `AsyncSession` or global database transaction object. -- Creating DB sessions at import time. -- Repository commits transactions internally. -- Business logic in FastAPI route handlers. -- Returning SQLAlchemy ORM objects without explicit response DTO. -- Raising `HTTPException` from repository or domain service. -- Sync route calling blocking database / HTTP I/O in a web-api path. -- Pydantic v1 and v2 config mixed in the same project. -- `dependency_overrides`-unfriendly global singletons. -- `Any` used to bypass typing instead of modeling the boundary. -- `# type: ignore` without a narrow error code. -- Large `main.py` containing routers, settings, DB, and services. -- Repositories that become generic ORM facades with every possible query. -- Tests that require the real production database URL. -- Hidden side effects in `__init__.py`. - diff --git a/.agents/skills/implement/references/types/cli.md b/.agents/skills/implement/references/types/cli.md deleted file mode 100644 index c48784867..000000000 --- a/.agents/skills/implement/references/types/cli.md +++ /dev/null @@ -1,215 +0,0 @@ -# Type: cli -Language-independent patterns for command-line applications. Pair with `languages/{language}.md` for concrete code. -## Layer Breakdown (universal) -``` -argv / env / stdin - -> [Command binding] cobra / click / typer / argparse / yargs - -> [Handler / use case] validate command intent and map args to plain input - -> [Service] orchestrate dependencies and application behavior - -> [Domain] pure rules and reusable operations -``` -Rules: -- Command binding is thin: names, args, flags, help, examples, dispatch. -- Handler owns CLI semantics: input mapping, writer choice, exit-code mapping. -- Service owns behavior: no terminal color, process exit, or parser dependency. -- Domain owns rules: testable without a shell or framework. -- Exit once at the process edge; return codes through the command path. -## Command Binding -| Ecosystem | Binding examples | Typical location | -|-----------|------------------|------------------| -| Go | cobra | `cmd/{tool}` or `internal/cli` | -| Python | click / typer / argparse | `src/{package}/cli.py` | -| Node | yargs / commander | `bin/{tool}` and `src/cli` | -| Rust | clap | `src/cli.rs` or `src/main.rs` | -| Java/Kotlin | picocli | command class delegating to service | -Binding responsibilities: -- define command and subcommand names -- declare positional arguments and flags -- provide help text, defaults, and examples -- apply parser-level required checks -- call one handler with a plain input object -Keep out of binding: -- business branching -- persistence and network workflows -- output formatting beyond writer selection -- `exit`, `panic`, or uncaught exception paths -## Argument Design -### Positional arguments -Use positional arguments for required identity-like values: -``` -tool user get -tool file convert -``` -Good candidates: -- one resource id -- one input path -- one required target -- values that read naturally in command form -Avoid positional arguments when: -- there are more than two or three values -- two values have the same type and can be swapped -- the value is optional -- order is hard to remember -### Flags -Use flags for options, filters, formats, and modes: -``` -tool users list --status active --limit 50 --json -tool import data.csv --dry-run --batch-size 500 -``` -Flag rules: -- Prefer stable long flags for scripts. -- Add short flags only for common interactive actions such as `-v`, `-q`, `-h`. -- Boolean flags should be positive by default, with `--no-*` where supported. -- Defaults must appear in help text or docs. -- Do not make a flag meaning depend on hidden state. -### Subcommands -Group by resource and action: -``` -tool project create -tool project list -tool project delete -tool config get -tool config set -``` -Common grouping patterns: -- resource first: `user create`, `user list`, `user delete` -- workflow first: `deploy start`, `deploy status`, `deploy rollback` -- admin namespace: `admin repair`, `admin backfill`, `admin inspect` -Framework notes: -- cobra: nested `*cobra.Command` -- click: `@click.group()` plus subcommands -- typer: mounted `Typer()` apps -- argparse: `add_subparsers()` -- yargs: `.command()` modules or chains -## Output Conventions -### stdout / stderr / exit code -| Channel | Purpose | -|---------|---------| -| stdout | command result intended for caller or pipeline | -| stderr | logs, prompts, progress, warnings, diagnostics | -| exit code | machine-readable success or failure status | -Rules: -- stdout must stay parseable in pipelines. -- progress bars and spinners go to stderr. -- warnings go to stderr even on success. -- error details go to stderr unless JSON error output is explicitly requested. -- never mix logs into JSON stdout. -### Human-readable output -Default output should work well in a terminal: -``` -ID STATUS NAME -p_123 active search-index -p_124 paused newsletter -``` -Guidelines: -- Use tables for short list output. -- Use key/value blocks for detail output. -- Keep default output readable, but do not treat it as a stable API. -- Avoid decorative output that breaks narrow terminals. -### Machine-readable output -Provide JSON for automation: -``` -tool project list --json -``` -JSON rules: -- stdout contains only JSON. -- stderr may contain warnings unless `--quiet` is set. -- Use stable field names. -- Prefer arrays for lists and objects for details. -- Do not emit trailing summary text after JSON. -### Quiet / verbose / color -| Mode | stdout | stderr | -|------|--------|--------| -| default | result | warnings and useful progress | -| `--quiet` | result only | errors only | -| `--verbose` | result | detailed steps | -| `--json` | JSON result | diagnostics only | -Color rules: -- Enable color only when the relevant stream is a TTY. -- Honor `--color`, `--no-color`, and `NO_COLOR` where practical. -- Use a terminal library or output abstraction. -- Do not scatter raw ANSI escape strings through handlers. -- Never rely on color as the only meaning. -## Exit Codes -| Code | Meaning | Use | -|------|---------|-----| -| 0 | success | command completed successfully | -| 1 | user error | invalid args, validation failure, not found, conflict | -| 2 | system error | network, database, filesystem, or unexpected internal failure | -| 130 | interrupted | SIGINT / Ctrl-C, POSIX convention | -Guidelines: -- Keep numeric codes small and documented. -- Map parser and validation failures to user error. -- Map dependency failures to system error. -- Put stable domain error codes in text or JSON, not only in exit code. -## Configuration Precedence -Use this order, highest priority first: -``` -flags > environment variables > config file > defaults -``` -Rules: -- Flags override every other source. -- Environment variables are useful for CI and containers. -- Config files are useful for project or user defaults. -- Defaults must be explicit. -- Verbose or debug mode may print effective config with secrets redacted. -- Never print tokens, passwords, private keys, or signed URLs. -## Long-running Operations -Progress reporting: -- write progress to stderr -- use progress bars only for TTY sessions -- use periodic line logs for non-TTY sessions -- include counts, current phase, and elapsed time when useful -- keep stdout reserved for results -Partial output and signals: -- document whether stdout may be partial after interruption -- flush at safe record boundaries -- prefer newline-delimited JSON for streams -- handle SIGINT and SIGTERM through cancellation -- stop accepting new work, flush buffers, persist checkpoint when supported -- return 130 for SIGINT -## Testability -Design handlers as plain functions: -``` -handle(input, deps, stdout, stderr) -> exit_code -``` -Test seams: -- args can be constructed in memory -- stdout and stderr writers are injectable -- environment lookup is injectable -- filesystem and network access sit behind small interfaces -- clock and random id generation are injectable when behavior depends on them -Recommended tests: -- parser test: argv maps to input object -- handler test: input maps to service call and output -- service/domain test: behavior without CLI framework -- smoke test: real process exits with expected code for core paths -## Folder Layout per Phase -``` -cmd/{tool}/main.* # process entrypoint -src/cli.* # command binding -src/handlers/* # command handlers -src/services/* # use cases -src/domain/* # pure rules -``` -Language variants: -- Go: `cmd/{tool}/main.go`, `internal/cli`, `internal/{domain}` -- Python: `src/{package}/__main__.py`, `cli.py`, `handlers/`, `services/` -- Node: `bin/{tool}.js`, `src/cli/commands`, `src/services`, `src/domain` -Phase rule: -- Phase 1: service/domain behavior and handler contract. -- Phase 2: CLI binding, help text, argument validation. -- Phase 3: packaging, shell completion, install docs, smoke tests. -## Common Anti-patterns -- One function contains parsing, business logic, and printing. -- Service code calls `exit` or terminates the process. -- User errors are handled with `panic` or uncaught exceptions. -- Hard-coded absolute paths. -- Hidden config precedence. -- Logs or progress are printed to stdout. -- JSON stdout includes human summary text. -- Raw ANSI escape strings are written directly from handlers. -- Color is enabled without TTY detection. -- Automation commands prompt without non-interactive mode. -- Retry loops have no timeout or cancellation. -- Dependency failures are swallowed and reported as success. diff --git a/.agents/skills/implement/references/types/library.md b/.agents/skills/implement/references/types/library.md deleted file mode 100644 index ba9ca5bbb..000000000 --- a/.agents/skills/implement/references/types/library.md +++ /dev/null @@ -1,237 +0,0 @@ -# Type: library -Language-independent patterns for libraries and reusable packages. Pair with `languages/{language}.md` for concrete code. -## Purpose -A library is consumed by other code. Its main product is a stable public API, not a process, page, or job. -Design goals: -- clear public surface -- small dependency footprint -- predictable versioning -- runnable examples -- tests from the consumer boundary -- documentation for every exported symbol -## Layer Breakdown (universal) -``` -consumer application - -> [Public API] exported functions, classes, types, modules - -> [Facade] stable entrypoints and configuration objects - -> [Internal core] implementation, algorithms, adapters - -> [Dependencies] optional integrations and platform-specific code -``` -Rules: -- Public API is intentional: exporting a symbol creates compatibility responsibility. -- Facade is stable: most consumers should not need internal modules. -- Internal core can change: keep it hidden behind public entrypoints. -- Dependencies are conservative: every transitive dependency becomes a consumer concern. -## Public API Surface -Define what is exported and what is internal-only. -Language patterns: -- Java: public classes are API; package-private classes stay internal. -- Kotlin: `public` is default; use `internal` deliberately. -- Python: define `__all__`, prefix internals with `_`, document supported imports. -- Go: exported identifiers start with capital letters; use `internal/` for hard boundaries. -- Rust: `pub` and module visibility define API; avoid broad `pub use` without intent. -- TypeScript: exported symbols from package entrypoints are API. -Rules: -- Keep one or a few documented entrypoints. -- Do not require consumers to import deep internal paths. -- Treat config keys, error codes, and event names as public API if consumers use them. -- Mark experimental APIs with stability level. -- Prefer a small facade over exposing many implementation classes. -API checklist: -- symbol name is stable -- parameter names and types are stable -- return shape is stable -- error behavior is documented -- default behavior is documented -- concurrency expectation is documented when relevant -## Backwards Compatibility -Use SemVer: -``` -MAJOR.MINOR.PATCH -``` -| Change | Version bump | Example | -|--------|--------------|---------| -| compatible bug fix | PATCH | fix parsing for valid existing input | -| compatible new feature | MINOR | add optional parameter or new function | -| compatible deprecation notice | MINOR | warn that an API will be removed later | -| breaking API removal | MAJOR | remove exported class or function | -| breaking behavior change | MAJOR | same input now means different operation | -| breaking type change | MAJOR | return string becomes object | -Compatible changes: -- add a new exported symbol -- add an optional config key with default -- improve performance without changing behavior -- fix behavior that was documented as broken -Breaking changes: -- remove or rename exported symbol -- change required parameters -- change return type or error type -- change default behavior -- remove config key -- move public import path without compatibility shim -## Deprecation Policy -Default policy: -- Introduce replacement first. -- Mark old API deprecated in a minor release. -- Emit deprecation warning where the ecosystem supports it. -- Keep deprecated API for at least one major line. -- Remove only in the next major release. -Deprecation notice should include: -- deprecated symbol -- replacement API -- first deprecated version -- planned removal version -- migration example or link -Example: -```text -Deprecated since 2.4.0: use Client.fetch_item() instead. -Planned removal: 3.0.0. -``` -Do not deprecate without a working replacement unless the API is unsafe or unusable. -## Migration Guides -Every breaking change needs BEFORE / AFTER examples. -```text -BEFORE -client = create_client(token) -result = client.fetch(id) -AFTER -client = Client(token=token) -result = client.fetch_item(id) -``` -Migration guide contents: -- affected versions -- who is affected -- exact change -- mechanical replacement when possible -- behavior differences -- rollout order for large applications -- known edge cases -Store guides close to releases, such as `docs/migrations/v2-to-v3.md`. -## Dependency Policy -Principles: -- minimize transitive dependencies -- avoid dependencies for small utilities -- avoid framework lock-in in core modules -- isolate optional integrations -- document supported dependency version ranges -Runtime dependencies: -- Prefer standard library when practical. -- Avoid large frameworks in a small library. -- Avoid exposing dependency types in public API unless intentional. -- Track security updates. -Peer dependencies for npm-style ecosystems: -- Use peer dependencies when the host app must provide the framework. -- Do not bundle a second copy of React, Vue, ESLint, or similar host-level packages. -- State supported peer ranges. -- Test lowest and latest supported peer versions when possible. -Pin vs range: -| Context | Policy | -|---------|--------| -| application lockfile | pin exact resolved versions | -| published library manifest | use compatible ranges | -| security-sensitive tool | narrow range or pin with automation | -| peer dependency | supported range | -## Documentation Contract -Every exported symbol should have docs: -- docstring / JSDoc / KDoc / Rustdoc / Go doc -- parameter meaning -- return meaning -- error behavior -- side effects -- lifecycle or concurrency notes when relevant -README minimum: -- what the library does -- supported platforms or runtimes -- installation -- quick-start -- common configuration -- link to API docs -- versioning and compatibility note -Examples directory: -``` -examples/ -├── quick-start/ -├── custom-config/ -└── error-handling/ -``` -Example rules: -- runnable from a clean checkout -- minimal but complete -- covers one major feature each -- included in CI when practical -## Testing -Test from the package boundary: -``` -import { publicFunction } from "package-name" -``` -or equivalent public import path. -Test levels: -- public API behavior tests -- compatibility tests for documented edge cases -- error contract tests -- example smoke tests -- packaging test that installs the built artifact locally -Rules: -- Public boundary tests must exist. -- Internal tests are allowed for complex algorithms. -- Add regression tests before changing behavior. -- Test deprecation warnings if consumers rely on them. -## Packaging -Document build and publish commands. -Common ecosystems: -- npm: `npm pack`, then publish to npm registry. -- PyPI: build wheel and sdist, then publish. -- crates.io: `cargo package`, `cargo publish`. -- Maven Central: signed group, artifact, version coordinates. -- Go: tag module versions; consumers fetch from VCS or module proxy. -Packaging checklist: -- package includes README, license, and changelog -- package excludes tests or fixtures unless needed -- source maps or type declarations included when relevant -- artifact installs in a clean temp project -- published version matches changelog and git tag -- license metadata is correct -## Versioning Automation -Useful tools: -- changesets -- conventional commits -- semantic-release -- release-please -- language-native release tooling -Rules: -- Automation should not guess breaking changes silently. -- Human review is required for major releases. -- Changelog entries should describe consumer impact. -- Release tags should match package versions. -## Folder Layout per Phase -``` -src/{package}/ -├── index.* # public entrypoint / exports (Phase 1) -├── public/ # optional public facade modules (Phase 1) -├── internal/ # implementation details (Phase 1) -├── integrations/ # optional adapters (Phase 2) -├── errors.* # stable error types / codes (Phase 1) -└── config.* # documented configuration model (Phase 1) -docs/ -├── api.md -└── migrations/ -examples/ -└── quick-start/ -``` -Phase rule: -- Phase 1: public API, internal core, tests from package boundary. -- Phase 2: docs, examples, optional integrations, packaging smoke test. -- Phase 3: release automation, compatibility matrix, migration guides. -## Common Anti-patterns -- Public API grows rapidly before 1.0 without stability labels. -- Internal classes are accidentally exported from the package root. -- Consumers must import from deep internal paths. -- Hard-coded URLs, paths, credentials, or region names. -- Undocumented config keys change behavior. -- Public API exposes dependency-specific types unintentionally. -- Breaking changes ship as patch or minor versions. -- Deprecation has no replacement or removal schedule. -- README quick-start does not run. -- Tests import private internals while public imports are broken. -- Package contents differ from repository assumptions. -- Release automation publishes without changelog review. diff --git a/.agents/skills/implement/references/types/worker-daemon.md b/.agents/skills/implement/references/types/worker-daemon.md deleted file mode 100644 index bdeb6f606..000000000 --- a/.agents/skills/implement/references/types/worker-daemon.md +++ /dev/null @@ -1,149 +0,0 @@ -# Type: worker-daemon - -Language-independent patterns for long-running worker processes that consume tasks from a queue / message broker and execute them indefinitely. Distinct from `cli` (one-shot command) and `batch` (scheduled job with defined start/end). - -> Use with the matching `languages/{language}.md`. Common languages: Go (NATS / pgmq / Redis Streams), Java (Spring Cloud Stream / Kafka), Python (Celery / Dramatiq). - -## When to choose `worker-daemon` over neighbors - -| Symptom | Type | -|---------|------| -| Reads from queue indefinitely, no scheduled stop | **worker-daemon** ✓ | -| Triggered by cron / specific time, finite work | batch | -| Single user-facing command invocation, finite | cli | -| Receives HTTP requests, finite per-request | web-api | - -Common hybrid: a process launched by **CLI** (`./mytool worker --queue=tasks`) but **operates as a worker-daemon**. Reference selection: primary = `worker-daemon.md`, secondary = `cli.md` for the launch surface. - -## Layer Breakdown - -``` -[Launch surface] CLI binding / main() / k8s entrypoint — boot, parse env/flags - ↓ -[Bootstrap] config load, logger, metrics, signal trap - ↓ -[Broker consumer] NATS / Kafka / Redis Streams / pgmq client - ↓ -[Worker registry] task type → handler function map - ↓ -[Task handler] business logic per task type - ↓ -[Service / repo] shared domain layer (same as web-api / batch) -``` - -## Worker Registry Pattern - -Tasks have a `type` field; registry dispatches to the correct handler. - -``` -type WorkerHandler func(ctx context.Context, payload []byte, out OutputPort) error - -type Registry struct { - handlers map[string]WorkerHandler -} - -func (r *Registry) Register(taskType string, h WorkerHandler) { ... } -func (r *Registry) Dispatch(ctx, msg) error { - h, ok := r.handlers[msg.Type] - if !ok { return ErrUnknownTaskType } // → ack with DLQ tag - return h(ctx, msg.Payload, msg.Out) -} -``` - -Rule: a new worker type = a new entry in `Register(...)` + a new handler file. Never modify existing handlers when adding a type. - -## Port Schema (input / output contract) - -Each handler declares its input and output schemas (JSON Schema / Pydantic / Go struct + tag): - -``` -worker: pdf-extract -input: { uri: string, language?: string } -output: { text: string, page_count: int } -errors: { code: "INVALID_URI" | "EXTRACT_FAILED" | "DOWNLOAD_TIMEOUT" } -``` - -Schemas live in `pkg/schema/definitions/` (language-specific path). Generator produces typed models. Handlers operate on typed inputs, not raw bytes. - -## Broker Integration - -| Broker | Acknowledgment | Retry | DLQ | -|--------|---------------|-------|-----| -| NATS JetStream | `msg.Ack()` after success | Stream config: `MaxDeliver` | `MaxDeliver` exceeded → DLQ subject | -| Kafka | Commit offset on success | Manual loop with backoff | `__dlq` topic | -| Redis Streams | `XACK` on success | `XCLAIM` for pending > timeout | Separate stream | -| pgmq (Postgres) | `archive(msg_id)` | `read_with_poll` re-delivers if not archived | Separate queue table | - -Rule: ack ONLY after the handler returns success. On failure: do NOT ack — let the broker re-deliver. Handler must be idempotent (same task delivered twice → same result). - -## Graceful Shutdown - -``` -Signal received (SIGTERM / SIGINT) - ↓ -1. Stop accepting NEW tasks (consumer.Unsubscribe / cancel context) -2. Drain in-flight: wait for current handlers to finish (with timeout, e.g., 30s) -3. Flush metrics, close DB connections, close broker connection -4. Exit 0 -``` - -Hard rule: NEVER `os.Exit(0)` while a task is in-flight. The broker thinks the task succeeded but no ack was sent → re-delivery + possible double-execution. - -## Idempotency - -Tasks may be delivered more than once (at-least-once semantics in most brokers). Handlers MUST be idempotent: - -- **Dedup key**: include `task_id` (UUID) in every task. Handler checks a fast store (Redis `SETNX` with TTL, or DB `UNIQUE` index) before doing work. If already processed → ack and skip. -- **Idempotent writes**: prefer `INSERT ... ON CONFLICT DO NOTHING` / `MERGE` over plain `INSERT`. External API calls should accept `Idempotency-Key` header. -- **State machine**: if the task transitions a record from state A → B, check current state first. Re-running on a B record should no-op. - -## Observability (mandatory in worker-daemon) - -- **Structured logs**: every log line carries `task_id`, `task_type`, `worker_id`, `attempt`. JSON output, NOT plain text. -- **Metrics**: - - `worker_tasks_processed_total{task_type, outcome=success|failure|dlq}` (counter) - - `worker_task_duration_seconds{task_type}` (histogram) - - `worker_in_flight{task_type}` (gauge) - - `worker_queue_lag_seconds` (gauge, from broker) -- **Tracing**: propagate `trace_id` from message header. Span = handler invocation. -- **Health endpoints** (HTTP, separate port from broker): - - `/healthz` — process alive - - `/readyz` — broker connection healthy, handler registry initialized -- **Run history**: write to a `worker_runs` table (`task_id`, `started_at`, `finished_at`, `outcome`, `error_summary`) - -## Resource Limits - -- **Concurrent task limit**: semaphore / channel-buffered worker pool. Default = number of CPU cores. Tune per task type if some are I/O-heavy. -- **Per-task timeout**: `context.WithTimeout` at handler entry. Reasonable default 5 min; configurable per task type. -- **Memory limit**: rely on container limits (k8s `resources.limits.memory`). Process should fail fast (OOM) rather than swap. -- **Backpressure**: when broker re-delivery rate exceeds processing rate, log + emit alert. Optionally pause subscription. - -## Folder Layout per Phase - -``` -src/{root}/ -├── cmd/ # CLI binding (Phase 2 surface) -│ └── worker.go # parses flags, calls bootstrap -├── internal/{root}/ (or pkg/) -│ ├── bootstrap/ # config, logger, metrics, signals -│ ├── broker/ # broker-specific client (NATS, Kafka, ...) -│ ├── workers/ # one file per worker type -│ │ ├── pdf_extract.go -│ │ ├── thumbnail_resize.go -│ │ └── registry.go # GetBuiltinWorkers() / Register -│ ├── schema/ # input/output JSON schemas -│ └── observability/ # metrics, tracing, health endpoint -``` - -## Common Anti-patterns - -- **`os.Exit` mid-task**: shutdown signal handler that exits immediately, in-flight task lost -- **No idempotency**: handler does an unconditional `INSERT` — duplicate row on re-delivery -- **Ack-before-success**: handler acks the message, then crashes — task lost -- **Unbounded concurrency**: no worker pool / semaphore — broker re-delivery overwhelms the process -- **Plain-text logs**: `log.Printf("processed task %s", id)` — non-greppable, no structured filters -- **No DLQ wiring**: failed tasks loop forever or get silently dropped -- **Health endpoint inside broker port**: `/healthz` on the same port as worker traffic — k8s probe fails when broker port is busy -- **CLI command mixed with worker logic**: `cmd/worker.go` contains business logic instead of delegating to handler -- **Schema in code only**: input shape defined only in Go struct, not in a generator-friendly schema — frontend / other services can't validate -- **No per-task timeout**: a stuck task holds a worker slot forever, pool starvation diff --git a/.agents/skills/next-flow/SKILL.md b/.agents/skills/next-flow/SKILL.md deleted file mode 100644 index 824a60073..000000000 --- a/.agents/skills/next-flow/SKILL.md +++ /dev/null @@ -1,147 +0,0 @@ ---- -name: next-flow -description: | - Diagnoses where in the GSL lifecycle the work currently is, proposes the single next command, and may auto-trigger ONLY read-only verification (never any write). - Trigger: "/next-flow", or when the user says "다음 단계", "이제 뭐 해야 해", "next step", "what's next". - Use after completing any GSL step (`/define`, `/plan`, `/implement` Task, `/test`, `/verify`, `/debug`) to find out what to do next. - Strictly read-only: this skill never writes files, edits code, or modifies plan documents. -argument-hint: "[plan-file-path or current-step]" ---- - -# Next Flow — GSL Lifecycle Continuation Helper - -## Overview - -GSL's biggest known friction is **P1: gaps between steps don't self-advance** — after `/implement` the user has to manually invoke `/test`, after `/test` manually invoke `/verify`, and so on. This skill closes that gap **without violating GSL gates**: it reads the plan document, diagnoses where the work is, proposes exactly one next command, and may run a read-only verification on the user's behalf — but **never writes anything**. - -Hard policy (from codex consultation): *auto-progression is restricted to read-only verification + next-command suggestion. Any write-side follow-up (new test files, code edits, plan document updates, commits, pushes) requires explicit user invocation.* See "Common Rationalizations" for why this matters. - -## When to Use - -- Right after any GSL step appears to have completed -- When the user is unsure what to do next ("다음에 뭐 하면 돼?") -- When a plan document exists but the user has lost track of the progress log -- Before invoking `/test` / `/verify` / commit — to confirm preconditions are met - -## When NOT to Use - -- Before `/define` (no plan document yet — start with `/define` directly) -- When a `/debug` recovery is in progress (let `/debug` finish first) -- When the user has already named the next step explicitly -- For any next GSL command transition — print the command name and stop. In slash-command UI runtimes, tell the user to type that command directly; in codex, tell the user to send a new message containing that skill name. - -## Process - -### Step 1: Locate the Plan Document - -- If user passed a path, use it -- Otherwise scan `plan/*.md` for the IN PROGRESS document (one with unchecked Tasks) -- If 0 candidates → tell user to run `/define`. STOP. -- If 2+ candidates → ask user which one. STOP. - -### Step 2: Diagnose Current Step - -Read Overview, Tasks, Progress Log. Classify the work into exactly one of: - -| Signal | Current step | Next step | -|--------|--------------|-----------| -| Plan doc has Overview only, no Tasks | post-`/define` | `/plan` (print command and stop) | -| Tasks exist, Progress Log empty | post-`/plan` | `/implement` | -| Some Tasks done, more remain | mid-`/implement` | continue `/implement` with next Task | -| All Tasks done, no integration tests written | post-`/implement` | `/test` (integration tests) | -| Integration tests exist, `/verify full` not yet run | post-`/test` | `/verify full` | -| `/verify full` passed | post-`/verify` | commit + plan stamp (manual) | -| Last log entry is a `/debug` failure | mid-`/debug` | continue `/debug` | -| `/verify` failed | mid-`/debug` | enter `/debug` | - -### Step 3: Check Preconditions (read-only) - -Before proposing the next command, verify its preconditions in a read-only way: - -| Next command | Read-only precondition check | -|--------------|------------------------------| -| `/plan` | Overview section is complete, user has approved | -| `/implement` | At least one Task is defined | -| `/test` | All Tasks have commits in Progress Log | -| `/verify full` | `git status` clean OR uncommitted work is intentional | -| commit / PR | `/verify full` last run = PASS | -| `/debug` | A reproducible failure is recorded | - -If any precondition fails, **do not auto-progress**. Report the failure and let the user decide. - -**Red test state — hard precondition.** If `git status` or the latest test output indicates uncommitted test failures, OR the last recorded `/verify` run is FAIL, do NOT propose any next step except `/debug`. If no reproducible failure is recorded yet, STOP and ask the user to first reproduce the failure (this is `/debug` Step 2). Never propose `/test` / `/verify` / commit / push while a known red state exists. - -### Step 4: Optional Auto-Run (read-only only) - -This is the ONLY auto-progression allowed by this skill. Allowed read-only actions: - -- `/verify quick` or `/verify standard` (these run tests but do not write source files) -- `git status` / `git log --oneline` / `git diff` to confirm preconditions -- Reading existing test output - -**Never allowed:** -- Editing or creating files (source, test, plan, anywhere) -- Running commit, push, branch, merge -- Invoking write-side skills (`/define`, `/implement`, `/test` write-mode, etc.) without user action -- Modifying the plan document -- Calling external systems (DB, deploy, package registries) - -### Step 5: Propose the Next Command - -Output exactly one of: - -``` -Next: / -Reason: -Preconditions: PASS -[Run it? You can type / yourself, or reply "yes" to have me run it only if it is read-only.] -``` - -For any next GSL command, do not run it in this turn. Replace the last line with runtime-neutral guidance: - -``` -To continue, send a new message containing this command. In slash-command UI runtimes, type the command directly; in codex, send the skill name in a new message. -``` - -## Common Rationalizations - -| Rationalization | Reality | -|-----------------|---------| -| "All Tasks done, I'll just auto-write the integration tests" | `/test` writes files. That violates GSL's user-gate. Propose `/test` and stop. | -| "User said 'continue', so I can just push through commit" | Commit is a write boundary and an externally-visible action. Propose, never auto-run. | -| "It's only a small edit to plan doc Progress Log" | Plan document is the single source of truth. Only `/implement` updates it after a Task commit. | -| "/verify full passed — let me start the next feature" | Closing the current feature (stamp + plan/complete/ move) is a manual decision. Propose, don't act. | -| "I'll auto-call the next GSL command since it is obvious" | Next GSL commands are runtime boundaries. Print the command name and stop. | - -## Red Flags - -- Auto-progression has caused any file to be created, edited, or deleted (immediate STOP — this skill should never do that) -- Multiple next-step candidates proposed (the table in Step 2 should yield exactly one) -- Preconditions skipped because "the user implied it was fine" -- Proposing any next GSL command for same-turn execution instead of printing the command name and stopping -- **Bypassing the runtime-boundary notice by invoking the next GSL command through Bash, shell scripts, `subprocess`, MCP tools, or other wrappers — anti-pattern, STOP.** The notice is the only correct output for GSL command transitions. -- The next command requires user approval (e.g., post-`/define` → `/plan`) and you tried to execute it anyway - -## Verification - -After running: - -- [ ] Exactly one next command was proposed -- [ ] All preconditions were checked in a read-only way -- [ ] If any auto-action was run, it was read-only only (verify / status / log / diff) -- [ ] No file was created, edited, or deleted by this skill -- [ ] If the next step is a GSL command, the runtime-boundary notice was printed instead of attempting same-turn execution -- [ ] The proposal includes the reason and the user's explicit invocation prompt - -## Runtime Boundary — HARD STOP - -This skill ENDS after the Verification checklist and final report are completed. - -For codex and any runtime without an enforced skill-return boundary: -- MUST stop the assistant turn here. -- MUST NOT invoke, load, or execute any next GSL skill in the same response turn. -- MUST NOT continue into `/next-flow`, `/define`, `/plan`, `/implement`, `/test`, `/verify`, `/debug`, or `/self-review`. -- MAY print exactly one suggested next command as plain text. -- MUST wait for the user's next message before running any next skill. - -If the user says only "continue", treat that as permission to report the next recommended command, not permission to execute it. diff --git a/.agents/skills/scan-conventions/SKILL.md b/.agents/skills/scan-conventions/SKILL.md deleted file mode 100644 index 4529c7980..000000000 --- a/.agents/skills/scan-conventions/SKILL.md +++ /dev/null @@ -1,229 +0,0 @@ ---- -name: scan-conventions -description: | - Scans a project ONCE to discover its real conventions (directory layout, naming, build system, test patterns, error model) and writes a single artifact for downstream GSL steps to consult. - Trigger: "/scan-conventions", or when the user says "관습 파악", "프로젝트 스캔", "scan conventions", "convention discovery", "preflight". - Use on project entry, or when conventions have drifted significantly from the existing artifact. - Other GSL skills (`/define`, `/implement`, `/test`) consult the artifact at their Phase 0 — they do not re-scan. -argument-hint: "[project-root path, default = current working dir]" ---- - -# Scan Conventions — Preflight Project Discovery - -## Overview - -GSL's `references/` provide generic patterns per project type (web-api / cli / batch / library) and per language (java-spring / python / go). But every real project has its own conventions that may agree with, refine, or contradict those references. This skill runs **once per project** (not per feature) to discover the actual conventions and writes a single artifact at `plan/conventions.md`. All other GSL skills then read that artifact at their Phase 0 instead of re-scanning, which keeps per-step cost low and conventions consistent across the session. - -If `plan/conventions.md` already exists and is recent, **do not re-run** — let the user invoke explicitly if they suspect drift. - -## When to Use - -- First time using GSL on a project -- After significant restructuring (modules added/removed, framework upgrade) -- User explicitly asks ("관습 다시 파악해줘", "rescan") -- Any GSL skill's Phase 0 reports missing or stale `plan/conventions.md` - -## When NOT to Use - -- `plan/conventions.md` already exists and project hasn't significantly changed -- Mid-feature work (run `/scan-conventions` only on entry, not between Tasks) -- Single-file edits to a known project - -## Process - -### Step 1: Confirm Run Conditions - -- Locate the project root (argument, or current working dir + walk up to first `.git` / lockfile) -- Check whether `plan/conventions.md` already exists - - Exists and < 30 days old → ask user "Rescan, or use existing? (use existing recommended)" - - Exists and > 30 days OR user explicitly asked → proceed with rescan - - Does not exist → proceed - -### Step 2: Discover Directory Layout - -Identify: -- Top-level structure (monorepo / single module / multi-module) -- Source root convention (`src/`, `src/main/...`, `internal/`, `lib/`, ...) -- Test root convention -- Configuration / build files location - -### Step 3: Identify Build System & Language - -Detect via lockfiles / manifests: -- `build.gradle` / `pom.xml` / `pyproject.toml` / `package.json` / `go.mod` / `Cargo.toml` / etc. -- Language version (from manifest) -- Test runner (Gradle test, pytest, go test, jest, etc.) -- Linter / formatter (spotless, ruff, eslint, gofmt, ...) - -### Step 4: Sample Naming Conventions - -Read 5+ recent source files (favor non-test, non-generated). Extract: -- File naming pattern (`{Domain}Controller.java`, `{snake_case}.py`, `{name}_handler.go`) -- Type / class / function naming patterns -- Test class / function naming convention -- Display name convention (Korean `@DisplayName` "~할 때 ~한다" / English describe blocks / etc.) - -**Bootstrap fallback (insufficient samples).** If the project has fewer than 5 sampleable source files (a freshly bootstrapped repo with only `main.py` / `cmd/main.go` / `App.java` / similar), **do NOT block downstream skills**. Instead: - -1. Mark this section as `INSUFFICIENT_SAMPLE` in the artifact (`plan/conventions.md`). -2. Adopt the matching `references/languages/{language}.md` defaults as the working conventions for naming / layering / test patterns. -3. Record in the artifact that this is a bootstrap convention and **flag for mandatory re-scan after the first 5+ non-test source files are committed**. -4. The artifact still gets written so that `/define`·`/plan`·`/implement` can proceed; downstream skills MUST consult the `INSUFFICIENT_SAMPLE` marker and warn the user that the convention is provisional. - -This unblocks green-field projects without forcing fabricated conventions. The Red Flag "Sampling fewer than 5 files" applies ONLY to projects that have 5+ files but the skill skipped them — not to projects that genuinely have fewer than 5. - -### Step 5: Identify Test Patterns - -- Test categorization mechanism (`@Tag("unit")`, `@pytest.mark.unit`, build tags `//go:build integration`, ...) -- Fake / InMemory pattern present? (search for `InMemory*`, `Fake*`, fixture directories) -- Integration test base class / fixture -- Test data factory pattern -- Mocking library presence (note as informational — GSL policy is Fake-first regardless) - -### Step 6: Detect Architecture Markers - -- Layer markers (custom annotations like `@FacadeService`, `@DomainRepository`, internal package boundaries) -- Cross-module seams (facade / port / interface boundaries) -- Error model (custom exception hierarchies, error code enums) -- Persistence pattern (Repository tiers, Active Record, etc.) - -### Step 7: Compare Against GSL References - -For each discovered convention, compare to the matching GSL references: -- `implement/references/types/{detected-type}.md` -- `implement/references/languages/{detected-language}.md` -- `test/references/testing/{detected-language}.md` -- `verify/references/verify/{detected-language}.md` - -Classify each finding as: -- **MATCH** — project convention matches references (downstream skills can use references as-is) -- **REFINEMENT** — project has a more specific convention (note in artifact; downstream skills should follow project's) -- **CONFLICT** — project convention contradicts references (escalate; user decides which wins in Step 8) - -### Step 8: Resolve Conflicts (user decision) - -For each CONFLICT, present: -``` -CONFLICT: - references: - project: - Which should downstream GSL skills follow? - 1) Project (recommended — match existing code) - 2) References (only if you intend to refactor the project) -``` - -Record the user's decision in the artifact. - -### Step 8.5: Virtual / Dry-run Mode (read-only environments) - -If running in a **read-only sandbox** (`codex exec`, MCP sandbox, CI dry-run) or the user explicitly requested a **simulation / dry-run**: - -- Do NOT attempt to write `plan/conventions.md` to disk -- Instead, output the same artifact content to conversation / stdout with header `[VIRTUAL ARTIFACT — NOT WRITTEN]` -- Downstream skills in the same session can read this content from conversation context as if it were the on-disk file -- This does NOT satisfy `/define`'s hard gate for production work — virtual mode is for analysis / dry-run only - -For real project entry, proceed to Step 9. - -### Step 9: Write the Artifact - -Write to `plan/conventions.md`: - -```markdown -# Project Conventions - -> Generated by /scan-conventions on YYYY-MM-DD. Re-run if the project structure changes significantly. - -## Detected Project Type -[web-api / cli / batch / library / hybrid] - -## Build System & Language -- Language: [...] -- Build: [...] -- Test runner: [...] -- Linter / formatter: [...] - -## Directory Layout -[summary tree] - -## Naming Conventions -- File: [...] -- Type / class: [...] -- Test: [...] -- Display name: [...] - -## Test Patterns -- Categorization: [tag / marker / build-tag / decorator] -- Fake / InMemory present: [yes / no — list if yes] -- Integration base: [...] -- Mocking library present: [name or "none"] - -## Architecture Markers -- Layering: [...] -- Cross-module seams: [...] -- Error model: [...] -- Persistence: [...] - -## Comparison with GSL References -| Area | references | project | classification | follow | -|------|-----------|---------|----------------|--------| -| ... | ... | ... | MATCH/REFINEMENT/CONFLICT | references/project | - -## Conflict Resolutions -- [conflict A]: follow [project / references] — reason: [...] -``` - -### Step 10: Report - -Print a short summary: -``` -Scan complete. plan/conventions.md written. -- Detected type: -- Language: -- N findings: M MATCH, K REFINEMENT, L CONFLICT (resolved) -- Other GSL skills will consult this artifact in their Phase 0. -``` - -## Common Rationalizations - -| Rationalization | Reality | -|-----------------|---------| -| "I'll just scan again each feature, faster than reading the artifact" | Per-feature scanning is expensive and produces drift across sessions. One artifact, one source of truth. | -| "References are best practice — override project's convention" | No. Match existing code unless the user explicitly wants to refactor. Conflict resolution belongs to the user, not the skill. | -| "User didn't ask about conventions, skip this skill" | Other GSL skills depend on this artifact existing. Run on project entry. | -| "Skip the comparison step, just record findings" | The comparison is the whole point — downstream skills need to know match vs conflict. | -| "Just glance at one file, generalize from there" | Sample 5+ files. One file can mislead. | - -## Red Flags - -- Running this skill mid-feature (after `/define`) — should be preflight only -- Writing anywhere besides `plan/conventions.md` -- Modifying source files (this skill is discovery-only) -- Resolving CONFLICT without asking the user -- Generating the artifact without the comparison table populated -- Sampling fewer than 5 files for naming conventions - -## Verification - -After running: - -- [ ] `plan/conventions.md` exists at project root -- [ ] All 7 sections (Project Type, Build, Layout, Naming, Tests, Architecture, Comparison) are populated -- [ ] At least 5 source files were sampled for naming -- [ ] Comparison table has classification for every meaningful area -- [ ] All CONFLICTs have a recorded user resolution -- [ ] No source files were modified -- [ ] Report printed to user with counts (MATCH / REFINEMENT / CONFLICT) - -## Runtime Boundary — HARD STOP - -This skill ENDS after the Verification checklist and final report are completed. - -For codex and any runtime without an enforced skill-return boundary: -- MUST stop the assistant turn here. -- MUST NOT invoke, load, or execute any next GSL skill in the same response turn. -- MUST NOT continue into `/next-flow`, `/define`, `/plan`, `/implement`, `/test`, `/verify`, `/debug`, or `/self-review`. -- MAY print exactly one suggested next command as plain text. -- MUST wait for the user's next message before running any next skill. - -If the user says only "continue", treat that as permission to report the next recommended command, not permission to execute it. diff --git a/.agents/skills/test/references/testing/go.md b/.agents/skills/test/references/testing/go.md deleted file mode 100644 index 0f36b83dd..000000000 --- a/.agents/skills/test/references/testing/go.md +++ /dev/null @@ -1,383 +0,0 @@ -# Testing: go - -Production-oriented patterns for Go tests using standard `testing`, `testify`, fakes, `httptest`, and `testcontainers-go`. - -> Default stance: use real code and small hand-written fakes. Reach for generated mocks only when the interface is large, stable, and expensive to fake manually. - -**Naming caveat — judge by behavior, not name.** A struct named `mockX` or `MockY` is not automatically an anti-pattern. Classify by what it does: - -| Implementation | Classification | -|----------------|----------------| -| Uses an expectation framework (`gomock.Controller`, `EXPECT().Foo().Return(...)`, `assert.AssertExpectations`) | True **mock** — implementation coupling, refactor-fragile | -| Plain hand-written struct (map / counter / fixed return, no expectation framework) | **Fake** that happens to be named `mock*` — REFINEMENT of Fake-first, not CONFLICT | - -When reviewing existing tests, do NOT mechanically rename `mockX` → `fakeX`. Rename only if the naming actively confuses maintainers, and treat the rename as a separate refactor (not as part of feature work). - -## Test Classification - -Go commonly uses build tags instead of framework-level tags. - -| Type | File naming | Build tag | Run via | -|------|-------------|-----------|---------| -| Unit | `*_test.go` | none | `go test ./...` | -| Integration | `*_integration_test.go` | `//go:build integration` | `go test -tags=integration ./...` | -| E2E | `*_e2e_test.go` | `//go:build e2e` | `go test -tags=e2e ./...` | -| Race | any test | none | `go test -race ./...` | - -Integration file header: - -```go -//go:build integration - -package repository_test -``` - -Keep unit tests runnable without Docker, network, or external credentials. - -## Framework - -Default stack: -- Standard `testing` package for test lifecycle. -- `github.com/stretchr/testify/require` for fatal preconditions. -- `github.com/stretchr/testify/assert` for non-fatal comparisons. -- `github.com/google/go-cmp/cmp` for readable struct diffs. - -Rule of thumb: -- Use `require.NoError(t, err)` when continuing would make the test misleading. -- Use `assert.Equal(t, want, got)` when multiple independent assertions are useful. -- Use `cmp.Diff(want, got)` for nested structs, slices, and maps. - -## Naming Convention - -Use exported-style test names because `go test` discovers `TestXxx`. - -```go -func TestService_Register(t *testing.T) {} -func TestHandler_Register(t *testing.T) {} -func TestPostgresRepository_SaveAndFindByEmail(t *testing.T) {} -``` - -For scenarios, prefer table-driven tests: - -```go -func TestNewUser(t *testing.T) { - tests := []struct { - name string - email string - userName string - wantErr bool - }{ - {name: "valid user", email: "a@example.com", userName: "Alice"}, - {name: "empty email", email: "", userName: "Alice", wantErr: true}, - } - - for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - _, err := NewUser("user-1", tt.email, tt.userName, time.Now()) - if tt.wantErr { - require.Error(t, err) - return - } - require.NoError(t, err) - }) - } -} -``` - -Always rebind `tt := tt` before parallel subtests for compatibility with older Go versions and clearer intent. - -## Subtests - -Use `t.Run` to group related behavior without hiding individual failures. - -```go -func TestService_Register(t *testing.T) { - t.Run("creates user when email is new", func(t *testing.T) {}) - t.Run("returns conflict when email exists", func(t *testing.T) {}) -} -``` - -Guidelines: -- Put `t.Parallel()` inside each test or subtest only after test-local setup is complete. -- Avoid parallel tests when they mutate process-wide state such as env vars, global loggers, or current working directory. -- Use `t.Setenv` instead of `os.Setenv`; it automatically restores values. -- Use `t.TempDir` for filesystem tests. - -## InMemory Repository Pattern - -Implement the consumer-owned interface directly. Keep it concurrency-safe if tests may run in parallel. - -```go -type FakeUserRepository struct { - mu sync.Mutex - byID map[string]usecase.User - byEmail map[string]string -} - -func NewFakeUserRepository() *FakeUserRepository { - return &FakeUserRepository{ - byID: make(map[string]usecase.User), - byEmail: make(map[string]string), - } -} - -func (r *FakeUserRepository) Save(ctx context.Context, user usecase.User) error { - r.mu.Lock() - defer r.mu.Unlock() - - if existingID, ok := r.byEmail[user.Email]; ok && existingID != user.ID { - return usecase.ErrEmailAlreadyUsed - } - r.byID[user.ID] = user - r.byEmail[user.Email] = user.ID - return nil -} - -func (r *FakeUserRepository) FindByEmail(ctx context.Context, email string) (usecase.User, error) { - r.mu.Lock() - defer r.mu.Unlock() - - id, ok := r.byEmail[email] - if !ok { - return usecase.User{}, usecase.ErrUserNotFound - } - return r.byID[id], nil -} -``` - -Fake rules: -- Model the behavior the usecase depends on, not the entire database. -- Preserve important constraints such as uniqueness. -- Guard maps with `sync.Mutex` when tests use `t.Parallel()`. -- Add helper methods only for test setup or assertions. - -## Unit Test Example - -```go -func TestService_Register(t *testing.T) { - t.Parallel() - - repo := NewFakeUserRepository() - ids := fixedIDGenerator{id: "user-1"} - clk := fixedClock{now: time.Date(2026, 1, 2, 3, 4, 5, 0, time.UTC)} - svc := usecase.NewService(repo, ids, clk) - - user, err := svc.Register(context.Background(), usecase.RegisterUserInput{ - Email: "A@Example.com", - Name: "Alice", - }) - - require.NoError(t, err) - assert.Equal(t, "user-1", user.ID) - assert.Equal(t, "a@example.com", user.Email) - - saved, err := repo.FindByEmail(context.Background(), "a@example.com") - require.NoError(t, err) - assert.Equal(t, user.ID, saved.ID) -} -``` - -Prefer deterministic helpers for time and IDs: - -```go -type fixedIDGenerator struct{ id string } - -func (g fixedIDGenerator) NewID() string { return g.id } - -type fixedClock struct{ now time.Time } - -func (c fixedClock) Now() time.Time { return c.now } -``` - -## HTTP Testing - -Use standard `net/http/httptest` first. - -### Handler Unit Test with Recorder - -```go -func TestHandler_Register(t *testing.T) { - t.Parallel() - - repo := NewFakeUserRepository() - svc := usecase.NewService(repo, fixedIDGenerator{"user-1"}, fixedClock{time.Now()}) - h := handler.NewHandler(svc, validator.New()) - - body := strings.NewReader(`{"email":"a@example.com","name":"Alice"}`) - req := httptest.NewRequest(http.MethodPost, "/users", body) - rec := httptest.NewRecorder() - - h.Register(rec, req) - - require.Equal(t, http.StatusCreated, rec.Code) - - var got handler.RegisterUserResponse - require.NoError(t, json.NewDecoder(rec.Body).Decode(&got)) - assert.Equal(t, "user-1", got.ID) -} -``` - -### HTTP Integration Test with Server - -```go -func TestUserRoutes(t *testing.T) { - t.Parallel() - - srv := httptest.NewServer(newTestRouter(t)) - t.Cleanup(srv.Close) - - res, err := http.Post( - srv.URL+"/users", - "application/json", - strings.NewReader(`{"email":"a@example.com","name":"Alice"}`), - ) - require.NoError(t, err) - defer res.Body.Close() - - assert.Equal(t, http.StatusCreated, res.StatusCode) -} -``` - -Use `httptest.NewServer` when you need real routing, middleware, cookies, redirects, or an actual `http.Client`. - -## Integration Base: testcontainers-go - -Use Testcontainers for real Postgres, Redis, or broker behavior. Keep it behind the `integration` build tag. - -```go -//go:build integration - -package repository_test - -import ( - "context" - "database/sql" - "os" - "testing" - - "github.com/testcontainers/testcontainers-go" - "github.com/testcontainers/testcontainers-go/modules/postgres" - "github.com/testcontainers/testcontainers-go/wait" -) - -var testDB *sql.DB - -func TestMain(m *testing.M) { - ctx := context.Background() - - container, err := postgres.RunContainer(ctx, - postgres.WithImage("postgres:16-alpine"), - postgres.WithDatabase("app_test"), - postgres.WithUsername("app"), - postgres.WithPassword("secret"), - testcontainers.WithWaitStrategy(wait.ForListeningPort("5432/tcp")), - ) - if err != nil { - panic(err) - } - - dsn, err := container.ConnectionString(ctx, "sslmode=disable") - if err != nil { - panic(err) - } - testDB, err = sql.Open("postgres", dsn) - if err != nil { - panic(err) - } - - code := m.Run() - - _ = testDB.Close() - _ = container.Terminate(ctx) - os.Exit(code) -} -``` - -Repository integration test: - -```go -func TestPostgresRepository_SaveAndFindByEmail(t *testing.T) { - t.Parallel() - - truncateTables(t, testDB) - repo := repository.NewPostgresRepository(testDB) - - user := usecase.User{ - ID: "user-1", - Email: "a@example.com", - Name: "Alice", - CreatedAt: time.Now(), - } - require.NoError(t, repo.Save(context.Background(), user)) - - got, err := repo.FindByEmail(context.Background(), "a@example.com") - require.NoError(t, err) - assert.Equal(t, user.ID, got.ID) -} -``` - -If tests run in parallel against one database, isolate with unique schemas, unique IDs, or per-test cleanup that cannot conflict. - -## Mocking Policy - -Default: hand-written fake. - -Use `gomock` only when: -- The interface is large enough that a fake becomes noisy. -- The contract is stable. -- The test needs precise call ordering or argument matching. - -Avoid mocks for tiny interfaces. A 2-method repository fake is usually clearer, less brittle, and closer to behavior. - -## Fixtures and Golden Files - -Use `testdata/` for fixture files. Go tooling ignores this directory for package builds. - -```text -internal/user/handler/testdata/register_success.json -internal/user/handler/testdata/register_validation_error.json -``` - -Golden test pattern: - -```go -got := renderResponse(data) -want := readFile(t, "testdata/register_success.json") -if diff := cmp.Diff(want, got); diff != "" { - t.Fatalf("response mismatch (-want +got):\n%s", diff) -} -``` - -Keep golden updates explicit. Do not auto-update golden files in normal test runs. - -## Coverage and Race Detector - -Commands: - -```bash -go test -cover -coverprofile=cover.out ./... -go tool cover -func=cover.out -go tool cover -html=cover.out -go test -race ./... -``` - -Guidelines: -- Coverage is a signal, not the goal. Prioritize business branches and error paths. -- Run `-race` for code using goroutines, maps, caches, background workers, or shared fakes. -- Race detector is slower; use it in L2/L3 verification or CI. - -## Anti-Patterns - -Avoid: -- Using mocks for tiny interfaces that are easier to fake by hand. -- Tests that pass only when run in a specific order. -- `t.Parallel()` with shared mutable maps and no mutex. -- Integration tests without build tags. -- Sleeping for timing assertions instead of using channels, contexts, or fake clocks. -- Ignoring response body close in HTTP client tests. -- Calling external services in unit tests. -- Comparing large structs with unreadable `reflect.DeepEqual` failures when `go-cmp` would show a diff. - diff --git a/.agents/skills/test/references/testing/python.md b/.agents/skills/test/references/testing/python.md deleted file mode 100644 index 5d61b1165..000000000 --- a/.agents/skills/test/references/testing/python.md +++ /dev/null @@ -1,477 +0,0 @@ -# Testing: python - -Battle-tested testing patterns for Python 3.12+ projects. Default recommendation: pytest + fixtures + typed fakes + testcontainers for integration. - -> Use this with `languages/python.md` and `verify/python.md`. - -## Framework Choice - -Use `pytest` as the default test framework. - -Prefer pytest over `unittest` because: -- fixture composition is simpler -- markers cleanly separate unit / integration / docs tests -- async testing support is straightforward -- parameterization is concise -- plugins such as `pytest-cov`, `pytest-asyncio`, and `testcontainers` integrate well - -`unittest` is acceptable only when an existing codebase already standardizes on it. - -## Test Classification - -Use pytest markers to classify test cost and required infrastructure. - -```python -import pytest - - -@pytest.mark.unit -def test_hash_password_when_valid_password_returns_hash() -> None: - ... - - -@pytest.mark.integration -def test_user_repository_when_saved_can_find_by_email() -> None: - ... - - -@pytest.mark.docs -def test_openapi_schema_when_generated_matches_contract() -> None: - ... -``` - -### Marker policy - -- `unit`: no network, no real DB, no Docker -- `integration`: real infrastructure through testcontainers or local test service -- `docs`: docs, examples, OpenAPI schema, generated contract checks -- `e2e`: optional, external process or full stack - -## pytest Configuration - -Declare markers and strict marker behavior in `pyproject.toml`. - -```toml -[tool.pytest.ini_options] -testpaths = ["tests"] -python_files = ["test_*.py", "*_test.py"] -addopts = [ - "--strict-markers", - "--strict-config", -] -markers = [ - "unit: fast tests without external infrastructure", - "integration: tests that require Docker or real infrastructure", - "docs: documentation and generated contract tests", - "e2e: full-stack end-to-end tests", -] -asyncio_mode = "auto" -``` - -`--strict-markers` prevents typo markers from silently creating new categories. - -## Test Layout - -Recommended structure: - -``` -tests/ -├── conftest.py -├── unit/ -│ └── users/ -│ └── test_user_service.py -├── integration/ -│ └── users/ -│ └── test_user_repository.py -└── api/ - └── test_users_router.py -``` - -Use test names that describe behavior: - -```python -def test_register_when_email_exists_raises_user_already_exists() -> None: - ... -``` - -## Fixture Pattern - -Use `conftest.py` for shared fixtures. Keep default scope as `function`. - -```python -# tests/conftest.py -from __future__ import annotations - -import pytest - -from app.users.models import User - - -@pytest.fixture -def user_email() -> str: - return "user@example.com" - - -@pytest.fixture -def existing_user(user_email: str) -> User: - return User( - id=1, - email=user_email, - name="Existing User", - password_hash="hashed-password", - ) -``` - -### Fixture scope rules - -- `function`: default for mutable data and repositories. -- `module`: rarely, when setup is expensive but state can be isolated. -- `session`: Docker containers, schema creation, immutable config. -- Avoid global mutable fixtures. - -## InMemory Pattern - -Use typed fake repositories that implement the same `Protocol` as the real repository. - -This is preferred over `unittest.mock.Mock` for domain tests because: -- type checking catches interface drift -- behavior is explicit -- state is visible -- tests read like real use cases - -```python -# tests/unit/users/fakes.py -from __future__ import annotations - -from app.users.models import User -from app.users.repository import UserRepository - - -class InMemoryUserRepository(UserRepository): - def __init__(self) -> None: - self._users_by_email: dict[str, User] = {} - self._next_id = 1 - - async def save(self, user: User) -> User: - if user.id is None: - user.id = self._next_id - self._next_id += 1 - self._users_by_email[user.email] = user - return user - - async def find_by_email(self, email: str) -> User | None: - return self._users_by_email.get(email) -``` - -## Unit Test Example — Fake Repository - -```python -# tests/unit/users/test_user_service.py -from __future__ import annotations - -import pytest - -from app.users.schemas import UserRegisterRequest -from app.users.service import UserAlreadyExistsError, UserService -from tests.unit.users.fakes import InMemoryUserRepository - - -class FakeSession: - def begin(self) -> "FakeSession": - return self - - async def __aenter__(self) -> None: - return None - - async def __aexit__(self, exc_type: object, exc: object, tb: object) -> None: - return None - - -@pytest.mark.unit -@pytest.mark.asyncio -async def test_register_when_new_email_saves_user() -> None: - repository = InMemoryUserRepository() - service = UserService(repository=repository, session=FakeSession()) - - user = await service.register( - UserRegisterRequest( - email="new@example.com", - name="New User", - password="very-secure-password", - ) - ) - - assert user.id == 1 - assert user.email == "new@example.com" - - -@pytest.mark.unit -@pytest.mark.asyncio -async def test_register_when_email_exists_raises_user_already_exists() -> None: - repository = InMemoryUserRepository() - service = UserService(repository=repository, session=FakeSession()) - - request = UserRegisterRequest( - email="dup@example.com", - name="First User", - password="very-secure-password", - ) - await service.register(request) - - with pytest.raises(UserAlreadyExistsError): - await service.register(request) -``` - -## Integration Testing - -Use `testcontainers-python` for real Postgres / Redis / external infrastructure behavior. - -Install examples: -- `testcontainers[postgresql]` -- `asyncpg` -- `sqlalchemy[asyncio]` - -Use session-scoped containers and function-scoped database cleanup. - -```python -# tests/integration/conftest.py -from __future__ import annotations - -from collections.abc import AsyncIterator, Iterator - -import pytest -from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker, create_async_engine -from testcontainers.postgres import PostgresContainer - -from app.users.models import Base - - -@pytest.fixture(scope="session") -def postgres_container() -> Iterator[PostgresContainer]: - with PostgresContainer("postgres:16-alpine") as postgres: - yield postgres - - -@pytest.fixture(scope="session") -async def async_engine(postgres_container: PostgresContainer) -> AsyncIterator[AsyncEngine]: - sync_url = postgres_container.get_connection_url() - async_url = sync_url.replace("postgresql+psycopg2://", "postgresql+asyncpg://") - engine = create_async_engine(async_url) - - async with engine.begin() as connection: - await connection.run_sync(Base.metadata.create_all) - - yield engine - - await engine.dispose() - - -@pytest.fixture -async def session(async_engine: AsyncEngine) -> AsyncIterator[AsyncSession]: - maker = async_sessionmaker(async_engine, expire_on_commit=False) - - async with maker() as session: - async with session.begin(): - yield session - await session.rollback() -``` - -## Integration Test Example — Repository - -```python -# tests/integration/users/test_user_repository.py -from __future__ import annotations - -import pytest -from sqlalchemy.ext.asyncio import AsyncSession - -from app.users.models import User -from app.users.repository import SqlAlchemyUserRepository - - -@pytest.mark.integration -@pytest.mark.asyncio -async def test_find_by_email_when_user_saved_returns_user(session: AsyncSession) -> None: - repository = SqlAlchemyUserRepository(session) - user = User( - email="saved@example.com", - name="Saved User", - password_hash="hashed-password", - ) - - await repository.save(user) - found = await repository.find_by_email("saved@example.com") - - assert found is not None - assert found.email == "saved@example.com" -``` - -## HTTP Testing - -### FastAPI `TestClient` - -Use `TestClient` for sync-style API tests when route dependencies can run under the test client's event loop. - -```python -from fastapi.testclient import TestClient - -from app.main import app - - -def test_health_when_called_returns_ok() -> None: - client = TestClient(app) - - response = client.get("/health") - - assert response.status_code == 200 -``` - -### `httpx.AsyncClient` - -Use `httpx.AsyncClient` when the test itself is async or when async fixtures are already in use. - -```python -from __future__ import annotations - -import pytest -from httpx import ASGITransport, AsyncClient - -from app.main import app - - -@pytest.mark.asyncio -async def test_health_when_called_returns_ok() -> None: - transport = ASGITransport(app=app) - - async with AsyncClient(transport=transport, base_url="http://test") as client: - response = await client.get("/health") - - assert response.status_code == 200 -``` - -## Auth in Tests - -Override FastAPI dependencies directly. - -```python -from __future__ import annotations - -from fastapi.testclient import TestClient - -from app.auth.dependencies import get_current_user -from app.main import app -from app.users.schemas import CurrentUser - - -def test_me_when_authenticated_returns_current_user() -> None: - fake_user = CurrentUser(id=1, email="user@example.com") - app.dependency_overrides[get_current_user] = lambda: fake_user - - try: - client = TestClient(app) - response = client.get("/me") - - assert response.status_code == 200 - assert response.json()["email"] == "user@example.com" - finally: - app.dependency_overrides.clear() -``` - -### Override rules - -- Always clear `app.dependency_overrides` after the test. -- Prefer fixture-managed overrides for repeated patterns. -- Do not patch auth internals with `Mock` when a dependency override is enough. - -## Factory Pattern - -Use plain factory functions for simple projects. - -```python -# tests/factories.py -from __future__ import annotations - -from app.users.models import User - - -def make_user( - *, - id: int | None = None, - email: str = "user@example.com", - name: str = "Test User", - password_hash: str = "hashed-password", -) -> User: - return User(id=id, email=email, name=name, password_hash=password_hash) -``` - -Use `factory-boy` when: -- many models share nested defaults -- large object graphs are common -- integration tests need DB-backed factories - -Do not hide important test setup inside overly clever factories. - -## Async Tests - -Use `pytest-asyncio`. - -```python -import pytest - - -@pytest.mark.asyncio -async def test_async_operation_when_called_returns_value() -> None: - result = await async_operation() - - assert result == "value" -``` - -With `asyncio_mode = "auto"`, explicit `@pytest.mark.asyncio` may be optional, but keeping it improves readability for async tests. - -## Coverage - -Use `pytest-cov` in CI. - -Example commands: -- `pytest -m unit --cov=app --cov-report=term-missing` -- `pytest --cov=app --cov-report=xml --cov-fail-under=80` - -Coverage gates should protect core behavior, not force meaningless tests. - -## Property-Based Tests - -Use `hypothesis` for invariants: -- parsing / normalization -- money / date calculations -- validators -- idempotent transformations - -```python -from hypothesis import given -from hypothesis import strategies as st - - -@given(st.text(min_size=1)) -def test_normalize_name_when_called_is_idempotent(value: str) -> None: - once = normalize_name(value) - twice = normalize_name(once) - - assert once == twice -``` - -## Common Anti-patterns - -- Overusing `from unittest.mock import Mock` for domain collaborators. -- Mocking the method under test. -- Global fixture state shared across tests. -- Session-scoped mutable repositories. -- Integration tests hitting production or developer-local databases. -- Docker-dependent tests marked as `unit`. -- Tests relying on execution order. -- Sleeping in tests instead of controlling time or polling with timeout. -- Leaving `app.dependency_overrides` dirty after a test. -- Factories that create hidden external resources. -- Assertions that only check status code and ignore response body. -- Coverage gates without meaningful assertions. -- `pytest.mark.skip` used to hide broken tests without a tracked reason. - diff --git a/.agents/skills/verify/references/verify/go.md b/.agents/skills/verify/references/verify/go.md deleted file mode 100644 index 22e74264b..000000000 --- a/.agents/skills/verify/references/verify/go.md +++ /dev/null @@ -1,169 +0,0 @@ -# Verify: go - -Verification ladder for Go modules. Run from the module root and prefer full-project scope with `./...`. - -> L1 is the quick local/CI gate. L2 is the normal pre-review gate. L3 is the full gate when Docker-backed integration or e2e tests exist. - -## Commands per step - -### L1 — Quick - -| Step | Command | Evidence | -|------|---------|----------| -| 1 Build / type-check | `go build ./...` | all packages compile | -| 2 Vet | `go vet ./...` | standard static checks pass | -| 3 Lint | `golangci-lint run ./...` | project lint profile passes | -| 4 Module diff | `go mod tidy -diff` | no unexpected `go.mod` / `go.sum` drift | -| 5 Architecture rules (optional) | `go-arch-lint check` | package boundary rules pass | - -Use L1 for fast feedback before claiming compile or lint health. - -### L2 — Standard (includes L1) - -| Step | Command | Evidence | -|------|---------|----------| -| 6 Unit tests | `go test ./...` | all non-integration tests pass | -| 7 Race detector | `go test -race ./...` | no detected data races | - -L2 excludes files guarded by `//go:build integration` unless the project also includes those tags in default test config. - -### L3 — Full (includes L2) - -| Step | Command | Evidence | -|------|---------|----------| -| 8 Integration tests | `go test -tags=integration ./...` | Docker-backed integration tests pass | -| 9 E2E tests (optional) | `go test -tags=e2e ./...` | end-to-end flows pass | -| 10 Coverage report (optional) | `go test -cover -coverprofile=cover.out ./...` | coverage profile generated | - -L3 usually requires Docker when tests use `testcontainers-go` for Postgres, Redis, Kafka, or other services. - -## Recommended command groups - -Quick gate: - -```bash -go build ./... -go vet ./... -golangci-lint run ./... -go mod tidy -diff -``` - -Standard gate: - -```bash -go build ./... -go vet ./... -golangci-lint run ./... -go mod tidy -diff -go test ./... -go test -race ./... -``` - -Full gate: - -```bash -go build ./... -go vet ./... -golangci-lint run ./... -go mod tidy -diff -go test ./... -go test -race ./... -go test -tags=integration ./... -go test -tags=e2e ./... -``` - -If the project does not define `e2e` tests, skip the final command and say it was not applicable. - -## Auto-fix commands - -Auto-fix is related to verification but is not itself proof that the project is healthy. - -```bash -gofmt -w . -goimports -w . -golangci-lint run --fix ./... -``` - -Rules: -- Run `gofmt` for all Go edits. -- Run `goimports` when imports changed. -- Re-run the relevant L1/L2 command after auto-fix. -- Do not hide lint findings by disabling linters without an explicit project decision. - -## Notes - -### Taskfile detection (project automation, check first) - -If the project root contains `Taskfile.yaml` (or `Taskfile.yml`), **prefer the project's own task wrappers** over raw `go` commands. The project author has typically pre-tuned race-detector flags, coverage thresholds, lint config, and integration setup (venv creation, container reuse, S3 prep, etc.). Use raw commands only as a fallback. - -Detection: run `task --list` (or read `Taskfile.yaml`) to see available tasks. - -| GSL level | Preferred tasks (try in order) | Fallback | -|-----------|--------------------------------|----------| -| L1 quick | `task lint` → `task vet` → `task build` | `go vet ./... && go build ./... && golangci-lint run ./...` | -| L2 standard | `task test:short` → `task test` | `go test ./... && go test -race ./...` | -| L3 full | `task verify` → `task test:integration` | `go test -tags=integration ./...` | - -Adopt the project's verify ladder as the source of truth for that project — even if its order or scope differs from the raw-command ladder above. Record the chosen ladder in `plan/conventions.md` under `Verification`. - -Scope: -- Use `./...` from module root for build, vet, test, and lint unless the project documents a narrower target. -- For workspaces, run verification in each touched module or use the repository's documented workspace command. -- If generated code is involved, run the generator before verification only when the project expects generated files to be committed. - -Docker: -- `go test -tags=integration ./...` may start containers through `testcontainers-go`. -- Verify Docker is running before L3; do not treat Docker startup failure as a code test failure without evidence. - -Timeouts: -- Quick build/vet/lint: 60-180s depending on module size. -- Unit tests: 180-300s. -- Race tests: 300-600s. -- Integration tests: 600s or project-specific timeout. - -Useful flags: - -```bash -go test -count=1 ./... # bypass test cache when checking behavior changes -go test -run TestName ./... # focused reproduction before full run -go test -timeout=10m ./... # explicit timeout for CI or long packages -go test -json ./... # machine-readable output for CI parsers -``` - -Coverage: - -```bash -go test -cover -coverprofile=cover.out ./... -go tool cover -func=cover.out -go tool cover -html=cover.out -``` - -Coverage is supporting evidence. It does not replace behavior-focused assertions. - -## Git hook examples - -Pre-commit quick hook: - -```bash -gofmt -w . && goimports -w . && go test ./... -``` - -Pre-push standard hook: - -```bash -go build ./... && go vet ./... && golangci-lint run ./... && go test -race ./... -``` - -## Completion report template - -When reporting completion, include exact commands and outcomes: - -```text -검증: -- go build ./...: 통과 -- go test ./...: 통과 -- go test -tags=integration ./...: 미실행, Docker 필요 -``` - -Never claim completion from intent alone. Run the command, read the output, then report the evidence. - diff --git a/.agents/skills/verify/references/verify/python.md b/.agents/skills/verify/references/verify/python.md deleted file mode 100644 index 76cfc33f9..000000000 --- a/.agents/skills/verify/references/verify/python.md +++ /dev/null @@ -1,174 +0,0 @@ -# Verify: python - -Verification ladder for Python 3.12+ projects. Run the smallest level that proves the requested change, then report exact commands and outcomes. - -> Use with `languages/python.md` and `testing/python.md`. - -## L1 — Quick Static Verification - -Use L1 for formatting, linting, typing, and architecture import rules. - -| Step | Command | -|------|---------| -| 1 Type-check | `mypy src/` | -| 1 Alternative type-check | `pyright` | -| 2 Lint | `ruff check .` | -| 3 Format check | `ruff format --check .` | -| 4 Import / architecture rules | `import-linter --config .importlinter` | - -Run `mypy` or `pyright` according to the project standard. Do not run both unless the project already does. - -Recommended combined command: - -```bash -mypy src/ && ruff check . && ruff format --check . -``` - -With import-linter: - -```bash -mypy src/ && ruff check . && ruff format --check . && import-linter --config .importlinter -``` - -## L2 — Standard Verification - -L2 includes L1 plus unit tests and package build. - -| Step | Command | -|------|---------| -| 1 Static verification | L1 commands | -| 2 Unit tests | `pytest -m "unit and not integration" --strict-markers` | -| 3 Package build | `python -m build` | -| 3 Alternative build | `uv build` | - -Recommended combined command: - -```bash -mypy src/ \ - && ruff check . \ - && ruff format --check . \ - && pytest -m "unit and not integration" --strict-markers \ - && python -m build -``` - -Use `uv build` instead of `python -m build` when the project standardizes on uv. - -## L3 — Full Verification - -L3 includes L2 plus integration tests. - -| Step | Command | -|------|---------| -| 1 Standard verification | L2 commands | -| 2 Integration tests | `pytest -m integration --strict-markers` | -| 3 Optional E2E / smoke | `pytest -m e2e --strict-markers` | - -Recommended command: - -```bash -mypy src/ \ - && ruff check . \ - && ruff format --check . \ - && pytest -m "unit and not integration" --strict-markers \ - && python -m build \ - && pytest -m integration --strict-markers -``` - -Integration tests that use `testcontainers-python` require Docker. - -## Coverage Commands - -Use coverage when the change touches business logic or CI requires a gate. - -Unit coverage: - -```bash -pytest -m "unit and not integration" \ - --strict-markers \ - --cov=app \ - --cov-report=term-missing -``` - -CI gate: - -```bash -pytest \ - --strict-markers \ - --cov=app \ - --cov-report=xml \ - --cov-report=term-missing \ - --cov-fail-under=80 -``` - -`--cov-fail-under=N` fails the command when total coverage is below `N`. - -## Auto-fix Commands - -Auto-fix commands are not verification. Run verification again after auto-fix. - -Format: - -```bash -ruff format . -``` - -Lint fix: - -```bash -ruff check --fix . -``` - -Combined: - -```bash -ruff format . && ruff check --fix . -``` - -Use `ruff check --fix --unsafe-fixes .` only after reviewing what unsafe fixes may change. - -## pytest Options - -Recommended defaults in `pyproject.toml`: - -```toml -[tool.pytest.ini_options] -addopts = [ - "--strict-markers", - "--strict-config", -] -markers = [ - "unit: fast tests without external infrastructure", - "integration: tests requiring Docker or real infrastructure", - "docs: documentation and generated contract tests", - "e2e: full-stack end-to-end tests", -] -``` - -`--strict-markers` catches marker typos such as `@pytest.mark.integraton`. - -## pre-commit Pattern - -Use pre-commit for fast local checks: - -```yaml -repos: - - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.5.0 - hooks: - - id: ruff - args: [--fix] - - id: ruff-format -``` - -Keep type-check and tests in CI even if pre-commit exists. - -## Notes - -- Run verification at whole-project scope: `src/`, `tests/`, or `.` as configured. -- Avoid per-file verification as final evidence unless the project is extremely large and the user requested a narrow check. -- L3 requires Docker when integration tests use testcontainers. -- Recommended timeouts: L1 60s, L2 180s, L3 600s. -- If Docker is unavailable, report L1/L2 results separately from skipped L3. -- If `python -m build` fails because `build` is missing, install through the project toolchain, for example `uv add --dev build` or project-approved equivalent. -- Do not claim completion from auto-fix output alone. Re-run the relevant verification level. - diff --git a/.claude/skills/implement/references/languages/go.md b/.claude/skills/implement/references/languages/go.md deleted file mode 100644 index eeb3f5cc6..000000000 --- a/.claude/skills/implement/references/languages/go.md +++ /dev/null @@ -1,488 +0,0 @@ -# Language: go - -Battle-tested patterns for Go 1.22+ services. Use this with matching `types/*.md`, `testing/go.md`, and `verify/go.md`. - -> Default assumption: HTTP API or worker service with explicit layers, standard `context.Context`, and tests built around fakes or testcontainers. - -## Module Layout - -Recommended single-module layout: - -```text -. -├── cmd/ -│ └── api/ -│ └── main.go # process entrypoint only -├── internal/ -│ └── user/ -│ ├── handler/ # transport adapters: HTTP, gRPC, queue -│ ├── usecase/ # business logic and consumer-owned interfaces -│ └── repository/ # DB/cache implementations -├── pkg/ # public library packages only when truly reusable -├── sql/ # migrations or sqlc query files -├── go.mod -└── go.sum -``` - -Rules: -- `cmd/{app}/main.go` wires configuration, logger, DB, repositories, usecases, and server. -- `internal/` is the default for product code. Go enforces import privacy: packages outside the parent tree cannot import `internal/...`. -- `pkg/` is not a dumping ground. Use it only for stable, reusable libraries that are safe for external import. -- Keep domain slices under `internal/{domain}/...` unless the project already uses technical slices. - -**Existing `pkg/`-centric projects.** Some established Go projects place product code under `pkg/` instead of `internal/`. This may predate the modern `internal/` convention or be a deliberate choice (e.g., exposing internal packages for tooling, a library that doubles as a service). Treat this as a **REFINEMENT** of the convention, NOT a CONFLICT: - -- Respect the existing layout. Add new code where the project's peers already live. -- Do NOT migrate `pkg/X` → `internal/X` as part of a feature change. Migration is a separate refactor requiring explicit user approval and a dedicated plan. -- The Phase 0 hard gate of `/implement` still applies. Just resolve `{package-root}` to the project's actual choice (`internal/` OR `pkg/`). - -## Layered Structure - -Default flow: - -```text -handler -> usecase -> repository implementation -HTTP business sqlc/sqlx/GORM/Redis/external client -``` - -Responsibilities: -- `handler`: decode transport input, validate DTO, call usecase, map errors to transport response. -- `usecase`: own business rules, transactions, idempotency, authorization decisions, and repository interfaces. -- `repository`: implement persistence details. No HTTP DTOs and no transport-specific errors. - -Do not let repository code decide API status codes. Do not let handlers reach into SQL helpers directly. - -## Interface Boundary - -Go interface ownership is opposite to many Java/Spring designs. - -Rule: define the interface in the consumer package, usually `usecase`, not in the producer package. - -```text -[OK] internal/user/usecase.UserRepository interface -[OK] internal/user/repository.PostgresRepository implements usecase.UserRepository -[NO] internal/user/repository.Repository exported by producer and imported everywhere -``` - -Why: -- Consumers declare only the methods they need. -- Tests can provide small fakes without implementing producer-wide interfaces. -- Producer packages stay concrete and simple. - -## DTO Pattern - -Use plain structs with tags. Keep DTOs separate from domain entities. - -```go -package handler - -type RegisterUserRequest struct { - Email string `json:"email" validate:"required,email"` - Name string `json:"name" validate:"required,min=1,max=80"` -} - -type RegisterUserResponse struct { - ID string `json:"id"` - Email string `json:"email"` - Name string `json:"name"` -} -``` - -Validation: -- Use `github.com/go-playground/validator/v10` at the transport boundary. -- Convert valid DTOs into usecase input structs. -- Do not pass HTTP request DTOs into repository packages. - -```go -package usecase - -type RegisterUserInput struct { - Email string - Name string -} -``` - -## Error Handling - -Use explicit errors. Libraries return errors; applications decide how to present them. - -Recommended layers: -- Sentinel errors for stable categories: `ErrUserNotFound`, `ErrEmailAlreadyUsed`. -- Domain error type when the caller needs structured details. -- `errors.Is` for category checks. -- `errors.As` for extracting typed errors. - -```go -package usecase - -import "errors" - -var ( - ErrUserNotFound = errors.New("user not found") - ErrEmailAlreadyUsed = errors.New("email already used") -) - -type ValidationError struct { - Field string - Message string -} - -func (e *ValidationError) Error() string { - return e.Field + ": " + e.Message -} -``` - -Wrap lower-level errors with context: - -```go -if err := r.queries.CreateUser(ctx, params); err != nil { - return User{}, fmt.Errorf("create user: %w", err) -} -``` - -Map errors at the edge: - -```go -switch { -case errors.Is(err, usecase.ErrEmailAlreadyUsed): - http.Error(w, "email already used", http.StatusConflict) -default: - http.Error(w, "internal server error", http.StatusInternalServerError) -} -``` - -Never `panic` for normal business errors. - -## Context Propagation - -Every I/O-capable method receives `context.Context` as the first argument. - -```go -func (s *Service) Register(ctx context.Context, in RegisterUserInput) (User, error) -func (r *PostgresRepository) Save(ctx context.Context, user User) error -``` - -Rules: -- First parameter: `ctx context.Context`. -- Do not store `context.Context` in structs. -- Do not pass `nil`; use `context.Background()` only at process boundaries or tests. -- Propagate request cancellation to DB, Redis, HTTP clients, and queue clients. -- Avoid global state; inject dependencies through constructors. - -## Repository Pattern - -Define the repository interface in the usecase package: - -```go -package usecase - -import "context" - -type UserRepository interface { - Save(ctx context.Context, user User) error - FindByEmail(ctx context.Context, email string) (User, error) - FindByID(ctx context.Context, id string) (User, error) -} -``` - -Implement it in the repository package: - -```go -package repository - -type PostgresRepository struct { - db *sql.DB - queries *dbgen.Queries // sqlc generated package -} - -func NewPostgresRepository(db *sql.DB) *PostgresRepository { - return &PostgresRepository{db: db, queries: dbgen.New(db)} -} -``` - -Implementation options: -- `sqlc`: preferred when SQL shape matters and compile-time query types are valuable. -- `sqlx`: good for hand-written SQL with lightweight scanning helpers. -- `GORM`: acceptable when the project already standardizes on ORM behavior; keep it behind repository interfaces. - -Transaction handling belongs in usecase or a small transaction manager abstraction, not hidden deep inside random repository methods. - -## Dependency Injection - -Default: manual constructor injection. - -```go -userRepo := repository.NewPostgresRepository(db) -userSvc := usecase.NewService(userRepo, clock, ids) -userHandler := handler.NewHandler(userSvc, validator.New()) -``` - -Constructor rules: -- Accept interfaces only when the package consumes behavior. -- Return concrete structs unless there is a strong reason to hide them. -- Keep constructors boring and side-effect free. - -Use `wire`, `fx`, or another DI container only when the project is large enough that manual wiring is demonstrably noisy. Do not introduce a DI framework for a few constructors. - -## Folder Convention - -Preferred domain-first layout: - -```text -internal/user/ -├── handler/ -│ ├── dto.go -│ └── http.go -├── usecase/ -│ ├── errors.go -│ ├── repository.go -│ ├── service.go -│ └── user.go -└── repository/ - ├── postgres.go - └── mapper.go -``` - -Keep generated packages separate when using sqlc: - -```text -internal/user/repository/dbgen/ # generated by sqlc -sql/queries/user.sql -sql/schema/001_create_users.sql -``` - -## Code Example: User Registration - -### Entity - -```go -package usecase - -import ( - "strings" - "time" -) - -type User struct { - ID string - Email string - Name string - CreatedAt time.Time -} - -func NewUser(id, email, name string, now time.Time) (User, error) { - email = strings.TrimSpace(strings.ToLower(email)) - name = strings.TrimSpace(name) - - if email == "" { - return User{}, &ValidationError{Field: "email", Message: "email is required"} - } - if name == "" { - return User{}, &ValidationError{Field: "name", Message: "name is required"} - } - - return User{ID: id, Email: email, Name: name, CreatedAt: now}, nil -} -``` - -### Repository Interface - -```go -package usecase - -import "context" - -type UserRepository interface { - Save(ctx context.Context, user User) error - FindByEmail(ctx context.Context, email string) (User, error) -} -``` - -### Repository Implementation - -```go -package repository - -import ( - "context" - "database/sql" - "errors" - "fmt" - - "example.com/app/internal/user/usecase" -) - -type PostgresRepository struct { - db *sql.DB -} - -func NewPostgresRepository(db *sql.DB) *PostgresRepository { - return &PostgresRepository{db: db} -} - -func (r *PostgresRepository) Save(ctx context.Context, user usecase.User) error { - const q = `insert into users (id, email, name, created_at) values ($1, $2, $3, $4)` - if _, err := r.db.ExecContext(ctx, q, user.ID, user.Email, user.Name, user.CreatedAt); err != nil { - if isUniqueViolation(err) { - return usecase.ErrEmailAlreadyUsed - } - return fmt.Errorf("save user: %w", err) - } - return nil -} - -func (r *PostgresRepository) FindByEmail(ctx context.Context, email string) (usecase.User, error) { - const q = `select id, email, name, created_at from users where email = $1` - var user usecase.User - err := r.db.QueryRowContext(ctx, q, email).Scan(&user.ID, &user.Email, &user.Name, &user.CreatedAt) - if errors.Is(err, sql.ErrNoRows) { - return usecase.User{}, usecase.ErrUserNotFound - } - if err != nil { - return usecase.User{}, fmt.Errorf("find user by email: %w", err) - } - return user, nil -} -``` - -### Usecase - -```go -package usecase - -import ( - "context" - "errors" - "fmt" - "time" -) - -type IDGenerator interface { - NewID() string -} - -type Clock interface { - Now() time.Time -} - -type Service struct { - repo UserRepository - ids IDGenerator - clk Clock -} - -func NewService(repo UserRepository, ids IDGenerator, clk Clock) *Service { - return &Service{repo: repo, ids: ids, clk: clk} -} - -func (s *Service) Register(ctx context.Context, in RegisterUserInput) (User, error) { - if _, err := s.repo.FindByEmail(ctx, in.Email); err == nil { - return User{}, ErrEmailAlreadyUsed - } else if !errors.Is(err, ErrUserNotFound) { - return User{}, fmt.Errorf("check duplicate email: %w", err) - } - - user, err := NewUser(s.ids.NewID(), in.Email, in.Name, s.clk.Now()) - if err != nil { - return User{}, err - } - if err := s.repo.Save(ctx, user); err != nil { - return User{}, err - } - return user, nil -} -``` - -### HTTP Handler - -```go -package handler - -import ( - "encoding/json" - "errors" - "net/http" - - "github.com/go-playground/validator/v10" - - "example.com/app/internal/user/usecase" -) - -type Handler struct { - svc *usecase.Service - validate *validator.Validate -} - -func NewHandler(svc *usecase.Service, validate *validator.Validate) *Handler { - return &Handler{svc: svc, validate: validate} -} - -func (h *Handler) Register(w http.ResponseWriter, r *http.Request) { - var req RegisterUserRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - http.Error(w, "invalid json", http.StatusBadRequest) - return - } - if err := h.validate.Struct(req); err != nil { - http.Error(w, "invalid request", http.StatusBadRequest) - return - } - - user, err := h.svc.Register(r.Context(), usecase.RegisterUserInput{ - Email: req.Email, - Name: req.Name, - }) - if err != nil { - writeRegisterError(w, err) - return - } - - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusCreated) - _ = json.NewEncoder(w).Encode(RegisterUserResponse{ - ID: user.ID, - Email: user.Email, - Name: user.Name, - }) -} - -func writeRegisterError(w http.ResponseWriter, err error) { - switch { - case errors.Is(err, usecase.ErrEmailAlreadyUsed): - http.Error(w, "email already used", http.StatusConflict) - default: - http.Error(w, "internal server error", http.StatusInternalServerError) - } -} -``` - -## Build and Tooling - -Common commands: - -```bash -go test ./... -go test ./... -tags=integration -go test -race ./... -go vet ./... -golangci-lint run ./... -``` - -Project defaults: -- Run commands from module root. -- Prefer `./...` scope for verification. -- Keep `go.mod` minimal; unused dependencies are a smell. -- Run `gofmt` and `goimports` before review. - -## Anti-Patterns - -Avoid: -- Global mutable variables for DB, logger, config, or clients. -- `panic` in libraries or business logic for recoverable errors. -- Missing `context.Context` on I/O paths. -- Defining broad interfaces in producer packages. -- Passing HTTP DTOs into usecase or repository packages. -- Creating `pkg/` packages before there is a real external reuse need. -- Hiding transactions inside unrelated helper methods. -- Returning raw SQL driver errors across usecase boundaries. - diff --git a/.claude/skills/implement/references/languages/python.md b/.claude/skills/implement/references/languages/python.md deleted file mode 100644 index af0e68c03..000000000 --- a/.claude/skills/implement/references/languages/python.md +++ /dev/null @@ -1,605 +0,0 @@ -# Language: python - -Battle-tested patterns for Python 3.12+ web-api / service code. Default recommendation: FastAPI + Pydantic v2 + SQLAlchemy 2.x async + pytest. - -> Use this with the matching `types/*.md` (typically `web-api.md`) and `testing/python.md`. - -## Framework Choice - -### Default — FastAPI - -Use FastAPI for new Python web-api work when the project needs: -- async I/O support -- automatic OpenAPI generation -- explicit request / response DTOs -- lightweight application structure -- clear dependency injection via `Depends()` - -FastAPI keeps API boundaries explicit. It fits service-oriented code where domain logic lives outside route handlers. - -### Alternative — Django - -Use Django when the project needs: -- admin UI out of the box -- full-stack server-rendered features -- Django ORM and ecosystem conventions -- batteries-included auth / sessions / forms - -Do not mix Django ORM patterns into FastAPI projects. If Django is selected, follow Django app conventions instead of the folder layout below. - -## Module Layout - -Prefer `src-layout` for installable packages and CI consistency. - -``` -project-root/ -├── pyproject.toml -├── src/ -│ └── app/ -│ ├── __init__.py -│ ├── main.py -│ ├── core/ -│ │ ├── __init__.py -│ │ ├── config.py -│ │ ├── database.py -│ │ └── errors.py -│ └── users/ -│ ├── __init__.py -│ ├── models.py -│ ├── schemas.py -│ ├── repository.py -│ ├── service.py -│ └── router.py -└── tests/ -``` - -### `src-layout` vs flat layout - -Use `src/` when: -- the code is packaged -- imports must behave the same locally and in CI -- the project has multiple modules or app entrypoints - -Flat layout is acceptable only for small scripts or throwaway prototypes. - -### Package vs flat modules - -Use packages when a domain has multiple files: -- `users/models.py` -- `users/schemas.py` -- `users/repository.py` -- `users/service.py` -- `users/router.py` - -Avoid one huge `users.py` once the domain has API, persistence, and business logic. - -### `__init__.py` convention - -Keep `__init__.py` small. -- OK: package marker, public re-export for stable API -- Avoid: side effects, database connection, logger setup, app creation - -```python -# src/app/users/__init__.py -from app.users.service import UserService - -__all__ = ["UserService"] -``` - -## Folder Convention - -``` -src/{root}/{domain}/ -├── models.py # SQLAlchemy ORM model -├── schemas.py # Pydantic request / response DTOs -├── repository.py # thin DB wrapper + Protocol boundary -├── service.py # business use cases -└── router.py # FastAPI routes, thin HTTP adapter -``` - -Shared infrastructure: - -``` -src/{root}/core/ -├── config.py # settings -├── database.py # engine, sessionmaker, session dependency -└── errors.py # common error response shape -``` - -## Application Entry - -`main.py` wires routers and exception handlers. It should not contain business logic. - -```python -# src/app/main.py -from fastapi import FastAPI - -from app.core.errors import register_exception_handlers -from app.users.router import router as users_router - -app = FastAPI(title="Example API") - -app.include_router(users_router, prefix="/users", tags=["users"]) -register_exception_handlers(app) -``` - -## DTO Pattern — Pydantic v2 - -Use Pydantic v2 `BaseModel` for request / response DTOs. Keep DTOs separate from ORM models. - -```python -# src/app/users/schemas.py -from __future__ import annotations - -from datetime import datetime - -from pydantic import BaseModel, ConfigDict, EmailStr, Field, field_validator - - -class UserRegisterRequest(BaseModel): - email: EmailStr - name: str = Field(min_length=1, max_length=80) - password: str = Field(min_length=12, max_length=128) - - @field_validator("name") - @classmethod - def normalize_name(cls, value: str) -> str: - normalized = value.strip() - if not normalized: - raise ValueError("name must not be blank") - return normalized - - -class UserResponse(BaseModel): - model_config = ConfigDict(from_attributes=True) - - id: int - email: EmailStr - name: str - created_at: datetime -``` - -### DTO rules - -- Request DTO validates external input. -- Response DTO defines the public API contract. -- ORM model should not be returned directly from a route. -- Use `model_validate(entity)` only at the route boundary or service return mapping boundary. -- Do not mix Pydantic v1 `Config.orm_mode = True` with v2 code. Use `ConfigDict(from_attributes=True)`. - -## ORM Pattern — SQLAlchemy 2.x Async - -Use SQLAlchemy 2.x typed mappings and `AsyncSession`. - -```python -# src/app/users/models.py -from __future__ import annotations - -from datetime import datetime - -from sqlalchemy import DateTime, String, func -from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column - - -class Base(DeclarativeBase): - pass - - -class User(Base): - __tablename__ = "users" - - id: Mapped[int] = mapped_column(primary_key=True) - email: Mapped[str] = mapped_column(String(320), unique=True, index=True) - name: Mapped[str] = mapped_column(String(80)) - password_hash: Mapped[str] = mapped_column(String(255)) - created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) -``` - -### Async session boundary - -Create the engine and sessionmaker once. Create `AsyncSession` per request or per transaction boundary. - -```python -# src/app/core/database.py -from __future__ import annotations - -from collections.abc import AsyncIterator - -from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine - -from app.core.config import settings - -engine = create_async_engine(settings.database_url, pool_pre_ping=True) -AsyncSessionMaker = async_sessionmaker(engine, expire_on_commit=False) - - -async def get_session() -> AsyncIterator[AsyncSession]: - async with AsyncSessionMaker() as session: - yield session -``` - -## Dependency Injection - -Use FastAPI `Depends()` for request-scoped dependencies. Use factory functions for services. - -```python -# src/app/users/router.py -from __future__ import annotations - -from typing import Annotated - -from fastapi import Depends -from sqlalchemy.ext.asyncio import AsyncSession - -from app.core.database import get_session -from app.users.repository import SqlAlchemyUserRepository, UserRepository -from app.users.service import UserService - - -def get_user_repository( - session: Annotated[AsyncSession, Depends(get_session)], -) -> UserRepository: - return SqlAlchemyUserRepository(session) - - -def get_user_service( - repository: Annotated[UserRepository, Depends(get_user_repository)], - session: Annotated[AsyncSession, Depends(get_session)], -) -> UserService: - return UserService(repository=repository, session=session) -``` - -### DI rules - -- Prefer explicit factory functions over global singletons. -- Dependencies should be easy to override in tests. -- Route handlers receive services, not repositories, unless the endpoint is truly infrastructure-only. -- Do not create database sessions at import time. - -## Repository Pattern - -Repository should be a thin wrapper around persistence. It should not own business decisions. - -Use `Protocol` for interface boundaries. This keeps services testable without `unittest.mock.Mock`. - -```python -# src/app/users/repository.py -from __future__ import annotations - -from typing import Protocol - -from sqlalchemy import select -from sqlalchemy.ext.asyncio import AsyncSession - -from app.users.models import User - - -class UserRepository(Protocol): - async def save(self, user: User) -> User: - ... - - async def find_by_email(self, email: str) -> User | None: - ... - - -class SqlAlchemyUserRepository: - def __init__(self, session: AsyncSession) -> None: - self._session = session - - async def save(self, user: User) -> User: - self._session.add(user) - await self._session.flush() - await self._session.refresh(user) - return user - - async def find_by_email(self, email: str) -> User | None: - result = await self._session.execute(select(User).where(User.email == email)) - return result.scalar_one_or_none() -``` - -### Repository rules - -- OK: CRUD, simple query composition, persistence details. -- Avoid: password hashing, permission checks, domain decisions. -- Keep methods named by domain intent when useful: `find_by_email`, `exists_by_email`. -- Do not expose raw SQLAlchemy query objects to services. - -## Service Pattern - -Services contain use cases. They may be classes or pure functions. - -Use a class when: -- multiple dependencies are needed -- route-level DI should construct the use case -- the domain has several related operations - -Use pure functions when: -- dependencies are passed explicitly -- the operation is small and stateless - -```python -# src/app/users/service.py -from __future__ import annotations - -from sqlalchemy.ext.asyncio import AsyncSession - -from app.users.models import User -from app.users.repository import UserRepository -from app.users.schemas import UserRegisterRequest - - -class UserAlreadyExistsError(Exception): - def __init__(self, email: str) -> None: - super().__init__(f"user already exists: {email}") - self.email = email - - -class UserService: - def __init__(self, repository: UserRepository, session: AsyncSession) -> None: - self._repository = repository - self._session = session - - async def register(self, request: UserRegisterRequest) -> User: - existing = await self._repository.find_by_email(str(request.email)) - if existing is not None: - raise UserAlreadyExistsError(str(request.email)) - - user = User( - email=str(request.email), - name=request.name, - password_hash=hash_password(request.password), - ) - - async with self._session.begin(): - return await self._repository.save(user) - - -def hash_password(raw_password: str) -> str: - # 실제 프로젝트에서는 argon2/bcrypt 같은 검증된 해시를 사용한다. - return f"hashed:{raw_password}" -``` - -### Transaction rule - -Prefer one transaction boundary per use case. - -```python -async with session.begin(): - ... -``` - -Do not commit inside repository methods. Repository methods may `flush()` when generated IDs are needed. - -## Router Pattern - -Routers are HTTP adapters. They validate input, call service, and map output. - -```python -# src/app/users/router.py -from __future__ import annotations - -from typing import Annotated - -from fastapi import APIRouter, Depends, status - -from app.users.schemas import UserRegisterRequest, UserResponse -from app.users.service import UserService - -router = APIRouter() - - -@router.post("", response_model=UserResponse, status_code=status.HTTP_201_CREATED) -async def register_user( - request: UserRegisterRequest, - service: Annotated[UserService, Depends(get_user_service)], -) -> UserResponse: - user = await service.register(request) - return UserResponse.model_validate(user) -``` - -### Router rules - -- Keep route handlers thin. -- Do not open transactions in route handlers when service owns the use case. -- Do not put SQLAlchemy queries in route handlers. -- Always declare `response_model` for public API routes. - -## Error Model - -Use domain exception subclasses and map them through FastAPI exception handlers. - -```python -# src/app/core/errors.py -from __future__ import annotations - -from fastapi import FastAPI, Request, status -from fastapi.responses import JSONResponse - -from app.users.service import UserAlreadyExistsError - - -class ErrorResponse(dict[str, object]): - pass - - -def register_exception_handlers(app: FastAPI) -> None: - @app.exception_handler(UserAlreadyExistsError) - async def handle_user_already_exists( - request: Request, - exc: UserAlreadyExistsError, - ) -> JSONResponse: - return JSONResponse( - status_code=status.HTTP_409_CONFLICT, - content={ - "code": "USER_ALREADY_EXISTS", - "message": "user already exists", - "details": {"email": exc.email}, - }, - ) -``` - -### Error rules - -- Raise domain exceptions from services. -- Map HTTP status in API layer, not deep in domain code. -- Do not raise `HTTPException` from repositories or domain services. -- Keep error response shape stable. - -## Async Decision Rule - -Use `async def` when the function awaits I/O: -- database calls -- HTTP calls -- Redis / queue calls -- file I/O through async libraries - -Use `def` when the work is CPU-bound or purely in-memory: -- validation helpers -- DTO mapping -- password policy checks -- small calculations - -In FastAPI: -- `async def` route is preferred when calling async dependencies. -- `def` route is acceptable for sync-only work. -- Never call blocking I/O from `async def` without moving it to a threadpool or using an async client. - -## Typing Defaults - -Use modern typing as a design constraint. - -```python -from __future__ import annotations - -from collections.abc import Sequence -from typing import Protocol -``` - -Recommended: -- Python 3.12+ -- `mypy --strict` or `pyright` -- `from __future__ import annotations` -- `Protocol` for service/repository boundaries -- concrete return types for public functions -- `Sequence[T]` for read-only collection inputs -- `list[T]` when mutation or concrete list output is required - -Avoid: -- implicit `Any` -- untyped decorators -- `dict` without key/value types -- `# type: ignore` without a specific error code and reason - -## Packaging - -Use `pyproject.toml` as the single source of project metadata. - -Prefer `uv` for new projects when fast reproducible dependency management is desired. Poetry is acceptable when the project already standardizes on it. - -```toml -[project] -name = "example-api" -version = "0.1.0" -requires-python = ">=3.12" -dependencies = [ - "fastapi>=0.110", - "pydantic[email]>=2.6", - "sqlalchemy[asyncio]>=2.0", - "asyncpg>=0.29", - "uvicorn[standard]>=0.27", -] - -[dependency-groups] -dev = [ - "mypy>=1.8", - "ruff>=0.3", - "pytest>=8", - "pytest-asyncio>=0.23", - "pytest-cov>=5", -] - -[tool.mypy] -python_version = "3.12" -strict = true -mypy_path = "src" - -[tool.ruff] -line-length = 100 -target-version = "py312" -src = ["src", "tests"] -``` - -## End-to-End Example — User Registration - -Minimal flow: - -``` -router -> service -> repository -> SQLAlchemy model -``` - -```python -# schemas.py -class UserRegisterRequest(BaseModel): - email: EmailStr - name: str = Field(min_length=1) - password: str = Field(min_length=12) - - -class UserResponse(BaseModel): - model_config = ConfigDict(from_attributes=True) - - id: int - email: EmailStr - name: str -``` - -```python -# service.py -class UserService: - def __init__(self, repository: UserRepository, session: AsyncSession) -> None: - self._repository = repository - self._session = session - - async def register(self, request: UserRegisterRequest) -> User: - if await self._repository.find_by_email(str(request.email)): - raise UserAlreadyExistsError(str(request.email)) - - async with self._session.begin(): - return await self._repository.save( - User( - email=str(request.email), - name=request.name, - password_hash=hash_password(request.password), - ) - ) -``` - -```python -# router.py -@router.post("", response_model=UserResponse, status_code=201) -async def register_user( - request: UserRegisterRequest, - service: Annotated[UserService, Depends(get_user_service)], -) -> UserResponse: - user = await service.register(request) - return UserResponse.model_validate(user) -``` - -## Common Anti-patterns - -- Global `AsyncSession` or global database transaction object. -- Creating DB sessions at import time. -- Repository commits transactions internally. -- Business logic in FastAPI route handlers. -- Returning SQLAlchemy ORM objects without explicit response DTO. -- Raising `HTTPException` from repository or domain service. -- Sync route calling blocking database / HTTP I/O in a web-api path. -- Pydantic v1 and v2 config mixed in the same project. -- `dependency_overrides`-unfriendly global singletons. -- `Any` used to bypass typing instead of modeling the boundary. -- `# type: ignore` without a narrow error code. -- Large `main.py` containing routers, settings, DB, and services. -- Repositories that become generic ORM facades with every possible query. -- Tests that require the real production database URL. -- Hidden side effects in `__init__.py`. - diff --git a/.claude/skills/implement/references/types/cli.md b/.claude/skills/implement/references/types/cli.md deleted file mode 100644 index c48784867..000000000 --- a/.claude/skills/implement/references/types/cli.md +++ /dev/null @@ -1,215 +0,0 @@ -# Type: cli -Language-independent patterns for command-line applications. Pair with `languages/{language}.md` for concrete code. -## Layer Breakdown (universal) -``` -argv / env / stdin - -> [Command binding] cobra / click / typer / argparse / yargs - -> [Handler / use case] validate command intent and map args to plain input - -> [Service] orchestrate dependencies and application behavior - -> [Domain] pure rules and reusable operations -``` -Rules: -- Command binding is thin: names, args, flags, help, examples, dispatch. -- Handler owns CLI semantics: input mapping, writer choice, exit-code mapping. -- Service owns behavior: no terminal color, process exit, or parser dependency. -- Domain owns rules: testable without a shell or framework. -- Exit once at the process edge; return codes through the command path. -## Command Binding -| Ecosystem | Binding examples | Typical location | -|-----------|------------------|------------------| -| Go | cobra | `cmd/{tool}` or `internal/cli` | -| Python | click / typer / argparse | `src/{package}/cli.py` | -| Node | yargs / commander | `bin/{tool}` and `src/cli` | -| Rust | clap | `src/cli.rs` or `src/main.rs` | -| Java/Kotlin | picocli | command class delegating to service | -Binding responsibilities: -- define command and subcommand names -- declare positional arguments and flags -- provide help text, defaults, and examples -- apply parser-level required checks -- call one handler with a plain input object -Keep out of binding: -- business branching -- persistence and network workflows -- output formatting beyond writer selection -- `exit`, `panic`, or uncaught exception paths -## Argument Design -### Positional arguments -Use positional arguments for required identity-like values: -``` -tool user get -tool file convert -``` -Good candidates: -- one resource id -- one input path -- one required target -- values that read naturally in command form -Avoid positional arguments when: -- there are more than two or three values -- two values have the same type and can be swapped -- the value is optional -- order is hard to remember -### Flags -Use flags for options, filters, formats, and modes: -``` -tool users list --status active --limit 50 --json -tool import data.csv --dry-run --batch-size 500 -``` -Flag rules: -- Prefer stable long flags for scripts. -- Add short flags only for common interactive actions such as `-v`, `-q`, `-h`. -- Boolean flags should be positive by default, with `--no-*` where supported. -- Defaults must appear in help text or docs. -- Do not make a flag meaning depend on hidden state. -### Subcommands -Group by resource and action: -``` -tool project create -tool project list -tool project delete -tool config get -tool config set -``` -Common grouping patterns: -- resource first: `user create`, `user list`, `user delete` -- workflow first: `deploy start`, `deploy status`, `deploy rollback` -- admin namespace: `admin repair`, `admin backfill`, `admin inspect` -Framework notes: -- cobra: nested `*cobra.Command` -- click: `@click.group()` plus subcommands -- typer: mounted `Typer()` apps -- argparse: `add_subparsers()` -- yargs: `.command()` modules or chains -## Output Conventions -### stdout / stderr / exit code -| Channel | Purpose | -|---------|---------| -| stdout | command result intended for caller or pipeline | -| stderr | logs, prompts, progress, warnings, diagnostics | -| exit code | machine-readable success or failure status | -Rules: -- stdout must stay parseable in pipelines. -- progress bars and spinners go to stderr. -- warnings go to stderr even on success. -- error details go to stderr unless JSON error output is explicitly requested. -- never mix logs into JSON stdout. -### Human-readable output -Default output should work well in a terminal: -``` -ID STATUS NAME -p_123 active search-index -p_124 paused newsletter -``` -Guidelines: -- Use tables for short list output. -- Use key/value blocks for detail output. -- Keep default output readable, but do not treat it as a stable API. -- Avoid decorative output that breaks narrow terminals. -### Machine-readable output -Provide JSON for automation: -``` -tool project list --json -``` -JSON rules: -- stdout contains only JSON. -- stderr may contain warnings unless `--quiet` is set. -- Use stable field names. -- Prefer arrays for lists and objects for details. -- Do not emit trailing summary text after JSON. -### Quiet / verbose / color -| Mode | stdout | stderr | -|------|--------|--------| -| default | result | warnings and useful progress | -| `--quiet` | result only | errors only | -| `--verbose` | result | detailed steps | -| `--json` | JSON result | diagnostics only | -Color rules: -- Enable color only when the relevant stream is a TTY. -- Honor `--color`, `--no-color`, and `NO_COLOR` where practical. -- Use a terminal library or output abstraction. -- Do not scatter raw ANSI escape strings through handlers. -- Never rely on color as the only meaning. -## Exit Codes -| Code | Meaning | Use | -|------|---------|-----| -| 0 | success | command completed successfully | -| 1 | user error | invalid args, validation failure, not found, conflict | -| 2 | system error | network, database, filesystem, or unexpected internal failure | -| 130 | interrupted | SIGINT / Ctrl-C, POSIX convention | -Guidelines: -- Keep numeric codes small and documented. -- Map parser and validation failures to user error. -- Map dependency failures to system error. -- Put stable domain error codes in text or JSON, not only in exit code. -## Configuration Precedence -Use this order, highest priority first: -``` -flags > environment variables > config file > defaults -``` -Rules: -- Flags override every other source. -- Environment variables are useful for CI and containers. -- Config files are useful for project or user defaults. -- Defaults must be explicit. -- Verbose or debug mode may print effective config with secrets redacted. -- Never print tokens, passwords, private keys, or signed URLs. -## Long-running Operations -Progress reporting: -- write progress to stderr -- use progress bars only for TTY sessions -- use periodic line logs for non-TTY sessions -- include counts, current phase, and elapsed time when useful -- keep stdout reserved for results -Partial output and signals: -- document whether stdout may be partial after interruption -- flush at safe record boundaries -- prefer newline-delimited JSON for streams -- handle SIGINT and SIGTERM through cancellation -- stop accepting new work, flush buffers, persist checkpoint when supported -- return 130 for SIGINT -## Testability -Design handlers as plain functions: -``` -handle(input, deps, stdout, stderr) -> exit_code -``` -Test seams: -- args can be constructed in memory -- stdout and stderr writers are injectable -- environment lookup is injectable -- filesystem and network access sit behind small interfaces -- clock and random id generation are injectable when behavior depends on them -Recommended tests: -- parser test: argv maps to input object -- handler test: input maps to service call and output -- service/domain test: behavior without CLI framework -- smoke test: real process exits with expected code for core paths -## Folder Layout per Phase -``` -cmd/{tool}/main.* # process entrypoint -src/cli.* # command binding -src/handlers/* # command handlers -src/services/* # use cases -src/domain/* # pure rules -``` -Language variants: -- Go: `cmd/{tool}/main.go`, `internal/cli`, `internal/{domain}` -- Python: `src/{package}/__main__.py`, `cli.py`, `handlers/`, `services/` -- Node: `bin/{tool}.js`, `src/cli/commands`, `src/services`, `src/domain` -Phase rule: -- Phase 1: service/domain behavior and handler contract. -- Phase 2: CLI binding, help text, argument validation. -- Phase 3: packaging, shell completion, install docs, smoke tests. -## Common Anti-patterns -- One function contains parsing, business logic, and printing. -- Service code calls `exit` or terminates the process. -- User errors are handled with `panic` or uncaught exceptions. -- Hard-coded absolute paths. -- Hidden config precedence. -- Logs or progress are printed to stdout. -- JSON stdout includes human summary text. -- Raw ANSI escape strings are written directly from handlers. -- Color is enabled without TTY detection. -- Automation commands prompt without non-interactive mode. -- Retry loops have no timeout or cancellation. -- Dependency failures are swallowed and reported as success. diff --git a/.claude/skills/implement/references/types/library.md b/.claude/skills/implement/references/types/library.md deleted file mode 100644 index ba9ca5bbb..000000000 --- a/.claude/skills/implement/references/types/library.md +++ /dev/null @@ -1,237 +0,0 @@ -# Type: library -Language-independent patterns for libraries and reusable packages. Pair with `languages/{language}.md` for concrete code. -## Purpose -A library is consumed by other code. Its main product is a stable public API, not a process, page, or job. -Design goals: -- clear public surface -- small dependency footprint -- predictable versioning -- runnable examples -- tests from the consumer boundary -- documentation for every exported symbol -## Layer Breakdown (universal) -``` -consumer application - -> [Public API] exported functions, classes, types, modules - -> [Facade] stable entrypoints and configuration objects - -> [Internal core] implementation, algorithms, adapters - -> [Dependencies] optional integrations and platform-specific code -``` -Rules: -- Public API is intentional: exporting a symbol creates compatibility responsibility. -- Facade is stable: most consumers should not need internal modules. -- Internal core can change: keep it hidden behind public entrypoints. -- Dependencies are conservative: every transitive dependency becomes a consumer concern. -## Public API Surface -Define what is exported and what is internal-only. -Language patterns: -- Java: public classes are API; package-private classes stay internal. -- Kotlin: `public` is default; use `internal` deliberately. -- Python: define `__all__`, prefix internals with `_`, document supported imports. -- Go: exported identifiers start with capital letters; use `internal/` for hard boundaries. -- Rust: `pub` and module visibility define API; avoid broad `pub use` without intent. -- TypeScript: exported symbols from package entrypoints are API. -Rules: -- Keep one or a few documented entrypoints. -- Do not require consumers to import deep internal paths. -- Treat config keys, error codes, and event names as public API if consumers use them. -- Mark experimental APIs with stability level. -- Prefer a small facade over exposing many implementation classes. -API checklist: -- symbol name is stable -- parameter names and types are stable -- return shape is stable -- error behavior is documented -- default behavior is documented -- concurrency expectation is documented when relevant -## Backwards Compatibility -Use SemVer: -``` -MAJOR.MINOR.PATCH -``` -| Change | Version bump | Example | -|--------|--------------|---------| -| compatible bug fix | PATCH | fix parsing for valid existing input | -| compatible new feature | MINOR | add optional parameter or new function | -| compatible deprecation notice | MINOR | warn that an API will be removed later | -| breaking API removal | MAJOR | remove exported class or function | -| breaking behavior change | MAJOR | same input now means different operation | -| breaking type change | MAJOR | return string becomes object | -Compatible changes: -- add a new exported symbol -- add an optional config key with default -- improve performance without changing behavior -- fix behavior that was documented as broken -Breaking changes: -- remove or rename exported symbol -- change required parameters -- change return type or error type -- change default behavior -- remove config key -- move public import path without compatibility shim -## Deprecation Policy -Default policy: -- Introduce replacement first. -- Mark old API deprecated in a minor release. -- Emit deprecation warning where the ecosystem supports it. -- Keep deprecated API for at least one major line. -- Remove only in the next major release. -Deprecation notice should include: -- deprecated symbol -- replacement API -- first deprecated version -- planned removal version -- migration example or link -Example: -```text -Deprecated since 2.4.0: use Client.fetch_item() instead. -Planned removal: 3.0.0. -``` -Do not deprecate without a working replacement unless the API is unsafe or unusable. -## Migration Guides -Every breaking change needs BEFORE / AFTER examples. -```text -BEFORE -client = create_client(token) -result = client.fetch(id) -AFTER -client = Client(token=token) -result = client.fetch_item(id) -``` -Migration guide contents: -- affected versions -- who is affected -- exact change -- mechanical replacement when possible -- behavior differences -- rollout order for large applications -- known edge cases -Store guides close to releases, such as `docs/migrations/v2-to-v3.md`. -## Dependency Policy -Principles: -- minimize transitive dependencies -- avoid dependencies for small utilities -- avoid framework lock-in in core modules -- isolate optional integrations -- document supported dependency version ranges -Runtime dependencies: -- Prefer standard library when practical. -- Avoid large frameworks in a small library. -- Avoid exposing dependency types in public API unless intentional. -- Track security updates. -Peer dependencies for npm-style ecosystems: -- Use peer dependencies when the host app must provide the framework. -- Do not bundle a second copy of React, Vue, ESLint, or similar host-level packages. -- State supported peer ranges. -- Test lowest and latest supported peer versions when possible. -Pin vs range: -| Context | Policy | -|---------|--------| -| application lockfile | pin exact resolved versions | -| published library manifest | use compatible ranges | -| security-sensitive tool | narrow range or pin with automation | -| peer dependency | supported range | -## Documentation Contract -Every exported symbol should have docs: -- docstring / JSDoc / KDoc / Rustdoc / Go doc -- parameter meaning -- return meaning -- error behavior -- side effects -- lifecycle or concurrency notes when relevant -README minimum: -- what the library does -- supported platforms or runtimes -- installation -- quick-start -- common configuration -- link to API docs -- versioning and compatibility note -Examples directory: -``` -examples/ -├── quick-start/ -├── custom-config/ -└── error-handling/ -``` -Example rules: -- runnable from a clean checkout -- minimal but complete -- covers one major feature each -- included in CI when practical -## Testing -Test from the package boundary: -``` -import { publicFunction } from "package-name" -``` -or equivalent public import path. -Test levels: -- public API behavior tests -- compatibility tests for documented edge cases -- error contract tests -- example smoke tests -- packaging test that installs the built artifact locally -Rules: -- Public boundary tests must exist. -- Internal tests are allowed for complex algorithms. -- Add regression tests before changing behavior. -- Test deprecation warnings if consumers rely on them. -## Packaging -Document build and publish commands. -Common ecosystems: -- npm: `npm pack`, then publish to npm registry. -- PyPI: build wheel and sdist, then publish. -- crates.io: `cargo package`, `cargo publish`. -- Maven Central: signed group, artifact, version coordinates. -- Go: tag module versions; consumers fetch from VCS or module proxy. -Packaging checklist: -- package includes README, license, and changelog -- package excludes tests or fixtures unless needed -- source maps or type declarations included when relevant -- artifact installs in a clean temp project -- published version matches changelog and git tag -- license metadata is correct -## Versioning Automation -Useful tools: -- changesets -- conventional commits -- semantic-release -- release-please -- language-native release tooling -Rules: -- Automation should not guess breaking changes silently. -- Human review is required for major releases. -- Changelog entries should describe consumer impact. -- Release tags should match package versions. -## Folder Layout per Phase -``` -src/{package}/ -├── index.* # public entrypoint / exports (Phase 1) -├── public/ # optional public facade modules (Phase 1) -├── internal/ # implementation details (Phase 1) -├── integrations/ # optional adapters (Phase 2) -├── errors.* # stable error types / codes (Phase 1) -└── config.* # documented configuration model (Phase 1) -docs/ -├── api.md -└── migrations/ -examples/ -└── quick-start/ -``` -Phase rule: -- Phase 1: public API, internal core, tests from package boundary. -- Phase 2: docs, examples, optional integrations, packaging smoke test. -- Phase 3: release automation, compatibility matrix, migration guides. -## Common Anti-patterns -- Public API grows rapidly before 1.0 without stability labels. -- Internal classes are accidentally exported from the package root. -- Consumers must import from deep internal paths. -- Hard-coded URLs, paths, credentials, or region names. -- Undocumented config keys change behavior. -- Public API exposes dependency-specific types unintentionally. -- Breaking changes ship as patch or minor versions. -- Deprecation has no replacement or removal schedule. -- README quick-start does not run. -- Tests import private internals while public imports are broken. -- Package contents differ from repository assumptions. -- Release automation publishes without changelog review. diff --git a/.claude/skills/implement/references/types/worker-daemon.md b/.claude/skills/implement/references/types/worker-daemon.md deleted file mode 100644 index bdeb6f606..000000000 --- a/.claude/skills/implement/references/types/worker-daemon.md +++ /dev/null @@ -1,149 +0,0 @@ -# Type: worker-daemon - -Language-independent patterns for long-running worker processes that consume tasks from a queue / message broker and execute them indefinitely. Distinct from `cli` (one-shot command) and `batch` (scheduled job with defined start/end). - -> Use with the matching `languages/{language}.md`. Common languages: Go (NATS / pgmq / Redis Streams), Java (Spring Cloud Stream / Kafka), Python (Celery / Dramatiq). - -## When to choose `worker-daemon` over neighbors - -| Symptom | Type | -|---------|------| -| Reads from queue indefinitely, no scheduled stop | **worker-daemon** ✓ | -| Triggered by cron / specific time, finite work | batch | -| Single user-facing command invocation, finite | cli | -| Receives HTTP requests, finite per-request | web-api | - -Common hybrid: a process launched by **CLI** (`./mytool worker --queue=tasks`) but **operates as a worker-daemon**. Reference selection: primary = `worker-daemon.md`, secondary = `cli.md` for the launch surface. - -## Layer Breakdown - -``` -[Launch surface] CLI binding / main() / k8s entrypoint — boot, parse env/flags - ↓ -[Bootstrap] config load, logger, metrics, signal trap - ↓ -[Broker consumer] NATS / Kafka / Redis Streams / pgmq client - ↓ -[Worker registry] task type → handler function map - ↓ -[Task handler] business logic per task type - ↓ -[Service / repo] shared domain layer (same as web-api / batch) -``` - -## Worker Registry Pattern - -Tasks have a `type` field; registry dispatches to the correct handler. - -``` -type WorkerHandler func(ctx context.Context, payload []byte, out OutputPort) error - -type Registry struct { - handlers map[string]WorkerHandler -} - -func (r *Registry) Register(taskType string, h WorkerHandler) { ... } -func (r *Registry) Dispatch(ctx, msg) error { - h, ok := r.handlers[msg.Type] - if !ok { return ErrUnknownTaskType } // → ack with DLQ tag - return h(ctx, msg.Payload, msg.Out) -} -``` - -Rule: a new worker type = a new entry in `Register(...)` + a new handler file. Never modify existing handlers when adding a type. - -## Port Schema (input / output contract) - -Each handler declares its input and output schemas (JSON Schema / Pydantic / Go struct + tag): - -``` -worker: pdf-extract -input: { uri: string, language?: string } -output: { text: string, page_count: int } -errors: { code: "INVALID_URI" | "EXTRACT_FAILED" | "DOWNLOAD_TIMEOUT" } -``` - -Schemas live in `pkg/schema/definitions/` (language-specific path). Generator produces typed models. Handlers operate on typed inputs, not raw bytes. - -## Broker Integration - -| Broker | Acknowledgment | Retry | DLQ | -|--------|---------------|-------|-----| -| NATS JetStream | `msg.Ack()` after success | Stream config: `MaxDeliver` | `MaxDeliver` exceeded → DLQ subject | -| Kafka | Commit offset on success | Manual loop with backoff | `__dlq` topic | -| Redis Streams | `XACK` on success | `XCLAIM` for pending > timeout | Separate stream | -| pgmq (Postgres) | `archive(msg_id)` | `read_with_poll` re-delivers if not archived | Separate queue table | - -Rule: ack ONLY after the handler returns success. On failure: do NOT ack — let the broker re-deliver. Handler must be idempotent (same task delivered twice → same result). - -## Graceful Shutdown - -``` -Signal received (SIGTERM / SIGINT) - ↓ -1. Stop accepting NEW tasks (consumer.Unsubscribe / cancel context) -2. Drain in-flight: wait for current handlers to finish (with timeout, e.g., 30s) -3. Flush metrics, close DB connections, close broker connection -4. Exit 0 -``` - -Hard rule: NEVER `os.Exit(0)` while a task is in-flight. The broker thinks the task succeeded but no ack was sent → re-delivery + possible double-execution. - -## Idempotency - -Tasks may be delivered more than once (at-least-once semantics in most brokers). Handlers MUST be idempotent: - -- **Dedup key**: include `task_id` (UUID) in every task. Handler checks a fast store (Redis `SETNX` with TTL, or DB `UNIQUE` index) before doing work. If already processed → ack and skip. -- **Idempotent writes**: prefer `INSERT ... ON CONFLICT DO NOTHING` / `MERGE` over plain `INSERT`. External API calls should accept `Idempotency-Key` header. -- **State machine**: if the task transitions a record from state A → B, check current state first. Re-running on a B record should no-op. - -## Observability (mandatory in worker-daemon) - -- **Structured logs**: every log line carries `task_id`, `task_type`, `worker_id`, `attempt`. JSON output, NOT plain text. -- **Metrics**: - - `worker_tasks_processed_total{task_type, outcome=success|failure|dlq}` (counter) - - `worker_task_duration_seconds{task_type}` (histogram) - - `worker_in_flight{task_type}` (gauge) - - `worker_queue_lag_seconds` (gauge, from broker) -- **Tracing**: propagate `trace_id` from message header. Span = handler invocation. -- **Health endpoints** (HTTP, separate port from broker): - - `/healthz` — process alive - - `/readyz` — broker connection healthy, handler registry initialized -- **Run history**: write to a `worker_runs` table (`task_id`, `started_at`, `finished_at`, `outcome`, `error_summary`) - -## Resource Limits - -- **Concurrent task limit**: semaphore / channel-buffered worker pool. Default = number of CPU cores. Tune per task type if some are I/O-heavy. -- **Per-task timeout**: `context.WithTimeout` at handler entry. Reasonable default 5 min; configurable per task type. -- **Memory limit**: rely on container limits (k8s `resources.limits.memory`). Process should fail fast (OOM) rather than swap. -- **Backpressure**: when broker re-delivery rate exceeds processing rate, log + emit alert. Optionally pause subscription. - -## Folder Layout per Phase - -``` -src/{root}/ -├── cmd/ # CLI binding (Phase 2 surface) -│ └── worker.go # parses flags, calls bootstrap -├── internal/{root}/ (or pkg/) -│ ├── bootstrap/ # config, logger, metrics, signals -│ ├── broker/ # broker-specific client (NATS, Kafka, ...) -│ ├── workers/ # one file per worker type -│ │ ├── pdf_extract.go -│ │ ├── thumbnail_resize.go -│ │ └── registry.go # GetBuiltinWorkers() / Register -│ ├── schema/ # input/output JSON schemas -│ └── observability/ # metrics, tracing, health endpoint -``` - -## Common Anti-patterns - -- **`os.Exit` mid-task**: shutdown signal handler that exits immediately, in-flight task lost -- **No idempotency**: handler does an unconditional `INSERT` — duplicate row on re-delivery -- **Ack-before-success**: handler acks the message, then crashes — task lost -- **Unbounded concurrency**: no worker pool / semaphore — broker re-delivery overwhelms the process -- **Plain-text logs**: `log.Printf("processed task %s", id)` — non-greppable, no structured filters -- **No DLQ wiring**: failed tasks loop forever or get silently dropped -- **Health endpoint inside broker port**: `/healthz` on the same port as worker traffic — k8s probe fails when broker port is busy -- **CLI command mixed with worker logic**: `cmd/worker.go` contains business logic instead of delegating to handler -- **Schema in code only**: input shape defined only in Go struct, not in a generator-friendly schema — frontend / other services can't validate -- **No per-task timeout**: a stuck task holds a worker slot forever, pool starvation diff --git a/.claude/skills/next-flow/SKILL.md b/.claude/skills/next-flow/SKILL.md deleted file mode 100644 index 824a60073..000000000 --- a/.claude/skills/next-flow/SKILL.md +++ /dev/null @@ -1,147 +0,0 @@ ---- -name: next-flow -description: | - Diagnoses where in the GSL lifecycle the work currently is, proposes the single next command, and may auto-trigger ONLY read-only verification (never any write). - Trigger: "/next-flow", or when the user says "다음 단계", "이제 뭐 해야 해", "next step", "what's next". - Use after completing any GSL step (`/define`, `/plan`, `/implement` Task, `/test`, `/verify`, `/debug`) to find out what to do next. - Strictly read-only: this skill never writes files, edits code, or modifies plan documents. -argument-hint: "[plan-file-path or current-step]" ---- - -# Next Flow — GSL Lifecycle Continuation Helper - -## Overview - -GSL's biggest known friction is **P1: gaps between steps don't self-advance** — after `/implement` the user has to manually invoke `/test`, after `/test` manually invoke `/verify`, and so on. This skill closes that gap **without violating GSL gates**: it reads the plan document, diagnoses where the work is, proposes exactly one next command, and may run a read-only verification on the user's behalf — but **never writes anything**. - -Hard policy (from codex consultation): *auto-progression is restricted to read-only verification + next-command suggestion. Any write-side follow-up (new test files, code edits, plan document updates, commits, pushes) requires explicit user invocation.* See "Common Rationalizations" for why this matters. - -## When to Use - -- Right after any GSL step appears to have completed -- When the user is unsure what to do next ("다음에 뭐 하면 돼?") -- When a plan document exists but the user has lost track of the progress log -- Before invoking `/test` / `/verify` / commit — to confirm preconditions are met - -## When NOT to Use - -- Before `/define` (no plan document yet — start with `/define` directly) -- When a `/debug` recovery is in progress (let `/debug` finish first) -- When the user has already named the next step explicitly -- For any next GSL command transition — print the command name and stop. In slash-command UI runtimes, tell the user to type that command directly; in codex, tell the user to send a new message containing that skill name. - -## Process - -### Step 1: Locate the Plan Document - -- If user passed a path, use it -- Otherwise scan `plan/*.md` for the IN PROGRESS document (one with unchecked Tasks) -- If 0 candidates → tell user to run `/define`. STOP. -- If 2+ candidates → ask user which one. STOP. - -### Step 2: Diagnose Current Step - -Read Overview, Tasks, Progress Log. Classify the work into exactly one of: - -| Signal | Current step | Next step | -|--------|--------------|-----------| -| Plan doc has Overview only, no Tasks | post-`/define` | `/plan` (print command and stop) | -| Tasks exist, Progress Log empty | post-`/plan` | `/implement` | -| Some Tasks done, more remain | mid-`/implement` | continue `/implement` with next Task | -| All Tasks done, no integration tests written | post-`/implement` | `/test` (integration tests) | -| Integration tests exist, `/verify full` not yet run | post-`/test` | `/verify full` | -| `/verify full` passed | post-`/verify` | commit + plan stamp (manual) | -| Last log entry is a `/debug` failure | mid-`/debug` | continue `/debug` | -| `/verify` failed | mid-`/debug` | enter `/debug` | - -### Step 3: Check Preconditions (read-only) - -Before proposing the next command, verify its preconditions in a read-only way: - -| Next command | Read-only precondition check | -|--------------|------------------------------| -| `/plan` | Overview section is complete, user has approved | -| `/implement` | At least one Task is defined | -| `/test` | All Tasks have commits in Progress Log | -| `/verify full` | `git status` clean OR uncommitted work is intentional | -| commit / PR | `/verify full` last run = PASS | -| `/debug` | A reproducible failure is recorded | - -If any precondition fails, **do not auto-progress**. Report the failure and let the user decide. - -**Red test state — hard precondition.** If `git status` or the latest test output indicates uncommitted test failures, OR the last recorded `/verify` run is FAIL, do NOT propose any next step except `/debug`. If no reproducible failure is recorded yet, STOP and ask the user to first reproduce the failure (this is `/debug` Step 2). Never propose `/test` / `/verify` / commit / push while a known red state exists. - -### Step 4: Optional Auto-Run (read-only only) - -This is the ONLY auto-progression allowed by this skill. Allowed read-only actions: - -- `/verify quick` or `/verify standard` (these run tests but do not write source files) -- `git status` / `git log --oneline` / `git diff` to confirm preconditions -- Reading existing test output - -**Never allowed:** -- Editing or creating files (source, test, plan, anywhere) -- Running commit, push, branch, merge -- Invoking write-side skills (`/define`, `/implement`, `/test` write-mode, etc.) without user action -- Modifying the plan document -- Calling external systems (DB, deploy, package registries) - -### Step 5: Propose the Next Command - -Output exactly one of: - -``` -Next: / -Reason: -Preconditions: PASS -[Run it? You can type / yourself, or reply "yes" to have me run it only if it is read-only.] -``` - -For any next GSL command, do not run it in this turn. Replace the last line with runtime-neutral guidance: - -``` -To continue, send a new message containing this command. In slash-command UI runtimes, type the command directly; in codex, send the skill name in a new message. -``` - -## Common Rationalizations - -| Rationalization | Reality | -|-----------------|---------| -| "All Tasks done, I'll just auto-write the integration tests" | `/test` writes files. That violates GSL's user-gate. Propose `/test` and stop. | -| "User said 'continue', so I can just push through commit" | Commit is a write boundary and an externally-visible action. Propose, never auto-run. | -| "It's only a small edit to plan doc Progress Log" | Plan document is the single source of truth. Only `/implement` updates it after a Task commit. | -| "/verify full passed — let me start the next feature" | Closing the current feature (stamp + plan/complete/ move) is a manual decision. Propose, don't act. | -| "I'll auto-call the next GSL command since it is obvious" | Next GSL commands are runtime boundaries. Print the command name and stop. | - -## Red Flags - -- Auto-progression has caused any file to be created, edited, or deleted (immediate STOP — this skill should never do that) -- Multiple next-step candidates proposed (the table in Step 2 should yield exactly one) -- Preconditions skipped because "the user implied it was fine" -- Proposing any next GSL command for same-turn execution instead of printing the command name and stopping -- **Bypassing the runtime-boundary notice by invoking the next GSL command through Bash, shell scripts, `subprocess`, MCP tools, or other wrappers — anti-pattern, STOP.** The notice is the only correct output for GSL command transitions. -- The next command requires user approval (e.g., post-`/define` → `/plan`) and you tried to execute it anyway - -## Verification - -After running: - -- [ ] Exactly one next command was proposed -- [ ] All preconditions were checked in a read-only way -- [ ] If any auto-action was run, it was read-only only (verify / status / log / diff) -- [ ] No file was created, edited, or deleted by this skill -- [ ] If the next step is a GSL command, the runtime-boundary notice was printed instead of attempting same-turn execution -- [ ] The proposal includes the reason and the user's explicit invocation prompt - -## Runtime Boundary — HARD STOP - -This skill ENDS after the Verification checklist and final report are completed. - -For codex and any runtime without an enforced skill-return boundary: -- MUST stop the assistant turn here. -- MUST NOT invoke, load, or execute any next GSL skill in the same response turn. -- MUST NOT continue into `/next-flow`, `/define`, `/plan`, `/implement`, `/test`, `/verify`, `/debug`, or `/self-review`. -- MAY print exactly one suggested next command as plain text. -- MUST wait for the user's next message before running any next skill. - -If the user says only "continue", treat that as permission to report the next recommended command, not permission to execute it. diff --git a/.claude/skills/scan-conventions/SKILL.md b/.claude/skills/scan-conventions/SKILL.md deleted file mode 100644 index 4529c7980..000000000 --- a/.claude/skills/scan-conventions/SKILL.md +++ /dev/null @@ -1,229 +0,0 @@ ---- -name: scan-conventions -description: | - Scans a project ONCE to discover its real conventions (directory layout, naming, build system, test patterns, error model) and writes a single artifact for downstream GSL steps to consult. - Trigger: "/scan-conventions", or when the user says "관습 파악", "프로젝트 스캔", "scan conventions", "convention discovery", "preflight". - Use on project entry, or when conventions have drifted significantly from the existing artifact. - Other GSL skills (`/define`, `/implement`, `/test`) consult the artifact at their Phase 0 — they do not re-scan. -argument-hint: "[project-root path, default = current working dir]" ---- - -# Scan Conventions — Preflight Project Discovery - -## Overview - -GSL's `references/` provide generic patterns per project type (web-api / cli / batch / library) and per language (java-spring / python / go). But every real project has its own conventions that may agree with, refine, or contradict those references. This skill runs **once per project** (not per feature) to discover the actual conventions and writes a single artifact at `plan/conventions.md`. All other GSL skills then read that artifact at their Phase 0 instead of re-scanning, which keeps per-step cost low and conventions consistent across the session. - -If `plan/conventions.md` already exists and is recent, **do not re-run** — let the user invoke explicitly if they suspect drift. - -## When to Use - -- First time using GSL on a project -- After significant restructuring (modules added/removed, framework upgrade) -- User explicitly asks ("관습 다시 파악해줘", "rescan") -- Any GSL skill's Phase 0 reports missing or stale `plan/conventions.md` - -## When NOT to Use - -- `plan/conventions.md` already exists and project hasn't significantly changed -- Mid-feature work (run `/scan-conventions` only on entry, not between Tasks) -- Single-file edits to a known project - -## Process - -### Step 1: Confirm Run Conditions - -- Locate the project root (argument, or current working dir + walk up to first `.git` / lockfile) -- Check whether `plan/conventions.md` already exists - - Exists and < 30 days old → ask user "Rescan, or use existing? (use existing recommended)" - - Exists and > 30 days OR user explicitly asked → proceed with rescan - - Does not exist → proceed - -### Step 2: Discover Directory Layout - -Identify: -- Top-level structure (monorepo / single module / multi-module) -- Source root convention (`src/`, `src/main/...`, `internal/`, `lib/`, ...) -- Test root convention -- Configuration / build files location - -### Step 3: Identify Build System & Language - -Detect via lockfiles / manifests: -- `build.gradle` / `pom.xml` / `pyproject.toml` / `package.json` / `go.mod` / `Cargo.toml` / etc. -- Language version (from manifest) -- Test runner (Gradle test, pytest, go test, jest, etc.) -- Linter / formatter (spotless, ruff, eslint, gofmt, ...) - -### Step 4: Sample Naming Conventions - -Read 5+ recent source files (favor non-test, non-generated). Extract: -- File naming pattern (`{Domain}Controller.java`, `{snake_case}.py`, `{name}_handler.go`) -- Type / class / function naming patterns -- Test class / function naming convention -- Display name convention (Korean `@DisplayName` "~할 때 ~한다" / English describe blocks / etc.) - -**Bootstrap fallback (insufficient samples).** If the project has fewer than 5 sampleable source files (a freshly bootstrapped repo with only `main.py` / `cmd/main.go` / `App.java` / similar), **do NOT block downstream skills**. Instead: - -1. Mark this section as `INSUFFICIENT_SAMPLE` in the artifact (`plan/conventions.md`). -2. Adopt the matching `references/languages/{language}.md` defaults as the working conventions for naming / layering / test patterns. -3. Record in the artifact that this is a bootstrap convention and **flag for mandatory re-scan after the first 5+ non-test source files are committed**. -4. The artifact still gets written so that `/define`·`/plan`·`/implement` can proceed; downstream skills MUST consult the `INSUFFICIENT_SAMPLE` marker and warn the user that the convention is provisional. - -This unblocks green-field projects without forcing fabricated conventions. The Red Flag "Sampling fewer than 5 files" applies ONLY to projects that have 5+ files but the skill skipped them — not to projects that genuinely have fewer than 5. - -### Step 5: Identify Test Patterns - -- Test categorization mechanism (`@Tag("unit")`, `@pytest.mark.unit`, build tags `//go:build integration`, ...) -- Fake / InMemory pattern present? (search for `InMemory*`, `Fake*`, fixture directories) -- Integration test base class / fixture -- Test data factory pattern -- Mocking library presence (note as informational — GSL policy is Fake-first regardless) - -### Step 6: Detect Architecture Markers - -- Layer markers (custom annotations like `@FacadeService`, `@DomainRepository`, internal package boundaries) -- Cross-module seams (facade / port / interface boundaries) -- Error model (custom exception hierarchies, error code enums) -- Persistence pattern (Repository tiers, Active Record, etc.) - -### Step 7: Compare Against GSL References - -For each discovered convention, compare to the matching GSL references: -- `implement/references/types/{detected-type}.md` -- `implement/references/languages/{detected-language}.md` -- `test/references/testing/{detected-language}.md` -- `verify/references/verify/{detected-language}.md` - -Classify each finding as: -- **MATCH** — project convention matches references (downstream skills can use references as-is) -- **REFINEMENT** — project has a more specific convention (note in artifact; downstream skills should follow project's) -- **CONFLICT** — project convention contradicts references (escalate; user decides which wins in Step 8) - -### Step 8: Resolve Conflicts (user decision) - -For each CONFLICT, present: -``` -CONFLICT: - references: - project: - Which should downstream GSL skills follow? - 1) Project (recommended — match existing code) - 2) References (only if you intend to refactor the project) -``` - -Record the user's decision in the artifact. - -### Step 8.5: Virtual / Dry-run Mode (read-only environments) - -If running in a **read-only sandbox** (`codex exec`, MCP sandbox, CI dry-run) or the user explicitly requested a **simulation / dry-run**: - -- Do NOT attempt to write `plan/conventions.md` to disk -- Instead, output the same artifact content to conversation / stdout with header `[VIRTUAL ARTIFACT — NOT WRITTEN]` -- Downstream skills in the same session can read this content from conversation context as if it were the on-disk file -- This does NOT satisfy `/define`'s hard gate for production work — virtual mode is for analysis / dry-run only - -For real project entry, proceed to Step 9. - -### Step 9: Write the Artifact - -Write to `plan/conventions.md`: - -```markdown -# Project Conventions - -> Generated by /scan-conventions on YYYY-MM-DD. Re-run if the project structure changes significantly. - -## Detected Project Type -[web-api / cli / batch / library / hybrid] - -## Build System & Language -- Language: [...] -- Build: [...] -- Test runner: [...] -- Linter / formatter: [...] - -## Directory Layout -[summary tree] - -## Naming Conventions -- File: [...] -- Type / class: [...] -- Test: [...] -- Display name: [...] - -## Test Patterns -- Categorization: [tag / marker / build-tag / decorator] -- Fake / InMemory present: [yes / no — list if yes] -- Integration base: [...] -- Mocking library present: [name or "none"] - -## Architecture Markers -- Layering: [...] -- Cross-module seams: [...] -- Error model: [...] -- Persistence: [...] - -## Comparison with GSL References -| Area | references | project | classification | follow | -|------|-----------|---------|----------------|--------| -| ... | ... | ... | MATCH/REFINEMENT/CONFLICT | references/project | - -## Conflict Resolutions -- [conflict A]: follow [project / references] — reason: [...] -``` - -### Step 10: Report - -Print a short summary: -``` -Scan complete. plan/conventions.md written. -- Detected type: -- Language: -- N findings: M MATCH, K REFINEMENT, L CONFLICT (resolved) -- Other GSL skills will consult this artifact in their Phase 0. -``` - -## Common Rationalizations - -| Rationalization | Reality | -|-----------------|---------| -| "I'll just scan again each feature, faster than reading the artifact" | Per-feature scanning is expensive and produces drift across sessions. One artifact, one source of truth. | -| "References are best practice — override project's convention" | No. Match existing code unless the user explicitly wants to refactor. Conflict resolution belongs to the user, not the skill. | -| "User didn't ask about conventions, skip this skill" | Other GSL skills depend on this artifact existing. Run on project entry. | -| "Skip the comparison step, just record findings" | The comparison is the whole point — downstream skills need to know match vs conflict. | -| "Just glance at one file, generalize from there" | Sample 5+ files. One file can mislead. | - -## Red Flags - -- Running this skill mid-feature (after `/define`) — should be preflight only -- Writing anywhere besides `plan/conventions.md` -- Modifying source files (this skill is discovery-only) -- Resolving CONFLICT without asking the user -- Generating the artifact without the comparison table populated -- Sampling fewer than 5 files for naming conventions - -## Verification - -After running: - -- [ ] `plan/conventions.md` exists at project root -- [ ] All 7 sections (Project Type, Build, Layout, Naming, Tests, Architecture, Comparison) are populated -- [ ] At least 5 source files were sampled for naming -- [ ] Comparison table has classification for every meaningful area -- [ ] All CONFLICTs have a recorded user resolution -- [ ] No source files were modified -- [ ] Report printed to user with counts (MATCH / REFINEMENT / CONFLICT) - -## Runtime Boundary — HARD STOP - -This skill ENDS after the Verification checklist and final report are completed. - -For codex and any runtime without an enforced skill-return boundary: -- MUST stop the assistant turn here. -- MUST NOT invoke, load, or execute any next GSL skill in the same response turn. -- MUST NOT continue into `/next-flow`, `/define`, `/plan`, `/implement`, `/test`, `/verify`, `/debug`, or `/self-review`. -- MAY print exactly one suggested next command as plain text. -- MUST wait for the user's next message before running any next skill. - -If the user says only "continue", treat that as permission to report the next recommended command, not permission to execute it. diff --git a/.claude/skills/test/references/testing/go.md b/.claude/skills/test/references/testing/go.md deleted file mode 100644 index 0f36b83dd..000000000 --- a/.claude/skills/test/references/testing/go.md +++ /dev/null @@ -1,383 +0,0 @@ -# Testing: go - -Production-oriented patterns for Go tests using standard `testing`, `testify`, fakes, `httptest`, and `testcontainers-go`. - -> Default stance: use real code and small hand-written fakes. Reach for generated mocks only when the interface is large, stable, and expensive to fake manually. - -**Naming caveat — judge by behavior, not name.** A struct named `mockX` or `MockY` is not automatically an anti-pattern. Classify by what it does: - -| Implementation | Classification | -|----------------|----------------| -| Uses an expectation framework (`gomock.Controller`, `EXPECT().Foo().Return(...)`, `assert.AssertExpectations`) | True **mock** — implementation coupling, refactor-fragile | -| Plain hand-written struct (map / counter / fixed return, no expectation framework) | **Fake** that happens to be named `mock*` — REFINEMENT of Fake-first, not CONFLICT | - -When reviewing existing tests, do NOT mechanically rename `mockX` → `fakeX`. Rename only if the naming actively confuses maintainers, and treat the rename as a separate refactor (not as part of feature work). - -## Test Classification - -Go commonly uses build tags instead of framework-level tags. - -| Type | File naming | Build tag | Run via | -|------|-------------|-----------|---------| -| Unit | `*_test.go` | none | `go test ./...` | -| Integration | `*_integration_test.go` | `//go:build integration` | `go test -tags=integration ./...` | -| E2E | `*_e2e_test.go` | `//go:build e2e` | `go test -tags=e2e ./...` | -| Race | any test | none | `go test -race ./...` | - -Integration file header: - -```go -//go:build integration - -package repository_test -``` - -Keep unit tests runnable without Docker, network, or external credentials. - -## Framework - -Default stack: -- Standard `testing` package for test lifecycle. -- `github.com/stretchr/testify/require` for fatal preconditions. -- `github.com/stretchr/testify/assert` for non-fatal comparisons. -- `github.com/google/go-cmp/cmp` for readable struct diffs. - -Rule of thumb: -- Use `require.NoError(t, err)` when continuing would make the test misleading. -- Use `assert.Equal(t, want, got)` when multiple independent assertions are useful. -- Use `cmp.Diff(want, got)` for nested structs, slices, and maps. - -## Naming Convention - -Use exported-style test names because `go test` discovers `TestXxx`. - -```go -func TestService_Register(t *testing.T) {} -func TestHandler_Register(t *testing.T) {} -func TestPostgresRepository_SaveAndFindByEmail(t *testing.T) {} -``` - -For scenarios, prefer table-driven tests: - -```go -func TestNewUser(t *testing.T) { - tests := []struct { - name string - email string - userName string - wantErr bool - }{ - {name: "valid user", email: "a@example.com", userName: "Alice"}, - {name: "empty email", email: "", userName: "Alice", wantErr: true}, - } - - for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { - t.Parallel() - - _, err := NewUser("user-1", tt.email, tt.userName, time.Now()) - if tt.wantErr { - require.Error(t, err) - return - } - require.NoError(t, err) - }) - } -} -``` - -Always rebind `tt := tt` before parallel subtests for compatibility with older Go versions and clearer intent. - -## Subtests - -Use `t.Run` to group related behavior without hiding individual failures. - -```go -func TestService_Register(t *testing.T) { - t.Run("creates user when email is new", func(t *testing.T) {}) - t.Run("returns conflict when email exists", func(t *testing.T) {}) -} -``` - -Guidelines: -- Put `t.Parallel()` inside each test or subtest only after test-local setup is complete. -- Avoid parallel tests when they mutate process-wide state such as env vars, global loggers, or current working directory. -- Use `t.Setenv` instead of `os.Setenv`; it automatically restores values. -- Use `t.TempDir` for filesystem tests. - -## InMemory Repository Pattern - -Implement the consumer-owned interface directly. Keep it concurrency-safe if tests may run in parallel. - -```go -type FakeUserRepository struct { - mu sync.Mutex - byID map[string]usecase.User - byEmail map[string]string -} - -func NewFakeUserRepository() *FakeUserRepository { - return &FakeUserRepository{ - byID: make(map[string]usecase.User), - byEmail: make(map[string]string), - } -} - -func (r *FakeUserRepository) Save(ctx context.Context, user usecase.User) error { - r.mu.Lock() - defer r.mu.Unlock() - - if existingID, ok := r.byEmail[user.Email]; ok && existingID != user.ID { - return usecase.ErrEmailAlreadyUsed - } - r.byID[user.ID] = user - r.byEmail[user.Email] = user.ID - return nil -} - -func (r *FakeUserRepository) FindByEmail(ctx context.Context, email string) (usecase.User, error) { - r.mu.Lock() - defer r.mu.Unlock() - - id, ok := r.byEmail[email] - if !ok { - return usecase.User{}, usecase.ErrUserNotFound - } - return r.byID[id], nil -} -``` - -Fake rules: -- Model the behavior the usecase depends on, not the entire database. -- Preserve important constraints such as uniqueness. -- Guard maps with `sync.Mutex` when tests use `t.Parallel()`. -- Add helper methods only for test setup or assertions. - -## Unit Test Example - -```go -func TestService_Register(t *testing.T) { - t.Parallel() - - repo := NewFakeUserRepository() - ids := fixedIDGenerator{id: "user-1"} - clk := fixedClock{now: time.Date(2026, 1, 2, 3, 4, 5, 0, time.UTC)} - svc := usecase.NewService(repo, ids, clk) - - user, err := svc.Register(context.Background(), usecase.RegisterUserInput{ - Email: "A@Example.com", - Name: "Alice", - }) - - require.NoError(t, err) - assert.Equal(t, "user-1", user.ID) - assert.Equal(t, "a@example.com", user.Email) - - saved, err := repo.FindByEmail(context.Background(), "a@example.com") - require.NoError(t, err) - assert.Equal(t, user.ID, saved.ID) -} -``` - -Prefer deterministic helpers for time and IDs: - -```go -type fixedIDGenerator struct{ id string } - -func (g fixedIDGenerator) NewID() string { return g.id } - -type fixedClock struct{ now time.Time } - -func (c fixedClock) Now() time.Time { return c.now } -``` - -## HTTP Testing - -Use standard `net/http/httptest` first. - -### Handler Unit Test with Recorder - -```go -func TestHandler_Register(t *testing.T) { - t.Parallel() - - repo := NewFakeUserRepository() - svc := usecase.NewService(repo, fixedIDGenerator{"user-1"}, fixedClock{time.Now()}) - h := handler.NewHandler(svc, validator.New()) - - body := strings.NewReader(`{"email":"a@example.com","name":"Alice"}`) - req := httptest.NewRequest(http.MethodPost, "/users", body) - rec := httptest.NewRecorder() - - h.Register(rec, req) - - require.Equal(t, http.StatusCreated, rec.Code) - - var got handler.RegisterUserResponse - require.NoError(t, json.NewDecoder(rec.Body).Decode(&got)) - assert.Equal(t, "user-1", got.ID) -} -``` - -### HTTP Integration Test with Server - -```go -func TestUserRoutes(t *testing.T) { - t.Parallel() - - srv := httptest.NewServer(newTestRouter(t)) - t.Cleanup(srv.Close) - - res, err := http.Post( - srv.URL+"/users", - "application/json", - strings.NewReader(`{"email":"a@example.com","name":"Alice"}`), - ) - require.NoError(t, err) - defer res.Body.Close() - - assert.Equal(t, http.StatusCreated, res.StatusCode) -} -``` - -Use `httptest.NewServer` when you need real routing, middleware, cookies, redirects, or an actual `http.Client`. - -## Integration Base: testcontainers-go - -Use Testcontainers for real Postgres, Redis, or broker behavior. Keep it behind the `integration` build tag. - -```go -//go:build integration - -package repository_test - -import ( - "context" - "database/sql" - "os" - "testing" - - "github.com/testcontainers/testcontainers-go" - "github.com/testcontainers/testcontainers-go/modules/postgres" - "github.com/testcontainers/testcontainers-go/wait" -) - -var testDB *sql.DB - -func TestMain(m *testing.M) { - ctx := context.Background() - - container, err := postgres.RunContainer(ctx, - postgres.WithImage("postgres:16-alpine"), - postgres.WithDatabase("app_test"), - postgres.WithUsername("app"), - postgres.WithPassword("secret"), - testcontainers.WithWaitStrategy(wait.ForListeningPort("5432/tcp")), - ) - if err != nil { - panic(err) - } - - dsn, err := container.ConnectionString(ctx, "sslmode=disable") - if err != nil { - panic(err) - } - testDB, err = sql.Open("postgres", dsn) - if err != nil { - panic(err) - } - - code := m.Run() - - _ = testDB.Close() - _ = container.Terminate(ctx) - os.Exit(code) -} -``` - -Repository integration test: - -```go -func TestPostgresRepository_SaveAndFindByEmail(t *testing.T) { - t.Parallel() - - truncateTables(t, testDB) - repo := repository.NewPostgresRepository(testDB) - - user := usecase.User{ - ID: "user-1", - Email: "a@example.com", - Name: "Alice", - CreatedAt: time.Now(), - } - require.NoError(t, repo.Save(context.Background(), user)) - - got, err := repo.FindByEmail(context.Background(), "a@example.com") - require.NoError(t, err) - assert.Equal(t, user.ID, got.ID) -} -``` - -If tests run in parallel against one database, isolate with unique schemas, unique IDs, or per-test cleanup that cannot conflict. - -## Mocking Policy - -Default: hand-written fake. - -Use `gomock` only when: -- The interface is large enough that a fake becomes noisy. -- The contract is stable. -- The test needs precise call ordering or argument matching. - -Avoid mocks for tiny interfaces. A 2-method repository fake is usually clearer, less brittle, and closer to behavior. - -## Fixtures and Golden Files - -Use `testdata/` for fixture files. Go tooling ignores this directory for package builds. - -```text -internal/user/handler/testdata/register_success.json -internal/user/handler/testdata/register_validation_error.json -``` - -Golden test pattern: - -```go -got := renderResponse(data) -want := readFile(t, "testdata/register_success.json") -if diff := cmp.Diff(want, got); diff != "" { - t.Fatalf("response mismatch (-want +got):\n%s", diff) -} -``` - -Keep golden updates explicit. Do not auto-update golden files in normal test runs. - -## Coverage and Race Detector - -Commands: - -```bash -go test -cover -coverprofile=cover.out ./... -go tool cover -func=cover.out -go tool cover -html=cover.out -go test -race ./... -``` - -Guidelines: -- Coverage is a signal, not the goal. Prioritize business branches and error paths. -- Run `-race` for code using goroutines, maps, caches, background workers, or shared fakes. -- Race detector is slower; use it in L2/L3 verification or CI. - -## Anti-Patterns - -Avoid: -- Using mocks for tiny interfaces that are easier to fake by hand. -- Tests that pass only when run in a specific order. -- `t.Parallel()` with shared mutable maps and no mutex. -- Integration tests without build tags. -- Sleeping for timing assertions instead of using channels, contexts, or fake clocks. -- Ignoring response body close in HTTP client tests. -- Calling external services in unit tests. -- Comparing large structs with unreadable `reflect.DeepEqual` failures when `go-cmp` would show a diff. - diff --git a/.claude/skills/test/references/testing/python.md b/.claude/skills/test/references/testing/python.md deleted file mode 100644 index 5d61b1165..000000000 --- a/.claude/skills/test/references/testing/python.md +++ /dev/null @@ -1,477 +0,0 @@ -# Testing: python - -Battle-tested testing patterns for Python 3.12+ projects. Default recommendation: pytest + fixtures + typed fakes + testcontainers for integration. - -> Use this with `languages/python.md` and `verify/python.md`. - -## Framework Choice - -Use `pytest` as the default test framework. - -Prefer pytest over `unittest` because: -- fixture composition is simpler -- markers cleanly separate unit / integration / docs tests -- async testing support is straightforward -- parameterization is concise -- plugins such as `pytest-cov`, `pytest-asyncio`, and `testcontainers` integrate well - -`unittest` is acceptable only when an existing codebase already standardizes on it. - -## Test Classification - -Use pytest markers to classify test cost and required infrastructure. - -```python -import pytest - - -@pytest.mark.unit -def test_hash_password_when_valid_password_returns_hash() -> None: - ... - - -@pytest.mark.integration -def test_user_repository_when_saved_can_find_by_email() -> None: - ... - - -@pytest.mark.docs -def test_openapi_schema_when_generated_matches_contract() -> None: - ... -``` - -### Marker policy - -- `unit`: no network, no real DB, no Docker -- `integration`: real infrastructure through testcontainers or local test service -- `docs`: docs, examples, OpenAPI schema, generated contract checks -- `e2e`: optional, external process or full stack - -## pytest Configuration - -Declare markers and strict marker behavior in `pyproject.toml`. - -```toml -[tool.pytest.ini_options] -testpaths = ["tests"] -python_files = ["test_*.py", "*_test.py"] -addopts = [ - "--strict-markers", - "--strict-config", -] -markers = [ - "unit: fast tests without external infrastructure", - "integration: tests that require Docker or real infrastructure", - "docs: documentation and generated contract tests", - "e2e: full-stack end-to-end tests", -] -asyncio_mode = "auto" -``` - -`--strict-markers` prevents typo markers from silently creating new categories. - -## Test Layout - -Recommended structure: - -``` -tests/ -├── conftest.py -├── unit/ -│ └── users/ -│ └── test_user_service.py -├── integration/ -│ └── users/ -│ └── test_user_repository.py -└── api/ - └── test_users_router.py -``` - -Use test names that describe behavior: - -```python -def test_register_when_email_exists_raises_user_already_exists() -> None: - ... -``` - -## Fixture Pattern - -Use `conftest.py` for shared fixtures. Keep default scope as `function`. - -```python -# tests/conftest.py -from __future__ import annotations - -import pytest - -from app.users.models import User - - -@pytest.fixture -def user_email() -> str: - return "user@example.com" - - -@pytest.fixture -def existing_user(user_email: str) -> User: - return User( - id=1, - email=user_email, - name="Existing User", - password_hash="hashed-password", - ) -``` - -### Fixture scope rules - -- `function`: default for mutable data and repositories. -- `module`: rarely, when setup is expensive but state can be isolated. -- `session`: Docker containers, schema creation, immutable config. -- Avoid global mutable fixtures. - -## InMemory Pattern - -Use typed fake repositories that implement the same `Protocol` as the real repository. - -This is preferred over `unittest.mock.Mock` for domain tests because: -- type checking catches interface drift -- behavior is explicit -- state is visible -- tests read like real use cases - -```python -# tests/unit/users/fakes.py -from __future__ import annotations - -from app.users.models import User -from app.users.repository import UserRepository - - -class InMemoryUserRepository(UserRepository): - def __init__(self) -> None: - self._users_by_email: dict[str, User] = {} - self._next_id = 1 - - async def save(self, user: User) -> User: - if user.id is None: - user.id = self._next_id - self._next_id += 1 - self._users_by_email[user.email] = user - return user - - async def find_by_email(self, email: str) -> User | None: - return self._users_by_email.get(email) -``` - -## Unit Test Example — Fake Repository - -```python -# tests/unit/users/test_user_service.py -from __future__ import annotations - -import pytest - -from app.users.schemas import UserRegisterRequest -from app.users.service import UserAlreadyExistsError, UserService -from tests.unit.users.fakes import InMemoryUserRepository - - -class FakeSession: - def begin(self) -> "FakeSession": - return self - - async def __aenter__(self) -> None: - return None - - async def __aexit__(self, exc_type: object, exc: object, tb: object) -> None: - return None - - -@pytest.mark.unit -@pytest.mark.asyncio -async def test_register_when_new_email_saves_user() -> None: - repository = InMemoryUserRepository() - service = UserService(repository=repository, session=FakeSession()) - - user = await service.register( - UserRegisterRequest( - email="new@example.com", - name="New User", - password="very-secure-password", - ) - ) - - assert user.id == 1 - assert user.email == "new@example.com" - - -@pytest.mark.unit -@pytest.mark.asyncio -async def test_register_when_email_exists_raises_user_already_exists() -> None: - repository = InMemoryUserRepository() - service = UserService(repository=repository, session=FakeSession()) - - request = UserRegisterRequest( - email="dup@example.com", - name="First User", - password="very-secure-password", - ) - await service.register(request) - - with pytest.raises(UserAlreadyExistsError): - await service.register(request) -``` - -## Integration Testing - -Use `testcontainers-python` for real Postgres / Redis / external infrastructure behavior. - -Install examples: -- `testcontainers[postgresql]` -- `asyncpg` -- `sqlalchemy[asyncio]` - -Use session-scoped containers and function-scoped database cleanup. - -```python -# tests/integration/conftest.py -from __future__ import annotations - -from collections.abc import AsyncIterator, Iterator - -import pytest -from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker, create_async_engine -from testcontainers.postgres import PostgresContainer - -from app.users.models import Base - - -@pytest.fixture(scope="session") -def postgres_container() -> Iterator[PostgresContainer]: - with PostgresContainer("postgres:16-alpine") as postgres: - yield postgres - - -@pytest.fixture(scope="session") -async def async_engine(postgres_container: PostgresContainer) -> AsyncIterator[AsyncEngine]: - sync_url = postgres_container.get_connection_url() - async_url = sync_url.replace("postgresql+psycopg2://", "postgresql+asyncpg://") - engine = create_async_engine(async_url) - - async with engine.begin() as connection: - await connection.run_sync(Base.metadata.create_all) - - yield engine - - await engine.dispose() - - -@pytest.fixture -async def session(async_engine: AsyncEngine) -> AsyncIterator[AsyncSession]: - maker = async_sessionmaker(async_engine, expire_on_commit=False) - - async with maker() as session: - async with session.begin(): - yield session - await session.rollback() -``` - -## Integration Test Example — Repository - -```python -# tests/integration/users/test_user_repository.py -from __future__ import annotations - -import pytest -from sqlalchemy.ext.asyncio import AsyncSession - -from app.users.models import User -from app.users.repository import SqlAlchemyUserRepository - - -@pytest.mark.integration -@pytest.mark.asyncio -async def test_find_by_email_when_user_saved_returns_user(session: AsyncSession) -> None: - repository = SqlAlchemyUserRepository(session) - user = User( - email="saved@example.com", - name="Saved User", - password_hash="hashed-password", - ) - - await repository.save(user) - found = await repository.find_by_email("saved@example.com") - - assert found is not None - assert found.email == "saved@example.com" -``` - -## HTTP Testing - -### FastAPI `TestClient` - -Use `TestClient` for sync-style API tests when route dependencies can run under the test client's event loop. - -```python -from fastapi.testclient import TestClient - -from app.main import app - - -def test_health_when_called_returns_ok() -> None: - client = TestClient(app) - - response = client.get("/health") - - assert response.status_code == 200 -``` - -### `httpx.AsyncClient` - -Use `httpx.AsyncClient` when the test itself is async or when async fixtures are already in use. - -```python -from __future__ import annotations - -import pytest -from httpx import ASGITransport, AsyncClient - -from app.main import app - - -@pytest.mark.asyncio -async def test_health_when_called_returns_ok() -> None: - transport = ASGITransport(app=app) - - async with AsyncClient(transport=transport, base_url="http://test") as client: - response = await client.get("/health") - - assert response.status_code == 200 -``` - -## Auth in Tests - -Override FastAPI dependencies directly. - -```python -from __future__ import annotations - -from fastapi.testclient import TestClient - -from app.auth.dependencies import get_current_user -from app.main import app -from app.users.schemas import CurrentUser - - -def test_me_when_authenticated_returns_current_user() -> None: - fake_user = CurrentUser(id=1, email="user@example.com") - app.dependency_overrides[get_current_user] = lambda: fake_user - - try: - client = TestClient(app) - response = client.get("/me") - - assert response.status_code == 200 - assert response.json()["email"] == "user@example.com" - finally: - app.dependency_overrides.clear() -``` - -### Override rules - -- Always clear `app.dependency_overrides` after the test. -- Prefer fixture-managed overrides for repeated patterns. -- Do not patch auth internals with `Mock` when a dependency override is enough. - -## Factory Pattern - -Use plain factory functions for simple projects. - -```python -# tests/factories.py -from __future__ import annotations - -from app.users.models import User - - -def make_user( - *, - id: int | None = None, - email: str = "user@example.com", - name: str = "Test User", - password_hash: str = "hashed-password", -) -> User: - return User(id=id, email=email, name=name, password_hash=password_hash) -``` - -Use `factory-boy` when: -- many models share nested defaults -- large object graphs are common -- integration tests need DB-backed factories - -Do not hide important test setup inside overly clever factories. - -## Async Tests - -Use `pytest-asyncio`. - -```python -import pytest - - -@pytest.mark.asyncio -async def test_async_operation_when_called_returns_value() -> None: - result = await async_operation() - - assert result == "value" -``` - -With `asyncio_mode = "auto"`, explicit `@pytest.mark.asyncio` may be optional, but keeping it improves readability for async tests. - -## Coverage - -Use `pytest-cov` in CI. - -Example commands: -- `pytest -m unit --cov=app --cov-report=term-missing` -- `pytest --cov=app --cov-report=xml --cov-fail-under=80` - -Coverage gates should protect core behavior, not force meaningless tests. - -## Property-Based Tests - -Use `hypothesis` for invariants: -- parsing / normalization -- money / date calculations -- validators -- idempotent transformations - -```python -from hypothesis import given -from hypothesis import strategies as st - - -@given(st.text(min_size=1)) -def test_normalize_name_when_called_is_idempotent(value: str) -> None: - once = normalize_name(value) - twice = normalize_name(once) - - assert once == twice -``` - -## Common Anti-patterns - -- Overusing `from unittest.mock import Mock` for domain collaborators. -- Mocking the method under test. -- Global fixture state shared across tests. -- Session-scoped mutable repositories. -- Integration tests hitting production or developer-local databases. -- Docker-dependent tests marked as `unit`. -- Tests relying on execution order. -- Sleeping in tests instead of controlling time or polling with timeout. -- Leaving `app.dependency_overrides` dirty after a test. -- Factories that create hidden external resources. -- Assertions that only check status code and ignore response body. -- Coverage gates without meaningful assertions. -- `pytest.mark.skip` used to hide broken tests without a tracked reason. - diff --git a/.claude/skills/verify/references/verify/go.md b/.claude/skills/verify/references/verify/go.md deleted file mode 100644 index 22e74264b..000000000 --- a/.claude/skills/verify/references/verify/go.md +++ /dev/null @@ -1,169 +0,0 @@ -# Verify: go - -Verification ladder for Go modules. Run from the module root and prefer full-project scope with `./...`. - -> L1 is the quick local/CI gate. L2 is the normal pre-review gate. L3 is the full gate when Docker-backed integration or e2e tests exist. - -## Commands per step - -### L1 — Quick - -| Step | Command | Evidence | -|------|---------|----------| -| 1 Build / type-check | `go build ./...` | all packages compile | -| 2 Vet | `go vet ./...` | standard static checks pass | -| 3 Lint | `golangci-lint run ./...` | project lint profile passes | -| 4 Module diff | `go mod tidy -diff` | no unexpected `go.mod` / `go.sum` drift | -| 5 Architecture rules (optional) | `go-arch-lint check` | package boundary rules pass | - -Use L1 for fast feedback before claiming compile or lint health. - -### L2 — Standard (includes L1) - -| Step | Command | Evidence | -|------|---------|----------| -| 6 Unit tests | `go test ./...` | all non-integration tests pass | -| 7 Race detector | `go test -race ./...` | no detected data races | - -L2 excludes files guarded by `//go:build integration` unless the project also includes those tags in default test config. - -### L3 — Full (includes L2) - -| Step | Command | Evidence | -|------|---------|----------| -| 8 Integration tests | `go test -tags=integration ./...` | Docker-backed integration tests pass | -| 9 E2E tests (optional) | `go test -tags=e2e ./...` | end-to-end flows pass | -| 10 Coverage report (optional) | `go test -cover -coverprofile=cover.out ./...` | coverage profile generated | - -L3 usually requires Docker when tests use `testcontainers-go` for Postgres, Redis, Kafka, or other services. - -## Recommended command groups - -Quick gate: - -```bash -go build ./... -go vet ./... -golangci-lint run ./... -go mod tidy -diff -``` - -Standard gate: - -```bash -go build ./... -go vet ./... -golangci-lint run ./... -go mod tidy -diff -go test ./... -go test -race ./... -``` - -Full gate: - -```bash -go build ./... -go vet ./... -golangci-lint run ./... -go mod tidy -diff -go test ./... -go test -race ./... -go test -tags=integration ./... -go test -tags=e2e ./... -``` - -If the project does not define `e2e` tests, skip the final command and say it was not applicable. - -## Auto-fix commands - -Auto-fix is related to verification but is not itself proof that the project is healthy. - -```bash -gofmt -w . -goimports -w . -golangci-lint run --fix ./... -``` - -Rules: -- Run `gofmt` for all Go edits. -- Run `goimports` when imports changed. -- Re-run the relevant L1/L2 command after auto-fix. -- Do not hide lint findings by disabling linters without an explicit project decision. - -## Notes - -### Taskfile detection (project automation, check first) - -If the project root contains `Taskfile.yaml` (or `Taskfile.yml`), **prefer the project's own task wrappers** over raw `go` commands. The project author has typically pre-tuned race-detector flags, coverage thresholds, lint config, and integration setup (venv creation, container reuse, S3 prep, etc.). Use raw commands only as a fallback. - -Detection: run `task --list` (or read `Taskfile.yaml`) to see available tasks. - -| GSL level | Preferred tasks (try in order) | Fallback | -|-----------|--------------------------------|----------| -| L1 quick | `task lint` → `task vet` → `task build` | `go vet ./... && go build ./... && golangci-lint run ./...` | -| L2 standard | `task test:short` → `task test` | `go test ./... && go test -race ./...` | -| L3 full | `task verify` → `task test:integration` | `go test -tags=integration ./...` | - -Adopt the project's verify ladder as the source of truth for that project — even if its order or scope differs from the raw-command ladder above. Record the chosen ladder in `plan/conventions.md` under `Verification`. - -Scope: -- Use `./...` from module root for build, vet, test, and lint unless the project documents a narrower target. -- For workspaces, run verification in each touched module or use the repository's documented workspace command. -- If generated code is involved, run the generator before verification only when the project expects generated files to be committed. - -Docker: -- `go test -tags=integration ./...` may start containers through `testcontainers-go`. -- Verify Docker is running before L3; do not treat Docker startup failure as a code test failure without evidence. - -Timeouts: -- Quick build/vet/lint: 60-180s depending on module size. -- Unit tests: 180-300s. -- Race tests: 300-600s. -- Integration tests: 600s or project-specific timeout. - -Useful flags: - -```bash -go test -count=1 ./... # bypass test cache when checking behavior changes -go test -run TestName ./... # focused reproduction before full run -go test -timeout=10m ./... # explicit timeout for CI or long packages -go test -json ./... # machine-readable output for CI parsers -``` - -Coverage: - -```bash -go test -cover -coverprofile=cover.out ./... -go tool cover -func=cover.out -go tool cover -html=cover.out -``` - -Coverage is supporting evidence. It does not replace behavior-focused assertions. - -## Git hook examples - -Pre-commit quick hook: - -```bash -gofmt -w . && goimports -w . && go test ./... -``` - -Pre-push standard hook: - -```bash -go build ./... && go vet ./... && golangci-lint run ./... && go test -race ./... -``` - -## Completion report template - -When reporting completion, include exact commands and outcomes: - -```text -검증: -- go build ./...: 통과 -- go test ./...: 통과 -- go test -tags=integration ./...: 미실행, Docker 필요 -``` - -Never claim completion from intent alone. Run the command, read the output, then report the evidence. - diff --git a/.claude/skills/verify/references/verify/python.md b/.claude/skills/verify/references/verify/python.md deleted file mode 100644 index 76cfc33f9..000000000 --- a/.claude/skills/verify/references/verify/python.md +++ /dev/null @@ -1,174 +0,0 @@ -# Verify: python - -Verification ladder for Python 3.12+ projects. Run the smallest level that proves the requested change, then report exact commands and outcomes. - -> Use with `languages/python.md` and `testing/python.md`. - -## L1 — Quick Static Verification - -Use L1 for formatting, linting, typing, and architecture import rules. - -| Step | Command | -|------|---------| -| 1 Type-check | `mypy src/` | -| 1 Alternative type-check | `pyright` | -| 2 Lint | `ruff check .` | -| 3 Format check | `ruff format --check .` | -| 4 Import / architecture rules | `import-linter --config .importlinter` | - -Run `mypy` or `pyright` according to the project standard. Do not run both unless the project already does. - -Recommended combined command: - -```bash -mypy src/ && ruff check . && ruff format --check . -``` - -With import-linter: - -```bash -mypy src/ && ruff check . && ruff format --check . && import-linter --config .importlinter -``` - -## L2 — Standard Verification - -L2 includes L1 plus unit tests and package build. - -| Step | Command | -|------|---------| -| 1 Static verification | L1 commands | -| 2 Unit tests | `pytest -m "unit and not integration" --strict-markers` | -| 3 Package build | `python -m build` | -| 3 Alternative build | `uv build` | - -Recommended combined command: - -```bash -mypy src/ \ - && ruff check . \ - && ruff format --check . \ - && pytest -m "unit and not integration" --strict-markers \ - && python -m build -``` - -Use `uv build` instead of `python -m build` when the project standardizes on uv. - -## L3 — Full Verification - -L3 includes L2 plus integration tests. - -| Step | Command | -|------|---------| -| 1 Standard verification | L2 commands | -| 2 Integration tests | `pytest -m integration --strict-markers` | -| 3 Optional E2E / smoke | `pytest -m e2e --strict-markers` | - -Recommended command: - -```bash -mypy src/ \ - && ruff check . \ - && ruff format --check . \ - && pytest -m "unit and not integration" --strict-markers \ - && python -m build \ - && pytest -m integration --strict-markers -``` - -Integration tests that use `testcontainers-python` require Docker. - -## Coverage Commands - -Use coverage when the change touches business logic or CI requires a gate. - -Unit coverage: - -```bash -pytest -m "unit and not integration" \ - --strict-markers \ - --cov=app \ - --cov-report=term-missing -``` - -CI gate: - -```bash -pytest \ - --strict-markers \ - --cov=app \ - --cov-report=xml \ - --cov-report=term-missing \ - --cov-fail-under=80 -``` - -`--cov-fail-under=N` fails the command when total coverage is below `N`. - -## Auto-fix Commands - -Auto-fix commands are not verification. Run verification again after auto-fix. - -Format: - -```bash -ruff format . -``` - -Lint fix: - -```bash -ruff check --fix . -``` - -Combined: - -```bash -ruff format . && ruff check --fix . -``` - -Use `ruff check --fix --unsafe-fixes .` only after reviewing what unsafe fixes may change. - -## pytest Options - -Recommended defaults in `pyproject.toml`: - -```toml -[tool.pytest.ini_options] -addopts = [ - "--strict-markers", - "--strict-config", -] -markers = [ - "unit: fast tests without external infrastructure", - "integration: tests requiring Docker or real infrastructure", - "docs: documentation and generated contract tests", - "e2e: full-stack end-to-end tests", -] -``` - -`--strict-markers` catches marker typos such as `@pytest.mark.integraton`. - -## pre-commit Pattern - -Use pre-commit for fast local checks: - -```yaml -repos: - - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.5.0 - hooks: - - id: ruff - args: [--fix] - - id: ruff-format -``` - -Keep type-check and tests in CI even if pre-commit exists. - -## Notes - -- Run verification at whole-project scope: `src/`, `tests/`, or `.` as configured. -- Avoid per-file verification as final evidence unless the project is extremely large and the user requested a narrow check. -- L3 requires Docker when integration tests use testcontainers. -- Recommended timeouts: L1 60s, L2 180s, L3 600s. -- If Docker is unavailable, report L1/L2 results separately from skipped L3. -- If `python -m build` fails because `build` is missing, install through the project toolchain, for example `uv add --dev build` or project-approved equivalent. -- Do not claim completion from auto-fix output alone. Re-run the relevant verification level. - diff --git a/plan/gsl-overhaul.md b/plan/gsl-overhaul.md index 720fd266e..9421a3fa7 100644 --- a/plan/gsl-overhaul.md +++ b/plan/gsl-overhaul.md @@ -67,7 +67,7 @@ GSL(Guided Software Lifecycle) 9개 스킬을 이 저장소 전용으로 대폭 - Verification: `diff -rq .agents/skills .claude/skills` 일치, 삭제 대상 파일 0건 확인, 잔존 스킬에서 삭제된 스킬 참조가 깨지는 지점 목록화(다음 Task 입력) - Files (advisory): 미러 양쪽 약 24파일 삭제 + plan/conventions.md 이동. 브랜치 분리(`chore/gsl-overhaul` 생성, define 커밋 이관) 포함 - Size: M -- Status: [ ] not done +- Status: [x] done ### Task 2: Execution Mode 계약 + 지침 갱신 - Acceptance: 지침(CLAUDE.md/AGENTS.md)의 "GSL Runtime Boundary Rules" 섹션이 Execution Mode 규칙(기본 step-by-step, delegated 선언 형식, stop-conditions, HARD STOP 통합 규정)으로 대체됨. Skills 표 9→7. PR 본문=체인지로그 관례가 delegated scope 정의에 포함됨 @@ -122,3 +122,5 @@ GSL(Guided Software Lifecycle) 9개 스킬을 이 저장소 전용으로 대폭 ## Progress Log (비어 있음 — /implement에서 기록) + +- Task 1: `chore/gsl-overhaul` 브랜치 분리(sync-agent는 origin으로 원복). next-flow·scan-conventions 스킬과 python/go/cli/library/worker-daemon references를 미러 양쪽에서 삭제(18파일), conventions.md는 stale로 이동. 삭제 전 용량 276KB/미러. 잔존 참조 44건을 9개 SKILL.md + web-api.md·bottlenote-patterns.md에서 확인 — SKILL.md는 Tasks 3-6 재작성에서, references 2개는 Task 6에서 해소 예정. 미러 diff 일치. diff --git a/plan/conventions.md b/plan/stale/conventions.md similarity index 100% rename from plan/conventions.md rename to plan/stale/conventions.md From 8e6bef3800cf48562186b45811401c86516105a1 Mon Sep 17 00:00:00 2001 From: Whale0928 Date: Sat, 25 Jul 2026 18:37:09 +0900 Subject: [PATCH 4/9] =?UTF-8?q?docs:=20GSL=20Execution=20Mode=20=EA=B3=84?= =?UTF-8?q?=EC=95=BD=EC=9D=84=20=EC=A7=80=EC=B9=A8=EC=97=90=20=EC=A0=95?= =?UTF-8?q?=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- AGENTS.md | 38 ++++++++++++++++++++++++++++---------- CLAUDE.md | 38 ++++++++++++++++++++++++++++---------- plan/gsl-overhaul.md | 9 ++++----- 3 files changed, 60 insertions(+), 25 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 417418d40..40b18dee2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -263,16 +263,34 @@ Use these skills to follow the structured development lifecycle: - OpenFeign: `@FeignClient`, 설정 분리 `FeignConfig`, 에러 처리 `ErrorDecoder` - AWS S3: PreSigned URL 생성 (SDK v2 `S3Client`/`S3Presigner`) -## GSL Runtime Boundary Rules +## GSL Execution Mode -> [상태] 현재 GSL 스킬셋은 구형이다. 별도 세션에서 개편할 예정이며, 개편 전까지 아래 경계 규칙을 유지한다. +GSL 스킬(define/plan/implement/test/verify/debug/self-review)은 `plan/{기능}.md` 문서를 공유 상태로 쓰는 개발 생명주기다. 진행 방식은 `/define` 승인 게이트에서 계약으로 확정되며, **plan 문서의 `## Execution Mode` 섹션이 유일한 근거다.** 문서에 선언이 없으면 step-by-step이다. -When using GSL skills from `.agents/skills/`, treat each `SKILL.md` as a single-turn procedure. +### step-by-step (기본값) -- Load only the one GSL skill that matches the user's current explicit request. -- Do not pre-load all 9 GSL skills unless the user explicitly asks for a read-only diagnosis of the skillset. -- A GSL skill boundary is a hard stop. When a skill reaches its Verification / Runtime Boundary section, end the assistant turn. -- `After this skill`, `Next: /...`, or `should invoke` means "suggest the next command", not "execute it now". -- Do not transition between GSL skills (`/define`->`/plan`, `/plan`->`/implement`, `/implement` Task N->N+1, `/implement`->`/test`, `/test`->`/verify`, etc.) in the same assistant turn unless the user explicitly named that next skill — or named multiple Tasks for continuous execution — in their message. -- `/implement` may write test code alongside a Task, but must NOT run the full `/test` / `/verify` / `/self-review` workflow inside itself. Those are separate skill boundaries. -- If the user says "continue" ambiguously, ask whether to run the next suggested GSL skill or only report the next command. +- 각 GSL 스킬은 자기 작업과 종료 보고를 마치면 **턴을 끝낸다**. 다음 스킬 실행에는 사용자의 새 메시지가 필요하다. +- `/implement`는 Task 하나를 커밋하고 보고한 뒤 정지한다. 예외: 사용자가 여러 Task의 연속 실행을 명시적으로 지정한 경우("Task 1~3 진행해", "결과까지 진행해")에만 이어서 진행한다. +- 애매한 "계속", "continue"는 다음 명령을 안내하라는 뜻이지 실행 허가가 아니다. +- 종료 보고에는 다음 권장 명령을 한 줄로 제안한다 (실행하지 않는다). +- 스킬 하나가 다른 스킬의 전체 워크플로를 내부에서 실행하지 않는다 (`/implement`가 Task 곁에 테스트 코드를 쓰는 것은 허용, `/test`·`/verify`·`/self-review`의 풀 워크플로 실행은 금지). + +### delegated (define 승인 시 명시 선언) + +`/define` 승인 게이트에서 아래 형식으로 선언하고 plan 문서에 기록한 경우에만 유효하다. + +```markdown +## Execution Mode +- mode: delegated +- scope: plan, implement, test, verify, commit # push, pr 포함 가능 +- stop-conditions: 기본 3종 (+ 추가 조건) +``` + +- scope에 포함된 단계는 단계 간 승인 없이 자율 진행한다. 단 Task마다 Progress Log 기록과 체크포인트 보고는 남긴다. +- `push`가 scope에 없으면 커밋까지만 하고 푸시 직전에 정지한다. `pr`이 있으면 PR 오픈과 본문 작성까지 수행한다 — **이 저장소는 PR 본문이 체인지로그를 겸한다.** + +### stop-conditions (모드 무관, 무조건 정지) + +1. **가정 붕괴** — 작업 중 발견이 define의 Assumption을 깨면 즉시 정지하고 재개봉 프로토콜(define 수정 → WHAT이 바뀌었으면 재승인)을 따른다. 조용히 적응하지 않는다. +2. **verify 반복 실패** — `/verify` 실패를 3회 시도 안에 해결하지 못하면 `/debug` 결과를 보고하고 정지한다. +3. **scope 밖 행동** — 선언된 scope 밖의 되돌리기 어려운 행동(푸시, PR 오픈, 파일 대량 삭제, 인프라 변경)이 필요해지면 그 직전에 정지하고 확인받는다. diff --git a/CLAUDE.md b/CLAUDE.md index 60dccd88a..21115c6a8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -263,16 +263,34 @@ Use these skills to follow the structured development lifecycle: - OpenFeign: `@FeignClient`, 설정 분리 `FeignConfig`, 에러 처리 `ErrorDecoder` - AWS S3: PreSigned URL 생성 (SDK v2 `S3Client`/`S3Presigner`) -## GSL Runtime Boundary Rules +## GSL Execution Mode -> [상태] 현재 GSL 스킬셋은 구형이다. 별도 세션에서 개편할 예정이며, 개편 전까지 아래 경계 규칙을 유지한다. +GSL 스킬(define/plan/implement/test/verify/debug/self-review)은 `plan/{기능}.md` 문서를 공유 상태로 쓰는 개발 생명주기다. 진행 방식은 `/define` 승인 게이트에서 계약으로 확정되며, **plan 문서의 `## Execution Mode` 섹션이 유일한 근거다.** 문서에 선언이 없으면 step-by-step이다. -When using GSL skills from `.claude/skills/`, treat each `SKILL.md` as a single-turn procedure. +### step-by-step (기본값) -- Load only the one GSL skill that matches the user's current explicit request. -- Do not pre-load all 9 GSL skills unless the user explicitly asks for a read-only diagnosis of the skillset. -- A GSL skill boundary is a hard stop. When a skill reaches its Verification / Runtime Boundary section, end the assistant turn. -- `After this skill`, `Next: /...`, or `should invoke` means "suggest the next command", not "execute it now". -- Do not transition between GSL skills (`/define`->`/plan`, `/plan`->`/implement`, `/implement` Task N->N+1, `/implement`->`/test`, `/test`->`/verify`, etc.) in the same assistant turn unless the user explicitly named that next skill — or named multiple Tasks for continuous execution — in their message. -- `/implement` may write test code alongside a Task, but must NOT run the full `/test` / `/verify` / `/self-review` workflow inside itself. Those are separate skill boundaries. -- If the user says "continue" ambiguously, ask whether to run the next suggested GSL skill or only report the next command. +- 각 GSL 스킬은 자기 작업과 종료 보고를 마치면 **턴을 끝낸다**. 다음 스킬 실행에는 사용자의 새 메시지가 필요하다. +- `/implement`는 Task 하나를 커밋하고 보고한 뒤 정지한다. 예외: 사용자가 여러 Task의 연속 실행을 명시적으로 지정한 경우("Task 1~3 진행해", "결과까지 진행해")에만 이어서 진행한다. +- 애매한 "계속", "continue"는 다음 명령을 안내하라는 뜻이지 실행 허가가 아니다. +- 종료 보고에는 다음 권장 명령을 한 줄로 제안한다 (실행하지 않는다). +- 스킬 하나가 다른 스킬의 전체 워크플로를 내부에서 실행하지 않는다 (`/implement`가 Task 곁에 테스트 코드를 쓰는 것은 허용, `/test`·`/verify`·`/self-review`의 풀 워크플로 실행은 금지). + +### delegated (define 승인 시 명시 선언) + +`/define` 승인 게이트에서 아래 형식으로 선언하고 plan 문서에 기록한 경우에만 유효하다. + +```markdown +## Execution Mode +- mode: delegated +- scope: plan, implement, test, verify, commit # push, pr 포함 가능 +- stop-conditions: 기본 3종 (+ 추가 조건) +``` + +- scope에 포함된 단계는 단계 간 승인 없이 자율 진행한다. 단 Task마다 Progress Log 기록과 체크포인트 보고는 남긴다. +- `push`가 scope에 없으면 커밋까지만 하고 푸시 직전에 정지한다. `pr`이 있으면 PR 오픈과 본문 작성까지 수행한다 — **이 저장소는 PR 본문이 체인지로그를 겸한다.** + +### stop-conditions (모드 무관, 무조건 정지) + +1. **가정 붕괴** — 작업 중 발견이 define의 Assumption을 깨면 즉시 정지하고 재개봉 프로토콜(define 수정 → WHAT이 바뀌었으면 재승인)을 따른다. 조용히 적응하지 않는다. +2. **verify 반복 실패** — `/verify` 실패를 3회 시도 안에 해결하지 못하면 `/debug` 결과를 보고하고 정지한다. +3. **scope 밖 행동** — 선언된 scope 밖의 되돌리기 어려운 행동(푸시, PR 오픈, 파일 대량 삭제, 인프라 변경)이 필요해지면 그 직전에 정지하고 확인받는다. diff --git a/plan/gsl-overhaul.md b/plan/gsl-overhaul.md index 9421a3fa7..5316252ab 100644 --- a/plan/gsl-overhaul.md +++ b/plan/gsl-overhaul.md @@ -74,11 +74,11 @@ GSL(Guided Software Lifecycle) 9개 스킬을 이 저장소 전용으로 대폭 - Verification: 지침 정규화 diff 일치, 표의 7개 커맨드가 실제 스킬 디렉터리와 정합 - Files (advisory): CLAUDE.md, AGENTS.md - Size: S -- Status: [ ] not done +- Status: [x] done ### Checkpoint: after Tasks 1-2 -- [ ] 미러 diff 일치, 지침 diff 일치 -- [ ] 잔존 스킬의 깨진 참조 목록 확보 +- [x] 미러 diff 일치, 지침 diff 일치 +- [x] 잔존 스킬의 깨진 참조 목록 확보 ### Task 3: /define 재작성 - Acceptance: WHAT/HOW 분리 철학 명문화, 하드 게이트를 conventions.md → 지침 존재로 교체, 승인 게이트에 Execution Mode 선언 절차 추가, 재개봉 프로토콜(가정 붕괴 시 STOP→수정→재승인) 명시, HARD STOP 보일러플레이트 제거(지침 참조 한 줄), 트리거에 상태 기반 조건 추가 @@ -121,6 +121,5 @@ GSL(Guided Software Lifecycle) 9개 스킬을 이 저장소 전용으로 대폭 ## Progress Log -(비어 있음 — /implement에서 기록) - - Task 1: `chore/gsl-overhaul` 브랜치 분리(sync-agent는 origin으로 원복). next-flow·scan-conventions 스킬과 python/go/cli/library/worker-daemon references를 미러 양쪽에서 삭제(18파일), conventions.md는 stale로 이동. 삭제 전 용량 276KB/미러. 잔존 참조 44건을 9개 SKILL.md + web-api.md·bottlenote-patterns.md에서 확인 — SKILL.md는 Tasks 3-6 재작성에서, references 2개는 Task 6에서 해소 예정. 미러 diff 일치. +- Task 2: 지침 양쪽의 "GSL Runtime Boundary Rules"를 "GSL Execution Mode" 섹션으로 교체. step-by-step 기본·delegated 선언 형식·stop-conditions 3종(가정 붕괴→재개봉, verify 3회 실패, scope 밖 행동)·PR 본문=체인지로그 관례를 명문화. 지침 정규화 diff 일치, Skills 표 7행이 실제 스킬 7종과 정합. Checkpoint 1-2 통과. From fa358789f996901721a7aa52bf9d9bfcdda471b7 Mon Sep 17 00:00:00 2001 From: Whale0928 Date: Sat, 25 Jul 2026 18:47:06 +0900 Subject: [PATCH 5/9] =?UTF-8?q?feat:=20/define=EC=9D=84=20WHAT=20=ED=99=95?= =?UTF-8?q?=EC=A0=95=EA=B3=BC=20=EC=9C=84=EC=9E=84=20=EA=B3=84=EC=95=BD?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=9E=AC=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- .agents/skills/define/SKILL.md | 199 +++++++++++++-------------------- .claude/skills/define/SKILL.md | 199 +++++++++++++-------------------- plan/gsl-overhaul.md | 3 +- 3 files changed, 154 insertions(+), 247 deletions(-) diff --git a/.agents/skills/define/SKILL.md b/.agents/skills/define/SKILL.md index c60108c7a..438acebbf 100644 --- a/.agents/skills/define/SKILL.md +++ b/.agents/skills/define/SKILL.md @@ -1,187 +1,140 @@ --- name: define description: | - Clarifies requirements before any code is written. Creates a plan document with assumptions, success criteria, and impact scope. - Trigger: "/define", or when the user says "이거 구현해줘", "기능 추가", "요구사항 정리", "define requirements", "spec this". - Use when starting a new feature, when requirements are vague, or when the scope of a change is unclear. - Do NOT write code during this skill — the output is a plan document, not implementation. + 코드를 쓰기 전에 WHAT(무엇을·왜)을 확정하고 plan 문서와 Execution Mode 계약을 만든다. + Trigger: "/define", 또는 사용자가 "이거 구현해줘", "기능 추가", "요구사항 정리", "define", "spec this"라고 할 때. + 상태 기반: 다중 파일 변경이 필요한 요청인데 plan/에 해당 기능 문서가 없을 때, 요청의 범위·성공 조건이 불명확할 때. + 이 스킬 안에서는 코드를 쓰지 않는다 — 산출물은 plan 문서다. argument-hint: "[feature description]" --- -# Define Requirements +# Define — WHAT 확정과 위임 계약 -## Overview +## 철학: WHAT과 HOW를 섞지 않는다 + +이 스킬은 **무엇을·왜**만 다룬다. 어떻게(파일 구조, 태스크 순서, 구현 방식)는 `/plan`과 `/implement`의 몫이다. -Write a structured specification before writing any code. The plan document is the shared source of truth — it defines what we are building, why, and how we will know it is done. Code without a spec is guessing. +이유는 재생성 비용의 비대칭이다. HOW는 틀리면 에이전트가 다시 짜면 되지만(싸다), WHAT은 사람의 의도를 끌어내야 해서 다시 만들기 비싸다. 그래서 사람의 협의는 여기에 집중하고, WHAT이 승인되면 이후 실패는 HOW의 실패로 격리된다. -This skill creates `plan/{feature-name}.md` with an Overview section. The `/plan` skill later adds Tasks to the same document; `/implement` fills the Progress Log. One feature = one document, from define through commit. +- 허용: 타당성 조사 (스키마가 이미 있는지, 어떤 모듈이 영향받는지 살펴보기) +- 금지: HOW 결정 (태스크 분해, 파일 목록 확정, 구현 방식 선택) -## When to Use +## 전제 -- Starting a new feature or significant change -- Requirements are ambiguous or incomplete -- The change touches 3+ files or multiple components -- The user gives a vague request ("이거 구현해줘", "추가해줘", "add this feature") +프로젝트 지침(CLAUDE.md/AGENTS.md)이 관습의 유일한 소유자다. 레이어 표준 15, 네이밍, 어노테이션, Flyway 규칙은 지침에 있으므로 여기서 다시 조사하지 않는다. ## When NOT to Use -- Bug fixes with clear reproduction (use `/debug`) -- Single-file changes with obvious scope -- Requirements already documented in a plan file -- Test-only work (use `/test`) +- 재현 가능한 버그 수정 → `/debug` +- 범위가 자명한 단일 파일 수정 → 바로 `/implement` +- 이미 plan 문서가 있는 기능 → `/plan` 또는 `/implement` +- 테스트만 추가 → `/test` ## Process -### Step 0: Confirm Project Conventions Exist (hard gate) - -**Before any other step**, check that `plan/conventions.md` exists at the project root. - -- If it does NOT exist → **STOP**. Tell the user to run `/scan-conventions` first. Impact analysis (Step 4) depends on knowing the project's actual conventions (naming, layering, persistence, test patterns). Do NOT proceed to Step 1. -- If it exists but is older than ~30 days → ask whether to rescan via `/scan-conventions` or use as-is. -- If it exists and is current → proceed to Step 1 and treat the artifact as authoritative for downstream analysis. - -### Step 1: Parse Request - -Identify what the user wants. Do NOT assume scope. +### Step 1: 요청 파싱 -- What domain or area is involved? -- Which modules / services / surfaces are touched? (API server, CLI, batch, library, frontend, ...) -- What is the expected externally visible behavior? -- Are there related features already implemented? +범위를 추측하지 않는다. 어떤 도메인인가, 어떤 모듈(product-api/admin-api/batch/mono)이 관련되나, 외부에서 관찰 가능한 기대 행동은 무엇인가. 불명확하면 진행 전에 묻는다. -If anything is unclear, ask before proceeding. Do NOT fill in ambiguous requirements silently. +### Step 2: 가정 표면화 -### Step 2: Surface Assumptions - -List every assumption explicitly. Each assumption is something that could be wrong. +모든 가정을 명시하고 사용자 확인을 받는다. 가정 하나하나가 "틀릴 수 있는 것"이다. ``` ASSUMPTIONS: -1. This feature targets the {module} (not {other-module}) -2. Authentication is required (not a public surface) -3. The {entity} already exists and does not need schema changes -4. {Pagination style} follows the project default --> Confirm or correct these before I proceed. +1. 이 기능은 product-api 대상이다 (admin-api 아님) +2. 인증 필요 (공개 엔드포인트 아님) +3. {엔티티}는 이미 존재하고 스키마 변경 불필요 +→ 확인 또는 수정해 주세요. ``` -Do NOT proceed without user confirmation on assumptions. - -**Superpowers absorption (if installed).** When Superpowers is active in the session, invoke its `brainstorming` skill as a sub-block before finalizing the assumption list — use the Socratic dialogue to surface deeper hidden assumptions, then merge its output into this section. The user-approval gate above still applies; brainstorming output does NOT bypass it. If Superpowers is not installed, skip this sub-block silently and proceed with GSL alone. +**확인 없이 진행 금지.** 가정을 조용히 채워 넣는 것이 재작업의 근원이다. -### Step 3: Define Success Criteria +### Step 3: 성공 기준 -Each criterion must be specific and testable. Translate vague requirements into concrete conditions. +각 기준은 구체적이고 검증 가능해야 한다. "더 좋게", "성능 개선" 같은 검증 불가 기준은 거부하고 구체화를 요청한다. ``` -REQUIREMENT: "add a usage statistics feature" - SUCCESS CRITERIA: -- {GET endpoint / CLI command} returns average, count, and distribution -- Response shape includes {field 1}, {field 2}, {field 3} -- Unauthenticated callers can {access / are rejected with 401} -- Latency target: < {N} ms at {scale} items --> Are these the right targets? +- GET {경로}가 평균·건수·분포를 반환한다 +- 미인증 호출은 401 +- 응답에 {필드1}, {필드2} 포함 +→ 이 목표가 맞습니까? ``` -Reject criteria that are not testable ("make it better", "improve performance"). +성공 기준도 가정과 같다 — 확인 없이 다음 Step으로 가지 않는다. -### Step 4: Analyze Impact Scope +### Step 4: 영향 범위 조사 -Check which parts of the system are affected. The exact checklist depends on the project type — consult `implement/references/types/{your-type}.md` for type-specific impact checklists. Generally: +결정이 아니라 조사다: 관련 모듈, cross-domain 결합(Facade 신설 필요?), 스키마 마이그레이션 여부(Flyway — 엔티티 변경 시 필수), 이벤트 발행/수신, 캐시, 필요한 테스트 계층, 외부 API 계약. -- **Modules / surfaces**: which ones are involved? -- **Cross-component coupling**: any new dependency between previously independent units? -- **Persistence**: schema migration needed? -- **Async / events**: new events published or consumed? -- **Caching**: invalidation policy affected? -- **Tests**: which test layers will be needed? -- **Docs / API contracts**: external consumers impacted? +### Step 5: plan 문서 작성 -### Step 5: Create Plan Document - -Create `plan/{feature-name}.md`. Use the user's preferred language for plan documents (the working convention is the user's natural language; English keywords for section headers are fine): +`plan/{feature-name}.md` 생성. 기능 하나 = 문서 하나. ```markdown -# Plan: [feature name] +# Plan: [기능명] ## Overview -[what we are building and why] +[무엇을, 왜] ### Assumptions -- [assumption 1] -- [assumption 2] - ### Success Criteria -- [specific, testable criterion 1] -- [specific, testable criterion 2] - ### Impact Scope -- [modules / files affected, schema, events, cache, tests] -``` - -This document will be extended by `/plan` (Tasks section) and `/implement` (Progress Log). -### Step 6: User Approval Gate +## Execution Mode +- mode: (승인 게이트에서 확정) -Present the complete Overview to the user. Do NOT proceed to `/plan` or `/implement` without explicit approval. +## Tasks +(/plan에서 작성) +## Progress Log ``` -Plan document created: plan/{feature-name}.md -Summary: -- Assumptions: [N] items listed -- Success criteria: [N] conditions defined -- Impact: [modules / surfaces affected] +### Step 6: 승인 게이트 = 계약 서명 -Approve to proceed to /plan for task breakdown? -``` +Overview 승인과 **Execution Mode 선언**을 함께 받는다. 이것이 이 스킬의 핵심 산출물이다. -## Common Rationalizations +``` +plan/{feature-name}.md 작성 완료 -| Rationalization | Reality | -|-----------------|---------| -| "This is simple, I don't need a spec" | Simple tasks still need acceptance criteria. A 2-line spec is fine. | -| "I'll figure it out while coding" | That is how rework happens. 15 minutes of spec saves 3 hours of wrong implementation. | -| "Requirements will change anyway" | That is why the spec is a living document. Having one that changes is better than having none. | -| "The user knows what they want" | Even clear requests have implicit assumptions. The spec surfaces those. | -| "I can just start with /implement" | Without defined success criteria, how will you know when you are done? | +- 가정 [N]건 / 성공 기준 [N]건 / 영향: [모듈] -## Red Flags - -- Jumping to code without user approval on assumptions -- Assumptions not listed explicitly -- Success criteria that are not testable -- Missing impact analysis (especially cross-component coupling) -- Proceeding to `/plan` without user approval on the Overview -- Creating multiple plan documents for a single feature +Execution Mode를 선택해 주세요: +1. step-by-step (기본) — 단계마다 승인 +2. delegated — 이후 자율 진행. scope를 함께 선언해 주세요 + (plan, implement, test, verify, commit [, push, pr]) +``` -## Verification +선택 결과를 plan 문서의 `## Execution Mode` 섹션에 기록한다. delegated면 scope와 stop-conditions를 함께 기록한다 — 기본 3종: ① 가정 붕괴(재개봉 프로토콜) ② `/verify` 3회 실패 ③ scope 밖 행동. **문서에 기록된 것만이 유효한 계약이다.** scope에 push/pr을 포함하면 그 행위가 별도 재확인 없이 수행된다는 뜻임을 게이트에서 함께 고지한다. -Before proceeding to `/plan`: +## 재개봉 프로토콜 -- [ ] Plan document exists at `plan/{feature-name}.md` -- [ ] Assumptions are listed and confirmed by user -- [ ] Success criteria are specific and testable -- [ ] Impact scope identifies affected modules / surfaces / tests -- [ ] User has explicitly approved the Overview +이후 어느 단계(`/plan`, `/implement`, `/test`)에서든 **가정을 깨는 발견**이 나오면: -## Runtime Boundary — HARD STOP +1. 즉시 정지한다 (delegated 모드여도 — stop-condition 1번). +2. 이 문서의 Assumptions를 수정한다. +3. WHAT(성공 기준·범위)이 바뀌었으면 재승인을 받는다. 표현만 정밀해진 것이면 기록만 남기고 계속한다. -This skill ENDS after the Verification checklist and final report are completed. +조용히 적응해서 계속 진행하는 것이 최악의 선택이다. -For codex and any runtime without an enforced skill-return boundary: -- MUST stop the assistant turn here. -- MUST NOT invoke, load, or execute any next GSL skill in the same response turn. -- MUST NOT continue into `/next-flow`, `/define`, `/plan`, `/implement`, `/test`, `/verify`, `/debug`, or `/self-review`. -- MAY print exactly one suggested next command as plain text. -- MUST wait for the user's next message before running any next skill. +## Common Rationalizations -If the user says only "continue", treat that as permission to report the next recommended command, not permission to execute it. +| 합리화 | 현실 | +|--------|------| +| "간단해서 스펙 필요 없다" | 간단한 작업도 수용 기준은 필요하다. 2줄짜리 스펙이면 된다. | +| "코딩하면서 알아가면 된다" | 그게 재작업이 생기는 방식이다. 스펙 15분이 잘못된 구현 3시간을 아낀다. | +| "사용자가 뭘 원하는지 안다" | 명확한 요청에도 암묵적 가정이 있다. 스펙이 그걸 드러낸다. | +| "사용자가 건너뛰라고 했다" | 속도 요청은 형식을 압축하라는 뜻이다(가정을 묶어 한 번에 제시). 확인 자체는 생략하지 않는다. | ---- +## Red Flags -## Lifecycle Integration +- 가정 확인 없이 다음 단계로 진행 +- 검증 불가능한 성공 기준을 그대로 수용 +- Execution Mode 선언 없이 게이트 통과 +- 기능 하나에 plan 문서 여러 개 생성 +- 이 스킬 안에서 코드 작성 또는 태스크 분해 -**Before this skill:** if `plan/conventions.md` does not exist, run `/scan-conventions` first — analysis relies on knowing the project's actual conventions (naming, layering, test patterns, build system). +## 종료 -**After this skill:** the next GSL skill is started by the user, not by this skill — see the Runtime Boundary section above. `/next-flow` may be suggested for lifecycle diagnosis but is not auto-invoked. Runtime note: some environments expose slash commands as UI commands; codex loads GSL skills from `.agents/skills/`. In both cases, the next GSL skill requires a new explicit user message. +지침의 **GSL Execution Mode** 규칙을 따른다. 산출물(문서 경로, 가정·기준 개수, 확정된 모드)을 보고한다. step-by-step이면 `Next: /plan`을 제안한 뒤 턴을 끝내고, delegated(scope에 plan 포함)면 `/plan`으로 계속한다. diff --git a/.claude/skills/define/SKILL.md b/.claude/skills/define/SKILL.md index c60108c7a..438acebbf 100644 --- a/.claude/skills/define/SKILL.md +++ b/.claude/skills/define/SKILL.md @@ -1,187 +1,140 @@ --- name: define description: | - Clarifies requirements before any code is written. Creates a plan document with assumptions, success criteria, and impact scope. - Trigger: "/define", or when the user says "이거 구현해줘", "기능 추가", "요구사항 정리", "define requirements", "spec this". - Use when starting a new feature, when requirements are vague, or when the scope of a change is unclear. - Do NOT write code during this skill — the output is a plan document, not implementation. + 코드를 쓰기 전에 WHAT(무엇을·왜)을 확정하고 plan 문서와 Execution Mode 계약을 만든다. + Trigger: "/define", 또는 사용자가 "이거 구현해줘", "기능 추가", "요구사항 정리", "define", "spec this"라고 할 때. + 상태 기반: 다중 파일 변경이 필요한 요청인데 plan/에 해당 기능 문서가 없을 때, 요청의 범위·성공 조건이 불명확할 때. + 이 스킬 안에서는 코드를 쓰지 않는다 — 산출물은 plan 문서다. argument-hint: "[feature description]" --- -# Define Requirements +# Define — WHAT 확정과 위임 계약 -## Overview +## 철학: WHAT과 HOW를 섞지 않는다 + +이 스킬은 **무엇을·왜**만 다룬다. 어떻게(파일 구조, 태스크 순서, 구현 방식)는 `/plan`과 `/implement`의 몫이다. -Write a structured specification before writing any code. The plan document is the shared source of truth — it defines what we are building, why, and how we will know it is done. Code without a spec is guessing. +이유는 재생성 비용의 비대칭이다. HOW는 틀리면 에이전트가 다시 짜면 되지만(싸다), WHAT은 사람의 의도를 끌어내야 해서 다시 만들기 비싸다. 그래서 사람의 협의는 여기에 집중하고, WHAT이 승인되면 이후 실패는 HOW의 실패로 격리된다. -This skill creates `plan/{feature-name}.md` with an Overview section. The `/plan` skill later adds Tasks to the same document; `/implement` fills the Progress Log. One feature = one document, from define through commit. +- 허용: 타당성 조사 (스키마가 이미 있는지, 어떤 모듈이 영향받는지 살펴보기) +- 금지: HOW 결정 (태스크 분해, 파일 목록 확정, 구현 방식 선택) -## When to Use +## 전제 -- Starting a new feature or significant change -- Requirements are ambiguous or incomplete -- The change touches 3+ files or multiple components -- The user gives a vague request ("이거 구현해줘", "추가해줘", "add this feature") +프로젝트 지침(CLAUDE.md/AGENTS.md)이 관습의 유일한 소유자다. 레이어 표준 15, 네이밍, 어노테이션, Flyway 규칙은 지침에 있으므로 여기서 다시 조사하지 않는다. ## When NOT to Use -- Bug fixes with clear reproduction (use `/debug`) -- Single-file changes with obvious scope -- Requirements already documented in a plan file -- Test-only work (use `/test`) +- 재현 가능한 버그 수정 → `/debug` +- 범위가 자명한 단일 파일 수정 → 바로 `/implement` +- 이미 plan 문서가 있는 기능 → `/plan` 또는 `/implement` +- 테스트만 추가 → `/test` ## Process -### Step 0: Confirm Project Conventions Exist (hard gate) - -**Before any other step**, check that `plan/conventions.md` exists at the project root. - -- If it does NOT exist → **STOP**. Tell the user to run `/scan-conventions` first. Impact analysis (Step 4) depends on knowing the project's actual conventions (naming, layering, persistence, test patterns). Do NOT proceed to Step 1. -- If it exists but is older than ~30 days → ask whether to rescan via `/scan-conventions` or use as-is. -- If it exists and is current → proceed to Step 1 and treat the artifact as authoritative for downstream analysis. - -### Step 1: Parse Request - -Identify what the user wants. Do NOT assume scope. +### Step 1: 요청 파싱 -- What domain or area is involved? -- Which modules / services / surfaces are touched? (API server, CLI, batch, library, frontend, ...) -- What is the expected externally visible behavior? -- Are there related features already implemented? +범위를 추측하지 않는다. 어떤 도메인인가, 어떤 모듈(product-api/admin-api/batch/mono)이 관련되나, 외부에서 관찰 가능한 기대 행동은 무엇인가. 불명확하면 진행 전에 묻는다. -If anything is unclear, ask before proceeding. Do NOT fill in ambiguous requirements silently. +### Step 2: 가정 표면화 -### Step 2: Surface Assumptions - -List every assumption explicitly. Each assumption is something that could be wrong. +모든 가정을 명시하고 사용자 확인을 받는다. 가정 하나하나가 "틀릴 수 있는 것"이다. ``` ASSUMPTIONS: -1. This feature targets the {module} (not {other-module}) -2. Authentication is required (not a public surface) -3. The {entity} already exists and does not need schema changes -4. {Pagination style} follows the project default --> Confirm or correct these before I proceed. +1. 이 기능은 product-api 대상이다 (admin-api 아님) +2. 인증 필요 (공개 엔드포인트 아님) +3. {엔티티}는 이미 존재하고 스키마 변경 불필요 +→ 확인 또는 수정해 주세요. ``` -Do NOT proceed without user confirmation on assumptions. - -**Superpowers absorption (if installed).** When Superpowers is active in the session, invoke its `brainstorming` skill as a sub-block before finalizing the assumption list — use the Socratic dialogue to surface deeper hidden assumptions, then merge its output into this section. The user-approval gate above still applies; brainstorming output does NOT bypass it. If Superpowers is not installed, skip this sub-block silently and proceed with GSL alone. +**확인 없이 진행 금지.** 가정을 조용히 채워 넣는 것이 재작업의 근원이다. -### Step 3: Define Success Criteria +### Step 3: 성공 기준 -Each criterion must be specific and testable. Translate vague requirements into concrete conditions. +각 기준은 구체적이고 검증 가능해야 한다. "더 좋게", "성능 개선" 같은 검증 불가 기준은 거부하고 구체화를 요청한다. ``` -REQUIREMENT: "add a usage statistics feature" - SUCCESS CRITERIA: -- {GET endpoint / CLI command} returns average, count, and distribution -- Response shape includes {field 1}, {field 2}, {field 3} -- Unauthenticated callers can {access / are rejected with 401} -- Latency target: < {N} ms at {scale} items --> Are these the right targets? +- GET {경로}가 평균·건수·분포를 반환한다 +- 미인증 호출은 401 +- 응답에 {필드1}, {필드2} 포함 +→ 이 목표가 맞습니까? ``` -Reject criteria that are not testable ("make it better", "improve performance"). +성공 기준도 가정과 같다 — 확인 없이 다음 Step으로 가지 않는다. -### Step 4: Analyze Impact Scope +### Step 4: 영향 범위 조사 -Check which parts of the system are affected. The exact checklist depends on the project type — consult `implement/references/types/{your-type}.md` for type-specific impact checklists. Generally: +결정이 아니라 조사다: 관련 모듈, cross-domain 결합(Facade 신설 필요?), 스키마 마이그레이션 여부(Flyway — 엔티티 변경 시 필수), 이벤트 발행/수신, 캐시, 필요한 테스트 계층, 외부 API 계약. -- **Modules / surfaces**: which ones are involved? -- **Cross-component coupling**: any new dependency between previously independent units? -- **Persistence**: schema migration needed? -- **Async / events**: new events published or consumed? -- **Caching**: invalidation policy affected? -- **Tests**: which test layers will be needed? -- **Docs / API contracts**: external consumers impacted? +### Step 5: plan 문서 작성 -### Step 5: Create Plan Document - -Create `plan/{feature-name}.md`. Use the user's preferred language for plan documents (the working convention is the user's natural language; English keywords for section headers are fine): +`plan/{feature-name}.md` 생성. 기능 하나 = 문서 하나. ```markdown -# Plan: [feature name] +# Plan: [기능명] ## Overview -[what we are building and why] +[무엇을, 왜] ### Assumptions -- [assumption 1] -- [assumption 2] - ### Success Criteria -- [specific, testable criterion 1] -- [specific, testable criterion 2] - ### Impact Scope -- [modules / files affected, schema, events, cache, tests] -``` - -This document will be extended by `/plan` (Tasks section) and `/implement` (Progress Log). -### Step 6: User Approval Gate +## Execution Mode +- mode: (승인 게이트에서 확정) -Present the complete Overview to the user. Do NOT proceed to `/plan` or `/implement` without explicit approval. +## Tasks +(/plan에서 작성) +## Progress Log ``` -Plan document created: plan/{feature-name}.md -Summary: -- Assumptions: [N] items listed -- Success criteria: [N] conditions defined -- Impact: [modules / surfaces affected] +### Step 6: 승인 게이트 = 계약 서명 -Approve to proceed to /plan for task breakdown? -``` +Overview 승인과 **Execution Mode 선언**을 함께 받는다. 이것이 이 스킬의 핵심 산출물이다. -## Common Rationalizations +``` +plan/{feature-name}.md 작성 완료 -| Rationalization | Reality | -|-----------------|---------| -| "This is simple, I don't need a spec" | Simple tasks still need acceptance criteria. A 2-line spec is fine. | -| "I'll figure it out while coding" | That is how rework happens. 15 minutes of spec saves 3 hours of wrong implementation. | -| "Requirements will change anyway" | That is why the spec is a living document. Having one that changes is better than having none. | -| "The user knows what they want" | Even clear requests have implicit assumptions. The spec surfaces those. | -| "I can just start with /implement" | Without defined success criteria, how will you know when you are done? | +- 가정 [N]건 / 성공 기준 [N]건 / 영향: [모듈] -## Red Flags - -- Jumping to code without user approval on assumptions -- Assumptions not listed explicitly -- Success criteria that are not testable -- Missing impact analysis (especially cross-component coupling) -- Proceeding to `/plan` without user approval on the Overview -- Creating multiple plan documents for a single feature +Execution Mode를 선택해 주세요: +1. step-by-step (기본) — 단계마다 승인 +2. delegated — 이후 자율 진행. scope를 함께 선언해 주세요 + (plan, implement, test, verify, commit [, push, pr]) +``` -## Verification +선택 결과를 plan 문서의 `## Execution Mode` 섹션에 기록한다. delegated면 scope와 stop-conditions를 함께 기록한다 — 기본 3종: ① 가정 붕괴(재개봉 프로토콜) ② `/verify` 3회 실패 ③ scope 밖 행동. **문서에 기록된 것만이 유효한 계약이다.** scope에 push/pr을 포함하면 그 행위가 별도 재확인 없이 수행된다는 뜻임을 게이트에서 함께 고지한다. -Before proceeding to `/plan`: +## 재개봉 프로토콜 -- [ ] Plan document exists at `plan/{feature-name}.md` -- [ ] Assumptions are listed and confirmed by user -- [ ] Success criteria are specific and testable -- [ ] Impact scope identifies affected modules / surfaces / tests -- [ ] User has explicitly approved the Overview +이후 어느 단계(`/plan`, `/implement`, `/test`)에서든 **가정을 깨는 발견**이 나오면: -## Runtime Boundary — HARD STOP +1. 즉시 정지한다 (delegated 모드여도 — stop-condition 1번). +2. 이 문서의 Assumptions를 수정한다. +3. WHAT(성공 기준·범위)이 바뀌었으면 재승인을 받는다. 표현만 정밀해진 것이면 기록만 남기고 계속한다. -This skill ENDS after the Verification checklist and final report are completed. +조용히 적응해서 계속 진행하는 것이 최악의 선택이다. -For codex and any runtime without an enforced skill-return boundary: -- MUST stop the assistant turn here. -- MUST NOT invoke, load, or execute any next GSL skill in the same response turn. -- MUST NOT continue into `/next-flow`, `/define`, `/plan`, `/implement`, `/test`, `/verify`, `/debug`, or `/self-review`. -- MAY print exactly one suggested next command as plain text. -- MUST wait for the user's next message before running any next skill. +## Common Rationalizations -If the user says only "continue", treat that as permission to report the next recommended command, not permission to execute it. +| 합리화 | 현실 | +|--------|------| +| "간단해서 스펙 필요 없다" | 간단한 작업도 수용 기준은 필요하다. 2줄짜리 스펙이면 된다. | +| "코딩하면서 알아가면 된다" | 그게 재작업이 생기는 방식이다. 스펙 15분이 잘못된 구현 3시간을 아낀다. | +| "사용자가 뭘 원하는지 안다" | 명확한 요청에도 암묵적 가정이 있다. 스펙이 그걸 드러낸다. | +| "사용자가 건너뛰라고 했다" | 속도 요청은 형식을 압축하라는 뜻이다(가정을 묶어 한 번에 제시). 확인 자체는 생략하지 않는다. | ---- +## Red Flags -## Lifecycle Integration +- 가정 확인 없이 다음 단계로 진행 +- 검증 불가능한 성공 기준을 그대로 수용 +- Execution Mode 선언 없이 게이트 통과 +- 기능 하나에 plan 문서 여러 개 생성 +- 이 스킬 안에서 코드 작성 또는 태스크 분해 -**Before this skill:** if `plan/conventions.md` does not exist, run `/scan-conventions` first — analysis relies on knowing the project's actual conventions (naming, layering, test patterns, build system). +## 종료 -**After this skill:** the next GSL skill is started by the user, not by this skill — see the Runtime Boundary section above. `/next-flow` may be suggested for lifecycle diagnosis but is not auto-invoked. Runtime note: some environments expose slash commands as UI commands; codex loads GSL skills from `.agents/skills/`. In both cases, the next GSL skill requires a new explicit user message. +지침의 **GSL Execution Mode** 규칙을 따른다. 산출물(문서 경로, 가정·기준 개수, 확정된 모드)을 보고한다. step-by-step이면 `Next: /plan`을 제안한 뒤 턴을 끝내고, delegated(scope에 plan 포함)면 `/plan`으로 계속한다. diff --git a/plan/gsl-overhaul.md b/plan/gsl-overhaul.md index 5316252ab..6c935eb4b 100644 --- a/plan/gsl-overhaul.md +++ b/plan/gsl-overhaul.md @@ -85,7 +85,7 @@ GSL(Guided Software Lifecycle) 9개 스킬을 이 저장소 전용으로 대폭 - Verification: 서브에이전트 시나리오 2건 — (a) 정상: 신규 기능 요청 → 가정·성공기준 도출 및 모드 선언까지, (b) 함정: 모호한 요청에 가정 확인 없이 진행하도록 유도 → 게이트가 막는지 - Files (advisory): define/SKILL.md × 미러 2 - Size: S -- Status: [ ] not done +- Status: [x] done ### Task 4: /plan 재작성 - Acceptance: 분해(자연 관절)→의존성 정렬→크기 검증 순서로 재구성, Task=승인 대상/Slice=에이전트 전권 구분 명문화, `Files:`를 advisory로 강등, Superpowers 블록 제거, HARD STOP 제거 @@ -123,3 +123,4 @@ GSL(Guided Software Lifecycle) 9개 스킬을 이 저장소 전용으로 대폭 - Task 1: `chore/gsl-overhaul` 브랜치 분리(sync-agent는 origin으로 원복). next-flow·scan-conventions 스킬과 python/go/cli/library/worker-daemon references를 미러 양쪽에서 삭제(18파일), conventions.md는 stale로 이동. 삭제 전 용량 276KB/미러. 잔존 참조 44건을 9개 SKILL.md + web-api.md·bottlenote-patterns.md에서 확인 — SKILL.md는 Tasks 3-6 재작성에서, references 2개는 Task 6에서 해소 예정. 미러 diff 일치. - Task 2: 지침 양쪽의 "GSL Runtime Boundary Rules"를 "GSL Execution Mode" 섹션으로 교체. step-by-step 기본·delegated 선언 형식·stop-conditions 3종(가정 붕괴→재개봉, verify 3회 실패, scope 밖 행동)·PR 본문=체인지로그 관례를 명문화. 지침 정규화 diff 일치, Skills 표 7행이 실제 스킬 7종과 정합. Checkpoint 1-2 통과. +- Task 3: /define 재작성 — WHAT/HOW 분리 철학, 지침 기반 하드 게이트, Execution Mode 선언 게이트(계약 서명), 재개봉 프로토콜, 상태 기반 트리거. 시나리오 검증(scenario-define): 정상 경로 완주 확인, 함정("가정 확인 넘어가고") 게이트 우회 불가 판정, 치명 0건·경미 7건. 경미 중 5건 반영(합리화 표에 외부 압박 행 추가, mode 플레이스홀더, stop-conditions 3종 인라인, Step 3 차단 문구, delegated 종료 분기·push/pr 고지). 188행 → 140행. From b31c24345571f425705c0afc80e0a28e8dc10063 Mon Sep 17 00:00:00 2001 From: Whale0928 Date: Sat, 25 Jul 2026 18:48:44 +0900 Subject: [PATCH 6/9] =?UTF-8?q?feat:=20/plan=EC=9D=84=20=EB=B6=84=ED=95=B4?= =?UTF-8?q?=EC=99=80=20=EC=A0=95=EB=A0=AC=20=EC=A4=91=EC=8B=AC=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=9E=AC=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- .agents/skills/plan/SKILL.md | 214 +++++++++++------------------------ .claude/skills/plan/SKILL.md | 214 +++++++++++------------------------ plan/gsl-overhaul.md | 3 +- 3 files changed, 134 insertions(+), 297 deletions(-) diff --git a/.agents/skills/plan/SKILL.md b/.agents/skills/plan/SKILL.md index 821f19007..cf079e2cc 100644 --- a/.agents/skills/plan/SKILL.md +++ b/.agents/skills/plan/SKILL.md @@ -1,202 +1,120 @@ --- name: plan description: | - Breaks work into ordered, verifiable tasks with acceptance criteria. - Trigger: "/plan", or when the user says "계획 세워줘", "태스크 분해", "plan this", "break it down". - Use after /define when requirements are clear and a plan document exists. - Adds the Tasks section to an existing plan document created by /define. + 승인된 define 문서를 받아 일을 자연 관절대로 분해하고 의존성 순서로 정렬해 Tasks를 만든다. + Trigger: "/plan", 또는 사용자가 "계획 세워줘", "태스크 분해", "plan this", "break it down"이라고 할 때. + 상태 기반: plan/{기능}.md에 Overview는 있으나 Tasks 섹션이 비어 있을 때. + Execution Mode가 delegated(scope에 plan 포함)면 승인 게이트 대신 체크포인트 보고로 진행한다. argument-hint: "[feature-name or plan file path]" --- -# Planning and Task Breakdown +# Plan — 분해와 정렬 -## Overview +## 철학: Task는 관절, Slice는 썰기 -Decompose work into small, verifiable tasks with explicit acceptance criteria. Good task breakdown is the difference between an agent that completes work reliably and one that produces a tangled mess. Every task should be small enough to implement, test, and commit in a single focused session. +정육 모델로 구분한다. 소를 등심·안심으로 나누는 부위 분해가 **Task**다 — 문제의 자연 관절(의존성, 모듈 경계, 리뷰 단위)을 따라 자르며, 올바른 답이 적고 틀리면 커밋 이력과 리뷰 단위가 오염되므로 **승인 대상**이다. 그 안심을 어떻게 썰어 요리할지가 **Slice**다 — 정답이 여럿이고 틀려도 다시 썰면 되므로 **에이전트 전권**이며 `/implement`에서 결정한다. -This skill adds the Tasks section to an existing plan document created by `/define`. +따라서 이 스킬의 순서는 **분해 → 정렬 → 크기 검증**이다. 크기는 절단 기준이 아니라 관절을 제대로 찾았는지의 검산이다. -**External workflow precedence.** When Superpowers' `writing-plans` skill is active in the same session, GSL `/plan` takes precedence on the same prompt. Invoke `writing-plans` as a sub-block to enrich Dependency Analysis (Step 2) and the Task list (Step 3) — but it does NOT replace GSL's vertical-slice rule, L-size ban (8+ files), Checkpoint cadence, or user-approval gate. If `writing-plans` produces a horizontal task list, **STOP and rewrite as vertical slices** before presenting to the user. +## 전제 -## When to Use +`plan/{feature-name}.md`에 승인된 Overview(Assumptions·Success Criteria·Impact Scope)와 Execution Mode 선언이 있어야 한다. 없으면 `/define`을 안내하고 종료한다. -- After `/define` has created a plan document with Overview -- When 3+ files need changes -- When work spans multiple modules or surfaces -- When the implementation order is not obvious +## 가정 붕괴 시 즉시 중단 (전역 규칙) + +어느 Step에서든 define의 Assumption을 깨는 발견이 나오면 **그 자리에서 중단한다**. Tasks를 아직 기록하지 않았으면 기록하지 않고, 이미 기록했다면 해당 기록을 되돌린다. 이것이 stop-condition 1번(가정 붕괴)이며 delegated 위임보다 우선한다. + +중단 후 절차(재개봉 프로토콜): ① 발견 내용과 깨진 가정을 보고한다 ② define 문서의 Assumptions 수정안을 제시한다 ③ WHAT(성공 기준·범위)이 바뀌었으면 재승인을 받고, 표현만 정밀해진 것이면 기록만 남기고 계속한다. ## When NOT to Use -- No plan document exists yet (use `/define` first) -- Single-file changes with obvious scope (just `/implement` directly) -- Bug fixes (use `/debug` directly) +- plan 문서가 없다 → `/define` 먼저 +- 범위가 자명한 단일 파일 수정 → 바로 `/implement` +- 버그 수정 → `/debug` ## Process -### Step 1: Read Plan Document +### Step 1: define 산출물 읽기 -Open `plan/{feature-name}.md`. If it does not exist, prompt the user to run `/define` first. +Overview·Assumptions·Success Criteria·Impact Scope·Execution Mode를 읽는다. 여기 없는 요구사항을 이 단계에서 새로 만들지 않는다 — 부족하면 위 전역 규칙의 재개봉 프로토콜로 돌아간다. -Read: -- Overview: what are we building and why -- Assumptions: confirmed constraints -- Success Criteria: what "done" looks like -- Impact Scope: which modules / surfaces / tests are affected +### Step 2: 분해 — 자연 관절 찾기 -### Step 2: Dependency Analysis +Impact Scope를 보고 일의 관절을 찾는다. 관절의 기준: -Map what depends on what. Implementation order follows the dependency graph bottom-up. +- **의존성 경계**: 먼저 존재해야 다음이 성립하는 지점. entity → repository → service → controller는 **순서**의 기준이지, 레이어마다 Task를 만들라는 뜻이 아니다 +- **모듈 경계**: product-api / admin-api / mono / batch — 한 Task는 한 모듈에 집중 +- **리뷰 단위**: 리뷰어가 한 번에 이해하고 승인할 수 있는 덩어리 = 커밋 하나 -For the actual dependency layers, consult `implement/references/types/{your-type}.md` — for example: -- **web-api**: entity / model → repository → service → controller / route; DTOs and exceptions in parallel -- **cli**: domain model → command handler → CLI binding -- **batch**: job step → job → scheduler binding -- **library**: internal helpers → public API surface +**수직 슬라이스 원칙**: 레이어별 수평 분해(모든 DTO → 모든 Repository → 모든 Service)는 금지다. 스택을 관통하는 완결된 경로 하나씩 자른다. -Identify: -- What must be built first (foundation) -- What can be built in parallel (independent pieces) -- What depends on cross-module boundaries (coordination needed) +``` +나쁨(수평): Task 1: DTO 전부 / Task 2: Repository 전부 / Task 3: Service 전부 +좋음(수직): Task 1: 통계 조회 경로 (DTO+Repository+Service) / Task 2: Controller+문서 / Task 3: 통합 테스트 +``` -### Step 3: Create Task List +수평 금지의 기준은 "한 레이어를 여러 경로에 걸쳐 모았는가"다. 한 경로의 표면(Controller)을 뒤따르는 Task로 두는 것은 수직 경로의 순차 절단이라 무방하다. -Break work into Tasks. Each Task is a commit unit. +**사용자가 수평 분해를 명시적으로 요청하면**: 근거(마지막 Task까지 아무것도 동작하지 않고 중간 커밋이 검증 불가능해짐)를 한 번 제시하고 수직 대안을 권한다. 사용자가 재차 수평을 지시하면 그것이 결정이다 — 따르되, plan 문서에 "수평 분해: 사용자 결정"을 기록한다. -**Sizing guidelines:** +### Step 3: 의존성 정렬 -| Size | Files | Scope | Action | -|------|-------|-------|--------| -| **S** | 1-3 | Single component | Good as-is | -| **M** | 4-7 | One feature slice | Good as-is | -| **L** | 8+ | Multi-component | **Must split further** | +기반이 되는 Task를 앞에, 독립적인 Task는 병렬 가능으로 표시한다. cross-domain 결합(Facade 신설 등)이 있으면 그 계약을 별도 Task로 앞세운다. -**Prefer vertical slices:** build one complete path through the stack rather than all layers at once. +### Step 4: 크기 검증 (검산) -Bad (horizontal): -``` -Task 1: All DTOs -Task 2: All Repositories -Task 3: All Services -``` +- 8개 이상 파일을 건드리는 Task → 관절을 잘못 찾은 신호. 재분해한다 +- 제목에 "and"/"및"이 들어가는 Task → 아마 두 개다 +- 수용 기준을 3불릿 이내로 못 쓰는 Task → 너무 넓다 -Good (vertical): -``` -Task 1: Statistics DTO + Repository + query -Task 2: Statistics Service + unit test -Task 3: Statistics Controller / handler / command -Task 4: Integration test -``` +크기 라벨: S(1-3파일) / M(4-7파일). L은 존재해선 안 된다. -### Step 4: Size Validation +### Step 5: Tasks 기록 -Check every Task: -- Is it L-sized (8+ files)? → Split it -- Does the title contain "and"? → Probably two Tasks -- Can acceptance criteria be described in 3 or fewer bullets? → If not, too broad -- Does it touch two independent subsystems? → Split by subsystem +plan 문서에 Tasks 섹션을 채운다. -### Step 5: Write Tasks to Plan Document - -Append the Tasks section and an empty Progress Log to the existing plan document. - -**Task entry format:** ```markdown -### Task N: [title] -- Acceptance: [specific, testable condition] -- Verification: [command or check] -- Files: [expected file changes] +### Task N: [제목] +- Acceptance: [관찰 가능하고 검증 가능한 조건 — 이것이 계약이다] +- Verification: [실행할 명령 또는 확인 방법] +- Files (advisory): [예상 파일 — 참고 추정일 뿐, 계약이 아니다] +- Depends: [선행 Task 번호 | 없음 — '없음'끼리는 병렬 가능] - Size: [S | M] - Status: [ ] not done - -## Progress Log -(empty — filled during /implement) ``` -Insert a checkpoint after every 2-3 Tasks: +2-3개 Task마다 Checkpoint를 삽입한다: ```markdown -### Checkpoint: after Tasks 1-3 -- [ ] Compiles / type-checks -- [ ] Unit tests pass -- [ ] Project architecture / lint rules pass +### Checkpoint: after Tasks N-M +- [ ] 컴파일 통과 / 단위 테스트 통과 / ArchUnit 룰 통과 ``` -### Step 6: User Approval Gate +`Files:`는 advisory다 — 계획 시점의 파일 예측은 낡는다. Task의 계약은 Acceptance뿐이다. -Present the task list with estimated order. Get explicit approval before proceeding to `/implement`. +마지막 Task 뒤에는 Checkpoint를 두지 않는다 — `/implement` Phase 4의 `/verify`가 그 역할을 한다. 총 Task가 2개 이하면 Checkpoint를 생략해도 된다. -``` -Tasks added to plan/{feature-name}.md - -Summary: -- [N] tasks defined (sizes: S × [n], M × [n]) -- Estimated commits: [N] -- Dependencies: [brief description] - -Approve to proceed to /implement? -``` +### Step 6: 게이트 (모드 분기) -## Plan Document Lifecycle - -``` -/define creates document → Status: IN PROGRESS -/plan adds Tasks → Tasks section populated -/implement checks off Tasks → Progress Log updated per Task commit -All Tasks done → Add completion stamp - → Move to plan/complete/ (or plan/stale/ if abandoned) -``` - -**One feature = one document.** Do not split a feature across multiple plan files. Do not create a new document if one already exists for this feature. +- **step-by-step**: 태스크 목록 요약(개수·크기 분포·의존 순서)을 제시하고 명시적 승인을 받은 뒤 턴을 끝낸다. 승인 전 `/implement` 진행 금지. **승인이 거부되면**: 거부 사유를 받아 Step 2부터 재분해한다. 사유가 분해 방식이 아니라 요구사항 자체라면 재개봉 프로토콜로 `/define`에 돌린다. +- **delegated (scope에 plan 포함)**: 승인 권한은 define 게이트에서 이미 위임받았다. 목록을 체크포인트 보고로 남기고 다음 단계로 계속한다. 단, 분해 중 가정을 깨는 발견이 있었다면 stop-condition 1번(재개봉)이 우선한다. ## Common Rationalizations -| Rationalization | Reality | -|-----------------|---------| -| "I'll figure it out as I go" | That is how rework happens. 10 minutes of planning saves hours. | -| "The tasks are obvious" | Write them down. Explicit tasks surface hidden dependencies and forgotten edge cases. | -| "Planning is overhead" | Planning IS the task. Implementation without a plan is just typing. | -| "I can hold it all in my head" | Context windows are finite. Written plans survive session boundaries and compaction. | -| "This is only 2 tasks, why bother" | Even 2 tasks benefit from acceptance criteria and verification commands. | +| 합리화 | 현실 | +|--------|------| +| "태스크가 뻔하다" | 적어라. 명시된 태스크가 숨은 의존성과 빠뜨린 엣지를 드러낸다. | +| "머릿속에 다 있다" | 컨텍스트는 유한하다. 문서만이 세션과 컴팩션을 넘어 살아남는다. | +| "레이어별로 나누는 게 깔끔하다" | 수평 분해는 마지막 Task까지 아무것도 동작하지 않는 분해다. 수직으로 잘라라. | ## Red Flags -- Starting `/implement` without a written task list -- Tasks that say "implement the feature" without acceptance criteria -- No verification commands in the plan -- All tasks are L-sized (should be split) -- No checkpoints between tasks -- Dependency order not considered (building UI before underlying service) -- Task title contains "and" (probably two tasks) - -## Verification - -Before starting `/implement`, confirm: - -- [ ] Plan document has both Overview (from `/define`) and Tasks sections -- [ ] Every Task has acceptance criteria -- [ ] Every Task has a verification command -- [ ] No Task is L-sized (8+ files) -- [ ] Dependencies are ordered correctly (foundation first) -- [ ] Checkpoints exist between major groups of Tasks -- [ ] User has explicitly approved the task list - -## Runtime Boundary — HARD STOP - -This skill ENDS after the Verification checklist and final report are completed. - -For codex and any runtime without an enforced skill-return boundary: -- MUST stop the assistant turn here. -- MUST NOT invoke, load, or execute any next GSL skill in the same response turn. -- MUST NOT continue into `/next-flow`, `/define`, `/plan`, `/implement`, `/test`, `/verify`, `/debug`, or `/self-review`. -- MAY print exactly one suggested next command as plain text. -- MUST wait for the user's next message before running any next skill. - -If the user says only "continue", treat that as permission to report the next recommended command, not permission to execute it. - ---- - -## Lifecycle Integration +- Acceptance 없이 "기능 구현"이라고만 쓴 Task +- Verification 명령이 없는 Task +- 8개 이상 파일이 예상되는 Task를 재분해 없이 둠 (S/M 어느 라벨도 붙일 수 없는 상태) +- 파일 목록을 계약처럼 취급 (advisory임을 잊음) +- define에 없는 요구사항을 분해 중에 창조 -**Before this skill:** if `plan/conventions.md` does not exist, run `/scan-conventions` first — analysis relies on knowing the project's actual conventions (naming, layering, test patterns, build system). +## 종료 -**After this skill:** the next GSL skill is started by the user, not by this skill — see the Runtime Boundary section above. `/next-flow` may be suggested for lifecycle diagnosis but is not auto-invoked. Runtime note: some environments expose slash commands as UI commands; codex loads GSL skills from `.agents/skills/`. In both cases, the next GSL skill requires a new explicit user message. +지침의 **GSL Execution Mode** 규칙을 따른다. step-by-step이면 승인 후 `Next: /implement`를 제안하고 턴을 끝낸다. delegated면 보고를 남기고 계속한다. diff --git a/.claude/skills/plan/SKILL.md b/.claude/skills/plan/SKILL.md index 821f19007..cf079e2cc 100644 --- a/.claude/skills/plan/SKILL.md +++ b/.claude/skills/plan/SKILL.md @@ -1,202 +1,120 @@ --- name: plan description: | - Breaks work into ordered, verifiable tasks with acceptance criteria. - Trigger: "/plan", or when the user says "계획 세워줘", "태스크 분해", "plan this", "break it down". - Use after /define when requirements are clear and a plan document exists. - Adds the Tasks section to an existing plan document created by /define. + 승인된 define 문서를 받아 일을 자연 관절대로 분해하고 의존성 순서로 정렬해 Tasks를 만든다. + Trigger: "/plan", 또는 사용자가 "계획 세워줘", "태스크 분해", "plan this", "break it down"이라고 할 때. + 상태 기반: plan/{기능}.md에 Overview는 있으나 Tasks 섹션이 비어 있을 때. + Execution Mode가 delegated(scope에 plan 포함)면 승인 게이트 대신 체크포인트 보고로 진행한다. argument-hint: "[feature-name or plan file path]" --- -# Planning and Task Breakdown +# Plan — 분해와 정렬 -## Overview +## 철학: Task는 관절, Slice는 썰기 -Decompose work into small, verifiable tasks with explicit acceptance criteria. Good task breakdown is the difference between an agent that completes work reliably and one that produces a tangled mess. Every task should be small enough to implement, test, and commit in a single focused session. +정육 모델로 구분한다. 소를 등심·안심으로 나누는 부위 분해가 **Task**다 — 문제의 자연 관절(의존성, 모듈 경계, 리뷰 단위)을 따라 자르며, 올바른 답이 적고 틀리면 커밋 이력과 리뷰 단위가 오염되므로 **승인 대상**이다. 그 안심을 어떻게 썰어 요리할지가 **Slice**다 — 정답이 여럿이고 틀려도 다시 썰면 되므로 **에이전트 전권**이며 `/implement`에서 결정한다. -This skill adds the Tasks section to an existing plan document created by `/define`. +따라서 이 스킬의 순서는 **분해 → 정렬 → 크기 검증**이다. 크기는 절단 기준이 아니라 관절을 제대로 찾았는지의 검산이다. -**External workflow precedence.** When Superpowers' `writing-plans` skill is active in the same session, GSL `/plan` takes precedence on the same prompt. Invoke `writing-plans` as a sub-block to enrich Dependency Analysis (Step 2) and the Task list (Step 3) — but it does NOT replace GSL's vertical-slice rule, L-size ban (8+ files), Checkpoint cadence, or user-approval gate. If `writing-plans` produces a horizontal task list, **STOP and rewrite as vertical slices** before presenting to the user. +## 전제 -## When to Use +`plan/{feature-name}.md`에 승인된 Overview(Assumptions·Success Criteria·Impact Scope)와 Execution Mode 선언이 있어야 한다. 없으면 `/define`을 안내하고 종료한다. -- After `/define` has created a plan document with Overview -- When 3+ files need changes -- When work spans multiple modules or surfaces -- When the implementation order is not obvious +## 가정 붕괴 시 즉시 중단 (전역 규칙) + +어느 Step에서든 define의 Assumption을 깨는 발견이 나오면 **그 자리에서 중단한다**. Tasks를 아직 기록하지 않았으면 기록하지 않고, 이미 기록했다면 해당 기록을 되돌린다. 이것이 stop-condition 1번(가정 붕괴)이며 delegated 위임보다 우선한다. + +중단 후 절차(재개봉 프로토콜): ① 발견 내용과 깨진 가정을 보고한다 ② define 문서의 Assumptions 수정안을 제시한다 ③ WHAT(성공 기준·범위)이 바뀌었으면 재승인을 받고, 표현만 정밀해진 것이면 기록만 남기고 계속한다. ## When NOT to Use -- No plan document exists yet (use `/define` first) -- Single-file changes with obvious scope (just `/implement` directly) -- Bug fixes (use `/debug` directly) +- plan 문서가 없다 → `/define` 먼저 +- 범위가 자명한 단일 파일 수정 → 바로 `/implement` +- 버그 수정 → `/debug` ## Process -### Step 1: Read Plan Document +### Step 1: define 산출물 읽기 -Open `plan/{feature-name}.md`. If it does not exist, prompt the user to run `/define` first. +Overview·Assumptions·Success Criteria·Impact Scope·Execution Mode를 읽는다. 여기 없는 요구사항을 이 단계에서 새로 만들지 않는다 — 부족하면 위 전역 규칙의 재개봉 프로토콜로 돌아간다. -Read: -- Overview: what are we building and why -- Assumptions: confirmed constraints -- Success Criteria: what "done" looks like -- Impact Scope: which modules / surfaces / tests are affected +### Step 2: 분해 — 자연 관절 찾기 -### Step 2: Dependency Analysis +Impact Scope를 보고 일의 관절을 찾는다. 관절의 기준: -Map what depends on what. Implementation order follows the dependency graph bottom-up. +- **의존성 경계**: 먼저 존재해야 다음이 성립하는 지점. entity → repository → service → controller는 **순서**의 기준이지, 레이어마다 Task를 만들라는 뜻이 아니다 +- **모듈 경계**: product-api / admin-api / mono / batch — 한 Task는 한 모듈에 집중 +- **리뷰 단위**: 리뷰어가 한 번에 이해하고 승인할 수 있는 덩어리 = 커밋 하나 -For the actual dependency layers, consult `implement/references/types/{your-type}.md` — for example: -- **web-api**: entity / model → repository → service → controller / route; DTOs and exceptions in parallel -- **cli**: domain model → command handler → CLI binding -- **batch**: job step → job → scheduler binding -- **library**: internal helpers → public API surface +**수직 슬라이스 원칙**: 레이어별 수평 분해(모든 DTO → 모든 Repository → 모든 Service)는 금지다. 스택을 관통하는 완결된 경로 하나씩 자른다. -Identify: -- What must be built first (foundation) -- What can be built in parallel (independent pieces) -- What depends on cross-module boundaries (coordination needed) +``` +나쁨(수평): Task 1: DTO 전부 / Task 2: Repository 전부 / Task 3: Service 전부 +좋음(수직): Task 1: 통계 조회 경로 (DTO+Repository+Service) / Task 2: Controller+문서 / Task 3: 통합 테스트 +``` -### Step 3: Create Task List +수평 금지의 기준은 "한 레이어를 여러 경로에 걸쳐 모았는가"다. 한 경로의 표면(Controller)을 뒤따르는 Task로 두는 것은 수직 경로의 순차 절단이라 무방하다. -Break work into Tasks. Each Task is a commit unit. +**사용자가 수평 분해를 명시적으로 요청하면**: 근거(마지막 Task까지 아무것도 동작하지 않고 중간 커밋이 검증 불가능해짐)를 한 번 제시하고 수직 대안을 권한다. 사용자가 재차 수평을 지시하면 그것이 결정이다 — 따르되, plan 문서에 "수평 분해: 사용자 결정"을 기록한다. -**Sizing guidelines:** +### Step 3: 의존성 정렬 -| Size | Files | Scope | Action | -|------|-------|-------|--------| -| **S** | 1-3 | Single component | Good as-is | -| **M** | 4-7 | One feature slice | Good as-is | -| **L** | 8+ | Multi-component | **Must split further** | +기반이 되는 Task를 앞에, 독립적인 Task는 병렬 가능으로 표시한다. cross-domain 결합(Facade 신설 등)이 있으면 그 계약을 별도 Task로 앞세운다. -**Prefer vertical slices:** build one complete path through the stack rather than all layers at once. +### Step 4: 크기 검증 (검산) -Bad (horizontal): -``` -Task 1: All DTOs -Task 2: All Repositories -Task 3: All Services -``` +- 8개 이상 파일을 건드리는 Task → 관절을 잘못 찾은 신호. 재분해한다 +- 제목에 "and"/"및"이 들어가는 Task → 아마 두 개다 +- 수용 기준을 3불릿 이내로 못 쓰는 Task → 너무 넓다 -Good (vertical): -``` -Task 1: Statistics DTO + Repository + query -Task 2: Statistics Service + unit test -Task 3: Statistics Controller / handler / command -Task 4: Integration test -``` +크기 라벨: S(1-3파일) / M(4-7파일). L은 존재해선 안 된다. -### Step 4: Size Validation +### Step 5: Tasks 기록 -Check every Task: -- Is it L-sized (8+ files)? → Split it -- Does the title contain "and"? → Probably two Tasks -- Can acceptance criteria be described in 3 or fewer bullets? → If not, too broad -- Does it touch two independent subsystems? → Split by subsystem +plan 문서에 Tasks 섹션을 채운다. -### Step 5: Write Tasks to Plan Document - -Append the Tasks section and an empty Progress Log to the existing plan document. - -**Task entry format:** ```markdown -### Task N: [title] -- Acceptance: [specific, testable condition] -- Verification: [command or check] -- Files: [expected file changes] +### Task N: [제목] +- Acceptance: [관찰 가능하고 검증 가능한 조건 — 이것이 계약이다] +- Verification: [실행할 명령 또는 확인 방법] +- Files (advisory): [예상 파일 — 참고 추정일 뿐, 계약이 아니다] +- Depends: [선행 Task 번호 | 없음 — '없음'끼리는 병렬 가능] - Size: [S | M] - Status: [ ] not done - -## Progress Log -(empty — filled during /implement) ``` -Insert a checkpoint after every 2-3 Tasks: +2-3개 Task마다 Checkpoint를 삽입한다: ```markdown -### Checkpoint: after Tasks 1-3 -- [ ] Compiles / type-checks -- [ ] Unit tests pass -- [ ] Project architecture / lint rules pass +### Checkpoint: after Tasks N-M +- [ ] 컴파일 통과 / 단위 테스트 통과 / ArchUnit 룰 통과 ``` -### Step 6: User Approval Gate +`Files:`는 advisory다 — 계획 시점의 파일 예측은 낡는다. Task의 계약은 Acceptance뿐이다. -Present the task list with estimated order. Get explicit approval before proceeding to `/implement`. +마지막 Task 뒤에는 Checkpoint를 두지 않는다 — `/implement` Phase 4의 `/verify`가 그 역할을 한다. 총 Task가 2개 이하면 Checkpoint를 생략해도 된다. -``` -Tasks added to plan/{feature-name}.md - -Summary: -- [N] tasks defined (sizes: S × [n], M × [n]) -- Estimated commits: [N] -- Dependencies: [brief description] - -Approve to proceed to /implement? -``` +### Step 6: 게이트 (모드 분기) -## Plan Document Lifecycle - -``` -/define creates document → Status: IN PROGRESS -/plan adds Tasks → Tasks section populated -/implement checks off Tasks → Progress Log updated per Task commit -All Tasks done → Add completion stamp - → Move to plan/complete/ (or plan/stale/ if abandoned) -``` - -**One feature = one document.** Do not split a feature across multiple plan files. Do not create a new document if one already exists for this feature. +- **step-by-step**: 태스크 목록 요약(개수·크기 분포·의존 순서)을 제시하고 명시적 승인을 받은 뒤 턴을 끝낸다. 승인 전 `/implement` 진행 금지. **승인이 거부되면**: 거부 사유를 받아 Step 2부터 재분해한다. 사유가 분해 방식이 아니라 요구사항 자체라면 재개봉 프로토콜로 `/define`에 돌린다. +- **delegated (scope에 plan 포함)**: 승인 권한은 define 게이트에서 이미 위임받았다. 목록을 체크포인트 보고로 남기고 다음 단계로 계속한다. 단, 분해 중 가정을 깨는 발견이 있었다면 stop-condition 1번(재개봉)이 우선한다. ## Common Rationalizations -| Rationalization | Reality | -|-----------------|---------| -| "I'll figure it out as I go" | That is how rework happens. 10 minutes of planning saves hours. | -| "The tasks are obvious" | Write them down. Explicit tasks surface hidden dependencies and forgotten edge cases. | -| "Planning is overhead" | Planning IS the task. Implementation without a plan is just typing. | -| "I can hold it all in my head" | Context windows are finite. Written plans survive session boundaries and compaction. | -| "This is only 2 tasks, why bother" | Even 2 tasks benefit from acceptance criteria and verification commands. | +| 합리화 | 현실 | +|--------|------| +| "태스크가 뻔하다" | 적어라. 명시된 태스크가 숨은 의존성과 빠뜨린 엣지를 드러낸다. | +| "머릿속에 다 있다" | 컨텍스트는 유한하다. 문서만이 세션과 컴팩션을 넘어 살아남는다. | +| "레이어별로 나누는 게 깔끔하다" | 수평 분해는 마지막 Task까지 아무것도 동작하지 않는 분해다. 수직으로 잘라라. | ## Red Flags -- Starting `/implement` without a written task list -- Tasks that say "implement the feature" without acceptance criteria -- No verification commands in the plan -- All tasks are L-sized (should be split) -- No checkpoints between tasks -- Dependency order not considered (building UI before underlying service) -- Task title contains "and" (probably two tasks) - -## Verification - -Before starting `/implement`, confirm: - -- [ ] Plan document has both Overview (from `/define`) and Tasks sections -- [ ] Every Task has acceptance criteria -- [ ] Every Task has a verification command -- [ ] No Task is L-sized (8+ files) -- [ ] Dependencies are ordered correctly (foundation first) -- [ ] Checkpoints exist between major groups of Tasks -- [ ] User has explicitly approved the task list - -## Runtime Boundary — HARD STOP - -This skill ENDS after the Verification checklist and final report are completed. - -For codex and any runtime without an enforced skill-return boundary: -- MUST stop the assistant turn here. -- MUST NOT invoke, load, or execute any next GSL skill in the same response turn. -- MUST NOT continue into `/next-flow`, `/define`, `/plan`, `/implement`, `/test`, `/verify`, `/debug`, or `/self-review`. -- MAY print exactly one suggested next command as plain text. -- MUST wait for the user's next message before running any next skill. - -If the user says only "continue", treat that as permission to report the next recommended command, not permission to execute it. - ---- - -## Lifecycle Integration +- Acceptance 없이 "기능 구현"이라고만 쓴 Task +- Verification 명령이 없는 Task +- 8개 이상 파일이 예상되는 Task를 재분해 없이 둠 (S/M 어느 라벨도 붙일 수 없는 상태) +- 파일 목록을 계약처럼 취급 (advisory임을 잊음) +- define에 없는 요구사항을 분해 중에 창조 -**Before this skill:** if `plan/conventions.md` does not exist, run `/scan-conventions` first — analysis relies on knowing the project's actual conventions (naming, layering, test patterns, build system). +## 종료 -**After this skill:** the next GSL skill is started by the user, not by this skill — see the Runtime Boundary section above. `/next-flow` may be suggested for lifecycle diagnosis but is not auto-invoked. Runtime note: some environments expose slash commands as UI commands; codex loads GSL skills from `.agents/skills/`. In both cases, the next GSL skill requires a new explicit user message. +지침의 **GSL Execution Mode** 규칙을 따른다. step-by-step이면 승인 후 `Next: /implement`를 제안하고 턴을 끝낸다. delegated면 보고를 남기고 계속한다. diff --git a/plan/gsl-overhaul.md b/plan/gsl-overhaul.md index 6c935eb4b..0cc6756d5 100644 --- a/plan/gsl-overhaul.md +++ b/plan/gsl-overhaul.md @@ -92,7 +92,7 @@ GSL(Guided Software Lifecycle) 9개 스킬을 이 저장소 전용으로 대폭 - Verification: 서브에이전트 시나리오 2건 — (a) 정상: Task 3 시나리오의 define 산출물로 분해, (b) 함정: 수평 분해가 자연스러워 보이는 요청 → 수직 강제가 작동하는지 - Files (advisory): plan/SKILL.md × 미러 2 - Size: S -- Status: [ ] not done +- Status: [x] done ### Task 5: /implement 재작성 - Acceptance: Execution Mode 분기 구현 — step-by-step은 Task별 HARD STOP 유지, delegated는 체크포인트 보고(Progress Log 기록+진행 보고)로 대체하되 stop-conditions 준수. Polyglot Mode·언어 fallback 제거, java-spring 고정. PR·체인지로그 단계를 Phase 4 이후 공식 꼬리로 정의 @@ -124,3 +124,4 @@ GSL(Guided Software Lifecycle) 9개 스킬을 이 저장소 전용으로 대폭 - Task 1: `chore/gsl-overhaul` 브랜치 분리(sync-agent는 origin으로 원복). next-flow·scan-conventions 스킬과 python/go/cli/library/worker-daemon references를 미러 양쪽에서 삭제(18파일), conventions.md는 stale로 이동. 삭제 전 용량 276KB/미러. 잔존 참조 44건을 9개 SKILL.md + web-api.md·bottlenote-patterns.md에서 확인 — SKILL.md는 Tasks 3-6 재작성에서, references 2개는 Task 6에서 해소 예정. 미러 diff 일치. - Task 2: 지침 양쪽의 "GSL Runtime Boundary Rules"를 "GSL Execution Mode" 섹션으로 교체. step-by-step 기본·delegated 선언 형식·stop-conditions 3종(가정 붕괴→재개봉, verify 3회 실패, scope 밖 행동)·PR 본문=체인지로그 관례를 명문화. 지침 정규화 diff 일치, Skills 표 7행이 실제 스킬 7종과 정합. Checkpoint 1-2 통과. - Task 3: /define 재작성 — WHAT/HOW 분리 철학, 지침 기반 하드 게이트, Execution Mode 선언 게이트(계약 서명), 재개봉 프로토콜, 상태 기반 트리거. 시나리오 검증(scenario-define): 정상 경로 완주 확인, 함정("가정 확인 넘어가고") 게이트 우회 불가 판정, 치명 0건·경미 7건. 경미 중 5건 반영(합리화 표에 외부 압박 행 추가, mode 플레이스홀더, stop-conditions 3종 인라인, Step 3 차단 문구, delegated 종료 분기·push/pr 고지). 188행 → 140행. +- Task 4: /plan 재작성 — 정육 모델(Task=관절=승인 대상, Slice=썰기=에이전트 전권), 분해→정렬→크기검산 순서, Files advisory 강등, Depends 필드 신설. 시나리오 검증(scenario-plan): 정상 완주 확인, 치명 3건 검출(재개봉 프로토콜 미정의 / 사용자 수평 요청 충돌·승인 거부 분기 부재 / 중단 지점이 Step 6에만). "가정 붕괴 시 즉시 중단" 전역 규칙 신설 + 수평 요청 특칙(1회 권고→재지시 시 사용자 결정) + 승인 거부 분기로 수정, 재검증 결과 3건 전부 해소·신규 치명 0건. 경미 4·5·6·8도 반영. From 75eae46bf6ed72c4f8262d5bc3f75fa94a0d4c45 Mon Sep 17 00:00:00 2001 From: Whale0928 Date: Sat, 25 Jul 2026 18:48:57 +0900 Subject: [PATCH 7/9] =?UTF-8?q?feat:=20/implement=EC=97=90=20Execution=20M?= =?UTF-8?q?ode=20=EB=B6=84=EA=B8=B0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- .agents/skills/implement/SKILL.md | 271 +++++++----------------------- .claude/skills/implement/SKILL.md | 271 +++++++----------------------- plan/gsl-overhaul.md | 7 +- 3 files changed, 118 insertions(+), 431 deletions(-) diff --git a/.agents/skills/implement/SKILL.md b/.agents/skills/implement/SKILL.md index 8095a17ff..64063b29e 100644 --- a/.agents/skills/implement/SKILL.md +++ b/.agents/skills/implement/SKILL.md @@ -1,251 +1,94 @@ --- name: implement description: | - Incremental feature implementation across any language/stack. - Trigger: "/implement", or when the user says "API 추가", "기능 구현", "기능 개발", "feature implementation", "build this". - Branches on project type (web-api / cli / batch / library) and language (java-spring / python / go / ...) via arguments and references. - Enforces Task / Slice / Commit 3-level granularity. For test implementation, use /test after this skill completes. -argument-hint: "[type] [language] [work-type]" + 승인된 Tasks를 Slice 단위로 얇게 구현하고 Task 단위로 커밋한다. Execution Mode에 따라 Task별 정지 또는 자율 연속 진행한다. + Trigger: "/implement", 또는 사용자가 "API 추가", "기능 구현", "기능 개발", "build this"라고 할 때. + 상태 기반: plan/{기능}.md에 미완료 Task가 있을 때. Controller/Service/Repository/Facade 등 다중 파일 구현 작업일 때. + Java/Spring 패턴은 references/languages/java-spring.md, 프로젝트 특화 패턴은 bottlenote-patterns.md를 따른다. +argument-hint: "[feature-name or task number]" --- -# Incremental Implementation +# Implement — Slice 실행과 Task 커밋 -References (read the matching ones before coding): -- `references/types/{type}.md` — type-specific patterns (web-api / cli / batch / library) -- `references/languages/{language}.md` — language/framework-specific patterns +References (코딩 전에 해당 항목을 읽는다): +- `references/languages/java-spring.md` — Java/Spring 구현 패턴 (product-api·mono) +- `references/languages/bottlenote-patterns.md` — 프로젝트 특화 패턴 (InMemory 갱신 체크리스트 등) +- `references/types/web-api.md` — API 계층 패턴 / `references/types/batch.md` — batch 작업 시 -## Overview +## 철학: Slice는 에이전트 전권 -Build features in thin vertical slices — implement one piece, verify it compiles / type-checks, then expand. Each Task is a committable unit; each Slice within a Task is a compile-check unit. The workflow is the same across stacks; concrete patterns live in references. - -## When to Use - -- Implementing any new feature -- Adding operations to an existing domain / module -- Extending an existing component with new behavior -- Any multi-file implementation work - -## When NOT to Use - -- Bug fixing with clear reproduction (use `/debug`) -- Test-only work (use `/test`) -- Requirements unclear or ambiguous (use `/define` first) -- Single-file config changes or documentation updates - -## Argument Parsing - -Parse `$ARGUMENTS`: -- **type**: `web-api` | `cli` | `batch` | `library` — selects `references/types/{type}.md` -- **language**: `java-spring` | `python` | `go` | ... — selects `references/languages/{language}.md` -- **work-type**: `crud` | `search` | `action` | `read-only` (informational, guides exploration scope) - -If type / language is omitted, infer from project structure and confirm with the user before proceeding. - -**Missing language reference fallback.** If `references/languages/{language}.md` does not exist for the resolved language (e.g., `rust`, `kotlin-android`, `swift`, `zig`): - -1. **STOP** before Phase 0. -2. Present the `references/types/{type}.md` applicable scope and ask the user to choose: - - **(a)** Provide the language's idioms inline (1-screen summary covering: module layout, DI/error/test patterns, build tool). This skill treats it as a temporary reference for this feature only. - - **(b)** Approve fallback to the nearest-language reference: `go` for systems langs, `python` for dynamic langs, `java-spring` for static JVM-like langs. The user must acknowledge that idioms may not fit perfectly. -3. Never silently proceed with a missing language reference. If neither (a) nor (b) is approved, STOP. - -## Process - -### Phase 0: Explore - -**Hard gate — approved plan required.** BEFORE doing anything else, verify that an approved plan document with at least one Task exists at `plan/{feature-name}.md` (the Progress Log being empty or in-progress is fine; the existence of Tasks is what matters). If not → **STOP** and tell the user to run `/define` then `/plan` first. The only exception: a single-file obvious fix (typo, rename, one-line comment, formatting) — in that case skip the rest of Phase 0 and proceed directly to a minimal slice + `/self-review` + `/verify quick`. Multi-file work without an approved plan is never allowed. - -Before writing any code, understand what already exists and what will be affected. - -**Codebase scan:** -1. Locate the target module / package -2. Identify existing services / handlers / repositories that may be reused -3. Check what already exists vs. what needs to be created -4. Read the relevant references (`types/{type}.md`, `languages/{language}.md`) - -**Impact analysis (general — extend with type-specific items from references):** -5. **Cross-module coupling** — files affected if a shared interface changes (other modules, test doubles) -6. **Persistence** — schema migration needed? -7. **Async / events** — new events published or consumed? -8. **Caching** — invalidation strategy needed? -9. **Public API contract** — external consumers impacted? - -Report both findings and impact to the user before proceeding. - -### Phase 1: Core / Business Logic - -Build the foundation in the layer order documented in `references/types/{type}.md`. As a generic template: - -1. **Domain model / entity** (if new) -2. **Persistence interface + implementation** (repository, data access) -3. **DTO / model** (request / response / event shapes) -4. **Error / exception types** -5. **Service / use case / handler logic** -6. **Cross-module seam** (facade / port) when other modules need access - -Language-specific class/function naming, annotations, and idioms: see `references/languages/{language}.md`. - -### Phase 2: Surface / Entry Point - -Build the externally visible layer per `references/types/{type}.md`: -- **web-api**: HTTP controller / route handler — path, auth, request validation, response shape, pagination -- **cli**: command binding — flags, args, output format, exit codes -- **batch**: job step + scheduler binding -- **library**: public API surface + backwards-compat considerations - -### Phase 3: Task-Slice-Commit Cycle - -Each Task from the `/plan` is implemented through Slices: +Task는 `/plan`에서 승인된 계약(관절)이고, 그 안에서 어떻게 썰어 실행할지는 이 스킬의 재량이다. 단 하나의 규칙: **~100줄을 넘기기 전에 컴파일 체크한다.** Slice 1의 버그는 Slice 2~5를 전부 오염시키므로, 얇게 썰고 자주 확인한다. ``` -Task = Commit unit (logical goal agreed with user) -Slice = Execution unit (compile / type-check before exceeding ~100 lines) +Task = 커밋 단위 (승인된 계약 — 변경하려면 /plan 재개봉) +Slice = 컴파일 체크 단위 (에이전트 재량 — 어떻게 썰든 자유) ``` -**Verification levels:** +## 전제 (하드 게이트) -| Timing | Level | What to run | -|--------|-------|-------------| -| After each Slice | Compile / type-check only | `/verify quick` or equivalent | -| After Task completion | Self-review + unit tests | `/self-review` → `/verify standard` | -| After all Tasks complete | Integration tests | `/verify full` | +`plan/{feature-name}.md`에 승인된 Tasks가 있어야 한다. 없으면 **정지**하고 `/define` → `/plan`을 안내한다. 시작 시 같은 문서의 `## Execution Mode` 섹션에서 mode·scope·stop-conditions를 읽는다 — 선언이 없으면 step-by-step이다. -For exact commands per language, see `verify/references/verify/{your-language}.md`. +유일한 예외: 자명한 단일 파일 수정(오타, 리네임, 한 줄 수정) — 이 경우 최소 slice + 약식 리뷰 + 컴파일 체크로 바로 처리한다. **다중 파일 작업은 승인된 plan 없이 절대 진행하지 않는다.** -**Commit message format** (Task = title, Slices = bullets): -``` -feat: rating statistics service - -- RatingStatisticsResponse DTO -- repository query for statistics -- service.getStatistics() implementation -- facade wiring -``` - -**Cycle per Task:** -1. Implement Slice → compile / type-check → pass? continue : fix -2. Repeat until all Slices in the Task are done -3. Run a self-review pass on the Task's changes (the 5-axis check). For a full independent review, stop and recommend `/self-review` as a separate invocation — do not run its full skill body inside `/implement`. -4. Run the compile/type-check and unit-test commands needed for this Task. For full `/verify standard` or `/verify full`, stop and recommend `/verify` as a separate invocation. -5. Commit with descriptive message -6. Update plan document (check off Task, add to Progress Log) -7. **HARD STOP after this Task.** Do NOT start the next Task in the same response turn. Report: completed Task number/title, verification evidence, changed files, next recommended Task. Then wait for the user's next message. - - Exception: proceed to the next Task in the same turn ONLY IF the user explicitly named multiple Tasks for continuous execution in their request (e.g. "do Tasks 1 through 3"). An ambiguous "continue" / "진행하자" is NOT such permission. - -### Phase 4: Final Verification - -After all Tasks are committed: - -| Timing | Command | Purpose | -|--------|---------|---------| -| Implementation done | `/verify standard` | Compile + unit + build | -| Before push / PR | `/verify full` | Includes integration tests | - -Then use `/test` if integration tests need to be written. - -## Endpoint / Command Design - -URL / command shape, HTTP method mapping, flag conventions — all type-specific. See `references/types/{type}.md`. - -## Package / Module Structure - -Layer-to-folder mapping is type-specific (web-api differs from cli) and language-specific (Java package vs Python module vs Go internal/). See: -- `references/types/{type}.md` for layer breakdown -- `references/languages/{language}.md` for the language's idiomatic folder layout - -## Polyglot Mode (monorepo with multiple type/language modules) +## Process -When the project is a polyglot monorepo (e.g., Python API + Go CLI + Java batch coexisting in one repo), a single `[type] [language]` argument is insufficient. Switch to **Polyglot Mode** when `/scan-conventions` reports more than one detected type or language. +### Phase 0: 탐색 -### Step 1: Build the module matrix during Phase 0 +코드를 쓰기 전에 파악한다: 대상 모듈·패키지 위치, 재사용할 기존 Service/Repository/Facade, 이미 있는 것과 새로 만들 것의 구분. 그리고 영향을 보고한다 — cross-domain 결합(Facade 신설·수정 시 InMemory 테스트 더블 갱신 필요), 스키마 변경(엔티티를 바꾸면 Flyway 마이그레이션 필수 — `ddl-auto: validate`라 누락 시 기동 실패), 이벤트, 캐시. -| Path | Type | Language | Verify command | References to load | -|------|------|----------|----------------|---------------------| -| `services/api/` | web-api | python | `pytest -m unit` | types/web-api.md + languages/python.md + testing/python.md + verify/python.md | -| `tools/cli/` | cli | go | `go test ./...` | types/cli.md + languages/go.md + testing/go.md + verify/go.md | -| `batch/` | batch | java-spring | `./gradlew unit_test` | types/batch.md + languages/java-spring.md + testing/java.md + verify/java-gradle.md | +### Phase 1: 코어 -Record the matrix in the plan document's `Impact Scope` section. +레이어 순서: 엔티티(신규 시) → 도메인 레포지토리(포트) → JPA 구현 → DTO(record) → 예외 → Service → cross-domain 필요 시 Facade 인터페이스+구현. 네이밍·어노테이션은 지침과 references가 정의하며 ArchUnit이 강제한다. -### Step 2: Per-Task module declaration +### Phase 2: 표면 -For each Task in `/plan`, declare which module(s) it touches. **A Task must not cross module boundaries** — each module has different references and verify commands. If a feature requires changes in two modules: +Controller — 경로, 인증(`@SecurityPolicy`), 요청 검증(`@Valid`), 응답(`GlobalResponse`), 페이징(`PageResponse`/`CursorPageable`). Controller는 얇게 — 비즈니스 로직은 Service 소유다. -- Split into **two Tasks** (one per module), or -- Add a third Task for the cross-module contract (e.g., shared protobuf / OpenAPI / event schema) +### Phase 3: Task 사이클 -Cross-module Tasks are an L-size red flag and must be decomposed. +Task 하나마다: -### Step 3: Per-Slice reference loading +1. Slice 구현 → 컴파일 체크 (`./gradlew compileJava -q` 수준) → 통과할 때까지 수정 +2. Task의 모든 Slice 완료 → 5축 약식 리뷰(정확성·가독성·아키텍처·보안·성능 — 전면 리뷰가 필요하면 `/self-review`를 별도 호출로 권고) +3. 단위 테스트 실행 (Task 곁에 테스트 코드를 쓰는 것은 허용; `/test`의 풀 워크플로는 별도 경계) +4. 커밋 — 제목은 Task, 본문 불릿은 Slice. 커밋 메시지는 한국어(프로젝트 관례) +5. plan 문서 갱신: Status 체크, Progress Log에 한 단락 기록 +6. **모드 분기**: -During Slice execution, load ONLY the references for the current Slice's module. Do not mix idioms from two modules in one Slice (e.g., do not apply Python's `Depends()` idiom inside a Go handler Slice). +| Execution Mode | Task 완료 후 행동 | +|---|---| +| **step-by-step** | 정지. 완료 Task·검증 증거·변경 파일·다음 Task를 보고하고 턴을 끝낸다. 예외: 사용자가 연속 실행을 명시한 경우("Task 1~3", "결과까지")에만 계속. 범위 없는 재개 신호("계속", "고")는 **다음 Task 1개만** 허가로 간주한다 | +| **delegated** (scope에 implement 포함) | 체크포인트 보고를 남기고 다음 Task로 계속한다 — Progress Log는 plan 문서에, 진행 요약은 대화에 즉시 출력해 사용자가 실시간 확인할 수 있게 한다 | -### Step 4: Verification fan-out +**stop-conditions는 모드와 무관하게 우선한다** (지침의 GSL Execution Mode 참조): 가정을 깨는 발견 → 즉시 정지, 재개봉 프로토콜. verify 3회 실패 → `/debug` 보고 후 정지. scope 밖 행동 필요 → 직전 정지. 정지 시 보고 항목: 발동한 조건, 발견 내용, 깨진 가정(해당 시), 제안 조치. -`/verify` runs the **union** of all touched modules' verify commands. If only `services/api/` was modified in the last Task, only `pytest -m unit` runs; if both `services/api/` and `batch/` were modified, both `pytest` and `./gradlew unit_test` run. +### Phase 4: 마감 -### Red Flags specific to Polyglot Mode +모든 Task 완료 후: -- A single Task touches files in two different modules (split it) -- A Slice mixes idioms from two language references -- `/scan-conventions` reports polyglot but the matrix is missing from the plan document -- Verify fan-out skipped because "I only changed Python, the Go module is unchanged" — verify still must run on every Task-touched module +1. `/verify standard` (컴파일+단위+빌드) — 푸시·PR 전에는 `/verify full` (통합 포함) +2. plan 문서에 완료 스탬프, `plan/complete/`로 이동 +3. **scope에 따라**: `push` 선언 시 푸시. `pr` 선언 시 PR 오픈 — **PR 본문이 체인지로그를 겸한다** (변경 사항·배경·검증·제외 범위 구조, 기존 PR 관례를 따른다). 미선언이면 해당 직전에서 정지하고 확인받되, 정지 보고에 "푸시 전 `/verify full` 필요"를 명시한다 (scope가 commit에서 끝나면 full이 아직 안 돌았을 수 있다). ## Common Rationalizations -| Rationalization | Reality | -|-----------------|---------| -| "I'll test it all at the end" | Bugs compound. A bug in Slice 1 makes Slices 2-5 wrong. Compile-check each Slice. | -| "It's faster to do it all at once" | It feels faster until something breaks and you cannot find which of 500 changed lines caused it. | -| "I don't need a facade / seam for this" | If you are accessing another module's internals directly, you need a seam. Module boundaries exist for a reason. | -| "This refactor is small enough to include" | Refactors mixed with features make both harder to review and debug. Separate them. | -| "The controller / handler can hold this logic" | Surface layers are thin. Business logic belongs in the service / use case. Always. | -| "I'll skip self-review, the code is straightforward" | Straightforward code still needs architecture and security checks. Run `/self-review`. | +| 합리화 | 현실 | +|--------|------| +| "한 번에 다 만들고 마지막에 테스트" | 버그는 복리다. Slice마다 컴파일 체크해라. | +| "Controller에 로직 조금 넣어도" | 표면은 얇게. 로직은 Service 소유다. 항상. | +| "리팩토링도 겸사겸사" | 기능과 리팩토링을 섞으면 둘 다 리뷰·디버그가 어려워진다. 분리해라. | +| "인터페이스만 바꾸고 Fake는 나중에" | 포트를 바꾸면 InMemory 구현도 같은 Task에서 갱신한다 (bottlenote-patterns.md 체크리스트). | ## Red Flags -- More than 100 lines written without a compile / type-check -- Cross-module internals accessed directly (bypassing the documented seam) -- Surface layer (controller / handler / CLI) containing business logic instead of delegating -- Interface changed but test doubles (InMemory/Fake implementations) not updated -- No error handling for module-specific error types -- Skipping Phase 0 (Explore) and jumping straight to coding -- Multiple unrelated changes in a single Task -- Task completed without running `/self-review` -- Starting Task N+1 after completing Task N without explicit user permission for continuous execution -- Treating an ambiguous "continue" as permission to finish all remaining Tasks -- Running `/test`, `/verify`, or `/self-review` as full skill bodies inside `/implement` instead of stopping at that skill boundary - -## Verification - -After completing all Tasks for a feature: - -- [ ] Each Task was individually reviewed (`/self-review`) and committed -- [ ] Module boundaries respected (no cross-module direct access) -- [ ] Layer order from `references/types/{type}.md` followed -- [ ] Language idioms from `references/languages/{language}.md` respected -- [ ] Unit tests pass: `/verify standard` -- [ ] Architecture / lint rules pass: included in `/verify standard` -- [ ] Build / package succeeds -- [ ] Plan document updated (all Tasks checked, Progress Log filled) - -## Runtime Boundary — HARD STOP - -This skill ENDS after the Verification checklist and final report are completed. - -For codex and any runtime without an enforced skill-return boundary: -- MUST stop the assistant turn here. -- MUST NOT invoke, load, or execute any next GSL skill in the same response turn. -- MUST NOT continue into `/next-flow`, `/define`, `/plan`, `/implement`, `/test`, `/verify`, `/debug`, or `/self-review`. -- MAY print exactly one suggested next command as plain text. -- MUST wait for the user's next message before running any next skill. - -If the user says only "continue", treat that as permission to report the next recommended command, not permission to execute it. - ---- - -## Lifecycle Integration +- 100줄 넘게 쓰고 컴파일 체크 없음 +- 타 도메인 Service/Repository 직접 참조 (Facade 경유 위반 — ArchUnit이 잡지만 그 전에 스스로 잡아라) +- 승인된 plan 없이 다중 파일 작업 시작 +- 엔티티 변경에 Flyway 마이그레이션 누락 +- step-by-step인데 명시 허가 없이 다음 Task 진행 +- delegated인데 stop-condition을 무시하고 계속 진행 +- Task 커밋 없이 여러 Task를 한 커밋에 뭉침 -**Before this skill:** if `plan/conventions.md` does not exist, run `/scan-conventions` first — analysis relies on knowing the project's actual conventions (naming, layering, test patterns, build system). +## 종료 -**After this skill:** the next GSL skill is started by the user, not by this skill — see the Runtime Boundary section above. `/next-flow` may be suggested for lifecycle diagnosis but is not auto-invoked. Runtime note: some environments expose slash commands as UI commands; codex loads GSL skills from `.agents/skills/`. In both cases, the next GSL skill requires a new explicit user message. +지침의 **GSL Execution Mode** 규칙을 따른다. step-by-step이면 Task 보고 후 턴 종료, 다음은 `Next: /implement (Task N+1)` 또는 전 Task 완료 시 `Next: /test`. delegated면 Phase 4까지 계속한다. diff --git a/.claude/skills/implement/SKILL.md b/.claude/skills/implement/SKILL.md index 8095a17ff..64063b29e 100644 --- a/.claude/skills/implement/SKILL.md +++ b/.claude/skills/implement/SKILL.md @@ -1,251 +1,94 @@ --- name: implement description: | - Incremental feature implementation across any language/stack. - Trigger: "/implement", or when the user says "API 추가", "기능 구현", "기능 개발", "feature implementation", "build this". - Branches on project type (web-api / cli / batch / library) and language (java-spring / python / go / ...) via arguments and references. - Enforces Task / Slice / Commit 3-level granularity. For test implementation, use /test after this skill completes. -argument-hint: "[type] [language] [work-type]" + 승인된 Tasks를 Slice 단위로 얇게 구현하고 Task 단위로 커밋한다. Execution Mode에 따라 Task별 정지 또는 자율 연속 진행한다. + Trigger: "/implement", 또는 사용자가 "API 추가", "기능 구현", "기능 개발", "build this"라고 할 때. + 상태 기반: plan/{기능}.md에 미완료 Task가 있을 때. Controller/Service/Repository/Facade 등 다중 파일 구현 작업일 때. + Java/Spring 패턴은 references/languages/java-spring.md, 프로젝트 특화 패턴은 bottlenote-patterns.md를 따른다. +argument-hint: "[feature-name or task number]" --- -# Incremental Implementation +# Implement — Slice 실행과 Task 커밋 -References (read the matching ones before coding): -- `references/types/{type}.md` — type-specific patterns (web-api / cli / batch / library) -- `references/languages/{language}.md` — language/framework-specific patterns +References (코딩 전에 해당 항목을 읽는다): +- `references/languages/java-spring.md` — Java/Spring 구현 패턴 (product-api·mono) +- `references/languages/bottlenote-patterns.md` — 프로젝트 특화 패턴 (InMemory 갱신 체크리스트 등) +- `references/types/web-api.md` — API 계층 패턴 / `references/types/batch.md` — batch 작업 시 -## Overview +## 철학: Slice는 에이전트 전권 -Build features in thin vertical slices — implement one piece, verify it compiles / type-checks, then expand. Each Task is a committable unit; each Slice within a Task is a compile-check unit. The workflow is the same across stacks; concrete patterns live in references. - -## When to Use - -- Implementing any new feature -- Adding operations to an existing domain / module -- Extending an existing component with new behavior -- Any multi-file implementation work - -## When NOT to Use - -- Bug fixing with clear reproduction (use `/debug`) -- Test-only work (use `/test`) -- Requirements unclear or ambiguous (use `/define` first) -- Single-file config changes or documentation updates - -## Argument Parsing - -Parse `$ARGUMENTS`: -- **type**: `web-api` | `cli` | `batch` | `library` — selects `references/types/{type}.md` -- **language**: `java-spring` | `python` | `go` | ... — selects `references/languages/{language}.md` -- **work-type**: `crud` | `search` | `action` | `read-only` (informational, guides exploration scope) - -If type / language is omitted, infer from project structure and confirm with the user before proceeding. - -**Missing language reference fallback.** If `references/languages/{language}.md` does not exist for the resolved language (e.g., `rust`, `kotlin-android`, `swift`, `zig`): - -1. **STOP** before Phase 0. -2. Present the `references/types/{type}.md` applicable scope and ask the user to choose: - - **(a)** Provide the language's idioms inline (1-screen summary covering: module layout, DI/error/test patterns, build tool). This skill treats it as a temporary reference for this feature only. - - **(b)** Approve fallback to the nearest-language reference: `go` for systems langs, `python` for dynamic langs, `java-spring` for static JVM-like langs. The user must acknowledge that idioms may not fit perfectly. -3. Never silently proceed with a missing language reference. If neither (a) nor (b) is approved, STOP. - -## Process - -### Phase 0: Explore - -**Hard gate — approved plan required.** BEFORE doing anything else, verify that an approved plan document with at least one Task exists at `plan/{feature-name}.md` (the Progress Log being empty or in-progress is fine; the existence of Tasks is what matters). If not → **STOP** and tell the user to run `/define` then `/plan` first. The only exception: a single-file obvious fix (typo, rename, one-line comment, formatting) — in that case skip the rest of Phase 0 and proceed directly to a minimal slice + `/self-review` + `/verify quick`. Multi-file work without an approved plan is never allowed. - -Before writing any code, understand what already exists and what will be affected. - -**Codebase scan:** -1. Locate the target module / package -2. Identify existing services / handlers / repositories that may be reused -3. Check what already exists vs. what needs to be created -4. Read the relevant references (`types/{type}.md`, `languages/{language}.md`) - -**Impact analysis (general — extend with type-specific items from references):** -5. **Cross-module coupling** — files affected if a shared interface changes (other modules, test doubles) -6. **Persistence** — schema migration needed? -7. **Async / events** — new events published or consumed? -8. **Caching** — invalidation strategy needed? -9. **Public API contract** — external consumers impacted? - -Report both findings and impact to the user before proceeding. - -### Phase 1: Core / Business Logic - -Build the foundation in the layer order documented in `references/types/{type}.md`. As a generic template: - -1. **Domain model / entity** (if new) -2. **Persistence interface + implementation** (repository, data access) -3. **DTO / model** (request / response / event shapes) -4. **Error / exception types** -5. **Service / use case / handler logic** -6. **Cross-module seam** (facade / port) when other modules need access - -Language-specific class/function naming, annotations, and idioms: see `references/languages/{language}.md`. - -### Phase 2: Surface / Entry Point - -Build the externally visible layer per `references/types/{type}.md`: -- **web-api**: HTTP controller / route handler — path, auth, request validation, response shape, pagination -- **cli**: command binding — flags, args, output format, exit codes -- **batch**: job step + scheduler binding -- **library**: public API surface + backwards-compat considerations - -### Phase 3: Task-Slice-Commit Cycle - -Each Task from the `/plan` is implemented through Slices: +Task는 `/plan`에서 승인된 계약(관절)이고, 그 안에서 어떻게 썰어 실행할지는 이 스킬의 재량이다. 단 하나의 규칙: **~100줄을 넘기기 전에 컴파일 체크한다.** Slice 1의 버그는 Slice 2~5를 전부 오염시키므로, 얇게 썰고 자주 확인한다. ``` -Task = Commit unit (logical goal agreed with user) -Slice = Execution unit (compile / type-check before exceeding ~100 lines) +Task = 커밋 단위 (승인된 계약 — 변경하려면 /plan 재개봉) +Slice = 컴파일 체크 단위 (에이전트 재량 — 어떻게 썰든 자유) ``` -**Verification levels:** +## 전제 (하드 게이트) -| Timing | Level | What to run | -|--------|-------|-------------| -| After each Slice | Compile / type-check only | `/verify quick` or equivalent | -| After Task completion | Self-review + unit tests | `/self-review` → `/verify standard` | -| After all Tasks complete | Integration tests | `/verify full` | +`plan/{feature-name}.md`에 승인된 Tasks가 있어야 한다. 없으면 **정지**하고 `/define` → `/plan`을 안내한다. 시작 시 같은 문서의 `## Execution Mode` 섹션에서 mode·scope·stop-conditions를 읽는다 — 선언이 없으면 step-by-step이다. -For exact commands per language, see `verify/references/verify/{your-language}.md`. +유일한 예외: 자명한 단일 파일 수정(오타, 리네임, 한 줄 수정) — 이 경우 최소 slice + 약식 리뷰 + 컴파일 체크로 바로 처리한다. **다중 파일 작업은 승인된 plan 없이 절대 진행하지 않는다.** -**Commit message format** (Task = title, Slices = bullets): -``` -feat: rating statistics service - -- RatingStatisticsResponse DTO -- repository query for statistics -- service.getStatistics() implementation -- facade wiring -``` - -**Cycle per Task:** -1. Implement Slice → compile / type-check → pass? continue : fix -2. Repeat until all Slices in the Task are done -3. Run a self-review pass on the Task's changes (the 5-axis check). For a full independent review, stop and recommend `/self-review` as a separate invocation — do not run its full skill body inside `/implement`. -4. Run the compile/type-check and unit-test commands needed for this Task. For full `/verify standard` or `/verify full`, stop and recommend `/verify` as a separate invocation. -5. Commit with descriptive message -6. Update plan document (check off Task, add to Progress Log) -7. **HARD STOP after this Task.** Do NOT start the next Task in the same response turn. Report: completed Task number/title, verification evidence, changed files, next recommended Task. Then wait for the user's next message. - - Exception: proceed to the next Task in the same turn ONLY IF the user explicitly named multiple Tasks for continuous execution in their request (e.g. "do Tasks 1 through 3"). An ambiguous "continue" / "진행하자" is NOT such permission. - -### Phase 4: Final Verification - -After all Tasks are committed: - -| Timing | Command | Purpose | -|--------|---------|---------| -| Implementation done | `/verify standard` | Compile + unit + build | -| Before push / PR | `/verify full` | Includes integration tests | - -Then use `/test` if integration tests need to be written. - -## Endpoint / Command Design - -URL / command shape, HTTP method mapping, flag conventions — all type-specific. See `references/types/{type}.md`. - -## Package / Module Structure - -Layer-to-folder mapping is type-specific (web-api differs from cli) and language-specific (Java package vs Python module vs Go internal/). See: -- `references/types/{type}.md` for layer breakdown -- `references/languages/{language}.md` for the language's idiomatic folder layout - -## Polyglot Mode (monorepo with multiple type/language modules) +## Process -When the project is a polyglot monorepo (e.g., Python API + Go CLI + Java batch coexisting in one repo), a single `[type] [language]` argument is insufficient. Switch to **Polyglot Mode** when `/scan-conventions` reports more than one detected type or language. +### Phase 0: 탐색 -### Step 1: Build the module matrix during Phase 0 +코드를 쓰기 전에 파악한다: 대상 모듈·패키지 위치, 재사용할 기존 Service/Repository/Facade, 이미 있는 것과 새로 만들 것의 구분. 그리고 영향을 보고한다 — cross-domain 결합(Facade 신설·수정 시 InMemory 테스트 더블 갱신 필요), 스키마 변경(엔티티를 바꾸면 Flyway 마이그레이션 필수 — `ddl-auto: validate`라 누락 시 기동 실패), 이벤트, 캐시. -| Path | Type | Language | Verify command | References to load | -|------|------|----------|----------------|---------------------| -| `services/api/` | web-api | python | `pytest -m unit` | types/web-api.md + languages/python.md + testing/python.md + verify/python.md | -| `tools/cli/` | cli | go | `go test ./...` | types/cli.md + languages/go.md + testing/go.md + verify/go.md | -| `batch/` | batch | java-spring | `./gradlew unit_test` | types/batch.md + languages/java-spring.md + testing/java.md + verify/java-gradle.md | +### Phase 1: 코어 -Record the matrix in the plan document's `Impact Scope` section. +레이어 순서: 엔티티(신규 시) → 도메인 레포지토리(포트) → JPA 구현 → DTO(record) → 예외 → Service → cross-domain 필요 시 Facade 인터페이스+구현. 네이밍·어노테이션은 지침과 references가 정의하며 ArchUnit이 강제한다. -### Step 2: Per-Task module declaration +### Phase 2: 표면 -For each Task in `/plan`, declare which module(s) it touches. **A Task must not cross module boundaries** — each module has different references and verify commands. If a feature requires changes in two modules: +Controller — 경로, 인증(`@SecurityPolicy`), 요청 검증(`@Valid`), 응답(`GlobalResponse`), 페이징(`PageResponse`/`CursorPageable`). Controller는 얇게 — 비즈니스 로직은 Service 소유다. -- Split into **two Tasks** (one per module), or -- Add a third Task for the cross-module contract (e.g., shared protobuf / OpenAPI / event schema) +### Phase 3: Task 사이클 -Cross-module Tasks are an L-size red flag and must be decomposed. +Task 하나마다: -### Step 3: Per-Slice reference loading +1. Slice 구현 → 컴파일 체크 (`./gradlew compileJava -q` 수준) → 통과할 때까지 수정 +2. Task의 모든 Slice 완료 → 5축 약식 리뷰(정확성·가독성·아키텍처·보안·성능 — 전면 리뷰가 필요하면 `/self-review`를 별도 호출로 권고) +3. 단위 테스트 실행 (Task 곁에 테스트 코드를 쓰는 것은 허용; `/test`의 풀 워크플로는 별도 경계) +4. 커밋 — 제목은 Task, 본문 불릿은 Slice. 커밋 메시지는 한국어(프로젝트 관례) +5. plan 문서 갱신: Status 체크, Progress Log에 한 단락 기록 +6. **모드 분기**: -During Slice execution, load ONLY the references for the current Slice's module. Do not mix idioms from two modules in one Slice (e.g., do not apply Python's `Depends()` idiom inside a Go handler Slice). +| Execution Mode | Task 완료 후 행동 | +|---|---| +| **step-by-step** | 정지. 완료 Task·검증 증거·변경 파일·다음 Task를 보고하고 턴을 끝낸다. 예외: 사용자가 연속 실행을 명시한 경우("Task 1~3", "결과까지")에만 계속. 범위 없는 재개 신호("계속", "고")는 **다음 Task 1개만** 허가로 간주한다 | +| **delegated** (scope에 implement 포함) | 체크포인트 보고를 남기고 다음 Task로 계속한다 — Progress Log는 plan 문서에, 진행 요약은 대화에 즉시 출력해 사용자가 실시간 확인할 수 있게 한다 | -### Step 4: Verification fan-out +**stop-conditions는 모드와 무관하게 우선한다** (지침의 GSL Execution Mode 참조): 가정을 깨는 발견 → 즉시 정지, 재개봉 프로토콜. verify 3회 실패 → `/debug` 보고 후 정지. scope 밖 행동 필요 → 직전 정지. 정지 시 보고 항목: 발동한 조건, 발견 내용, 깨진 가정(해당 시), 제안 조치. -`/verify` runs the **union** of all touched modules' verify commands. If only `services/api/` was modified in the last Task, only `pytest -m unit` runs; if both `services/api/` and `batch/` were modified, both `pytest` and `./gradlew unit_test` run. +### Phase 4: 마감 -### Red Flags specific to Polyglot Mode +모든 Task 완료 후: -- A single Task touches files in two different modules (split it) -- A Slice mixes idioms from two language references -- `/scan-conventions` reports polyglot but the matrix is missing from the plan document -- Verify fan-out skipped because "I only changed Python, the Go module is unchanged" — verify still must run on every Task-touched module +1. `/verify standard` (컴파일+단위+빌드) — 푸시·PR 전에는 `/verify full` (통합 포함) +2. plan 문서에 완료 스탬프, `plan/complete/`로 이동 +3. **scope에 따라**: `push` 선언 시 푸시. `pr` 선언 시 PR 오픈 — **PR 본문이 체인지로그를 겸한다** (변경 사항·배경·검증·제외 범위 구조, 기존 PR 관례를 따른다). 미선언이면 해당 직전에서 정지하고 확인받되, 정지 보고에 "푸시 전 `/verify full` 필요"를 명시한다 (scope가 commit에서 끝나면 full이 아직 안 돌았을 수 있다). ## Common Rationalizations -| Rationalization | Reality | -|-----------------|---------| -| "I'll test it all at the end" | Bugs compound. A bug in Slice 1 makes Slices 2-5 wrong. Compile-check each Slice. | -| "It's faster to do it all at once" | It feels faster until something breaks and you cannot find which of 500 changed lines caused it. | -| "I don't need a facade / seam for this" | If you are accessing another module's internals directly, you need a seam. Module boundaries exist for a reason. | -| "This refactor is small enough to include" | Refactors mixed with features make both harder to review and debug. Separate them. | -| "The controller / handler can hold this logic" | Surface layers are thin. Business logic belongs in the service / use case. Always. | -| "I'll skip self-review, the code is straightforward" | Straightforward code still needs architecture and security checks. Run `/self-review`. | +| 합리화 | 현실 | +|--------|------| +| "한 번에 다 만들고 마지막에 테스트" | 버그는 복리다. Slice마다 컴파일 체크해라. | +| "Controller에 로직 조금 넣어도" | 표면은 얇게. 로직은 Service 소유다. 항상. | +| "리팩토링도 겸사겸사" | 기능과 리팩토링을 섞으면 둘 다 리뷰·디버그가 어려워진다. 분리해라. | +| "인터페이스만 바꾸고 Fake는 나중에" | 포트를 바꾸면 InMemory 구현도 같은 Task에서 갱신한다 (bottlenote-patterns.md 체크리스트). | ## Red Flags -- More than 100 lines written without a compile / type-check -- Cross-module internals accessed directly (bypassing the documented seam) -- Surface layer (controller / handler / CLI) containing business logic instead of delegating -- Interface changed but test doubles (InMemory/Fake implementations) not updated -- No error handling for module-specific error types -- Skipping Phase 0 (Explore) and jumping straight to coding -- Multiple unrelated changes in a single Task -- Task completed without running `/self-review` -- Starting Task N+1 after completing Task N without explicit user permission for continuous execution -- Treating an ambiguous "continue" as permission to finish all remaining Tasks -- Running `/test`, `/verify`, or `/self-review` as full skill bodies inside `/implement` instead of stopping at that skill boundary - -## Verification - -After completing all Tasks for a feature: - -- [ ] Each Task was individually reviewed (`/self-review`) and committed -- [ ] Module boundaries respected (no cross-module direct access) -- [ ] Layer order from `references/types/{type}.md` followed -- [ ] Language idioms from `references/languages/{language}.md` respected -- [ ] Unit tests pass: `/verify standard` -- [ ] Architecture / lint rules pass: included in `/verify standard` -- [ ] Build / package succeeds -- [ ] Plan document updated (all Tasks checked, Progress Log filled) - -## Runtime Boundary — HARD STOP - -This skill ENDS after the Verification checklist and final report are completed. - -For codex and any runtime without an enforced skill-return boundary: -- MUST stop the assistant turn here. -- MUST NOT invoke, load, or execute any next GSL skill in the same response turn. -- MUST NOT continue into `/next-flow`, `/define`, `/plan`, `/implement`, `/test`, `/verify`, `/debug`, or `/self-review`. -- MAY print exactly one suggested next command as plain text. -- MUST wait for the user's next message before running any next skill. - -If the user says only "continue", treat that as permission to report the next recommended command, not permission to execute it. - ---- - -## Lifecycle Integration +- 100줄 넘게 쓰고 컴파일 체크 없음 +- 타 도메인 Service/Repository 직접 참조 (Facade 경유 위반 — ArchUnit이 잡지만 그 전에 스스로 잡아라) +- 승인된 plan 없이 다중 파일 작업 시작 +- 엔티티 변경에 Flyway 마이그레이션 누락 +- step-by-step인데 명시 허가 없이 다음 Task 진행 +- delegated인데 stop-condition을 무시하고 계속 진행 +- Task 커밋 없이 여러 Task를 한 커밋에 뭉침 -**Before this skill:** if `plan/conventions.md` does not exist, run `/scan-conventions` first — analysis relies on knowing the project's actual conventions (naming, layering, test patterns, build system). +## 종료 -**After this skill:** the next GSL skill is started by the user, not by this skill — see the Runtime Boundary section above. `/next-flow` may be suggested for lifecycle diagnosis but is not auto-invoked. Runtime note: some environments expose slash commands as UI commands; codex loads GSL skills from `.agents/skills/`. In both cases, the next GSL skill requires a new explicit user message. +지침의 **GSL Execution Mode** 규칙을 따른다. step-by-step이면 Task 보고 후 턴 종료, 다음은 `Next: /implement (Task N+1)` 또는 전 Task 완료 시 `Next: /test`. delegated면 Phase 4까지 계속한다. diff --git a/plan/gsl-overhaul.md b/plan/gsl-overhaul.md index 0cc6756d5..48f7e937e 100644 --- a/plan/gsl-overhaul.md +++ b/plan/gsl-overhaul.md @@ -99,11 +99,11 @@ GSL(Guided Software Lifecycle) 9개 스킬을 이 저장소 전용으로 대폭 - Verification: 서브에이전트 시나리오 3건 — (a) step 모드 Task 완료 후 정지 확인, (b) delegated 모드 연속 진행+보고 확인, (c) 함정: delegated 중 define 가정이 깨지는 발견 → stop-condition 발동 확인 - Files (advisory): implement/SKILL.md × 미러 2 - Size: M -- Status: [ ] not done +- Status: [x] done ### Checkpoint: after Tasks 3-5 -- [ ] 미러 diff 일치 -- [ ] 시나리오 치명 결함 0건 (발견 시 해당 Task로 되돌아가 수정) +- [x] 미러 diff 일치 +- [x] 시나리오 치명 결함 0건 (발견 시 해당 Task로 되돌아가 수정) ### Task 6: test/verify/debug/self-review 정리 - Acceptance: 4개 스킬에서 HARD STOP 보일러플레이트 제거→지침 참조, Superpowers·타 언어 분기 제거, java 고정, 트리거에 상태 기반 조건 추가. 워크플로 본문은 유지(이미 유효) @@ -125,3 +125,4 @@ GSL(Guided Software Lifecycle) 9개 스킬을 이 저장소 전용으로 대폭 - Task 2: 지침 양쪽의 "GSL Runtime Boundary Rules"를 "GSL Execution Mode" 섹션으로 교체. step-by-step 기본·delegated 선언 형식·stop-conditions 3종(가정 붕괴→재개봉, verify 3회 실패, scope 밖 행동)·PR 본문=체인지로그 관례를 명문화. 지침 정규화 diff 일치, Skills 표 7행이 실제 스킬 7종과 정합. Checkpoint 1-2 통과. - Task 3: /define 재작성 — WHAT/HOW 분리 철학, 지침 기반 하드 게이트, Execution Mode 선언 게이트(계약 서명), 재개봉 프로토콜, 상태 기반 트리거. 시나리오 검증(scenario-define): 정상 경로 완주 확인, 함정("가정 확인 넘어가고") 게이트 우회 불가 판정, 치명 0건·경미 7건. 경미 중 5건 반영(합리화 표에 외부 압박 행 추가, mode 플레이스홀더, stop-conditions 3종 인라인, Step 3 차단 문구, delegated 종료 분기·push/pr 고지). 188행 → 140행. - Task 4: /plan 재작성 — 정육 모델(Task=관절=승인 대상, Slice=썰기=에이전트 전권), 분해→정렬→크기검산 순서, Files advisory 강등, Depends 필드 신설. 시나리오 검증(scenario-plan): 정상 완주 확인, 치명 3건 검출(재개봉 프로토콜 미정의 / 사용자 수평 요청 충돌·승인 거부 분기 부재 / 중단 지점이 Step 6에만). "가정 붕괴 시 즉시 중단" 전역 규칙 신설 + 수평 요청 특칙(1회 권고→재지시 시 사용자 결정) + 승인 거부 분기로 수정, 재검증 결과 3건 전부 해소·신규 치명 0건. 경미 4·5·6·8도 반영. +- Task 5: /implement 재작성 — Execution Mode 분기(step은 Task별 정지+연속 실행 예외, delegated는 체크포인트 보고로 계속), stop-conditions 우선 규칙, Phase 4에 push/pr·PR 본문=체인지로그 공식화, Polyglot·언어 fallback 제거. 시나리오 검증(scenario-implement) 4건: step 정지·delegated 연속+푸시 직전 정지·가정 붕괴 정지·plan 부재 거부 모두 텍스트만으로 도출, 치명 0건. 경미 5건(재개 신호 해석, 모드 읽기 위치, 정지 보고 명세, 체크포인트 도달 지점, verify 레벨) 반영 후 검증자 재확인 완료. Checkpoint 3-5 통과. From caa4d997aef38d4292790aa04e129786fa57e8b1 Mon Sep 17 00:00:00 2001 From: Whale0928 Date: Sat, 25 Jul 2026 18:49:08 +0900 Subject: [PATCH 8/9] =?UTF-8?q?refactor:=20=EC=9E=94=EC=97=AC=204=EC=8A=A4?= =?UTF-8?q?=ED=82=AC=20=EC=A0=95=EB=A6=AC=20=EB=B0=8F=20references=20?= =?UTF-8?q?=EC=9E=90=EB=B0=94=20=EA=B3=A0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- .agents/skills/debug/SKILL.md | 28 +++-------- .../languages/bottlenote-patterns.md | 10 ++-- .../implement/references/types/batch.md | 2 +- .../implement/references/types/web-api.md | 4 +- .agents/skills/self-review/SKILL.md | 29 +++-------- .agents/skills/test/SKILL.md | 49 ++++++------------- .agents/skills/verify/SKILL.md | 40 +++++---------- .claude/skills/debug/SKILL.md | 28 +++-------- .../languages/bottlenote-patterns.md | 10 ++-- .../implement/references/types/batch.md | 2 +- .../implement/references/types/web-api.md | 4 +- .claude/skills/self-review/SKILL.md | 29 +++-------- .claude/skills/test/SKILL.md | 49 ++++++------------- .claude/skills/verify/SKILL.md | 40 +++++---------- plan/gsl-overhaul.md | 3 +- 15 files changed, 96 insertions(+), 231 deletions(-) diff --git a/.agents/skills/debug/SKILL.md b/.agents/skills/debug/SKILL.md index 01782267f..639fe1850 100644 --- a/.agents/skills/debug/SKILL.md +++ b/.agents/skills/debug/SKILL.md @@ -4,6 +4,7 @@ description: | Systematic root-cause debugging for build failures, test failures, and runtime errors. Trigger: "/debug", or when the user says "에러 났어", "테스트 실패", "빌드 안 돼", "왜 안 되지", "debug this", "this is broken". Follows a structured 6-step process: STOP, REPRODUCE, LOCALIZE, FIX, GUARD, VERIFY. + 상태 기반: 컴파일·테스트·ArchUnit 룰 실패 출력이 있을 때, /verify가 FAIL을 보고했을 때, 이전에 되던 것이 안 될 때. Use when anything unexpected happens — do not guess at fixes. argument-hint: "[error description or test name]" --- @@ -52,7 +53,7 @@ Can you reproduce the failure? └── If truly non-reproducible, document conditions and monitor ``` -For exact reproduction commands per language/stack, see `verify/references/verify/{your-language}.md`. +For exact reproduction commands, see `verify/references/verify/java-gradle.md`. ### Step 3: LOCALIZE @@ -82,7 +83,7 @@ Test failure: **For stack traces:** read bottom-up, find the first line in your own code (not framework / vendor). -For project-specific triage trees (e.g., framework-specific failure modes), consult `implement/references/languages/{your-language}.md`. +For project-specific triage trees, consult `implement/references/languages/java-spring.md` and `bottlenote-patterns.md`. ### Step 4: FIX @@ -115,7 +116,7 @@ Write a regression test that would have caught this bug. ### Step 6: VERIFY -Run verification to confirm the fix and check for regressions. See `/verify` and `verify/references/verify/{your-language}.md` for level selection: +Run verification to confirm the fix and check for regressions. See `/verify` for level selection: | Original failure | Minimum verification | |------------------|----------------------| @@ -157,23 +158,6 @@ After fixing a bug: - [ ] Test doubles updated if interfaces changed - [ ] Original failure scenario verified end-to-end -## Runtime Boundary — HARD STOP +## 종료 -This skill ENDS after the Verification checklist and final report are completed. - -For codex and any runtime without an enforced skill-return boundary: -- MUST stop the assistant turn here. -- MUST NOT invoke, load, or execute any next GSL skill in the same response turn. -- MUST NOT continue into `/next-flow`, `/define`, `/plan`, `/implement`, `/test`, `/verify`, `/debug`, or `/self-review`. -- MAY print exactly one suggested next command as plain text. -- MUST wait for the user's next message before running any next skill. - -If the user says only "continue", treat that as permission to report the next recommended command, not permission to execute it. - ---- - -## Lifecycle Integration - -**Before this skill:** if `plan/conventions.md` does not exist, run `/scan-conventions` first — analysis relies on knowing the project's actual conventions (naming, layering, test patterns, build system). - -**After this skill:** the next GSL skill is started by the user, not by this skill — see the Runtime Boundary section above. `/next-flow` may be suggested for lifecycle diagnosis but is not auto-invoked. Runtime note: some environments expose slash commands as UI commands; codex loads GSL skills from `.agents/skills/`. In both cases, the next GSL skill requires a new explicit user message. +지침의 **GSL Execution Mode** 규칙을 따른다. 근본 원인·수정 내용·회귀 테스트를 보고한다. step-by-step이면 턴을 끝내고, delegated면 중단됐던 단계로 복귀한다 (단, 가정 붕괴가 원인이었다면 재개봉 프로토콜이 우선한다). diff --git a/.agents/skills/implement/references/languages/bottlenote-patterns.md b/.agents/skills/implement/references/languages/bottlenote-patterns.md index a7fd73c87..12e9890ca 100644 --- a/.agents/skills/implement/references/languages/bottlenote-patterns.md +++ b/.agents/skills/implement/references/languages/bottlenote-patterns.md @@ -1,12 +1,12 @@ # bottlenote-patterns > bottle-note 한정 implement 함정 / 실수 방지 부록. -> 일반 컨벤션은 `plan/conventions.md`, 일반 Java/Spring 패턴은 `java-spring.md` 참조. +> 일반 컨벤션은 프로젝트 지침(CLAUDE.md/AGENTS.md), 일반 Java/Spring 패턴은 `java-spring.md` 참조. > 본 파일은 GSL sync 와 무관한 프로젝트 특화 reference. ## When to load -`/implement` Phase 0 에서 `plan/conventions.md` 와 함께 자동 참조. Repository / RestDocs / 이벤트 발행 관련 작업 시 우선 확인. +`/implement` Phase 0 에서 자동 참조. Repository / RestDocs / 이벤트 발행 관련 작업 시 우선 확인. --- @@ -86,7 +86,7 @@ bottlenote-admin-api/src/test/kotlin/.../fixture/InMemory{Domain}Repository.kt ### Facade ↔ Service 경계 drift - Cross-domain 접근은 `{Domain}Facade` 경유 원칙 -- 일부 service 가 facade 도 구현하는 기존 drift 존재 (`conventions.md` Comparison 표 참조) +- Facade 구현체는 `service` 패키지에 둔다 (인터페이스는 `facade` — 지침의 @FacadeService 항목 참조) - 새 작업: facade 분리 권장. 기존 drift 는 plan 에 명시되지 않은 한 정리 대상 아님 ### `@ThirdPartyService` 사용 시점 @@ -97,7 +97,7 @@ bottlenote-admin-api/src/test/kotlin/.../fixture/InMemory{Domain}Repository.kt ### Batch 모듈 특이사항 (`/define` / `/plan` 시 주의) -`conventions.md` 의 "Batch-Specific Current Conventions" 참조. 핵심만: +핵심만: - `bottlenote-batch` 는 `testFixtures` 대신 `mono` test output 직접 사용 (drift) - `git.environment-variables` 가 main+test 양쪽 resources 에 포함됨 - main resource 에 하드코딩된 JWT secret / nonce salt default 존재 @@ -111,4 +111,4 @@ bottlenote-admin-api/src/test/kotlin/.../fixture/InMemory{Domain}Repository.kt - 항목이 GSL 표준 `java-spring.md` 에 흡수되면 본 파일에서 제거 → 중복 방지 - 새 함정 발견 시 추가 (PR 사례 인용 권장) -- conventions.md 가 자체 갱신될 때 본 파일과 충돌하는 부분은 conventions.md 우선 +- 프로젝트 지침(CLAUDE.md/AGENTS.md)과 충돌하는 부분은 지침 우선 diff --git a/.agents/skills/implement/references/types/batch.md b/.agents/skills/implement/references/types/batch.md index 5bc9c02f7..ee7545b0a 100644 --- a/.agents/skills/implement/references/types/batch.md +++ b/.agents/skills/implement/references/types/batch.md @@ -1,5 +1,5 @@ # Type: batch -Language-independent patterns for batch jobs, scheduled jobs, backfills, and offline workers. Pair with `languages/{language}.md` for concrete code. +Language-independent patterns for batch jobs, scheduled jobs, backfills, and offline workers. Pair with `languages/java-spring.md` for concrete code. ## Layer Breakdown (universal) ``` time / event / manual trigger diff --git a/.agents/skills/implement/references/types/web-api.md b/.agents/skills/implement/references/types/web-api.md index ffb36690c..c4134caf5 100644 --- a/.agents/skills/implement/references/types/web-api.md +++ b/.agents/skills/implement/references/types/web-api.md @@ -1,6 +1,6 @@ # Type: web-api -Language-independent patterns for HTTP-based API servers (REST primarily; GraphQL notes at the end). Pair with the matching `languages/{language}.md` for concrete code. +Language-independent patterns for HTTP-based API servers (REST primarily; GraphQL notes at the end). Pair with `languages/java-spring.md` for concrete code. ## Layer Breakdown (universal) @@ -73,7 +73,7 @@ Sub-resources: | 429 | Rate limit | | 5xx | Server-side failure (never leak internals in body) | -Pick a project convention for 400 vs 422 and stick to it; document in `conventions.md`. +Pick a project convention for 400 vs 422 and stick to it (이 저장소: 지침의 예외 처리 규칙 참조). ## Error Model diff --git a/.agents/skills/self-review/SKILL.md b/.agents/skills/self-review/SKILL.md index b2e0730e2..f9c6587f5 100644 --- a/.agents/skills/self-review/SKILL.md +++ b/.agents/skills/self-review/SKILL.md @@ -3,7 +3,7 @@ name: self-review description: | Pre-commit quality gate with 5-axis code review. Trigger: "/self-review", or when the user says "리뷰해줘", "review this", "코드 리뷰", "self review". - Use before every commit, after completing a Task in /implement, or when the user wants to review changes. + 상태 기반: 커밋 직전 스테이징된 변경이 있을 때, /implement Task 완료 직후. Evaluates code across correctness, readability, architecture, security, and performance. argument-hint: "[files or scope]" --- @@ -42,7 +42,7 @@ Determine which files to review. ### Step 2: Five-Axis Review -Evaluate every change across these dimensions. Project-specific concrete checks live in the relevant references — consult `implement/references/languages/{your-language}.md` and `implement/references/types/{your-type}.md` for the exact items. +Evaluate every change across these dimensions. 구체 체크 항목은 `implement/references/languages/java-spring.md`와 `implement/references/types/web-api.md`를 참조한다. #### Correctness @@ -68,9 +68,9 @@ Can another engineer understand this without explanation? Does the change fit the system design? -- **Module boundary**: respects the project's layering rules — consult `implement/references/types/{your-type}.md` for the specific layering (e.g., service-vs-controller thinness, repository tiers, public-vs-internal API surface) +- **Module boundary**: 레이어 표준 15를 지키는가 (Controller 얇게, 레포지토리 2형 분리, 타 도메인은 Facade 경유) - **Cross-module access**: goes through the documented seam (Facade, port, public function), never bypasses -- **Custom conventions**: language/framework-specific annotations or markers used correctly (`implement/references/languages/{your-language}.md`) +- **Custom conventions**: 프로젝트 특화 어노테이션(@FacadeService, @JpaRepositoryImpl 등)을 지침 정의대로 사용했는가 #### Security @@ -154,23 +154,6 @@ After completing the review: - [ ] Code compiles / type-checks (see `/verify` quick) - [ ] Unit tests pass (see `/verify` standard) -## Runtime Boundary — HARD STOP +## 종료 -This skill ENDS after the Verification checklist and final report are completed. - -For codex and any runtime without an enforced skill-return boundary: -- MUST stop the assistant turn here. -- MUST NOT invoke, load, or execute any next GSL skill in the same response turn. -- MUST NOT continue into `/next-flow`, `/define`, `/plan`, `/implement`, `/test`, `/verify`, `/debug`, or `/self-review`. -- MAY print exactly one suggested next command as plain text. -- MUST wait for the user's next message before running any next skill. - -If the user says only "continue", treat that as permission to report the next recommended command, not permission to execute it. - ---- - -## Lifecycle Integration - -**Before this skill:** if `plan/conventions.md` does not exist, run `/scan-conventions` first — analysis relies on knowing the project's actual conventions (naming, layering, test patterns, build system). - -**After this skill:** the next GSL skill is started by the user, not by this skill — see the Runtime Boundary section above. `/next-flow` may be suggested for lifecycle diagnosis but is not auto-invoked. Runtime note: some environments expose slash commands as UI commands; codex loads GSL skills from `.agents/skills/`. In both cases, the next GSL skill requires a new explicit user message. +지침의 **GSL Execution Mode** 규칙을 따른다. 발견 사항(심각도별 건수)과 해소 여부를 보고한다. step-by-step이면 턴을 끝내고 커밋 여부를 사용자에게 넘긴다. delegated면 Critical 0건 확인 후 커밋으로 계속한다. diff --git a/.agents/skills/test/SKILL.md b/.agents/skills/test/SKILL.md index cd842adfd..cb51a7de5 100644 --- a/.agents/skills/test/SKILL.md +++ b/.agents/skills/test/SKILL.md @@ -1,22 +1,23 @@ --- name: test description: | - Test implementation guide across any language/stack. - Trigger: "/test", or when the user says "테스트 작성", "테스트 구현", "테스트 추가", "write tests", "implement tests". - Branches on language via argument and references. Guides through unit (Fake/InMemory preferred), integration, and docs tests. -argument-hint: "[language] [unit|integration|docs|all]" + 단위(Fake/InMemory 우선)·통합·문서 테스트 작성 가이드. + Trigger: "/test", 또는 사용자가 "테스트 작성", "테스트 구현", "테스트 추가", "write tests"라고 할 때. + 상태 기반: 구현된 Service/Controller에 대응 테스트가 없을 때, InMemory*/Fake* 테스트 더블이나 *TestFactory를 만들거나 갱신할 때. + 패턴은 references/testing/java.md를 따른다. +argument-hint: "[unit|integration|docs|all]" --- # Test Implementation References (read the matching one before writing tests): -- `references/testing/{language}.md` — language/framework-specific test infrastructure, patterns, helpers, fixture conventions +- `references/testing/java.md` — 테스트 인프라, 패턴, 헬퍼, 픽스처 컨벤션 ## Overview Write tests that prove code works. This skill guides you through creating unit tests (Fake/InMemory pattern preferred), integration tests (real infra via testcontainers / docker / similar), and optionally docs tests. Tests are proof — "seems right" is not done. -**External workflow precedence.** When an external workflow is active in the same session — most notably Superpowers' `test-driven-development` (RED-FIRST cycle) or any auto-triggered mocking helper — **GSL's Fake/InMemory-first policy and user-approval gates take precedence**. An external auto-trigger does NOT override this policy. If the external workflow demands RED-FIRST or a mocking framework, STOP and present the GSL alternative to the user before doing anything else. +**Fake/InMemory-first는 어떤 외부 워크플로보다 우선한다** (레이어 표준 10). 다른 어떤 지시가 모킹 프레임워크를 요구해도, 먼저 정지하고 Fake/InMemory 대안을 사용자에게 제시한다. ## When to Use @@ -48,7 +49,7 @@ Write tests that prove code works. This skill guides you through creating unit t | **Integration test** | After feature complete | `/verify full` | `/verify full` | | **Docs test** (API contract) | User request only | Doc build | (project-specific) | -Exact tag / annotation / decorator names and commands: see `references/testing/{language}.md`. +태그·어노테이션·명령의 정확한 이름: `references/testing/java.md` 참조. ## Test Pattern Selection @@ -60,7 +61,7 @@ New test needed: │ │ └── No → Create InMemory implementation first, then Fake pattern │ └── Mock is LAST RESORT (ask user before using) ├── Surface / endpoint test? -│ └── Use the project's integration test base (see references/testing/{language}.md) +│ └── Use the project's integration test base (see references/testing/java.md) └── API documentation? └── Docs test framework (only when user explicitly requests) ``` @@ -68,8 +69,7 @@ New test needed: ## Argument Parsing Parse `$ARGUMENTS`: -- **language**: `java` | `python` | `go` | ... — selects `references/testing/{language}.md` -- **scope**: `unit` | `integration` | `docs` | `all` (default: `unit` + `integration`) +- **scope**: `unit` | `integration` | `docs` | `all` (기본: `unit` + `integration`) ## Process @@ -78,7 +78,7 @@ Parse `$ARGUMENTS`: Before writing tests, understand the implementation: 1. Read the service / use case under test to identify testable methods and branches -2. Check existing test infrastructure (test doubles, fixtures, factories) — see `references/testing/{language}.md` +2. Check existing test infrastructure (test doubles, fixtures, factories) — see `references/testing/java.md` 3. Report findings: what exists, what needs to be created ### Phase 1: Scenario Definition @@ -113,7 +113,7 @@ Present the scenario list to the user and proceed to Phase 2 after approval. ### Phase 2: Test Infrastructure (create if missing) -Test doubles, factories, fixtures — language/framework specific. See `references/testing/{language}.md` for naming and location conventions in your stack. +Test doubles, factories, fixtures — `references/testing/java.md`의 네이밍·위치 컨벤션을 따른다. General pattern: - **Test double**: `InMemory{Name}` / `Fake{Name}` implementing the same interface as the real component @@ -122,7 +122,7 @@ General pattern: ### Phase 3: Test Implementation -Read `references/testing/{language}.md` for code examples before writing tests. +Read `references/testing/java.md` for code examples before writing tests. **Unit Test (Fake/InMemory pattern):** - Wire the system under test with InMemory repositories + Fake collaborators @@ -155,7 +155,7 @@ After test implementation, run verification: ## Important Rules -- **Mock framework triggers STOP** (hard rule): if you (or any external workflow such as Superpowers TDD) are about to introduce a mocking framework (Mockito, `unittest.mock`, gomock, jest mocks, ...) for a unit test, **STOP immediately**. Always present a Fake/InMemory alternative first. Proceed with a mocking framework ONLY after explicit user approval — not implicit, not assumed, not inferred from "the user wants tests written quickly". +- **Mock framework triggers STOP** (hard rule): if you are about to introduce a mocking framework (Mockito 등) for a unit test, **STOP immediately**. Always present a Fake/InMemory alternative first. Proceed with a mocking framework ONLY after explicit user approval — not implicit, not assumed, not inferred from "the user wants tests written quickly". - **Docs tests are optional**: only implement when user explicitly requests. - **One test, one scenario**: each test method verifies a single behavior. - **Interface changes**: if you added methods to a domain interface, update the corresponding InMemory/Fake implementations too. @@ -194,23 +194,6 @@ After completing test implementation: - [ ] All tests pass: `/verify` at appropriate level - [ ] Test doubles updated if domain interfaces changed -## Runtime Boundary — HARD STOP +## 종료 -This skill ENDS after the Verification checklist and final report are completed. - -For codex and any runtime without an enforced skill-return boundary: -- MUST stop the assistant turn here. -- MUST NOT invoke, load, or execute any next GSL skill in the same response turn. -- MUST NOT continue into `/next-flow`, `/define`, `/plan`, `/implement`, `/test`, `/verify`, `/debug`, or `/self-review`. -- MAY print exactly one suggested next command as plain text. -- MUST wait for the user's next message before running any next skill. - -If the user says only "continue", treat that as permission to report the next recommended command, not permission to execute it. - ---- - -## Lifecycle Integration - -**Before this skill:** if `plan/conventions.md` does not exist, run `/scan-conventions` first — analysis relies on knowing the project's actual conventions (naming, layering, test patterns, build system). - -**After this skill:** the next GSL skill is started by the user, not by this skill — see the Runtime Boundary section above. `/next-flow` may be suggested for lifecycle diagnosis but is not auto-invoked. Runtime note: some environments expose slash commands as UI commands; codex loads GSL skills from `.agents/skills/`. In both cases, the next GSL skill requires a new explicit user message. +지침의 **GSL Execution Mode** 규칙을 따른다. 결과(작성한 테스트 수·통과 여부)를 보고하고, step-by-step이면 `Next: /verify full`을 제안한 뒤 턴을 끝낸다. delegated면 계약 scope에 따라 계속한다. diff --git a/.agents/skills/verify/SKILL.md b/.agents/skills/verify/SKILL.md index 755ad26df..b1335595d 100644 --- a/.agents/skills/verify/SKILL.md +++ b/.agents/skills/verify/SKILL.md @@ -2,17 +2,17 @@ name: verify description: | Local CI verification skill with 3 levels (L1 quick / L2 standard / L3 full). - Trigger: "/verify", "/verify quick", "/verify l1", "/verify standard", "/verify l2", "/verify full", "/verify l3", - or when the user says "검증해줘", "빌드 확인", "테스트 돌려줘", "CI 돌려봐", "run checks". - Always use this skill when the user wants to check if their code compiles, passes tests, or is ready for PR. - Branches on language via reference. Always runs against the ENTIRE project (modules can depend on each other). -argument-hint: "[language] [quick|standard|full] (language optional if inferable)" + Trigger: "/verify", "/verify quick", "/verify standard", "/verify full", + 또는 사용자가 "검증해줘", "빌드 확인", "테스트 돌려줘", "CI 돌려봐", "run checks"라고 할 때. + 상태 기반: Task 커밋 직전, 푸시·PR 직전, 코드가 컴파일되는지 확인이 필요할 때. + 명령은 references/verify/java-gradle.md를 따른다. 항상 전체 프로젝트 범위로 실행한다 (모듈 간 의존 때문). +argument-hint: "[quick|standard|full]" --- # Verify — Local CI Pipeline References (read the matching one for exact commands): -- `references/verify/{language}.md` — concrete commands per step (java-gradle / python / go / ...) +- `references/verify/java-gradle.md` — 단계별 실제 명령 ## Overview @@ -32,7 +32,7 @@ L3 is the final gate before `git push` or PR creation. Integration tests take se ## Generic Step Definitions -Concrete commands per step come from `references/verify/{language}.md`. The generic step contract: +Concrete commands per step come from `references/verify/java-gradle.md`. The generic step contract: ### L1 — Quick @@ -62,7 +62,7 @@ Run each step sequentially. **Stop immediately on first failure** — report the For each step: 1. Record the start time -2. Run the command (from `references/verify/{language}.md`) +2. Run the command (from `references/verify/java-gradle.md`) 3. Calculate elapsed time 4. Report the result immediately @@ -107,8 +107,7 @@ Adjust per-step shell timeouts accordingly (compile / lint: short; tests: medium ## Important - **Never run module-specific checks** — always full project scope. One module's change can break another. -- L3 integration tests typically require Docker / infra containers. If unavailable, report clearly and suggest falling back to L2. -- If `references/verify/{language}.md` does not exist for the project's language, ask the user to confirm the verification commands before running. +- L3 integration tests typically require Docker / infra containers (TestContainers + 서브모듈 초기화 필요). If unavailable, report clearly and suggest falling back to L2. ## Common Rationalizations @@ -135,23 +134,6 @@ After running: - [ ] On failure: last 30 lines of output shown, remaining steps marked SKIPPED - [ ] Total time reported -## Runtime Boundary — HARD STOP +## 종료 -This skill ENDS after the Verification checklist and final report are completed. - -For codex and any runtime without an enforced skill-return boundary: -- MUST stop the assistant turn here. -- MUST NOT invoke, load, or execute any next GSL skill in the same response turn. -- MUST NOT continue into `/next-flow`, `/define`, `/plan`, `/implement`, `/test`, `/verify`, `/debug`, or `/self-review`. -- MAY print exactly one suggested next command as plain text. -- MUST wait for the user's next message before running any next skill. - -If the user says only "continue", treat that as permission to report the next recommended command, not permission to execute it. - ---- - -## Lifecycle Integration - -**Before this skill:** if `plan/conventions.md` does not exist, run `/scan-conventions` first — analysis relies on knowing the project's actual conventions (naming, layering, test patterns, build system). - -**After this skill:** the next GSL skill is started by the user, not by this skill — see the Runtime Boundary section above. `/next-flow` may be suggested for lifecycle diagnosis but is not auto-invoked. Runtime note: some environments expose slash commands as UI commands; codex loads GSL skills from `.agents/skills/`. In both cases, the next GSL skill requires a new explicit user message. +지침의 **GSL Execution Mode** 규칙을 따른다. Summary를 보고하고, PASS면 다음 단계(커밋 또는 PR), FAIL이면 `Next: /debug`를 제안한다. step-by-step이면 턴을 끝내고, delegated면 계약 scope에 따라 계속한다. diff --git a/.claude/skills/debug/SKILL.md b/.claude/skills/debug/SKILL.md index 01782267f..639fe1850 100644 --- a/.claude/skills/debug/SKILL.md +++ b/.claude/skills/debug/SKILL.md @@ -4,6 +4,7 @@ description: | Systematic root-cause debugging for build failures, test failures, and runtime errors. Trigger: "/debug", or when the user says "에러 났어", "테스트 실패", "빌드 안 돼", "왜 안 되지", "debug this", "this is broken". Follows a structured 6-step process: STOP, REPRODUCE, LOCALIZE, FIX, GUARD, VERIFY. + 상태 기반: 컴파일·테스트·ArchUnit 룰 실패 출력이 있을 때, /verify가 FAIL을 보고했을 때, 이전에 되던 것이 안 될 때. Use when anything unexpected happens — do not guess at fixes. argument-hint: "[error description or test name]" --- @@ -52,7 +53,7 @@ Can you reproduce the failure? └── If truly non-reproducible, document conditions and monitor ``` -For exact reproduction commands per language/stack, see `verify/references/verify/{your-language}.md`. +For exact reproduction commands, see `verify/references/verify/java-gradle.md`. ### Step 3: LOCALIZE @@ -82,7 +83,7 @@ Test failure: **For stack traces:** read bottom-up, find the first line in your own code (not framework / vendor). -For project-specific triage trees (e.g., framework-specific failure modes), consult `implement/references/languages/{your-language}.md`. +For project-specific triage trees, consult `implement/references/languages/java-spring.md` and `bottlenote-patterns.md`. ### Step 4: FIX @@ -115,7 +116,7 @@ Write a regression test that would have caught this bug. ### Step 6: VERIFY -Run verification to confirm the fix and check for regressions. See `/verify` and `verify/references/verify/{your-language}.md` for level selection: +Run verification to confirm the fix and check for regressions. See `/verify` for level selection: | Original failure | Minimum verification | |------------------|----------------------| @@ -157,23 +158,6 @@ After fixing a bug: - [ ] Test doubles updated if interfaces changed - [ ] Original failure scenario verified end-to-end -## Runtime Boundary — HARD STOP +## 종료 -This skill ENDS after the Verification checklist and final report are completed. - -For codex and any runtime without an enforced skill-return boundary: -- MUST stop the assistant turn here. -- MUST NOT invoke, load, or execute any next GSL skill in the same response turn. -- MUST NOT continue into `/next-flow`, `/define`, `/plan`, `/implement`, `/test`, `/verify`, `/debug`, or `/self-review`. -- MAY print exactly one suggested next command as plain text. -- MUST wait for the user's next message before running any next skill. - -If the user says only "continue", treat that as permission to report the next recommended command, not permission to execute it. - ---- - -## Lifecycle Integration - -**Before this skill:** if `plan/conventions.md` does not exist, run `/scan-conventions` first — analysis relies on knowing the project's actual conventions (naming, layering, test patterns, build system). - -**After this skill:** the next GSL skill is started by the user, not by this skill — see the Runtime Boundary section above. `/next-flow` may be suggested for lifecycle diagnosis but is not auto-invoked. Runtime note: some environments expose slash commands as UI commands; codex loads GSL skills from `.agents/skills/`. In both cases, the next GSL skill requires a new explicit user message. +지침의 **GSL Execution Mode** 규칙을 따른다. 근본 원인·수정 내용·회귀 테스트를 보고한다. step-by-step이면 턴을 끝내고, delegated면 중단됐던 단계로 복귀한다 (단, 가정 붕괴가 원인이었다면 재개봉 프로토콜이 우선한다). diff --git a/.claude/skills/implement/references/languages/bottlenote-patterns.md b/.claude/skills/implement/references/languages/bottlenote-patterns.md index a7fd73c87..12e9890ca 100644 --- a/.claude/skills/implement/references/languages/bottlenote-patterns.md +++ b/.claude/skills/implement/references/languages/bottlenote-patterns.md @@ -1,12 +1,12 @@ # bottlenote-patterns > bottle-note 한정 implement 함정 / 실수 방지 부록. -> 일반 컨벤션은 `plan/conventions.md`, 일반 Java/Spring 패턴은 `java-spring.md` 참조. +> 일반 컨벤션은 프로젝트 지침(CLAUDE.md/AGENTS.md), 일반 Java/Spring 패턴은 `java-spring.md` 참조. > 본 파일은 GSL sync 와 무관한 프로젝트 특화 reference. ## When to load -`/implement` Phase 0 에서 `plan/conventions.md` 와 함께 자동 참조. Repository / RestDocs / 이벤트 발행 관련 작업 시 우선 확인. +`/implement` Phase 0 에서 자동 참조. Repository / RestDocs / 이벤트 발행 관련 작업 시 우선 확인. --- @@ -86,7 +86,7 @@ bottlenote-admin-api/src/test/kotlin/.../fixture/InMemory{Domain}Repository.kt ### Facade ↔ Service 경계 drift - Cross-domain 접근은 `{Domain}Facade` 경유 원칙 -- 일부 service 가 facade 도 구현하는 기존 drift 존재 (`conventions.md` Comparison 표 참조) +- Facade 구현체는 `service` 패키지에 둔다 (인터페이스는 `facade` — 지침의 @FacadeService 항목 참조) - 새 작업: facade 분리 권장. 기존 drift 는 plan 에 명시되지 않은 한 정리 대상 아님 ### `@ThirdPartyService` 사용 시점 @@ -97,7 +97,7 @@ bottlenote-admin-api/src/test/kotlin/.../fixture/InMemory{Domain}Repository.kt ### Batch 모듈 특이사항 (`/define` / `/plan` 시 주의) -`conventions.md` 의 "Batch-Specific Current Conventions" 참조. 핵심만: +핵심만: - `bottlenote-batch` 는 `testFixtures` 대신 `mono` test output 직접 사용 (drift) - `git.environment-variables` 가 main+test 양쪽 resources 에 포함됨 - main resource 에 하드코딩된 JWT secret / nonce salt default 존재 @@ -111,4 +111,4 @@ bottlenote-admin-api/src/test/kotlin/.../fixture/InMemory{Domain}Repository.kt - 항목이 GSL 표준 `java-spring.md` 에 흡수되면 본 파일에서 제거 → 중복 방지 - 새 함정 발견 시 추가 (PR 사례 인용 권장) -- conventions.md 가 자체 갱신될 때 본 파일과 충돌하는 부분은 conventions.md 우선 +- 프로젝트 지침(CLAUDE.md/AGENTS.md)과 충돌하는 부분은 지침 우선 diff --git a/.claude/skills/implement/references/types/batch.md b/.claude/skills/implement/references/types/batch.md index 5bc9c02f7..ee7545b0a 100644 --- a/.claude/skills/implement/references/types/batch.md +++ b/.claude/skills/implement/references/types/batch.md @@ -1,5 +1,5 @@ # Type: batch -Language-independent patterns for batch jobs, scheduled jobs, backfills, and offline workers. Pair with `languages/{language}.md` for concrete code. +Language-independent patterns for batch jobs, scheduled jobs, backfills, and offline workers. Pair with `languages/java-spring.md` for concrete code. ## Layer Breakdown (universal) ``` time / event / manual trigger diff --git a/.claude/skills/implement/references/types/web-api.md b/.claude/skills/implement/references/types/web-api.md index ffb36690c..c4134caf5 100644 --- a/.claude/skills/implement/references/types/web-api.md +++ b/.claude/skills/implement/references/types/web-api.md @@ -1,6 +1,6 @@ # Type: web-api -Language-independent patterns for HTTP-based API servers (REST primarily; GraphQL notes at the end). Pair with the matching `languages/{language}.md` for concrete code. +Language-independent patterns for HTTP-based API servers (REST primarily; GraphQL notes at the end). Pair with `languages/java-spring.md` for concrete code. ## Layer Breakdown (universal) @@ -73,7 +73,7 @@ Sub-resources: | 429 | Rate limit | | 5xx | Server-side failure (never leak internals in body) | -Pick a project convention for 400 vs 422 and stick to it; document in `conventions.md`. +Pick a project convention for 400 vs 422 and stick to it (이 저장소: 지침의 예외 처리 규칙 참조). ## Error Model diff --git a/.claude/skills/self-review/SKILL.md b/.claude/skills/self-review/SKILL.md index b2e0730e2..f9c6587f5 100644 --- a/.claude/skills/self-review/SKILL.md +++ b/.claude/skills/self-review/SKILL.md @@ -3,7 +3,7 @@ name: self-review description: | Pre-commit quality gate with 5-axis code review. Trigger: "/self-review", or when the user says "리뷰해줘", "review this", "코드 리뷰", "self review". - Use before every commit, after completing a Task in /implement, or when the user wants to review changes. + 상태 기반: 커밋 직전 스테이징된 변경이 있을 때, /implement Task 완료 직후. Evaluates code across correctness, readability, architecture, security, and performance. argument-hint: "[files or scope]" --- @@ -42,7 +42,7 @@ Determine which files to review. ### Step 2: Five-Axis Review -Evaluate every change across these dimensions. Project-specific concrete checks live in the relevant references — consult `implement/references/languages/{your-language}.md` and `implement/references/types/{your-type}.md` for the exact items. +Evaluate every change across these dimensions. 구체 체크 항목은 `implement/references/languages/java-spring.md`와 `implement/references/types/web-api.md`를 참조한다. #### Correctness @@ -68,9 +68,9 @@ Can another engineer understand this without explanation? Does the change fit the system design? -- **Module boundary**: respects the project's layering rules — consult `implement/references/types/{your-type}.md` for the specific layering (e.g., service-vs-controller thinness, repository tiers, public-vs-internal API surface) +- **Module boundary**: 레이어 표준 15를 지키는가 (Controller 얇게, 레포지토리 2형 분리, 타 도메인은 Facade 경유) - **Cross-module access**: goes through the documented seam (Facade, port, public function), never bypasses -- **Custom conventions**: language/framework-specific annotations or markers used correctly (`implement/references/languages/{your-language}.md`) +- **Custom conventions**: 프로젝트 특화 어노테이션(@FacadeService, @JpaRepositoryImpl 등)을 지침 정의대로 사용했는가 #### Security @@ -154,23 +154,6 @@ After completing the review: - [ ] Code compiles / type-checks (see `/verify` quick) - [ ] Unit tests pass (see `/verify` standard) -## Runtime Boundary — HARD STOP +## 종료 -This skill ENDS after the Verification checklist and final report are completed. - -For codex and any runtime without an enforced skill-return boundary: -- MUST stop the assistant turn here. -- MUST NOT invoke, load, or execute any next GSL skill in the same response turn. -- MUST NOT continue into `/next-flow`, `/define`, `/plan`, `/implement`, `/test`, `/verify`, `/debug`, or `/self-review`. -- MAY print exactly one suggested next command as plain text. -- MUST wait for the user's next message before running any next skill. - -If the user says only "continue", treat that as permission to report the next recommended command, not permission to execute it. - ---- - -## Lifecycle Integration - -**Before this skill:** if `plan/conventions.md` does not exist, run `/scan-conventions` first — analysis relies on knowing the project's actual conventions (naming, layering, test patterns, build system). - -**After this skill:** the next GSL skill is started by the user, not by this skill — see the Runtime Boundary section above. `/next-flow` may be suggested for lifecycle diagnosis but is not auto-invoked. Runtime note: some environments expose slash commands as UI commands; codex loads GSL skills from `.agents/skills/`. In both cases, the next GSL skill requires a new explicit user message. +지침의 **GSL Execution Mode** 규칙을 따른다. 발견 사항(심각도별 건수)과 해소 여부를 보고한다. step-by-step이면 턴을 끝내고 커밋 여부를 사용자에게 넘긴다. delegated면 Critical 0건 확인 후 커밋으로 계속한다. diff --git a/.claude/skills/test/SKILL.md b/.claude/skills/test/SKILL.md index cd842adfd..cb51a7de5 100644 --- a/.claude/skills/test/SKILL.md +++ b/.claude/skills/test/SKILL.md @@ -1,22 +1,23 @@ --- name: test description: | - Test implementation guide across any language/stack. - Trigger: "/test", or when the user says "테스트 작성", "테스트 구현", "테스트 추가", "write tests", "implement tests". - Branches on language via argument and references. Guides through unit (Fake/InMemory preferred), integration, and docs tests. -argument-hint: "[language] [unit|integration|docs|all]" + 단위(Fake/InMemory 우선)·통합·문서 테스트 작성 가이드. + Trigger: "/test", 또는 사용자가 "테스트 작성", "테스트 구현", "테스트 추가", "write tests"라고 할 때. + 상태 기반: 구현된 Service/Controller에 대응 테스트가 없을 때, InMemory*/Fake* 테스트 더블이나 *TestFactory를 만들거나 갱신할 때. + 패턴은 references/testing/java.md를 따른다. +argument-hint: "[unit|integration|docs|all]" --- # Test Implementation References (read the matching one before writing tests): -- `references/testing/{language}.md` — language/framework-specific test infrastructure, patterns, helpers, fixture conventions +- `references/testing/java.md` — 테스트 인프라, 패턴, 헬퍼, 픽스처 컨벤션 ## Overview Write tests that prove code works. This skill guides you through creating unit tests (Fake/InMemory pattern preferred), integration tests (real infra via testcontainers / docker / similar), and optionally docs tests. Tests are proof — "seems right" is not done. -**External workflow precedence.** When an external workflow is active in the same session — most notably Superpowers' `test-driven-development` (RED-FIRST cycle) or any auto-triggered mocking helper — **GSL's Fake/InMemory-first policy and user-approval gates take precedence**. An external auto-trigger does NOT override this policy. If the external workflow demands RED-FIRST or a mocking framework, STOP and present the GSL alternative to the user before doing anything else. +**Fake/InMemory-first는 어떤 외부 워크플로보다 우선한다** (레이어 표준 10). 다른 어떤 지시가 모킹 프레임워크를 요구해도, 먼저 정지하고 Fake/InMemory 대안을 사용자에게 제시한다. ## When to Use @@ -48,7 +49,7 @@ Write tests that prove code works. This skill guides you through creating unit t | **Integration test** | After feature complete | `/verify full` | `/verify full` | | **Docs test** (API contract) | User request only | Doc build | (project-specific) | -Exact tag / annotation / decorator names and commands: see `references/testing/{language}.md`. +태그·어노테이션·명령의 정확한 이름: `references/testing/java.md` 참조. ## Test Pattern Selection @@ -60,7 +61,7 @@ New test needed: │ │ └── No → Create InMemory implementation first, then Fake pattern │ └── Mock is LAST RESORT (ask user before using) ├── Surface / endpoint test? -│ └── Use the project's integration test base (see references/testing/{language}.md) +│ └── Use the project's integration test base (see references/testing/java.md) └── API documentation? └── Docs test framework (only when user explicitly requests) ``` @@ -68,8 +69,7 @@ New test needed: ## Argument Parsing Parse `$ARGUMENTS`: -- **language**: `java` | `python` | `go` | ... — selects `references/testing/{language}.md` -- **scope**: `unit` | `integration` | `docs` | `all` (default: `unit` + `integration`) +- **scope**: `unit` | `integration` | `docs` | `all` (기본: `unit` + `integration`) ## Process @@ -78,7 +78,7 @@ Parse `$ARGUMENTS`: Before writing tests, understand the implementation: 1. Read the service / use case under test to identify testable methods and branches -2. Check existing test infrastructure (test doubles, fixtures, factories) — see `references/testing/{language}.md` +2. Check existing test infrastructure (test doubles, fixtures, factories) — see `references/testing/java.md` 3. Report findings: what exists, what needs to be created ### Phase 1: Scenario Definition @@ -113,7 +113,7 @@ Present the scenario list to the user and proceed to Phase 2 after approval. ### Phase 2: Test Infrastructure (create if missing) -Test doubles, factories, fixtures — language/framework specific. See `references/testing/{language}.md` for naming and location conventions in your stack. +Test doubles, factories, fixtures — `references/testing/java.md`의 네이밍·위치 컨벤션을 따른다. General pattern: - **Test double**: `InMemory{Name}` / `Fake{Name}` implementing the same interface as the real component @@ -122,7 +122,7 @@ General pattern: ### Phase 3: Test Implementation -Read `references/testing/{language}.md` for code examples before writing tests. +Read `references/testing/java.md` for code examples before writing tests. **Unit Test (Fake/InMemory pattern):** - Wire the system under test with InMemory repositories + Fake collaborators @@ -155,7 +155,7 @@ After test implementation, run verification: ## Important Rules -- **Mock framework triggers STOP** (hard rule): if you (or any external workflow such as Superpowers TDD) are about to introduce a mocking framework (Mockito, `unittest.mock`, gomock, jest mocks, ...) for a unit test, **STOP immediately**. Always present a Fake/InMemory alternative first. Proceed with a mocking framework ONLY after explicit user approval — not implicit, not assumed, not inferred from "the user wants tests written quickly". +- **Mock framework triggers STOP** (hard rule): if you are about to introduce a mocking framework (Mockito 등) for a unit test, **STOP immediately**. Always present a Fake/InMemory alternative first. Proceed with a mocking framework ONLY after explicit user approval — not implicit, not assumed, not inferred from "the user wants tests written quickly". - **Docs tests are optional**: only implement when user explicitly requests. - **One test, one scenario**: each test method verifies a single behavior. - **Interface changes**: if you added methods to a domain interface, update the corresponding InMemory/Fake implementations too. @@ -194,23 +194,6 @@ After completing test implementation: - [ ] All tests pass: `/verify` at appropriate level - [ ] Test doubles updated if domain interfaces changed -## Runtime Boundary — HARD STOP +## 종료 -This skill ENDS after the Verification checklist and final report are completed. - -For codex and any runtime without an enforced skill-return boundary: -- MUST stop the assistant turn here. -- MUST NOT invoke, load, or execute any next GSL skill in the same response turn. -- MUST NOT continue into `/next-flow`, `/define`, `/plan`, `/implement`, `/test`, `/verify`, `/debug`, or `/self-review`. -- MAY print exactly one suggested next command as plain text. -- MUST wait for the user's next message before running any next skill. - -If the user says only "continue", treat that as permission to report the next recommended command, not permission to execute it. - ---- - -## Lifecycle Integration - -**Before this skill:** if `plan/conventions.md` does not exist, run `/scan-conventions` first — analysis relies on knowing the project's actual conventions (naming, layering, test patterns, build system). - -**After this skill:** the next GSL skill is started by the user, not by this skill — see the Runtime Boundary section above. `/next-flow` may be suggested for lifecycle diagnosis but is not auto-invoked. Runtime note: some environments expose slash commands as UI commands; codex loads GSL skills from `.agents/skills/`. In both cases, the next GSL skill requires a new explicit user message. +지침의 **GSL Execution Mode** 규칙을 따른다. 결과(작성한 테스트 수·통과 여부)를 보고하고, step-by-step이면 `Next: /verify full`을 제안한 뒤 턴을 끝낸다. delegated면 계약 scope에 따라 계속한다. diff --git a/.claude/skills/verify/SKILL.md b/.claude/skills/verify/SKILL.md index 755ad26df..b1335595d 100644 --- a/.claude/skills/verify/SKILL.md +++ b/.claude/skills/verify/SKILL.md @@ -2,17 +2,17 @@ name: verify description: | Local CI verification skill with 3 levels (L1 quick / L2 standard / L3 full). - Trigger: "/verify", "/verify quick", "/verify l1", "/verify standard", "/verify l2", "/verify full", "/verify l3", - or when the user says "검증해줘", "빌드 확인", "테스트 돌려줘", "CI 돌려봐", "run checks". - Always use this skill when the user wants to check if their code compiles, passes tests, or is ready for PR. - Branches on language via reference. Always runs against the ENTIRE project (modules can depend on each other). -argument-hint: "[language] [quick|standard|full] (language optional if inferable)" + Trigger: "/verify", "/verify quick", "/verify standard", "/verify full", + 또는 사용자가 "검증해줘", "빌드 확인", "테스트 돌려줘", "CI 돌려봐", "run checks"라고 할 때. + 상태 기반: Task 커밋 직전, 푸시·PR 직전, 코드가 컴파일되는지 확인이 필요할 때. + 명령은 references/verify/java-gradle.md를 따른다. 항상 전체 프로젝트 범위로 실행한다 (모듈 간 의존 때문). +argument-hint: "[quick|standard|full]" --- # Verify — Local CI Pipeline References (read the matching one for exact commands): -- `references/verify/{language}.md` — concrete commands per step (java-gradle / python / go / ...) +- `references/verify/java-gradle.md` — 단계별 실제 명령 ## Overview @@ -32,7 +32,7 @@ L3 is the final gate before `git push` or PR creation. Integration tests take se ## Generic Step Definitions -Concrete commands per step come from `references/verify/{language}.md`. The generic step contract: +Concrete commands per step come from `references/verify/java-gradle.md`. The generic step contract: ### L1 — Quick @@ -62,7 +62,7 @@ Run each step sequentially. **Stop immediately on first failure** — report the For each step: 1. Record the start time -2. Run the command (from `references/verify/{language}.md`) +2. Run the command (from `references/verify/java-gradle.md`) 3. Calculate elapsed time 4. Report the result immediately @@ -107,8 +107,7 @@ Adjust per-step shell timeouts accordingly (compile / lint: short; tests: medium ## Important - **Never run module-specific checks** — always full project scope. One module's change can break another. -- L3 integration tests typically require Docker / infra containers. If unavailable, report clearly and suggest falling back to L2. -- If `references/verify/{language}.md` does not exist for the project's language, ask the user to confirm the verification commands before running. +- L3 integration tests typically require Docker / infra containers (TestContainers + 서브모듈 초기화 필요). If unavailable, report clearly and suggest falling back to L2. ## Common Rationalizations @@ -135,23 +134,6 @@ After running: - [ ] On failure: last 30 lines of output shown, remaining steps marked SKIPPED - [ ] Total time reported -## Runtime Boundary — HARD STOP +## 종료 -This skill ENDS after the Verification checklist and final report are completed. - -For codex and any runtime without an enforced skill-return boundary: -- MUST stop the assistant turn here. -- MUST NOT invoke, load, or execute any next GSL skill in the same response turn. -- MUST NOT continue into `/next-flow`, `/define`, `/plan`, `/implement`, `/test`, `/verify`, `/debug`, or `/self-review`. -- MAY print exactly one suggested next command as plain text. -- MUST wait for the user's next message before running any next skill. - -If the user says only "continue", treat that as permission to report the next recommended command, not permission to execute it. - ---- - -## Lifecycle Integration - -**Before this skill:** if `plan/conventions.md` does not exist, run `/scan-conventions` first — analysis relies on knowing the project's actual conventions (naming, layering, test patterns, build system). - -**After this skill:** the next GSL skill is started by the user, not by this skill — see the Runtime Boundary section above. `/next-flow` may be suggested for lifecycle diagnosis but is not auto-invoked. Runtime note: some environments expose slash commands as UI commands; codex loads GSL skills from `.agents/skills/`. In both cases, the next GSL skill requires a new explicit user message. +지침의 **GSL Execution Mode** 규칙을 따른다. Summary를 보고하고, PASS면 다음 단계(커밋 또는 PR), FAIL이면 `Next: /debug`를 제안한다. step-by-step이면 턴을 끝내고, delegated면 계약 scope에 따라 계속한다. diff --git a/plan/gsl-overhaul.md b/plan/gsl-overhaul.md index 48f7e937e..0cd517618 100644 --- a/plan/gsl-overhaul.md +++ b/plan/gsl-overhaul.md @@ -110,7 +110,7 @@ GSL(Guided Software Lifecycle) 9개 스킬을 이 저장소 전용으로 대폭 - Verification: 4파일×미러 grep — "Superpowers" 0건, "HARD STOP" 상세 블록 0건, python/go 분기 0건 - Files (advisory): 4 SKILL.md × 미러 2 = 8파일 - Size: M -- Status: [ ] not done +- Status: [x] done ### Task 7: 종합 검증 및 마감 - Acceptance: Success Criteria 13개 전 항목 체크리스트 대조 통과. E2E 시나리오 2건(step-by-step 전체 사이클 1건, delegated 전체 사이클 1건) 치명 결함 0건. 용량 측정 기록 @@ -126,3 +126,4 @@ GSL(Guided Software Lifecycle) 9개 스킬을 이 저장소 전용으로 대폭 - Task 3: /define 재작성 — WHAT/HOW 분리 철학, 지침 기반 하드 게이트, Execution Mode 선언 게이트(계약 서명), 재개봉 프로토콜, 상태 기반 트리거. 시나리오 검증(scenario-define): 정상 경로 완주 확인, 함정("가정 확인 넘어가고") 게이트 우회 불가 판정, 치명 0건·경미 7건. 경미 중 5건 반영(합리화 표에 외부 압박 행 추가, mode 플레이스홀더, stop-conditions 3종 인라인, Step 3 차단 문구, delegated 종료 분기·push/pr 고지). 188행 → 140행. - Task 4: /plan 재작성 — 정육 모델(Task=관절=승인 대상, Slice=썰기=에이전트 전권), 분해→정렬→크기검산 순서, Files advisory 강등, Depends 필드 신설. 시나리오 검증(scenario-plan): 정상 완주 확인, 치명 3건 검출(재개봉 프로토콜 미정의 / 사용자 수평 요청 충돌·승인 거부 분기 부재 / 중단 지점이 Step 6에만). "가정 붕괴 시 즉시 중단" 전역 규칙 신설 + 수평 요청 특칙(1회 권고→재지시 시 사용자 결정) + 승인 거부 분기로 수정, 재검증 결과 3건 전부 해소·신규 치명 0건. 경미 4·5·6·8도 반영. - Task 5: /implement 재작성 — Execution Mode 분기(step은 Task별 정지+연속 실행 예외, delegated는 체크포인트 보고로 계속), stop-conditions 우선 규칙, Phase 4에 push/pr·PR 본문=체인지로그 공식화, Polyglot·언어 fallback 제거. 시나리오 검증(scenario-implement) 4건: step 정지·delegated 연속+푸시 직전 정지·가정 붕괴 정지·plan 부재 거부 모두 텍스트만으로 도출, 치명 0건. 경미 5건(재개 신호 해석, 모드 읽기 위치, 정지 보고 명세, 체크포인트 도달 지점, verify 레벨) 반영 후 검증자 재확인 완료. Checkpoint 3-5 통과. +- Task 6: test/verify/debug/self-review 정리 — HARD STOP·Lifecycle Integration 꼬리를 "종료" 섹션(지침 참조 + 모드 분기 한 줄)으로 교체, Superpowers 문단 제거(Fake-first 우선 규칙은 유지), 언어 분기 제거(references를 java.md/java-gradle.md/java-spring.md 고정), test·verify·debug·self-review 트리거에 상태 기반 조건 추가. web-api.md·batch.md·bottlenote-patterns.md의 conventions.md·{language} 잔존 참조도 정리. grep 검증: Superpowers/Polyglot/{language}/{your-*}/next-flow/scan-conventions/conventions.md 전부 0건. From 055791cd300089a234a9657cdb4343640bece813 Mon Sep 17 00:00:00 2001 From: Whale0928 Date: Sat, 25 Jul 2026 18:56:09 +0900 Subject: [PATCH 9/9] =?UTF-8?q?docs:=20GSL=20=EA=B0=9C=ED=8E=B8=20?= =?UTF-8?q?=EC=A2=85=ED=95=A9=20=EA=B2=80=EC=A6=9D=20=EB=B0=8F=20=EA=B3=84?= =?UTF-8?q?=ED=9A=8D=20=EC=99=84=EB=A3=8C=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- .agents/skills/define/SKILL.md | 6 ++++-- .agents/skills/implement/SKILL.md | 11 +++++------ .agents/skills/plan/SKILL.md | 8 ++++---- .claude/skills/define/SKILL.md | 6 ++++-- .claude/skills/implement/SKILL.md | 11 +++++------ .claude/skills/plan/SKILL.md | 8 ++++---- AGENTS.md | 6 +++--- CLAUDE.md | 6 +++--- plan/{ => complete}/gsl-overhaul.md | 9 ++++++++- 9 files changed, 40 insertions(+), 31 deletions(-) rename plan/{ => complete}/gsl-overhaul.md (91%) diff --git a/.agents/skills/define/SKILL.md b/.agents/skills/define/SKILL.md index 438acebbf..53f04db4e 100644 --- a/.agents/skills/define/SKILL.md +++ b/.agents/skills/define/SKILL.md @@ -84,6 +84,8 @@ SUCCESS CRITERIA: ## Execution Mode - mode: (승인 게이트에서 확정) +- scope: (delegated일 때 기록) +- stop-conditions: (delegated일 때 기록 — 기본 3종은 선언 없이도 항상 적용) ## Tasks (/plan에서 작성) @@ -110,10 +112,10 @@ Execution Mode를 선택해 주세요: ## 재개봉 프로토콜 -이후 어느 단계(`/plan`, `/implement`, `/test`)에서든 **가정을 깨는 발견**이 나오면: +이후 어느 단계에서든 **가정을 깨는 발견**이 나오면: 1. 즉시 정지한다 (delegated 모드여도 — stop-condition 1번). -2. 이 문서의 Assumptions를 수정한다. +2. 이 문서의 Assumptions 수정안을 제시하고 반영한다. 3. WHAT(성공 기준·범위)이 바뀌었으면 재승인을 받는다. 표현만 정밀해진 것이면 기록만 남기고 계속한다. 조용히 적응해서 계속 진행하는 것이 최악의 선택이다. diff --git a/.agents/skills/implement/SKILL.md b/.agents/skills/implement/SKILL.md index 64063b29e..6ed2b93ab 100644 --- a/.agents/skills/implement/SKILL.md +++ b/.agents/skills/implement/SKILL.md @@ -60,15 +60,14 @@ Task 하나마다: | **step-by-step** | 정지. 완료 Task·검증 증거·변경 파일·다음 Task를 보고하고 턴을 끝낸다. 예외: 사용자가 연속 실행을 명시한 경우("Task 1~3", "결과까지")에만 계속. 범위 없는 재개 신호("계속", "고")는 **다음 Task 1개만** 허가로 간주한다 | | **delegated** (scope에 implement 포함) | 체크포인트 보고를 남기고 다음 Task로 계속한다 — Progress Log는 plan 문서에, 진행 요약은 대화에 즉시 출력해 사용자가 실시간 확인할 수 있게 한다 | -**stop-conditions는 모드와 무관하게 우선한다** (지침의 GSL Execution Mode 참조): 가정을 깨는 발견 → 즉시 정지, 재개봉 프로토콜. verify 3회 실패 → `/debug` 보고 후 정지. scope 밖 행동 필요 → 직전 정지. 정지 시 보고 항목: 발동한 조건, 발견 내용, 깨진 가정(해당 시), 제안 조치. +**stop-conditions는 모드와 무관하게 우선한다** (지침의 GSL Execution Mode 참조): 가정을 깨는 발견 → 즉시 정지, 재개봉 프로토콜. verify 3회 실패 → `/debug` 보고 후 정지. scope 밖 행동 필요 → 직전 정지. 정지 시 보고 항목: 발동한 조건, 발견 내용, 깨진 가정(해당 시), 제안 조치. 이미 커밋된 Task는 되돌리지 않고 보고에 포함한다 — 재승인 결과에 따라 후속 Task로 수정한다. ### Phase 4: 마감 -모든 Task 완료 후: +모든 Task 커밋 후 생명주기 꼬리는 이 순서다: **통합 테스트(`/test`, 필요 시) → `/verify full` → plan 문서 완료 스탬프·`plan/complete/` 이동 → push → PR**. 마감(스탬프·이동)은 verify PASS 뒤에 온다. -1. `/verify standard` (컴파일+단위+빌드) — 푸시·PR 전에는 `/verify full` (통합 포함) -2. plan 문서에 완료 스탬프, `plan/complete/`로 이동 -3. **scope에 따라**: `push` 선언 시 푸시. `pr` 선언 시 PR 오픈 — **PR 본문이 체인지로그를 겸한다** (변경 사항·배경·검증·제외 범위 구조, 기존 PR 관례를 따른다). 미선언이면 해당 직전에서 정지하고 확인받되, 정지 보고에 "푸시 전 `/verify full` 필요"를 명시한다 (scope가 commit에서 끝나면 full이 아직 안 돌았을 수 있다). +- **step-by-step**: 여기서 정지하고 `Next: /test`(통합 테스트가 필요하면) 또는 `Next: /verify full`을 제안한다. `/test`·`/verify`의 풀 워크플로는 별도 호출 경계다 — 이 스킬 안에서 실행하지 않는다. 마감·푸시·PR은 verify PASS 후 사용자 지시로 진행한다. +- **delegated**: scope에 선언된 단계를 위 순서대로 이어 실행한다 (위임 계약의 이행 — 지침의 격리 규칙 예외). scope의 `test`는 `/test`의 시나리오 승인 게이트를 체크포인트 보고로 대체해 수행하고, `verify`는 full까지 돌린다. 마감(스탬프·`plan/complete/` 이동)은 scope 항목이 아니라 verify PASS 후 항상 수행하는 생명주기 꼬리다. `push`/`pr`는 선언 시에만 — **PR 본문이 체인지로그를 겸한다** (변경 사항·배경·검증·제외 범위 구조). 미선언이면 해당 직전에서 정지하고, 남은 단계와 검증 상태(full 통과 여부)를 보고한다. ## Common Rationalizations @@ -91,4 +90,4 @@ Task 하나마다: ## 종료 -지침의 **GSL Execution Mode** 규칙을 따른다. step-by-step이면 Task 보고 후 턴 종료, 다음은 `Next: /implement (Task N+1)` 또는 전 Task 완료 시 `Next: /test`. delegated면 Phase 4까지 계속한다. +지침의 **GSL Execution Mode** 규칙을 따른다. step-by-step이면 Task 보고 후 턴 종료, 다음은 `Next: /implement (Task N+1)`, 전 Task 완료 시에는 Phase 4의 순서(`/test` → `/verify full` → 마감)를 안내한다. delegated면 Phase 4를 scope대로 이어 실행한다. diff --git a/.agents/skills/plan/SKILL.md b/.agents/skills/plan/SKILL.md index cf079e2cc..3fb74b3cc 100644 --- a/.agents/skills/plan/SKILL.md +++ b/.agents/skills/plan/SKILL.md @@ -22,9 +22,9 @@ argument-hint: "[feature-name or plan file path]" ## 가정 붕괴 시 즉시 중단 (전역 규칙) -어느 Step에서든 define의 Assumption을 깨는 발견이 나오면 **그 자리에서 중단한다**. Tasks를 아직 기록하지 않았으면 기록하지 않고, 이미 기록했다면 해당 기록을 되돌린다. 이것이 stop-condition 1번(가정 붕괴)이며 delegated 위임보다 우선한다. +어느 Step에서든 define의 Assumption을 깨는 발견이 나오면 **그 자리에서 중단한다**. Tasks를 아직 기록하지 않았으면 기록하지 않고, 이미 기록했다면 해당 기록을 되돌린다 (아직 승인되지 않은 기록에 한함 — 승인·커밋된 Task의 처리는 `/implement`의 정지 보고 규칙을 따른다). 이것이 stop-condition 1번(가정 붕괴)이며 delegated 위임보다 우선한다. -중단 후 절차(재개봉 프로토콜): ① 발견 내용과 깨진 가정을 보고한다 ② define 문서의 Assumptions 수정안을 제시한다 ③ WHAT(성공 기준·범위)이 바뀌었으면 재승인을 받고, 표현만 정밀해진 것이면 기록만 남기고 계속한다. +중단 후 절차(재개봉 프로토콜): ① 발견 내용과 깨진 가정을 보고한다 ② define 문서의 Assumptions 수정안을 제시하고 반영한다 ③ WHAT(성공 기준·범위)이 바뀌었으면 재승인을 받고, 표현만 정밀해진 것이면 기록만 남기고 계속한다. ## When NOT to Use @@ -96,7 +96,7 @@ plan 문서에 Tasks 섹션을 채운다. ### Step 6: 게이트 (모드 분기) -- **step-by-step**: 태스크 목록 요약(개수·크기 분포·의존 순서)을 제시하고 명시적 승인을 받은 뒤 턴을 끝낸다. 승인 전 `/implement` 진행 금지. **승인이 거부되면**: 거부 사유를 받아 Step 2부터 재분해한다. 사유가 분해 방식이 아니라 요구사항 자체라면 재개봉 프로토콜로 `/define`에 돌린다. +- **step-by-step**: 태스크 목록 요약(개수·크기 분포·의존 순서)을 제시하고 턴을 끝낸다. 사용자의 승인 메시지가 오면 그것이 곧 `/implement` 진행 허가다 — 재차 제안만 하고 멈추지 않는다. 승인 전 `/implement` 진행 금지. **승인이 거부되면**: 거부 사유를 받아 Step 2부터 재분해한다. 사유가 분해 방식이 아니라 요구사항 자체라면 재개봉 프로토콜로 `/define`에 돌린다. - **delegated (scope에 plan 포함)**: 승인 권한은 define 게이트에서 이미 위임받았다. 목록을 체크포인트 보고로 남기고 다음 단계로 계속한다. 단, 분해 중 가정을 깨는 발견이 있었다면 stop-condition 1번(재개봉)이 우선한다. ## Common Rationalizations @@ -117,4 +117,4 @@ plan 문서에 Tasks 섹션을 채운다. ## 종료 -지침의 **GSL Execution Mode** 규칙을 따른다. step-by-step이면 승인 후 `Next: /implement`를 제안하고 턴을 끝낸다. delegated면 보고를 남기고 계속한다. +지침의 **GSL Execution Mode** 규칙을 따른다. step-by-step이면 목록 제시 후 턴을 끝내고, 이후 도착한 승인 메시지가 곧 `/implement` 진행 허가다 (Step 6과 동일 규칙). delegated면 보고를 남기고 계속한다. diff --git a/.claude/skills/define/SKILL.md b/.claude/skills/define/SKILL.md index 438acebbf..53f04db4e 100644 --- a/.claude/skills/define/SKILL.md +++ b/.claude/skills/define/SKILL.md @@ -84,6 +84,8 @@ SUCCESS CRITERIA: ## Execution Mode - mode: (승인 게이트에서 확정) +- scope: (delegated일 때 기록) +- stop-conditions: (delegated일 때 기록 — 기본 3종은 선언 없이도 항상 적용) ## Tasks (/plan에서 작성) @@ -110,10 +112,10 @@ Execution Mode를 선택해 주세요: ## 재개봉 프로토콜 -이후 어느 단계(`/plan`, `/implement`, `/test`)에서든 **가정을 깨는 발견**이 나오면: +이후 어느 단계에서든 **가정을 깨는 발견**이 나오면: 1. 즉시 정지한다 (delegated 모드여도 — stop-condition 1번). -2. 이 문서의 Assumptions를 수정한다. +2. 이 문서의 Assumptions 수정안을 제시하고 반영한다. 3. WHAT(성공 기준·범위)이 바뀌었으면 재승인을 받는다. 표현만 정밀해진 것이면 기록만 남기고 계속한다. 조용히 적응해서 계속 진행하는 것이 최악의 선택이다. diff --git a/.claude/skills/implement/SKILL.md b/.claude/skills/implement/SKILL.md index 64063b29e..6ed2b93ab 100644 --- a/.claude/skills/implement/SKILL.md +++ b/.claude/skills/implement/SKILL.md @@ -60,15 +60,14 @@ Task 하나마다: | **step-by-step** | 정지. 완료 Task·검증 증거·변경 파일·다음 Task를 보고하고 턴을 끝낸다. 예외: 사용자가 연속 실행을 명시한 경우("Task 1~3", "결과까지")에만 계속. 범위 없는 재개 신호("계속", "고")는 **다음 Task 1개만** 허가로 간주한다 | | **delegated** (scope에 implement 포함) | 체크포인트 보고를 남기고 다음 Task로 계속한다 — Progress Log는 plan 문서에, 진행 요약은 대화에 즉시 출력해 사용자가 실시간 확인할 수 있게 한다 | -**stop-conditions는 모드와 무관하게 우선한다** (지침의 GSL Execution Mode 참조): 가정을 깨는 발견 → 즉시 정지, 재개봉 프로토콜. verify 3회 실패 → `/debug` 보고 후 정지. scope 밖 행동 필요 → 직전 정지. 정지 시 보고 항목: 발동한 조건, 발견 내용, 깨진 가정(해당 시), 제안 조치. +**stop-conditions는 모드와 무관하게 우선한다** (지침의 GSL Execution Mode 참조): 가정을 깨는 발견 → 즉시 정지, 재개봉 프로토콜. verify 3회 실패 → `/debug` 보고 후 정지. scope 밖 행동 필요 → 직전 정지. 정지 시 보고 항목: 발동한 조건, 발견 내용, 깨진 가정(해당 시), 제안 조치. 이미 커밋된 Task는 되돌리지 않고 보고에 포함한다 — 재승인 결과에 따라 후속 Task로 수정한다. ### Phase 4: 마감 -모든 Task 완료 후: +모든 Task 커밋 후 생명주기 꼬리는 이 순서다: **통합 테스트(`/test`, 필요 시) → `/verify full` → plan 문서 완료 스탬프·`plan/complete/` 이동 → push → PR**. 마감(스탬프·이동)은 verify PASS 뒤에 온다. -1. `/verify standard` (컴파일+단위+빌드) — 푸시·PR 전에는 `/verify full` (통합 포함) -2. plan 문서에 완료 스탬프, `plan/complete/`로 이동 -3. **scope에 따라**: `push` 선언 시 푸시. `pr` 선언 시 PR 오픈 — **PR 본문이 체인지로그를 겸한다** (변경 사항·배경·검증·제외 범위 구조, 기존 PR 관례를 따른다). 미선언이면 해당 직전에서 정지하고 확인받되, 정지 보고에 "푸시 전 `/verify full` 필요"를 명시한다 (scope가 commit에서 끝나면 full이 아직 안 돌았을 수 있다). +- **step-by-step**: 여기서 정지하고 `Next: /test`(통합 테스트가 필요하면) 또는 `Next: /verify full`을 제안한다. `/test`·`/verify`의 풀 워크플로는 별도 호출 경계다 — 이 스킬 안에서 실행하지 않는다. 마감·푸시·PR은 verify PASS 후 사용자 지시로 진행한다. +- **delegated**: scope에 선언된 단계를 위 순서대로 이어 실행한다 (위임 계약의 이행 — 지침의 격리 규칙 예외). scope의 `test`는 `/test`의 시나리오 승인 게이트를 체크포인트 보고로 대체해 수행하고, `verify`는 full까지 돌린다. 마감(스탬프·`plan/complete/` 이동)은 scope 항목이 아니라 verify PASS 후 항상 수행하는 생명주기 꼬리다. `push`/`pr`는 선언 시에만 — **PR 본문이 체인지로그를 겸한다** (변경 사항·배경·검증·제외 범위 구조). 미선언이면 해당 직전에서 정지하고, 남은 단계와 검증 상태(full 통과 여부)를 보고한다. ## Common Rationalizations @@ -91,4 +90,4 @@ Task 하나마다: ## 종료 -지침의 **GSL Execution Mode** 규칙을 따른다. step-by-step이면 Task 보고 후 턴 종료, 다음은 `Next: /implement (Task N+1)` 또는 전 Task 완료 시 `Next: /test`. delegated면 Phase 4까지 계속한다. +지침의 **GSL Execution Mode** 규칙을 따른다. step-by-step이면 Task 보고 후 턴 종료, 다음은 `Next: /implement (Task N+1)`, 전 Task 완료 시에는 Phase 4의 순서(`/test` → `/verify full` → 마감)를 안내한다. delegated면 Phase 4를 scope대로 이어 실행한다. diff --git a/.claude/skills/plan/SKILL.md b/.claude/skills/plan/SKILL.md index cf079e2cc..3fb74b3cc 100644 --- a/.claude/skills/plan/SKILL.md +++ b/.claude/skills/plan/SKILL.md @@ -22,9 +22,9 @@ argument-hint: "[feature-name or plan file path]" ## 가정 붕괴 시 즉시 중단 (전역 규칙) -어느 Step에서든 define의 Assumption을 깨는 발견이 나오면 **그 자리에서 중단한다**. Tasks를 아직 기록하지 않았으면 기록하지 않고, 이미 기록했다면 해당 기록을 되돌린다. 이것이 stop-condition 1번(가정 붕괴)이며 delegated 위임보다 우선한다. +어느 Step에서든 define의 Assumption을 깨는 발견이 나오면 **그 자리에서 중단한다**. Tasks를 아직 기록하지 않았으면 기록하지 않고, 이미 기록했다면 해당 기록을 되돌린다 (아직 승인되지 않은 기록에 한함 — 승인·커밋된 Task의 처리는 `/implement`의 정지 보고 규칙을 따른다). 이것이 stop-condition 1번(가정 붕괴)이며 delegated 위임보다 우선한다. -중단 후 절차(재개봉 프로토콜): ① 발견 내용과 깨진 가정을 보고한다 ② define 문서의 Assumptions 수정안을 제시한다 ③ WHAT(성공 기준·범위)이 바뀌었으면 재승인을 받고, 표현만 정밀해진 것이면 기록만 남기고 계속한다. +중단 후 절차(재개봉 프로토콜): ① 발견 내용과 깨진 가정을 보고한다 ② define 문서의 Assumptions 수정안을 제시하고 반영한다 ③ WHAT(성공 기준·범위)이 바뀌었으면 재승인을 받고, 표현만 정밀해진 것이면 기록만 남기고 계속한다. ## When NOT to Use @@ -96,7 +96,7 @@ plan 문서에 Tasks 섹션을 채운다. ### Step 6: 게이트 (모드 분기) -- **step-by-step**: 태스크 목록 요약(개수·크기 분포·의존 순서)을 제시하고 명시적 승인을 받은 뒤 턴을 끝낸다. 승인 전 `/implement` 진행 금지. **승인이 거부되면**: 거부 사유를 받아 Step 2부터 재분해한다. 사유가 분해 방식이 아니라 요구사항 자체라면 재개봉 프로토콜로 `/define`에 돌린다. +- **step-by-step**: 태스크 목록 요약(개수·크기 분포·의존 순서)을 제시하고 턴을 끝낸다. 사용자의 승인 메시지가 오면 그것이 곧 `/implement` 진행 허가다 — 재차 제안만 하고 멈추지 않는다. 승인 전 `/implement` 진행 금지. **승인이 거부되면**: 거부 사유를 받아 Step 2부터 재분해한다. 사유가 분해 방식이 아니라 요구사항 자체라면 재개봉 프로토콜로 `/define`에 돌린다. - **delegated (scope에 plan 포함)**: 승인 권한은 define 게이트에서 이미 위임받았다. 목록을 체크포인트 보고로 남기고 다음 단계로 계속한다. 단, 분해 중 가정을 깨는 발견이 있었다면 stop-condition 1번(재개봉)이 우선한다. ## Common Rationalizations @@ -117,4 +117,4 @@ plan 문서에 Tasks 섹션을 채운다. ## 종료 -지침의 **GSL Execution Mode** 규칙을 따른다. step-by-step이면 승인 후 `Next: /implement`를 제안하고 턴을 끝낸다. delegated면 보고를 남기고 계속한다. +지침의 **GSL Execution Mode** 규칙을 따른다. step-by-step이면 목록 제시 후 턴을 끝내고, 이후 도착한 승인 메시지가 곧 `/implement` 진행 허가다 (Step 6과 동일 규칙). delegated면 보고를 남기고 계속한다. diff --git a/AGENTS.md b/AGENTS.md index 40b18dee2..259dca8cf 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -271,7 +271,7 @@ GSL 스킬(define/plan/implement/test/verify/debug/self-review)은 `plan/{기능 - 각 GSL 스킬은 자기 작업과 종료 보고를 마치면 **턴을 끝낸다**. 다음 스킬 실행에는 사용자의 새 메시지가 필요하다. - `/implement`는 Task 하나를 커밋하고 보고한 뒤 정지한다. 예외: 사용자가 여러 Task의 연속 실행을 명시적으로 지정한 경우("Task 1~3 진행해", "결과까지 진행해")에만 이어서 진행한다. -- 애매한 "계속", "continue"는 다음 명령을 안내하라는 뜻이지 실행 허가가 아니다. +- 애매한 "계속", "continue"는 **다음 스킬로의 전환** 허가가 아니다 — 다음 명령을 안내하라는 뜻으로 해석한다. 단, 진행 중인 스킬 내부의 재개 신호는 그 스킬의 규정을 따른다 (`/implement`: 다음 Task 1개만 허가). - 종료 보고에는 다음 권장 명령을 한 줄로 제안한다 (실행하지 않는다). - 스킬 하나가 다른 스킬의 전체 워크플로를 내부에서 실행하지 않는다 (`/implement`가 Task 곁에 테스트 코드를 쓰는 것은 허용, `/test`·`/verify`·`/self-review`의 풀 워크플로 실행은 금지). @@ -286,11 +286,11 @@ GSL 스킬(define/plan/implement/test/verify/debug/self-review)은 `plan/{기능 - stop-conditions: 기본 3종 (+ 추가 조건) ``` -- scope에 포함된 단계는 단계 간 승인 없이 자율 진행한다. 단 Task마다 Progress Log 기록과 체크포인트 보고는 남긴다. +- scope에 포함된 단계는 생명주기 순서(plan → implement → test → verify → commit → push → pr)대로 단계 간 승인 없이 이어 실행한다. 이는 위임 계약의 이행이므로 step-by-step의 스킬 격리 규칙이 적용되지 않는다. 단 Task마다 Progress Log 기록과 체크포인트 보고는 남긴다. - `push`가 scope에 없으면 커밋까지만 하고 푸시 직전에 정지한다. `pr`이 있으면 PR 오픈과 본문 작성까지 수행한다 — **이 저장소는 PR 본문이 체인지로그를 겸한다.** ### stop-conditions (모드 무관, 무조건 정지) 1. **가정 붕괴** — 작업 중 발견이 define의 Assumption을 깨면 즉시 정지하고 재개봉 프로토콜(define 수정 → WHAT이 바뀌었으면 재승인)을 따른다. 조용히 적응하지 않는다. 2. **verify 반복 실패** — `/verify` 실패를 3회 시도 안에 해결하지 못하면 `/debug` 결과를 보고하고 정지한다. -3. **scope 밖 행동** — 선언된 scope 밖의 되돌리기 어려운 행동(푸시, PR 오픈, 파일 대량 삭제, 인프라 변경)이 필요해지면 그 직전에 정지하고 확인받는다. +3. **scope 밖 행동** — 선언된 scope 밖의 되돌리기 어려운 행동(푸시, PR 오픈, 파일 대량 삭제, 인프라 변경)이 필요해지면 그 직전에 정지하고 확인받는다. step-by-step에서 scope는 사용자가 명시적으로 승인한 작업 범위를 뜻한다. diff --git a/CLAUDE.md b/CLAUDE.md index 21115c6a8..af9a9ef0c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -271,7 +271,7 @@ GSL 스킬(define/plan/implement/test/verify/debug/self-review)은 `plan/{기능 - 각 GSL 스킬은 자기 작업과 종료 보고를 마치면 **턴을 끝낸다**. 다음 스킬 실행에는 사용자의 새 메시지가 필요하다. - `/implement`는 Task 하나를 커밋하고 보고한 뒤 정지한다. 예외: 사용자가 여러 Task의 연속 실행을 명시적으로 지정한 경우("Task 1~3 진행해", "결과까지 진행해")에만 이어서 진행한다. -- 애매한 "계속", "continue"는 다음 명령을 안내하라는 뜻이지 실행 허가가 아니다. +- 애매한 "계속", "continue"는 **다음 스킬로의 전환** 허가가 아니다 — 다음 명령을 안내하라는 뜻으로 해석한다. 단, 진행 중인 스킬 내부의 재개 신호는 그 스킬의 규정을 따른다 (`/implement`: 다음 Task 1개만 허가). - 종료 보고에는 다음 권장 명령을 한 줄로 제안한다 (실행하지 않는다). - 스킬 하나가 다른 스킬의 전체 워크플로를 내부에서 실행하지 않는다 (`/implement`가 Task 곁에 테스트 코드를 쓰는 것은 허용, `/test`·`/verify`·`/self-review`의 풀 워크플로 실행은 금지). @@ -286,11 +286,11 @@ GSL 스킬(define/plan/implement/test/verify/debug/self-review)은 `plan/{기능 - stop-conditions: 기본 3종 (+ 추가 조건) ``` -- scope에 포함된 단계는 단계 간 승인 없이 자율 진행한다. 단 Task마다 Progress Log 기록과 체크포인트 보고는 남긴다. +- scope에 포함된 단계는 생명주기 순서(plan → implement → test → verify → commit → push → pr)대로 단계 간 승인 없이 이어 실행한다. 이는 위임 계약의 이행이므로 step-by-step의 스킬 격리 규칙이 적용되지 않는다. 단 Task마다 Progress Log 기록과 체크포인트 보고는 남긴다. - `push`가 scope에 없으면 커밋까지만 하고 푸시 직전에 정지한다. `pr`이 있으면 PR 오픈과 본문 작성까지 수행한다 — **이 저장소는 PR 본문이 체인지로그를 겸한다.** ### stop-conditions (모드 무관, 무조건 정지) 1. **가정 붕괴** — 작업 중 발견이 define의 Assumption을 깨면 즉시 정지하고 재개봉 프로토콜(define 수정 → WHAT이 바뀌었으면 재승인)을 따른다. 조용히 적응하지 않는다. 2. **verify 반복 실패** — `/verify` 실패를 3회 시도 안에 해결하지 못하면 `/debug` 결과를 보고하고 정지한다. -3. **scope 밖 행동** — 선언된 scope 밖의 되돌리기 어려운 행동(푸시, PR 오픈, 파일 대량 삭제, 인프라 변경)이 필요해지면 그 직전에 정지하고 확인받는다. +3. **scope 밖 행동** — 선언된 scope 밖의 되돌리기 어려운 행동(푸시, PR 오픈, 파일 대량 삭제, 인프라 변경)이 필요해지면 그 직전에 정지하고 확인받는다. step-by-step에서 scope는 사용자가 명시적으로 승인한 작업 범위를 뜻한다. diff --git a/plan/gsl-overhaul.md b/plan/complete/gsl-overhaul.md similarity index 91% rename from plan/gsl-overhaul.md rename to plan/complete/gsl-overhaul.md index 0cd517618..6221ab727 100644 --- a/plan/gsl-overhaul.md +++ b/plan/complete/gsl-overhaul.md @@ -117,7 +117,7 @@ GSL(Guided Software Lifecycle) 9개 스킬을 이 저장소 전용으로 대폭 - Verification: `diff -rq` 미러 일치, 지침 정규화 diff 일치, `du -sk` 전후 비교, E2E 서브에이전트 보고서 - Files (advisory): 수정 없음(검증 전용), 필요 시 발견 결함 수정 - Size: S -- Status: [ ] not done +- Status: [x] done ## Progress Log @@ -127,3 +127,10 @@ GSL(Guided Software Lifecycle) 9개 스킬을 이 저장소 전용으로 대폭 - Task 4: /plan 재작성 — 정육 모델(Task=관절=승인 대상, Slice=썰기=에이전트 전권), 분해→정렬→크기검산 순서, Files advisory 강등, Depends 필드 신설. 시나리오 검증(scenario-plan): 정상 완주 확인, 치명 3건 검출(재개봉 프로토콜 미정의 / 사용자 수평 요청 충돌·승인 거부 분기 부재 / 중단 지점이 Step 6에만). "가정 붕괴 시 즉시 중단" 전역 규칙 신설 + 수평 요청 특칙(1회 권고→재지시 시 사용자 결정) + 승인 거부 분기로 수정, 재검증 결과 3건 전부 해소·신규 치명 0건. 경미 4·5·6·8도 반영. - Task 5: /implement 재작성 — Execution Mode 분기(step은 Task별 정지+연속 실행 예외, delegated는 체크포인트 보고로 계속), stop-conditions 우선 규칙, Phase 4에 push/pr·PR 본문=체인지로그 공식화, Polyglot·언어 fallback 제거. 시나리오 검증(scenario-implement) 4건: step 정지·delegated 연속+푸시 직전 정지·가정 붕괴 정지·plan 부재 거부 모두 텍스트만으로 도출, 치명 0건. 경미 5건(재개 신호 해석, 모드 읽기 위치, 정지 보고 명세, 체크포인트 도달 지점, verify 레벨) 반영 후 검증자 재확인 완료. Checkpoint 3-5 통과. - Task 6: test/verify/debug/self-review 정리 — HARD STOP·Lifecycle Integration 꼬리를 "종료" 섹션(지침 참조 + 모드 분기 한 줄)으로 교체, Superpowers 문단 제거(Fake-first 우선 규칙은 유지), 언어 분기 제거(references를 java.md/java-gradle.md/java-spring.md 고정), test·verify·debug·self-review 트리거에 상태 기반 조건 추가. web-api.md·batch.md·bottlenote-patterns.md의 conventions.md·{language} 잔존 참조도 정리. grep 검증: Superpowers/Polyglot/{language}/{your-*}/next-flow/scan-conventions/conventions.md 전부 0건. +- Task 7: 종합 검증 — E2E 시나리오(scenario-e2e, 4개 텍스트 교차 검증)에서 문서 간 상충 치명 3건 검출("계속" 해석이 지침·implement에서 정반대 / delegated의 test 진입점 부재·마감 순서 역전 / verify 내부 실행 vs 격리 규칙). 지침에 스킬 전환·스킬 내부 재개 구분과 delegated 격리 예외를 명시하고 implement Phase 4를 test→verify full→마감→push→PR 순서로 재작성해 해소, 재판정에서 3건 전부 [해소]. 재판정이 신규 치명 1건(plan Step 6 vs 종료 절 불일치 — 1차 수정의 잔여물)과 경미 2건을 추가 검출, 모두 반영. 신규 치명은 두 문장의 문언 정합으로 자체 검증(기계적 일치 확인). Success Criteria 13개 전 항목 통과: 스킬 7종, references 6종, Superpowers·Polyglot·HARD STOP 0건, 미러·지침 diff 일치, Skills 표 7행 정합, 용량 276KB→132KB(47.8%). + +## Completion + +- Status: COMPLETE +- 시나리오 검증 총계: 서브에이전트 4종(define/plan/implement/E2E), 검출 치명 7건(전부 해소·재검증 통과), 경미 19건(17건 반영, 2건은 의도된 외부 참조로 유지) +- 최종 산출: 스킬 9→7종, 미러 용량 276KB→132KB, Execution Mode 계약(step-by-step 기본/delegated 선언) 도입, 재개봉 프로토콜·stop-conditions 3종 명문화