@simplysm/sd-claude 13.0.77 → 13.0.80

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/claude/rules/sd-claude-rules.md +4 -63
  2. package/claude/rules/sd-simplysm-usage.md +7 -0
  3. package/claude/sd-session-start.sh +10 -0
  4. package/claude/skills/sd-api-review/SKILL.md +89 -0
  5. package/claude/skills/sd-check/SKILL.md +55 -57
  6. package/claude/skills/sd-commit/SKILL.md +37 -42
  7. package/claude/skills/sd-debug/SKILL.md +75 -265
  8. package/claude/skills/sd-document/SKILL.md +63 -53
  9. package/claude/skills/sd-document/_common.py +94 -0
  10. package/claude/skills/sd-document/extract_docx.py +19 -48
  11. package/claude/skills/sd-document/extract_pdf.py +22 -50
  12. package/claude/skills/sd-document/extract_pptx.py +17 -40
  13. package/claude/skills/sd-document/extract_xlsx.py +19 -40
  14. package/claude/skills/sd-email-analyze/SKILL.md +23 -31
  15. package/claude/skills/sd-email-analyze/email-analyzer.py +79 -65
  16. package/claude/skills/sd-init/SKILL.md +133 -0
  17. package/claude/skills/sd-plan/SKILL.md +69 -120
  18. package/claude/skills/sd-readme/SKILL.md +106 -131
  19. package/claude/skills/sd-review/SKILL.md +38 -155
  20. package/claude/skills/sd-simplify/SKILL.md +59 -0
  21. package/package.json +3 -2
  22. package/README.md +0 -297
  23. package/claude/refs/sd-angular.md +0 -127
  24. package/claude/refs/sd-code-conventions.md +0 -155
  25. package/claude/refs/sd-directories.md +0 -7
  26. package/claude/refs/sd-library-issue.md +0 -7
  27. package/claude/refs/sd-migration.md +0 -7
  28. package/claude/refs/sd-orm-v12.md +0 -81
  29. package/claude/refs/sd-orm.md +0 -23
  30. package/claude/refs/sd-service.md +0 -5
  31. package/claude/refs/sd-simplysm-docs.md +0 -52
  32. package/claude/refs/sd-solid.md +0 -68
  33. package/claude/refs/sd-workflow.md +0 -25
  34. package/claude/rules/sd-refs-linker.md +0 -52
  35. package/claude/sd-statusline.js +0 -296
  36. package/claude/skills/sd-api-name-review/SKILL.md +0 -154
  37. package/claude/skills/sd-brainstorm/SKILL.md +0 -215
  38. package/claude/skills/sd-debug/condition-based-waiting-example.ts +0 -158
  39. package/claude/skills/sd-debug/condition-based-waiting.md +0 -114
  40. package/claude/skills/sd-debug/defense-in-depth.md +0 -128
  41. package/claude/skills/sd-debug/find-polluter.sh +0 -64
  42. package/claude/skills/sd-debug/root-cause-tracing.md +0 -168
  43. package/claude/skills/sd-discuss/SKILL.md +0 -91
  44. package/claude/skills/sd-explore/SKILL.md +0 -118
  45. package/claude/skills/sd-plan-dev/SKILL.md +0 -294
  46. package/claude/skills/sd-plan-dev/code-quality-reviewer-prompt.md +0 -49
  47. package/claude/skills/sd-plan-dev/final-review-prompt.md +0 -50
  48. package/claude/skills/sd-plan-dev/implementer-prompt.md +0 -60
  49. package/claude/skills/sd-plan-dev/spec-reviewer-prompt.md +0 -45
  50. package/claude/skills/sd-review/api-reviewer-prompt.md +0 -75
  51. package/claude/skills/sd-review/code-reviewer-prompt.md +0 -82
  52. package/claude/skills/sd-review/convention-checker-prompt.md +0 -61
  53. package/claude/skills/sd-review/refactoring-analyzer-prompt.md +0 -92
  54. package/claude/skills/sd-skill/SKILL.md +0 -417
  55. package/claude/skills/sd-skill/anthropic-best-practices.md +0 -156
  56. package/claude/skills/sd-skill/cso-guide.md +0 -161
  57. package/claude/skills/sd-skill/examples/CLAUDE_MD_TESTING.md +0 -200
  58. package/claude/skills/sd-skill/persuasion-principles.md +0 -220
  59. package/claude/skills/sd-skill/testing-skills-with-subagents.md +0 -408
  60. package/claude/skills/sd-skill/writing-guide.md +0 -159
  61. package/claude/skills/sd-tdd/SKILL.md +0 -385
  62. package/claude/skills/sd-tdd/testing-anti-patterns.md +0 -317
  63. package/claude/skills/sd-use/SKILL.md +0 -67
  64. package/claude/skills/sd-worktree/SKILL.md +0 -78
