@simplysm/sd-claude 13.0.70 → 13.0.72

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.
@@ -0,0 +1,7 @@
1
+ # Simplysm Library Issue Reporting
2
+
3
+ Source code for `@simplysm/*` packages can be found in `node_modules/@simplysm/`. If debugging reveals the root cause is in the simplysm library itself, generate a GitHub issue-formatted text (title, reproduction steps, expected behavior, actual behavior) and display it to the user.
4
+
5
+ **Report facts only — do not suggest fixes or include code location hints. Do not auto-submit the issue — only display the text.**
6
+
7
+ The issue body must NEVER include internal analysis of library code (class names, variable names, style properties, inheritance chains, etc.). Only describe user-observable symptoms.
@@ -21,9 +21,9 @@ If a referenced file or document cannot be found, **stop immediately and ask the
21
21
 
22
22
  ### Questions vs. Code Requests — CRITICAL
23
23
 
24
- - **If the user asks a question** (e.g., "이건 이래?", "이거 뭐야?", "어떻게 동작해?") → **answer with text only**. Do NOT edit, write, or create any files.
24
+ - **If the user asks a question** (e.g., "Why is this like this?", "What is this?", "How does this work?") → **answer with text only**. Do NOT edit, write, or create any files.
25
25
  - **If the user discusses, explains, or shares opinions** → **respond with text only**. Do NOT touch any files.
26
- - **Only edit/write/create files when the user explicitly requests code changes** (e.g., "수정해줘", "만들어줘", "변경해줘", "추가해줘", "고쳐줘").
26
+ - **Only edit/write/create files when the user explicitly requests code changes** (e.g., "Fix this", "Create this", "Change this", "Add this").
27
27
  - Reading files to answer a question is fine. **Modifying files to answer a question is prohibited.**
28
28
 
29
29
  ### General Rules
@@ -19,6 +19,7 @@ Determine the major version by the `version` field in `package.json`.
19
19
  | Debugging, problem-solving, or planning approach | `.claude/refs/sd-workflow.md` |
20
20
  | Using `@simplysm/service-*` packages | `.claude/refs/sd-service.md` |
21
21
  | Migrating/porting code from another codebase | `.claude/refs/sd-migration.md` |
22
+ | Debugging in a project that uses `@simplysm/*` as an external dependency (not the simplysm monorepo itself) | `.claude/refs/sd-library-issue.md` |
22
23
 
23
24
  ## v12 only (< 13)
24
25
 