@@ -4,7 +4,6 @@
4
4
  import sys
5
5
  import os
6
6
  import io
7
- import subprocess
8
7
  import email
9
8
  import html
10
9
  import re
@@ -19,6 +18,7 @@ sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8", errors="repla
19
18
 
20
19
  def ensure_packages():
21
20
  """Auto-install required packages."""
21
+ import subprocess
22
22
  packages = {"extract-msg": "extract_msg"}
23
23
  missing = []
24
24
  for pip_name, import_name in packages.items():
@@ -35,11 +35,6 @@ def ensure_packages():
35
35
  )
36
36
 
37
37
 
38
- ensure_packages()
39
-
40
- import extract_msg # noqa: E402
41
-
42
-
43
38
  # ── Korean charset helpers ──────────────────────────────────────────
44
39
 
45
40
  KOREAN_CHARSET_MAP = {
@@ -81,39 +76,38 @@ def _parse_eml(filepath):
81
76
 
82
77
  body_plain = ""
83
78
  body_html = ""
79
+ attachments = []
80
+ inline_images = []
81
+ seen_cids = set()
84
82
 
85
- if msg.is_multipart():
86
- for part in msg.walk():
87
- ctype = part.get_content_type()
88
- cdisp = part.get_content_disposition()
89
- if cdisp == "attachment":
90
- continue
91
- if ctype == "text/plain" and not body_plain:
92
- body_plain = _get_text_eml(part)
93
- elif ctype == "text/html" and not body_html:
94
- body_html = _get_text_eml(part)
95
- else:
83
+ if not msg.is_multipart():
96
84
  ctype = msg.get_content_type()
97
85
  if ctype == "text/html":
98
86
  body_html = _get_text_eml(msg)
99
87
  else:
100
88
  body_plain = _get_text_eml(msg)
101
-
102
- attachments = []
103
- inline_images = []
104
- seen_cids = set()
89
+ return headers, body_plain, body_html, attachments, inline_images
105
90
 
106
91
  for part in msg.walk():
92
+ ctype = part.get_content_type()
93
+ cdisp = part.get_content_disposition()
107
94
  payload = part.get_payload(decode=True)
95
+
96
+ # Body extraction (non-attachment text parts)
97
+ if cdisp != "attachment" and payload is not None:
98
+ if ctype == "text/plain" and not body_plain:
99
+ body_plain = _get_text_eml(part)
100
+ elif ctype == "text/html" and not body_html:
101
+ body_html = _get_text_eml(part)
102
+
108
103
  if payload is None:
109
104
  continue
110
105
 
111
106
  content_id = (part.get("Content-ID") or "").strip("<> ")
112
- ctype = part.get_content_type()
113
107
  filename = part.get_filename()
114
108
 
115
- # Inline image: Content-ID + image type
116
- if content_id and ctype.startswith("image/"):
109
+ # Inline image
110
+ if _is_inline_image(content_id, ctype):
117
111
  if content_id not in seen_cids:
118
112
  seen_cids.add(content_id)
119
113
  ext = _guess_image_ext(ctype, filename)
@@ -126,10 +120,9 @@ def _parse_eml(filepath):
126
120
  })
127
121
  continue
128
122
 
129
- # Regular attachment: has filename
123
+ # Regular attachment
130
124
  if not filename:
131
125
  continue
132
- cdisp = part.get_content_disposition()
133
126
  if cdisp not in ("attachment", "inline", None):
134
127
  continue
135
128
  attachments.append({
@@ -143,6 +136,9 @@ def _parse_eml(filepath):
143
136
 
144
137
 
145
138
  def _parse_msg(filepath):
139
+ ensure_packages()
140
+ import extract_msg
141
+
146
142
  msg = extract_msg.openMsg(filepath)
147
143
  try:
148
144
  headers = {
@@ -186,7 +182,7 @@ def _parse_msg(filepath):
186
182
  "data": data,
187
183
  }
188
184
 
189
- if cid and mimetype.startswith("image/"):
185
+ if _is_inline_image(cid, mimetype):
190
186
  entry["content_id"] = cid.strip("<> ")
191
187
  inline_images.append(entry)
192
188
  else:
@@ -224,6 +220,25 @@ def _guess_image_ext(content_type, filename=None):
224
220
  return mapping.get(content_type, ".bin")
225
221
 
226
222
 
223
+ def _is_inline_image(content_id, content_type):
224
+ """Check if a MIME part is an inline image (has Content-ID + image MIME type)."""
225
+ return bool(content_id) and content_type.startswith("image/")
226
+
227
+
228
+ # ── Pre-compiled regexes ───────────────────────────────────────────
229
+
230
+ _RE_DATA_URI = re.compile(
231
+ r'<img[^>]+src=["\']data:image/([^;]+);base64,([^"\']+)["\']',
232
+ re.IGNORECASE,
233
+ )
234
+ _RE_STYLE = re.compile(r"<style[^>]*>.*?</style>", re.DOTALL | re.I)
235
+ _RE_SCRIPT = re.compile(r"<script[^>]*>.*?</script>", re.DOTALL | re.I)
236
+ _RE_BR = re.compile(r"<br\s*/?>", re.I)
237
+ _RE_BLOCK_CLOSE = re.compile(r"</(?:p|div|tr|li)>", re.I)
238
+ _RE_TAG = re.compile(r"<[^>]+>")
239
+ _RE_MULTI_NEWLINE = re.compile(r"\n{3,}")
240
+
241
+
227
242
  # ── File saving ────────────────────────────────────────────────────
228
243
 
229
244
 
@@ -232,10 +247,10 @@ def save_files(files, output_dir):
232
247
  os.makedirs(output_dir, exist_ok=True)
233
248
  result = []
234
249
  for f in files:
250
+ stem = Path(f["filename"]).stem
251
+ ext = Path(f["filename"]).suffix
235
252
  filepath = os.path.join(output_dir, f["filename"])
236
253
  if os.path.exists(filepath):
237
- stem = Path(filepath).stem
238
- ext = Path(filepath).suffix
239
254
  n = 1
240
255
  while os.path.exists(filepath):
241
256
  filepath = os.path.join(output_dir, f"{stem}_{n}{ext}")
@@ -248,44 +263,41 @@ def save_files(files, output_dir):
248
263
 
249
264
  def extract_data_uri_images(html_body, output_dir):
250
265
  """Extract base64 data URI images embedded in HTML body."""
251
- pattern = r'<img[^>]+src=["\']data:image/([^;]+);base64,([^"\']+)["\']'
252
- matches = re.findall(pattern, html_body, re.IGNORECASE)
266
+ matches = _RE_DATA_URI.findall(html_body)
253
267
  if not matches:
254
268
  return []
255
269
 
256
- os.makedirs(output_dir, exist_ok=True)
257
270
  images = []
258
271
  for i, (img_type, b64data) in enumerate(matches, 1):
259
272
  try:
260
273
  data = base64.b64decode(b64data)
261
- ext_map = {"jpeg": ".jpg", "svg+xml": ".svg"}
262
- ext = ext_map.get(img_type, f".{img_type}")
263
- filename = f"datauri_{i}{ext}"
264
- filepath = os.path.join(output_dir, filename)
265
- with open(filepath, "wb") as f:
266
- f.write(data)
274
+ content_type = f"image/{img_type}"
275
+ ext = _guess_image_ext(content_type)
267
276
  images.append({
268
- "filename": filename,
269
- "content_type": f"image/{img_type}",
277
+ "filename": f"datauri_{i}{ext}",
278
+ "content_type": content_type,
270
279
  "size": len(data),
271
- "saved_path": filepath,
280
+ "data": data,
272
281
  })
273
282
  except Exception:
274
283
  continue
275
- return images
284
+
285
+ if not images:
286
+ return []
287
+ return save_files(images, output_dir)
276
288
 
277
289
 
278
290
  # ── HTML stripping ──────────────────────────────────────────────────
279
291
 
280
292
 
281
293
  def strip_html(text):
282
- text = re.sub(r"<style[^>]*>.*?</style>", "", text, flags=re.DOTALL | re.I)
283
- text = re.sub(r"<script[^>]*>.*?</script>", "", text, flags=re.DOTALL | re.I)
284
- text = re.sub(r"<br\s*/?>", "\n", text, flags=re.I)
285
- text = re.sub(r"</(?:p|div|tr|li)>", "\n", text, flags=re.I)
286
- text = re.sub(r"<[^>]+>", "", text)
294
+ text = _RE_STYLE.sub("", text)
295
+ text = _RE_SCRIPT.sub("", text)
296
+ text = _RE_BR.sub("\n", text)
297
+ text = _RE_BLOCK_CLOSE.sub("\n", text)
298
+ text = _RE_TAG.sub("", text)
287
299
  text = html.unescape(text)
288
- text = re.sub(r"\n{3,}", "\n\n", text)
300
+ text = _RE_MULTI_NEWLINE.sub("\n\n", text)
289
301
  return text.strip()
290
302
 
291
303
 
@@ -303,6 +315,23 @@ def fmt_size(n):
303
315
  # ── Markdown report ─────────────────────────────────────────────────
304
316
 
305
317
 
318
+ def _render_file_table(title, items):
319
+ """Render a Markdown table of files with #, Filename, Size, Saved path columns."""
320
+ if not items:
321
+ return []
322
+ lines = [
323
+ f"## {title}\n",
324
+ "| # | Filename | Size | Saved path |",
325
+ "|---|--------|------|-----------|",
326
+ ]
327
+ for i, item in enumerate(items, 1):
328
+ lines.append(
329
+ f"| {i} | {item['filename']} | {fmt_size(item['size'])} | `{item['saved_path']}` |"
330
+ )
331
+ lines.append("")
332
+ return lines
333
+
334
+
306
335
  def build_report(filepath):
307
336
  headers, body_plain, body_html, attachments, inline_images = parse_email(filepath)
308
337
 
@@ -352,23 +381,8 @@ def build_report(filepath):
352
381
  out.append(body.strip() if body else "_(No body)_")
353
382
  out.append("")
354
383
 
355
- # ── Inline images
356
- if all_inline:
357
- out.append("## Inline Images\n")
358
- out.append("| # | Filename | Size | Saved path |")
359
- out.append("|---|--------|------|-----------|")
360
- for i, img in enumerate(all_inline, 1):
361
- out.append(f"| {i} | {img['filename']} | {fmt_size(img['size'])} | `{img['saved_path']}` |")
362
- out.append("")
363
-
364
- # ── Attachments
365
- if saved_attachments:
366
- out.append("## Attachments\n")
367
- out.append("| # | Filename | Size | Saved path |")
368
- out.append("|---|--------|------|-----------|")
369
- for i, a in enumerate(saved_attachments, 1):
370
- out.append(f"| {i} | {a['filename']} | {fmt_size(a['size'])} | `{a['saved_path']}` |")
371
- out.append("")
384
+ out.extend(_render_file_table("Inline Images", all_inline))
385
+ out.extend(_render_file_table("Attachments", saved_attachments))
372
386
 
373
387
  return "\n".join(out)
374
388
 
@@ -0,0 +1,133 @@
1
+ ---
2
+ name: sd-init
3
+ description: "초기화", "init", "sd-init", "CLAUDE.md 생성" 등을 요청할 때 사용.
4
+ ---
5
+
6
+ # SD Init — CLAUDE.md 자동 생성
7
+
8
+ 프로젝트를 분석하여 CLAUDE.md를 자동 생성한다. 기존 파일이 있으면 덮어쓴다.
9
+
10
+ ---
11
+
12
+ ## Step 1: 패키지 매니저 감지
13
+
14
+ 프로젝트 루트의 lock 파일로 PM을 판별하라:
15
+
16
+ 1. `pnpm-lock.yaml` → pnpm
17
+ 2. `yarn.lock` → yarn
18
+ 3. 그외 → npm
19
+
20
+ ## Step 2: 스크립트 분석
21
+
22
+ `package.json`의 `scripts`를 읽고, 각 스크립트가 호출하는 CLI 도구에 대해 Bash로 `--help`등의 도움말 보기를 실행하여 사용 가능한 인자와 플래그를 파악하라.
23
+
24
+ 파악한 정보를 바탕으로 스크립트를 카테고리별(개발, 빌드, 테스트, 린트 등)로 그룹화하고, 각 스크립트의 기본 사용법과 주요 플래그 예시를 정리하라.
25
+
26
+ ## Step 3: 코딩 규칙 분석
27
+
28
+ 프로젝트 루트에서 아래 설정 파일들을 찾아 읽어라 (존재하는 것만):
29
+
30
+ - ESLint: `eslint.config.*`, `.eslintrc.*`, `packages/*/eslint.*` 등
31
+ - Prettier: `.prettierrc*`, `prettier.config.*`
32
+ - EditorConfig: `.editorconfig`
33
+ - TypeScript: `tsconfig.json` (루트)의 `compilerOptions` 중 `strict`, `noImplicitAny` 등 코드 스타일에 영향을 주는 옵션
34
+ - Stylelint: `.stylelintrc*`, `stylelint.config.*`
35
+
36
+ Claude가 규칙과 반대로 수정하기를 제안할 정도의 자주 실수할 내용들만 대폭 간결하게 정리하라.
37
+
38
+ ## Step 4: CLAUDE.md 생성
39
+
40
+ 아래 정보를 종합하여 프로젝트 루트에 `CLAUDE.md`를 작성하라:
41
+
42
+ - **프로젝트 정보**: `package.json`의 `name`, `description`
43
+ - **PM**: Step 1에서 감지한 패키지 매니저
44
+ - **모노레포 구조**: `workspaces` 필드 또는 `pnpm-workspace.yaml`이 있으면 워크스페이스 경로를 간단히 기술
45
+ - **기술스택**: `dependencies`/`devDependencies`에서 주요 기술(프레임워크, 번들러, 테스트 도구 등)을 파악하여 아주 간단히 기술
46
+ - **명령어**: Step 2에서 정리한 스크립트 사용법
47
+ - **코딩 규칙**: Step 3에서 분석한 규칙 중 Claude가 지켜야 할 것들. `## 코딩 규칙` 섹션으로 작성
48
+
49
+ ### 참고 예시
50
+
51
+ 아래는 잘 작성된 CLAUDE.md의 예시다. 형식을 그대로 복사하지 말고, 프로젝트 특성에 맞게 유연하게 작성하라.
52
+
53
+ ```markdown
54
+ # Simplysm
55
+
56
+ pnpm 모노레포. 패키지 경로: `packages/*`, 테스트: `tests/*`
57
+
58
+ ## 명령어
59
+
60
+ 모든 명령어는 내부적으로 `pnpm sd-cli <명령>`을 실행한다. 모든 명령에 `--debug` 플래그 사용 가능.
61
+ `[targets..]`를 지정하지 않으면 `sd.config.ts`에 정의된 전체 패키지 대상으로 실행된다.
62
+ 타겟은 패키지 경로로 지정한다 (예: `packages/core-common`, `tests/orm`).
63
+
64
+ ### 개발
65
+
66
+ ​```bash
67
+ pnpm dev [targets..] # client+server 패키지 개발 모드 실행
68
+ pnpm dev packages/solid-demo # 특정 패키지만 dev 모드
69
+ pnpm dev -o key=value # sd.config.ts에 옵션 전달
70
+
71
+ pnpm watch [targets..] # 라이브러리 패키지 빌드 워치 모드
72
+ pnpm watch packages/core-common # 특정 패키지만 워치
73
+ ​```
74
+
75
+ ### 빌드 & 배포
76
+
77
+ ​```bash
78
+ pnpm build [targets..] # 프로덕션 빌드
79
+ pnpm build packages/solid # 특정 패키지만 빌드
80
+
81
+ pnpm pub [targets..] # 빌드 후 배포 (npm/sftp)
82
+ pnpm pub --no-build # 빌드 생략하고 배포만
83
+ pnpm pub --dry-run # 실제 배포 없이 시뮬레이션
84
+ ​```
85
+
86
+ ### 코드 품질 검사
87
+
88
+ ​```bash
89
+ pnpm typecheck [targets..] # TypeScript 타입 체크
90
+ pnpm lint [targets..] # ESLint + Stylelint 실행
91
+ pnpm lint:fix [targets..] # 린트 자동 수정 (--fix)
92
+ pnpm check [targets..] # 전체 검사 (typecheck + lint + test 병렬)
93
+ pnpm vitest [targets..] # vitest 워치 모드
94
+ pnpm vitest run [targets..] # 테스트 1회 실행
95
+ ​```
96
+
97
+ ## 아키텍처
98
+
99
+ 의존 방향: 위 → 아래. `core-common`은 내부 의존 없는 leaf 패키지.
100
+
101
+ ​```
102
+ Apps: solid-demo (client) / solid-demo-server (server)
103
+ UI: solid (SolidJS + Tailwind)
104
+ Service: service-server (Fastify) / service-client / service-common
105
+ ORM: orm-node (MySQL/PostgreSQL/MSSQL) / orm-common
106
+ Core: core-common (neutral) / core-browser / core-node
107
+ Tools: sd-cli, lint, excel, storage, sd-claude, mcp-playwright
108
+ ​```
109
+
110
+
111
+ ## 통합 테스트
112
+
113
+ `tests/` 폴더에 위치. `pnpm vitest run tests/orm` 등으로 실행.
114
+
115
+ - `tests/orm` — DB 커넥션, DbContext, 이스케이프 테스트 (MySQL, PostgreSQL, MSSQL). Docker 필요.
116
+ - `tests/service` — 서비스 클라이언트-서버 통신 테스트.
117
+
118
+ ## 코딩 규칙
119
+
120
+ - `import type` 필수 (`verbatimModuleSyntax`), `#private` 금지 → `private` 사용
121
+ - `console.*` 금지, `if (str)` 금지 → `str !== ""` 명시 비교 (nullable boolean/object는 허용)
122
+ - `Buffer` 금지 → `Uint8Array`, `events` 금지 → `@simplysm/core-common`의 `EventEmitter`
123
+ - SolidJS: props 구조 분해 금지, `.map()` 대신 `<For>`, `className` 대신 `class`
124
+ - Prettier: 100자, 2칸 스페이스, 세미콜론, trailing comma, LF
125
+ ```
126
+
127
+ ## Step 5: 완료 안내
128
+
129
+ 생성이 완료되면 아래를 출력하라:
130
+
131
+ ```
132
+ CLAUDE.md가 생성되었습니다. 커밋하려면 /sd-commit 을 실행하세요.
133
+ ```
@@ -1,154 +1,103 @@
1
1
  ---
2
2
  name: sd-plan
3
- description: "Implementation plan from brainstorm designs (explicit invocation only)"
3
+ description: 이 스킬은 사용자가 "계획 세워줘", "plan 만들어", "sd-plan", "구현 계획", "작업 계획" 등을 요청할 때 사용한다.
4
4
  ---
5
5
 
6
- # Writing Plans
6
+ # SD Plan — 명확한 계획서 생성
7
7
 
8
- ## Prerequisite Check
9
-
10
- **MANDATORY:** Before proceeding, verify that `sd-brainstorm` has already been completed in this conversation.
11
-
12
- - If a brainstorm design document exists (discussed or saved in this session) → proceed.
13
- - If NOT → **STOP** and tell the user (in the system's configured language) that sd-plan requires sd-brainstorm to be completed first, then invoke sd-brainstorm instead.
14
-
15
- ## Overview
16
-
17
- Write comprehensive implementation plans assuming the engineer has zero context for our codebase and questionable taste. Document everything they need to know: which files to touch for each task, code, testing, docs they might need to check, how to test it. Give them the whole plan as bite-sized tasks. DRY. YAGNI. TDD. Frequent commits.
18
-
19
- Assume they are a skilled developer, but know almost nothing about our toolset or problem domain. Assume they don't know good test design very well.
20
-
21
- When a task uses a codebase-specific utility (hook, helper, style token) or test pattern, add a one-line explanation of what it does and the source file path. Example: "`createMountTransition(open)` — manages mount/unmount with CSS transitions (`packages/solid/src/hooks/createMountTransition.ts`)". This applies to test utilities and patterns too — if a test uses a framework-specific pattern (e.g., SolidJS `createRoot` for reactive context), explain why that pattern is needed.
22
-
23
- **Announce at start:** "I'm using the sd-plan skill to create the implementation plan."
24
-
25
- **Save plans to:** `docs/plans/YYYY-MM-DD-<feature-name>.md`
26
-
27
- ## Bite-Sized Task Granularity
28
-
29
- **Each step is one action (2-5 minutes):**
30
- - "Write the failing test" - step
31
- - "Run it to make sure it fails" - step
32
- - "Implement the minimal code to make the test pass" - step
33
- - "Run the tests and make sure they pass" - step
34
- - "Commit" - step
35
-
36
- **Step size limit:** If a single step produces more than ~30 lines of code, it is too large. Split it into multiple steps (e.g., "Define types and interfaces" → "Create context and hook" → "Implement provider component").
37
-
38
- **TDD means YAGNI per step:** Step 3 ("Write minimal implementation") must implement ONLY what's needed to pass Step 1's test — nothing more. If the component needs additional behavior (e.g., FIFO eviction, remove), that behavior goes in a SUBSEQUENT task with its own failing test first. Do NOT implement the full component in one task and then test it after the fact.
39
-
40
- ## Task Ordering
41
-
42
- **Shared resources BEFORE consumers.** Tasks must be ordered so that every file a task imports already exists from a prior task.
43
-
44
- - Types, config, i18n entries → before components that use them
45
- - Provider → before components that call useX() hooks
46
- - If Task B imports from Task A's file → Task A must come first
47
-
48
- ## Plan Document Header
49
-
50
- **Every plan MUST start with this header:**
51
-
52
- ```markdown
53
- # [Feature Name] Implementation Plan
54
-
55
- > **For Claude:** REQUIRED SUB-SKILL: Use sd-plan-dev to implement this plan task-by-task.
56
-
57
- **Goal:** [One sentence describing what this builds]
58
-
59
- **Architecture:** [2-3 sentences about approach]
60
-
61
- **Tech Stack:** [Key technologies/libraries]
8
+ 사용자의 작업 요청을 받아 초기 계획서를 생성한 뒤, 불명확한 부분을 반복적으로 검토하고 질문하여 완벽히 명확한 계획서를 만든다.
62
9
 
63
10
  ---
64
- ```
65
11
 
66
- ## Package Manager Detection
12
+ ## Step 1: 입력 확인
67
13
 
68
- When writing run commands in the plan, detect the package manager:
69
- - If `pnpm-lock.yaml` exists in project root use `pnpm`
70
- - If `yarn.lock` exists in project root use `yarn`
71
- - Otherwise use `npm`
14
+ - 작업 설명을 아래 우선순위로 확보하라:
15
+ 1. **작업 요청**: 사용자가 스킬 호출 함께 전달한 작업 설명
16
+ 2. **현재 대화**: 작업 요청이 없으면 현재 대화 컨텍스트에서 작업 내용을 파악
17
+ 3. **AskUserQuestion**: 둘로도 파악이 안 되면 "어떤 작업에 대한 계획서를 만들까요? 작업 내용을 설명해 주세요."라고 질문
18
+ - 충분한 작업 설명을 확보한 후 Step 2로 진행하라.
72
19
 
73
- `$PM` in the task template below refers to the detected package manager.
20
+ ---
74
21
 
75
- ## Task Structure
22
+ ## Step 2: 계획서 생성 + 명확화 반복
76
23
 
77
- ```markdown
78
- ### Task N: [Component Name]
24
+ ### 2-1. 초안 작성
79
25
 
80
- **Files:**
81
- - Create: `exact/path/to/file.ts`
82
- - Modify: `exact/path/to/existing.ts:123-145`
83
- - Test: `exact/path/to/tests/file.spec.ts`
26
+ 계획서 초안을 작성하라. 각 구현 단계는 **검증 → 구현 → 확인** 순서로 배치하라:
27
+ - 테스트 프레임워크가 있으면 → 테스트 코드 먼저 작성
28
+ - 테스트 프레임워크가 없으면 → CLI 실행, dry-run 등 검증 방법 명시
29
+ - 비코드 작업이면 → 자체 검증 체크리스트 먼저 정의
84
30
 
85
- **Step 1: Write the failing test**
31
+ ### 2-2. 명확화 사이클
86
32
 
87
- ```typescript
88
- test("specific behavior", () => {
89
- const result = functionUnderTest(input);
90
- expect(result).toBe(expected);
91
- });
92
- ```
33
+ 아래를 **불명확 항목이 0개가 될 때까지** 반복하라:
93
34
 
94
- **Step 2: Run test to verify it fails**
35
+ 1. **추출**: 계획서를 아래 "불명확 판단 기준" 12개 항목에 전부 대조하여 불명확 항목을 나열하라.
36
+ 2. **의존성 분석**: 항목 간 의존 관계를 파악하라. ("A가 정해져야 B를 질문 가능" → B는 A에 의존)
37
+ 3. **질문**: 의존 대상이 없는 항목들을 AskUserQuestion 도구 **하나당 단 하나의 질문**만 하라. 각 질문에 2~5개 선택지를 제시하라.
38
+ 4. **반영**: 답변을 모두 반영하여 계획서를 업데이트하고, 1번으로 돌아가라.
95
39
 
96
- Run: `$PM run vitest exact/path/to/tests/file.spec.ts --run`
97
- Expected: FAIL with "functionUnderTest is not defined"
40
+ 불명확 항목 0개 **Step 2.5 최종 검증**으로 이동.
98
41
 
99
- **Step 3: Write minimal implementation**
42
+ ### 불명확 판단 기준
100
43
 
101
- ```typescript
102
- function functionUnderTest(input: InputType): OutputType {
103
- return expected;
104
- }
105
- ```
44
+ > **핵심 원칙**: 사용자가 명시하지 않았고, 코드베이스에서 확인되지 않은 것은 **모두 추측/가정으로 간주**하여 불명확 항목으로 취급하라. Claude가 자신있게 작성했더라도 출처가 불분명하면 불명확이다.
106
45
 
107
- **Step 4: Run test to verify it passes**
46
+ 아래 12개 항목을 **매 검토 전부** 대조하라. "해당 없음"이라고 넘기려면 구체적 근거(사용자 발언 또는 코드베이스 확인)가 있어야 한다.
108
47
 
109
- Run: `$PM run vitest exact/path/to/tests/file.spec.ts --run`
110
- Expected: PASS
48
+ 1. **사용자 미명시 가정**: 사용자가 말하지 않았는데 Claude가 채워넣은 결정사항
49
+ 2. **구체성 부족**: HOW 없이 "적절히 처리", "필요에 따라" 등의 표현
50
+ 3. **범위 모호**: IN/OUT 스코프가 정의되지 않음
51
+ 4. **미지정 동작**: 에러, 유효하지 않은 입력, 기본값 등이 지정되지 않음
52
+ 5. **알 수 없는 제약조건**: 성능, 호환성, 플랫폼 요구사항이 불분명
53
+ 6. **누락된 엣지케이스**: 경계 조건, 동시성, 빈 상태 등
54
+ 7. **모호한 파일/함수 참조**: 구체적 경로 없이 "관련 파일을 수정" 등
55
+ 8. **불명확한 순서/의존성**: 단계 간 선후 관계 미명시
56
+ 9. **추측 표현**: "아마", "~일 수 있음", "TBD", "???" 등
57
+ 10. **통합 세부사항 누락**: API 계약, 데이터 형식, 인터페이스 미정의
58
+ 11. **실패/롤백 전략 부재**: 실패 시 대응 방안 없음
59
+ 12. **검증 방법 미정의**: 구현 단계에 대응하는 검증 방법이 없음
111
60
 
112
- **Step 5: Commit**
61
+ ---
113
62
 
114
- ```bash
115
- git add exact/path/to/tests/file.spec.ts exact/path/to/file.ts
116
- git commit -m "feat: add specific feature"
117
- ```
118
- ```
63
+ ## Step 2.5: 최종 검증 (불명확 없음 선언 전 필수)
119
64
 
120
- ## Test Requirement
65
+ "불명확 없음"을 선언하기 **직전에 반드시** 아래를 수행하라:
121
66
 
122
- **Every task that creates or modifies logic MUST include a test.** No exceptions.
67
+ 1. 계획서의 **모든 단계를 처음부터 끝까지** 다시 읽으며, 12개 기준을 번 더 전수 대조하라.
68
+ 2. 특히 다음을 집중 확인하라:
69
+ - Claude가 스스로 결정한 부분 중 사용자 확인을 받지 않은 것이 있는가?
70
+ - "~한다", "~로 처리한다" 등 단정적으로 쓴 부분의 근거가 사용자 발언 또는 코드베이스에 있는가?
71
+ - 구체적 파일 경로, 함수명, 데이터 구조가 빠진 곳은 없는가?
72
+ 3. 이 검증에서 **하나라도** 불명확 항목이 발견되면 Step 2의 질문 사이클로 돌아가라.
73
+ 4. 진짜로 없으면 → Step 3으로 이동.
123
74
 
124
- - If the logic is testable with unit tests → write a vitest test file. This includes: pure functions, state management, timers/lifecycle logic (use `vi.useFakeTimers()`), event handlers, and state transitions.
125
- - If the logic is UI-only (visual rendering, Portal placement, CSS animation) → include a manual verification step with exact instructions ("Open the browser, click X, expect Y")
126
- - The **Files:** section must list the test file: `Test: exact/path/to/tests/file.spec.ts`
127
- - If you find yourself writing a task with no test step → **STOP and add one**
75
+ ---
128
76
 
129
- ## Remember
130
- - Exact file paths always
131
- - Cross-check the design document's file structure — every file listed in the design MUST appear in the plan (create or modify)
132
- - Complete code in plan (not "add validation")
133
- - When modifying an existing file, show ALL necessary import additions/changes — not just the appended code
134
- - Code must compile cleanly — no unused imports or variables
135
- - Exact commands with expected output
136
- - DRY, YAGNI, TDD, frequent commits
77
+ ## Step 3: 최종 출력
137
78
 
138
- ## Related Skills
79
+ 모든 불명확 항목이 해소되었으면 완성된 계획서를 사용자에게 제시하고, AskUserQuestion으로 구현 승인을 요청하라.
139
80
 
140
- - **sd-brainstorm** prerequisite: creates the design this skill plans from
141
- - **sd-plan-dev** executes the plan this skill creates
81
+ 사용자가 승인하면 `.tmp/plans/{yyMMddHHmmss}_{topic}.md`에 계획서를 Write하라.
82
+ - 파일명 예시: `260311143052_progress-컴포넌트-추가.md`
83
+ - `yyMMddHHmmss`: 연월일시분초 (예: 260311143052)
84
+ - `{topic}`: 작업 내용 기반 짧은 케밥케이스 (예: progress-컴포넌트-추가)
142
85
 
143
- ## Execution Handoff
86
+ ---
144
87
 
145
- After saving the plan, **commit the plan document to git** before proceeding.
88
+ ## Step 4: 구현 완료 안내
146
89
 
147
- Then:
90
+ 사용자가 구현을 승인하면, 계획서에 따라 구현하라. 구현이 완료되면 아래 안내를 출력하라:
148
91
 
149
- - If in **yolo mode** (user chose "yolo" from sd-brainstorm): Immediately proceed to sd-plan-dev without asking. No confirmation needed.
150
- - Otherwise: Display this message **in the system's configured language** (detect from the language setting and translate accordingly):
151
- **"Plan complete and saved to `docs/plans/<filename>.md`. Ready to execute with sd-plan-dev?"**
92
+ - **코드 수정이 포함된 경우**:
93
+ ```
94
+ 구현이 완료되었습니다. 다음 단계를 순서대로 실행하는 것을 권장합니다:
95
+ 1. /sd-check — 타입체크 + 린트 + 테스트 검사 및 자동 수정
96
+ 2. /sd-simplify — 변경된 코드 단순화 리뷰
97
+ 3. /sd-commit — 변경사항 커밋
98
+ ```
152
99
 
153
- - **REQUIRED SUB-SKILL:** Use sd-plan-dev
154
- - Fresh fork per task + two-stage review (spec compliance → code quality)
100
+ - **코드 수정이 없는 경우** (설정, 문서 등):
101
+ ```
102
+ 구현이 완료되었습니다. 커밋하려면 /sd-commit 을 실행하세요.
103
+ ```