@@ -241,7 +241,7 @@ async function main() {
241
241
  const cwd = input.cwd ?? process.cwd();
242
242
  const folderName = path.basename(cwd);
243
243
 
244
- // 출력
244
+ // Output
245
245
  const dailyStr = dailyResetTime ? `${dailyPercent}%(${dailyResetTime})` : `${dailyPercent}%`;
246
246
  const weekStr = weekResetDay ? `${weekPercent}%(${weekResetDay})` : `${weekPercent}%`;
247
247
  const parts = [folderName, modelName, `${contextPercent}%`, dailyStr, weekStr];
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  name: sd-check
3
3
  description: "Typecheck, lint, test verification (explicit invocation only)"
4
- model: sonnet
5
4
  allowed-tools: Bash(npm run check), Bash(npm run typecheck), Bash(npm run lint --fix), Bash(npm run vitest)
6
5
  ---
7
6
 
@@ -50,7 +50,7 @@ type(scope): short description
50
50
  | ------------- | ---------------------------------------------------------------------------- |
51
51
  | `type` | `feat`, `fix`, `refactor`, `docs`, `test`, `chore`, `build`, `style`, `perf` |
52
52
  | `scope` | package name or area (e.g., `solid`, `core-common`, `orm-node`) |
53
- | `description` | imperative, lowercase, no period at end |
53
+ | `description` | written in the system's configured language, imperative, lowercase, no period at end |
54
54
 
55
55
  Examples:
56
56
 
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env python3
2
- """DOCX 파일에서 텍스트와 이미지를 문단 흐름 순서로 추출한다."""
2
+ """Extract text and images from DOCX files in paragraph flow order."""
3
3
 
4
4
  import sys
5
5
  import io
@@ -17,7 +17,7 @@ def ensure_packages():
17
17
  try:
18
18
  __import__(import_name)
19
19
  except ImportError:
20
- print(f"패키지 설치 중: {pip_name}...", file=sys.stderr)
20
+ print(f"Installing package: {pip_name}...", file=sys.stderr)
21
21
  subprocess.check_call([sys.executable, "-m", "pip", "install", pip_name],
22
22
  stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
23
23
 
@@ -71,7 +71,7 @@ def extract(file_path):
71
71
  elif not has_image and not text:
72
72
  continue
73
73
 
74
- # 추출
74
+ # Table extraction
75
75
  for t_idx, table in enumerate(doc.tables):
76
76
  print(f"\n### Table {t_idx + 1}\n")
77
77
  for row in table.rows:
@@ -79,9 +79,9 @@ def extract(file_path):
79
79
  print("| " + " | ".join(cells) + " |")
80
80
 
81
81
  if img_idx > 0:
82
- print(f"\n---\n이미지 {img_idx} 저장: {out_dir}")
82
+ print(f"\n---\n{img_idx} image(s) saved: {out_dir}")
83
83
  else:
84
- print("\n---\n이미지 없음")
84
+ print("\n---\nNo images")
85
85
 
86
86
 
87
87
  if __name__ == "__main__":
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env python3
2
- """PDF 파일에서 텍스트, 표, 이미지를 페이지별로 추출한다."""
2
+ """Extract text, tables, and images from PDF files page by page."""
3
3
 
4
4
  import sys
5
5
  import io
@@ -16,7 +16,7 @@ def ensure_packages():
16
16
  try:
17
17
  __import__(import_name)
18
18
  except ImportError:
19
- print(f"패키지 설치 중: {pip_name}...", file=sys.stderr)
19
+ print(f"Installing package: {pip_name}...", file=sys.stderr)
20
20
  subprocess.check_call([sys.executable, "-m", "pip", "install", pip_name],
21
21
  stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
22
22
 
@@ -32,7 +32,7 @@ def extract(file_path):
32
32
 
33
33
  print(f"# {Path(file_path).name}\n")
34
34
 
35
- # 텍스트 + 추출 (pdfplumber)
35
+ # Text + table extraction (pdfplumber)
36
36
  with pdfplumber.open(file_path) as pdf:
37
37
  for page_num, page in enumerate(pdf.pages, 1):
38
38
  print(f"## Page {page_num}\n")
@@ -52,7 +52,7 @@ def extract(file_path):
52
52
  print("| " + " | ".join(cells) + " |")
53
53
  print()
54
54
 
55
- # 이미지 추출 (pypdf)
55
+ # Image extraction (pypdf)
56
56
  reader = PdfReader(file_path)
57
57
  for page_num, page in enumerate(reader.pages, 1):
58
58
  if "/XObject" not in (page.get("/Resources") or {}):
@@ -79,19 +79,19 @@ def extract(file_path):
79
79
  img_path.write_bytes(obj._data if hasattr(obj, "_data") else b"")
80
80
  print(f"[IMG] (page={page_num}) {img_path}")
81
81
 
82
- # OCR 안내
82
+ # OCR notice
83
83
  if total_text_len == 0:
84
- print("\n⚠ 텍스트가 추출되지 않았습니다 (스캔 PDF일 있음).")
85
- print("OCR 필요합니다:")
86
- print(" 1. Tesseract OCR 설치: https://github.com/tesseract-ocr/tesseract")
84
+ print("\n⚠ No text was extracted (may be a scanned PDF).")
85
+ print("OCR is required:")
86
+ print(" 1. Install Tesseract OCR: https://github.com/tesseract-ocr/tesseract")
87
87
  print(" 2. pip install pytesseract pdf2image")
88
- print(" 3. pytesseract.image_to_string() 으로 추출")
88
+ print(" 3. Extract with pytesseract.image_to_string()")
89
89
 
90
90
  print()
91
91
  if img_idx > 0:
92
- print(f"---\n이미지 {img_idx} 저장: {out_dir}")
92
+ print(f"---\n{img_idx} image(s) saved: {out_dir}")
93
93
  else:
94
- print("---\n이미지 없음")
94
+ print("---\nNo images")
95
95
 
96
96
 
97
97
  if __name__ == "__main__":
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env python3
2
- """PPTX 파일에서 텍스트와 이미지를 슬라이드별 좌표와 함께 추출한다."""
2
+ """Extract text and images from PPTX files with per-slide coordinates."""
3
3
 
4
4
  import sys
5
5
  import io
@@ -16,13 +16,13 @@ def ensure_packages():
16
16
  try:
17
17
  __import__(import_name)
18
18
  except ImportError:
19
- print(f"패키지 설치 중: {pip_name}...", file=sys.stderr)
19
+ print(f"Installing package: {pip_name}...", file=sys.stderr)
20
20
  subprocess.check_call([sys.executable, "-m", "pip", "install", pip_name],
21
21
  stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
22
22
 
23
23
 
24
24
  def emu_to_inches(emu):
25
- """EMU 인치로 변환 (소수점 1자리)."""
25
+ """Convert EMU to inches (1 decimal place)."""
26
26
  if emu is None:
27
27
  return "?"
28
28
  return f"{emu / 914400:.1f}"
@@ -64,9 +64,9 @@ def extract(file_path):
64
64
  print()
65
65
 
66
66
  if img_idx > 0:
67
- print(f"---\n이미지 {img_idx} 저장: {out_dir}")
67
+ print(f"---\n{img_idx} image(s) saved: {out_dir}")
68
68
  else:
69
- print("---\n이미지 없음")
69
+ print("---\nNo images")
70
70
 
71
71
 
72
72
  if __name__ == "__main__":
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env python3
2
- """XLSX 파일에서 데이터와 이미지를 위치와 함께 추출한다."""
2
+ """Extract data and images from XLSX files with cell positions."""
3
3
 
4
4
  import sys
5
5
  import io
@@ -17,7 +17,7 @@ def ensure_packages():
17
17
  try:
18
18
  __import__(import_name)
19
19
  except ImportError:
20
- print(f"패키지 설치 중: {pip_name}...", file=sys.stderr)
20
+ print(f"Installing package: {pip_name}...", file=sys.stderr)
21
21
  subprocess.check_call([sys.executable, "-m", "pip", "install", pip_name],
22
22
  stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
23
23
 
@@ -37,10 +37,10 @@ def extract(file_path):
37
37
  ws = wb[sheet_name]
38
38
  print(f"## Sheet: {sheet_name}\n")
39
39
 
40
- # 데이터 추출
40
+ # Data extraction
41
41
  rows = list(ws.iter_rows(values_only=False))
42
42
  if not rows:
43
- print("( 시트)\n")
43
+ print("(empty sheet)\n")
44
44
  continue
45
45
 
46
46
  for row in rows:
@@ -53,7 +53,7 @@ def extract(file_path):
53
53
  cells.append(str(val).strip())
54
54
  print(f"[{row[0].coordinate.split('1')[0]}{row[0].row}] " + " | ".join(cells))
55
55
 
56
- # 이미지 추출
56
+ # Image extraction
57
57
  if ws._images:
58
58
  for img in ws._images:
59
59
  img_idx += 1
@@ -70,9 +70,9 @@ def extract(file_path):
70
70
  print()
71
71
 
72
72
  if img_idx > 0:
73
- print(f"---\n이미지 {img_idx} 저장: {out_dir}")
73
+ print(f"---\n{img_idx} image(s) saved: {out_dir}")
74
74
  else:
75
- print("---\n이미지 없음")
75
+ print("---\nNo images")
76
76
 
77
77
 
78
78
  if __name__ == "__main__":
@@ -12,13 +12,13 @@ import base64
12
12
  from email.policy import default as default_policy
13
13
  from pathlib import Path
14
14
 
15
- # stdout UTF-8 강제 (Windows 호환)
15
+ # Force stdout UTF-8 (Windows compatibility)
16
16
  sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
17
17
  sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8", errors="replace")
18
18
 
19
19
 
20
20
  def ensure_packages():
21
- """필요한 패키지 자동 설치."""
21
+ """Auto-install required packages."""
22
22
  packages = {"extract-msg": "extract_msg"}
23
23
  missing = []
24
24
  for pip_name, import_name in packages.items():
@@ -27,7 +27,7 @@ def ensure_packages():
27
27
  except ImportError:
28
28
  missing.append(pip_name)
29
29
  if missing:
30
- print(f"패키지 설치 중: {', '.join(missing)}...", file=sys.stderr)
30
+ print(f"Installing packages: {', '.join(missing)}...", file=sys.stderr)
31
31
  subprocess.check_call(
32
32
  [sys.executable, "-m", "pip", "install", "-q", *missing],
33
33
  stdout=subprocess.DEVNULL,
@@ -326,45 +326,45 @@ def build_report(filepath):
326
326
  saved_attachments = save_files(attachments, out_dir)
327
327
 
328
328
  out = []
329
- out.append("# 이메일 분석서\n")
330
- out.append(f"**원본 파일**: `{os.path.basename(filepath)}`\n")
331
-
332
- # ── 메일 정보
333
- out.append("## 메일 정보\n")
334
- out.append("| 항목 | 내용 |")
335
- out.append("|------|------|")
336
- out.append(f"| **제목** | {headers['subject']} |")
337
- out.append(f"| **보낸 사람** | {headers['from']} |")
338
- out.append(f"| **받는 사람** | {headers['to']} |")
329
+ out.append("# Email Analysis Report\n")
330
+ out.append(f"**Source file**: `{os.path.basename(filepath)}`\n")
331
+
332
+ # ── Email info
333
+ out.append("## Email Info\n")
334
+ out.append("| Field | Value |")
335
+ out.append("|-------|-------|")
336
+ out.append(f"| **Subject** | {headers['subject']} |")
337
+ out.append(f"| **From** | {headers['from']} |")
338
+ out.append(f"| **To** | {headers['to']} |")
339
339
  if headers["cc"]:
340
- out.append(f"| **참조** | {headers['cc']} |")
341
- out.append(f"| **날짜** | {headers['date']} |")
342
- out.append(f"| **첨부파일** | {len(saved_attachments)} |")
340
+ out.append(f"| **CC** | {headers['cc']} |")
341
+ out.append(f"| **Date** | {headers['date']} |")
342
+ out.append(f"| **Attachments** | {len(saved_attachments)} |")
343
343
  if all_inline:
344
- out.append(f"| **본문 이미지** | {len(all_inline)} |")
344
+ out.append(f"| **Inline images** | {len(all_inline)} |")
345
345
  out.append("")
346
346
 
347
- # ── 본문
348
- out.append("## 본문 내용\n")
347
+ # ── Body
348
+ out.append("## Body\n")
349
349
  body = body_plain
350
350
  if not body and body_html:
351
351
  body = strip_html(body_html)
352
- out.append(body.strip() if body else "_(본문 없음)_")
352
+ out.append(body.strip() if body else "_(No body)_")
353
353
  out.append("")
354
354
 
355
- # ── 본문 삽입 이미지
355
+ # ── Inline images
356
356
  if all_inline:
357
- out.append("## 본문 삽입 이미지\n")
358
- out.append("| # | 파일명 | 크기 | 저장 경로 |")
357
+ out.append("## Inline Images\n")
358
+ out.append("| # | Filename | Size | Saved path |")
359
359
  out.append("|---|--------|------|-----------|")
360
360
  for i, img in enumerate(all_inline, 1):
361
361
  out.append(f"| {i} | {img['filename']} | {fmt_size(img['size'])} | `{img['saved_path']}` |")
362
362
  out.append("")
363
363
 
364
- # ── 첨부파일
364
+ # ── Attachments
365
365
  if saved_attachments:
366
- out.append("## 첨부파일\n")
367
- out.append("| # | 파일명 | 크기 | 저장 경로 |")
366
+ out.append("## Attachments\n")
367
+ out.append("| # | Filename | Size | Saved path |")
368
368
  out.append("|---|--------|------|-----------|")
369
369
  for i, a in enumerate(saved_attachments, 1):
370
370
  out.append(f"| {i} | {a['filename']} | {fmt_size(a['size'])} | `{a['saved_path']}` |")
@@ -382,12 +382,12 @@ if __name__ == "__main__":
382
382
 
383
383
  path = sys.argv[1]
384
384
  if not os.path.isfile(path):
385
- print(f"파일을 찾을 없습니다: {path}", file=sys.stderr)
385
+ print(f"File not found: {path}", file=sys.stderr)
386
386
  sys.exit(1)
387
387
 
388
388
  ext = Path(path).suffix.lower()
389
389
  if ext not in (".eml", ".msg"):
390
- print(f"지원하지 않는 형식: {ext} (.eml 또는 .msg 지원)", file=sys.stderr)
390
+ print(f"Unsupported format: {ext} (only .eml and .msg are supported)", file=sys.stderr)
391
391
  sys.exit(1)
392
392
 
393
393
  print(build_report(path))
@@ -6,6 +6,51 @@ model: haiku
6
6
 
7
7
  # sd-worktree
8
8
 
9
+ ## CRITICAL SAFETY RULES — MERGE & STASH
10
+
11
+ **These rules are ABSOLUTE. No exceptions. Applies to ALL modes (yolo, normal, plan, etc.).**
12
+
13
+ 1. **NEVER run `git stash drop`, `git stash pop`, or `git stash clear`.**
14
+ - If you stashed something, ONLY use `git stash apply` (which keeps the stash intact).
15
+ - If stash is no longer needed, ASK the user before dropping it.
16
+
17
+ 2. **If `merge` fails or produces conflicts → STOP IMMEDIATELY and show this message:**
18
+ ```
19
+ ⚠️ A problem occurred during merge.
20
+ Please proceed with the merge manually.
21
+ (Error details: <print error/conflict info as-is>)
22
+ ```
23
+ - Show this message in the system's configured language.
24
+ - Do NOT attempt to resolve conflicts yourself.
25
+ - Do NOT run `git merge --abort` without asking.
26
+ - Do NOT proceed to `clean` after a failed merge.
27
+ - Do NOT retry or work around the error.
28
+ - Just show the message above and STOP. Do nothing else.
29
+
30
+ 3. **If `rebase` fails or produces conflicts → STOP IMMEDIATELY and show this message:**
31
+ ```
32
+ ⚠️ A problem occurred during rebase.
33
+ Please proceed with the rebase manually.
34
+ (Error details: <print error/conflict info as-is>)
35
+ ```
36
+ - Show this message in the system's configured language.
37
+ - Same rules as merge. Do NOT auto-resolve. Just show the message and STOP.
38
+
39
+ 4. **NEVER run destructive git commands during worktree workflows:**
40
+ - `git reset --hard`, `git checkout -- .`, `git restore .`, `git clean -f`
41
+ - `git branch -D` (force delete) — only `-d` (safe delete) is allowed
42
+ - `git stash drop`, `git stash pop`, `git stash clear`
43
+
44
+ 5. **Before ANY merge/rebase, verify:**
45
+ - Both main and worktree have NO uncommitted changes (`git status --porcelain`)
46
+ - If uncommitted changes exist → ask the user (do NOT auto-stash)
47
+
48
+ 6. **After merge completes, verify success before proceeding:**
49
+ - Check `git status` — if merge conflicts exist, STOP and report
50
+ - Do NOT proceed to `clean` until merge is confirmed successful
51
+
52
+ **Violation of these rules causes IRREVERSIBLE DATA LOSS.**
53
+
9
54
  ## Overview
10
55
 
11
56
  Create, merge, and clean up git worktrees under `.worktrees/`. Uses the current branch of the main working tree as the source branch.
@@ -44,6 +89,7 @@ node .claude/skills/sd-worktree/sd-worktree.mjs rebase [name]
44
89
  - Rebases the worktree branch onto the latest commit of the main branch
45
90
  - Errors if uncommitted changes exist → commit or stash first
46
91
  - Use when you want a clean history before merging
92
+ - **If rebase fails or conflicts → STOP. Report to user. Do NOT auto-resolve.**
47
93
 
48
94
  ### merge — Merge into main branch
49
95
 
@@ -56,6 +102,13 @@ node .claude/skills/sd-worktree/sd-worktree.mjs merge [name]
56
102
  - Errors if uncommitted changes exist → commit or stash first
57
103
  - After merge, always `cd <project-root>` (required for subsequent clean)
58
104
 
105
+ **MERGE SAFETY PROTOCOL:**
106
+ 1. Before merge: check BOTH main and worktree for uncommitted changes
107
+ 2. If the script exits with non-zero → show "Please proceed with the merge manually." message (in system language) and STOP.
108
+ 3. After merge: run `git status` in main to confirm no conflicts
109
+ 4. If conflicts or errors → show "Please proceed with the merge manually." message (in system language) and STOP.
110
+ 5. Only proceed to `clean` after confirming merge was fully successful
111
+
59
112
  ### clean — Remove worktree and delete branch
60
113
 
61
114
  ```bash
@@ -13,7 +13,7 @@ function getOutput(command) {
13
13
  return execSync(command, { encoding: "utf-8" }).trim();
14
14
  }
15
15
 
16
- // 메인 working tree 경로 (worktree 안에서 실행해도 정확)
16
+ // Main working tree path (accurate even when run inside a worktree)
17
17
  const mainWorktree = getOutput("git worktree list --porcelain")
18
18
  .split("\n")[0]
19
19
  .replace("worktree ", "");
@@ -71,7 +71,7 @@ switch (cmd) {
71
71
  console.error("Usage: sd-worktree.mjs merge [name] (or run inside .worktrees/<name>)");
72
72
  process.exit(1);
73
73
  }
74
- // uncommitted 변경 확인
74
+ // Check for uncommitted changes
75
75
  const worktreePath_m = resolve(mainWorktree, ".worktrees", name);
76
76
  if (existsSync(worktreePath_m)) {
77
77
  const status = getOutput(`git -C "${worktreePath_m}" status --porcelain`);
@@ -99,7 +99,7 @@ switch (cmd) {
99
99
  console.error(`Error: worktree '${name}' does not exist.`);
100
100
  process.exit(1);
101
101
  }
102
- // uncommitted 변경 확인
102
+ // Check for uncommitted changes
103
103
  const statusR = getOutput(`git -C "${worktreePath_r}" status --porcelain`);
104
104
  if (statusR) {
105
105
  console.error(`Error: worktree '${name}' has uncommitted changes:\n${statusR}`);
@@ -119,7 +119,7 @@ switch (cmd) {
119
119
  console.error("Usage: sd-worktree.mjs clean [name] (or run inside .worktrees/<name>)");
120
120
  process.exit(1);
121
121
  }
122
- // worktree 안에서 실행 차단
122
+ // Block execution from inside the worktree
123
123
  const worktreePath = resolve(mainWorktree, ".worktrees", name);
124
124
  const cwd = process.cwd();
125
125
  if (cwd === worktreePath || cwd.startsWith(worktreePath + "/")) {
@@ -134,7 +134,7 @@ switch (cmd) {
134
134
  try {
135
135
  run(`git worktree remove --force "${worktreePath}"`, { cwd: mainWorktree });
136
136
  } catch {
137
- // node_modules 등으로 git worktree remove 실패 수동 정리
137
+ // Manual cleanup when git worktree remove fails (e.g., due to node_modules)
138
138
  console.log("git worktree remove failed, cleaning up manually...");
139
139
  rmSync(worktreePath, { recursive: true, force: true });
140
140
  run("git worktree prune", { cwd: mainWorktree });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simplysm/sd-claude",
3
- "version": "13.0.70",
3
+ "version": "13.0.72",
4
4
  "description": "Simplysm Claude Code CLI — asset installer",
5
5
  "author": "simplysm",
6
6
  "license": "Apache-2.0",