@simplysm/sd-claude 14.0.75 → 14.0.77

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 (66) hide show
  1. package/claude/output-styles/sd-tone.md +128 -0
  2. package/claude/references/sd-simplysm14/apis/angular/README.md +28 -89
  3. package/claude/references/sd-simplysm14/apis/angular/app-structure.md +75 -32
  4. package/claude/references/sd-simplysm14/apis/angular/buttons.md +65 -29
  5. package/claude/references/sd-simplysm14/apis/angular/crud.md +86 -21
  6. package/claude/references/sd-simplysm14/apis/angular/forms.md +168 -42
  7. package/claude/references/sd-simplysm14/apis/angular/infrastructure.md +200 -49
  8. package/claude/references/sd-simplysm14/apis/angular/kanban.md +64 -20
  9. package/claude/references/sd-simplysm14/apis/angular/layout.md +75 -30
  10. package/claude/references/sd-simplysm14/apis/angular/modal.md +92 -40
  11. package/claude/references/sd-simplysm14/apis/angular/routing.md +86 -25
  12. package/claude/references/sd-simplysm14/apis/angular/selection-managers.md +72 -41
  13. package/claude/references/sd-simplysm14/apis/angular/shared-data.md +113 -21
  14. package/claude/references/sd-simplysm14/apis/angular/sheet.md +108 -33
  15. package/claude/references/sd-simplysm14/apis/angular/toast.md +81 -30
  16. package/claude/references/sd-simplysm14/apis/angular/visual.md +140 -32
  17. package/claude/references/sd-simplysm14/apis/capacitor-plugin-auto-update/README.md +46 -43
  18. package/claude/references/sd-simplysm14/apis/capacitor-plugin-intent/README.md +59 -48
  19. package/claude/references/sd-simplysm14/apis/capacitor-plugin-usb-storage/README.md +17 -7
  20. package/claude/references/sd-simplysm14/apis/core-common/README.md +43 -116
  21. package/claude/references/sd-simplysm14/apis/core-common/extensions.md +74 -109
  22. package/claude/references/sd-simplysm14/apis/core-common/features.md +40 -35
  23. package/claude/references/sd-simplysm14/apis/core-common/types.md +80 -106
  24. package/claude/references/sd-simplysm14/apis/core-common/utils.md +142 -111
  25. package/claude/references/sd-simplysm14/apis/core-node/README.md +7 -16
  26. package/claude/references/sd-simplysm14/apis/core-node/consola.md +33 -38
  27. package/claude/references/sd-simplysm14/apis/core-node/cpx.md +25 -33
  28. package/claude/references/sd-simplysm14/apis/core-node/fs-watcher.md +27 -38
  29. package/claude/references/sd-simplysm14/apis/core-node/fsx.md +32 -60
  30. package/claude/references/sd-simplysm14/apis/core-node/pathx.md +14 -45
  31. package/claude/references/sd-simplysm14/apis/core-node/worker.md +35 -81
  32. package/claude/references/sd-simplysm14/apis/excel/README.md +178 -80
  33. package/claude/references/sd-simplysm14/apis/lint/README.md +5 -0
  34. package/claude/references/sd-simplysm14/apis/orm-node/README.md +1 -1
  35. package/claude/references/sd-simplysm14/apis/sd-claude/README.md +28 -5
  36. package/claude/references/sd-simplysm14/apis/sd-cli/README.md +1 -1
  37. package/claude/references/sd-simplysm14/apis/service-client/README.md +57 -50
  38. package/claude/references/sd-simplysm14/apis/service-server/README.md +8 -15
  39. package/claude/references/sd-simplysm14/apis/service-server/auth.md +24 -16
  40. package/claude/references/sd-simplysm14/apis/service-server/builtin-services.md +55 -31
  41. package/claude/references/sd-simplysm14/apis/service-server/define-service.md +28 -44
  42. package/claude/references/sd-simplysm14/apis/service-server/internals.md +59 -18
  43. package/claude/references/sd-simplysm14/apis/service-server/server.md +37 -46
  44. package/claude/references/sd-simplysm14/manuals/client-component.md +3 -1
  45. package/claude/references/sd-simplysm14/manuals/logging.md +9 -8
  46. package/claude/rules/sd-base-rules.md +380 -217
  47. package/claude/settings.json +1 -0
  48. package/claude/skills/sd-commit/SKILL.md +31 -8
  49. package/claude/skills/sd-docs/SKILL.md +15 -10
  50. package/claude/skills/sd-docs/references/subagent-prompt.md +26 -8
  51. package/claude/skills/sd-impl/SKILL.md +1 -1
  52. package/claude/skills/sd-skill/references/skill-authoring.md +1 -1
  53. package/claude/skills/sd-spec/SKILL.md +22 -13
  54. package/claude/skills/sd-spec/references/spec-authoring.md +1 -1
  55. package/claude/skills/sd-unpack/SKILL.md +150 -26
  56. package/claude/skills/sd-unpack/scripts/handlers/__pycache__/_common.cpython-314.pyc +0 -0
  57. package/claude/skills/sd-unpack/scripts/handlers/__pycache__/eml_handler.cpython-314.pyc +0 -0
  58. package/claude/skills/sd-unpack/scripts/handlers/__pycache__/office_com.cpython-314.pyc +0 -0
  59. package/claude/skills/sd-unpack/scripts/handlers/__pycache__/pdf_handler.cpython-314.pyc +0 -0
  60. package/claude/skills/sd-unpack/scripts/handlers/_common.py +17 -2
  61. package/claude/skills/sd-unpack/scripts/handlers/eml_handler.py +100 -24
  62. package/claude/skills/sd-unpack/scripts/handlers/msg_handler.py +140 -27
  63. package/claude/skills/sd-unpack/scripts/handlers/office_com.py +698 -107
  64. package/claude/skills/sd-unpack/scripts/handlers/office_worker.py +34 -26
  65. package/claude/skills/sd-unpack/scripts/handlers/pdf_handler.py +130 -8
  66. package/package.json +1 -1
@@ -122,7 +122,11 @@ def cmd_ppt_png(args) -> None:
122
122
  # ====================================================================
123
123
 
124
124
  def cmd_excel_sheets(args) -> None:
125
- """input 은 이미 sheetProtection strip 된 사본 (호출자가 처리). 결과 sheet_ranges 는 stdout JSON."""
125
+ """input 은 이미 sheetProtection strip 된 사본 (호출자가 처리).
126
+
127
+ stdout JSON 형식: {"sheet_ranges": {raw_name: [last_row, last_col]},
128
+ "skipped": {raw_name: "PNG skip 사유"}}
129
+ """
126
130
  import pythoncom
127
131
  import win32com.client
128
132
 
@@ -131,6 +135,7 @@ def cmd_excel_sheets(args) -> None:
131
135
  sheet_names = json.loads(args.sheet_names) # [[idx, safe_name, raw_name], ...]
132
136
 
133
137
  sheet_ranges: dict[str, tuple[int, int]] = {}
138
+ skipped: dict[str, str] = {} # raw_name -> PNG skip 사유 (silent skip 금지)
134
139
  tmp_dir = Path(tempfile.mkdtemp(prefix="sd-unpack-worker-"))
135
140
 
136
141
  pythoncom.CoInitialize()
@@ -148,7 +153,7 @@ def cmd_excel_sheets(args) -> None:
148
153
  excel.Calculation = -4135 # xlCalculationManual
149
154
  wb.RemovePersonalInformation = False
150
155
  for idx, safe_name, raw_name in sheet_names:
151
- _export_one_sheet(wb, tmp_dir, sheets_dir, idx, safe_name, raw_name, sheet_ranges)
156
+ _export_one_sheet(wb, tmp_dir, sheets_dir, idx, safe_name, raw_name, sheet_ranges, skipped)
152
157
  finally:
153
158
  wb.Close(SaveChanges=False)
154
159
  finally:
@@ -157,13 +162,16 @@ def cmd_excel_sheets(args) -> None:
157
162
  pythoncom.CoUninitialize()
158
163
  shutil.rmtree(tmp_dir, ignore_errors=True)
159
164
 
160
- sys.stdout.write(json.dumps(sheet_ranges))
165
+ sys.stdout.write(json.dumps({"sheet_ranges": sheet_ranges, "skipped": skipped}))
161
166
 
162
167
 
163
168
  def _export_one_sheet(wb, tmp: Path, sheets_dir: Path,
164
169
  idx: str, safe_name: str, raw_name: str,
165
- sheet_ranges: dict) -> None:
166
- """한 시트 데이터 영역 → PNG. sheet_ranges 에 (last_row, last_col) 기록."""
170
+ sheet_ranges: dict, skipped: dict) -> None:
171
+ """한 시트 데이터 영역 → PNG. sheet_ranges 에 (last_row, last_col) 기록.
172
+
173
+ PNG export 실패 시 skipped[raw_name] 에 사유 기록 후 return (silent 금지, 호출자가 README 명시).
174
+ """
167
175
  ws = wb.Worksheets(raw_name)
168
176
  # xlSheetVisible=-1, xlSheetHidden=0, xlSheetVeryHidden=2
169
177
  # Hidden/VeryHidden 시트도 export 하려면 임시로 보이게.
@@ -196,10 +204,20 @@ def _export_one_sheet(wb, tmp: Path, sheets_dir: Path,
196
204
  # xlScreen=1, xlPicture=-4147
197
205
  # Format=xlBitmap(2) 는 화면 픽셀 버퍼 캡처라 Excel.Visible=False headless 환경에서 빈/부분 비트맵 생성됨 → xlPicture(EMF) 사용.
198
206
  # EMF 는 메타파일 명령으로 시트 콘텐츠 직접 직렬화 → 화면 렌더 의존 없음.
199
- # ChartObject pt 그대로. PNG 는 시각/레이아웃용, 정확한 텍스트는 .md 책임.
200
- # 사이즈 키우면 큰 시트가 Chart.Export PNG dimension 16-bit cap (65535px) 에 걸림.
207
+ # ChartObject pt 그대로. PNG 는 시각/레이아웃용, 정확한 텍스트는 .jsonl 책임.
201
208
  chart_w = data_range.Width
202
209
  chart_h = data_range.Height
210
+
211
+ # Chart.Export PNG dimension 16-bit cap (65535px) 회피.
212
+ # ChartObject 의 pt → px 변환은 약 96 dpi. 안전 margin 으로 60000px cap.
213
+ est_w_px = chart_w * 96 / 72
214
+ est_h_px = chart_h * 96 / 72
215
+ if est_w_px > 60000 or est_h_px > 60000:
216
+ skipped[raw_name] = (
217
+ f"16-bit cap 초과 (chart_w_pt={chart_w:.0f}, chart_h_pt={chart_h:.0f}, "
218
+ f"est_w_px={est_w_px:.0f}, est_h_px={est_h_px:.0f})"
219
+ )
220
+ return
203
221
  try:
204
222
  data_range.CopyPicture(Appearance=1, Format=-4147)
205
223
  chart_obj = ws.ChartObjects().Add(0, 0, chart_w, chart_h)
@@ -211,30 +229,20 @@ def _export_one_sheet(wb, tmp: Path, sheets_dir: Path,
211
229
  if tmp_png.exists():
212
230
  # long-path-safe copy
213
231
  shutil.copy2(long_str(tmp_png), long_str(sheets_dir / f"{idx}_{safe_name}.png"))
232
+ else:
233
+ skipped[raw_name] = "Chart.Export 산출 PNG 미생성"
214
234
  finally:
215
235
  chart_obj.Delete()
216
236
  except Exception as e:
217
- diag = [
218
- f"raw_name={raw_name!r}",
219
- f"type={getattr(ws, 'Type', '?')}",
220
- f"visible={original_visible}",
237
+ # PNG export 실패 — 시트 jsonl 은 호출자에서 별도 생성됨. sheet_ranges 이미 기록됨.
238
+ # 분석 정확도: 데이터(jsonl) 보존 우선, PNG 만 skip + 사유 명시.
239
+ reason_parts = [
240
+ f"Excel COM PNG export 실패",
241
+ f"chart_w_pt={chart_w:.0f}, chart_h_pt={chart_h:.0f}",
221
242
  f"last_row={last_row}, last_col={last_col}",
222
- f"chart_w_pt={chart_w:.1f}, chart_h_pt={chart_h:.1f}",
243
+ f"error={str(e)[:200]}",
223
244
  ]
224
- for attr in ("ProtectContents", "ProtectDrawingObjects", "AutoFilterMode"):
225
- try:
226
- diag.append(f"{attr}={getattr(ws, attr)}")
227
- except Exception as ee:
228
- diag.append(f"{attr}_FAIL={ee}")
229
- try:
230
- diag.append(f"chartobj_count={ws.ChartObjects().Count}")
231
- except Exception as ee:
232
- diag.append(f"chartobj_count_FAIL={ee}")
233
- raise RuntimeError(
234
- "Excel sheet PNG export failed: "
235
- + " | ".join(diag)
236
- + f" | original_error={e}"
237
- ) from e
245
+ skipped[raw_name] = " | ".join(reason_parts)
238
246
  finally:
239
247
  if original_visible != -1:
240
248
  ws.Visible = original_visible
@@ -1,6 +1,10 @@
1
- """PDF 핸들러. PyMuPDF (fitz) 로 페이지별 PNG + MD + embedded files 추출."""
1
+ """PDF 핸들러. PyMuPDF (fitz) 로 페이지별 PNG + 블록 단위 JSONL + 단위 노드.
2
+
3
+ 블록 단위 (text_block·image_block) 는 bbox 좌표 보존. 표는 find_tables() 로 셀 단위 노드.
4
+ """
2
5
  from __future__ import annotations
3
6
 
7
+ import json
4
8
  import os
5
9
  from pathlib import Path
6
10
 
@@ -12,6 +16,7 @@ def run(input_path: Path, out_dir: Path) -> None:
12
16
  import fitz # PyMuPDF
13
17
 
14
18
  pages_dir = out_dir / "pages"
19
+ images_dir = out_dir / "images"
15
20
  _common.mkdir(pages_dir)
16
21
 
17
22
  doc = fitz.open(_common.long_str(input_path))
@@ -19,11 +24,19 @@ def run(input_path: Path, out_dir: Path) -> None:
19
24
  page_summaries: list[str] = []
20
25
  for i, page in enumerate(doc, start=1):
21
26
  idx = f"{i:03d}"
22
- text = page.get_text("text") or ""
23
- _common.write_text(pages_dir / f"{idx}.md", text)
24
27
  pix = page.get_pixmap(dpi=300)
25
28
  pix.save(_common.long_str(pages_dir / f"{idx}.png"))
26
- page_summaries.append(f"`pages/{idx}.png` (시각) — `.md` ({len(text)}자)")
29
+
30
+ jsonl_lines, counts = _pdf_page_to_jsonl(page, i, images_dir)
31
+ _common.write_text(pages_dir / f"{idx}.jsonl", "\n".join(jsonl_lines))
32
+
33
+ parts = [
34
+ f"`pages/{idx}.png` `.jsonl`",
35
+ f"blocks {counts['text_blocks'] + counts['image_blocks']}",
36
+ ]
37
+ if counts["tables"]:
38
+ parts.append(f"tables {counts['tables']} (cells {counts['table_cells']})")
39
+ page_summaries.append(" — ".join([parts[0], ", ".join(parts[1:])]))
27
40
 
28
41
  # 임베드된 첨부 (embedded files)
29
42
  attachment_links: list[str] = []
@@ -36,12 +49,13 @@ def run(input_path: Path, out_dir: Path) -> None:
36
49
  _common.mkdir(attachments_dir)
37
50
  dst = _common.unique_path(attachments_dir, filename)
38
51
  _common.write_bytes(dst, data)
52
+ size = dst.stat().st_size
39
53
  recursed = maybe_recurse_attachment(dst, attachments_dir)
40
54
  if recursed is not None:
41
55
  os.unlink(_common.long_str(dst))
42
- attachment_links.append(f"attachments/{recursed.name}/")
56
+ attachment_links.append(f"attachments/{recursed.name}/ ({_common.format_size(size)})")
43
57
  else:
44
- attachment_links.append(f"attachments/{dst.name}")
58
+ attachment_links.append(f"attachments/{dst.name} ({_common.format_size(size)})")
45
59
  finally:
46
60
  doc.close()
47
61
 
@@ -50,7 +64,115 @@ def run(input_path: Path, out_dir: Path) -> None:
50
64
  source_name=input_path.name,
51
65
  source_size=input_path.stat().st_size,
52
66
  tool="PyMuPDF (fitz)",
53
- loss_notes="PDF 양식 필드(form field)는 텍스트 추출에 포함되지 않을 수 있음. 의심 시 _source.pdf 직접 확인.",
54
- sections={f"페이지 ( {len(page_summaries)}개)": page_summaries[:50]} if page_summaries else None,
67
+ loss_notes=(
68
+ "PDF 양식 필드(form field)·서명·OCR 미적용(스캔 PDF). "
69
+ "구조는 pages/<NNN>.jsonl (블록 bbox + 표 셀 단위 노드), 시각은 .png. "
70
+ "이미지 블록은 images/ 로 별도 저장."
71
+ ),
72
+ sections={f"페이지 (총 {len(page_summaries)}개)": page_summaries} if page_summaries else None,
55
73
  attachments=attachment_links,
56
74
  )
75
+
76
+
77
+ def _pdf_page_to_jsonl(
78
+ page, page_num: int, images_dir: Path,
79
+ ) -> tuple[list[str], dict[str, int]]:
80
+ """한 PDF 페이지 → jsonl 라인 list + counts.
81
+
82
+ 추출:
83
+ 1. get_text("dict") 의 모든 블록 (text_block·image_block) — 표 영역 겹쳐도 그대로 보존
84
+ 2. find_tables() 로 표 셀 단위 노드 추가 (블록과 중복 가능, Claude 가 양쪽 비교 판단)
85
+ """
86
+ counts = {"text_blocks": 0, "image_blocks": 0, "tables": 0, "table_cells": 0}
87
+
88
+ # 블록 추출 (모든 블록 보존)
89
+ page_dict = page.get_text("dict")
90
+ blocks = page_dict.get("blocks", [])
91
+
92
+ node_lines: list[dict] = []
93
+ block_idx = 0
94
+ for blk in blocks:
95
+ btype = blk.get("type", 0)
96
+ bbox = blk.get("bbox", [0, 0, 0, 0])
97
+
98
+ if btype == 0:
99
+ text_lines: list[str] = []
100
+ for line in blk.get("lines", []):
101
+ spans = line.get("spans", [])
102
+ line_text = "".join(span.get("text", "") for span in spans)
103
+ if line_text.strip():
104
+ text_lines.append(line_text)
105
+ text = "\n".join(text_lines).strip()
106
+ if not text:
107
+ continue
108
+ node_lines.append({
109
+ "page": page_num,
110
+ "block": block_idx,
111
+ "type": "text_block",
112
+ "bbox": [round(c, 2) for c in bbox],
113
+ "text": text,
114
+ })
115
+ counts["text_blocks"] += 1
116
+ block_idx += 1
117
+ elif btype == 1:
118
+ img_bytes = blk.get("image")
119
+ ext = (blk.get("ext") or "bin").lstrip(".")
120
+ ref = ""
121
+ if img_bytes:
122
+ _common.mkdir(images_dir)
123
+ img_filename = f"p{page_num:03d}_b{block_idx:03d}.{ext}"
124
+ _common.write_bytes(images_dir / img_filename, img_bytes)
125
+ ref = f"images/{img_filename}"
126
+ node_lines.append({
127
+ "page": page_num,
128
+ "block": block_idx,
129
+ "type": "image_block",
130
+ "bbox": [round(c, 2) for c in bbox],
131
+ "ref": ref,
132
+ })
133
+ counts["image_blocks"] += 1
134
+ block_idx += 1
135
+
136
+ # 표 셀 노드 (find_tables, 블록과 중복 가능 — 양쪽 다 보존)
137
+ tables: list = []
138
+ try:
139
+ finder = page.find_tables()
140
+ tables = list(finder.tables) if hasattr(finder, "tables") else list(finder)
141
+ except Exception:
142
+ tables = []
143
+ counts["tables"] = len(tables)
144
+
145
+ for t_idx, tab in enumerate(tables, start=1):
146
+ try:
147
+ rows = tab.extract()
148
+ t_bbox = [round(c, 2) for c in tab.bbox]
149
+ except Exception:
150
+ continue
151
+ for r_idx, row in enumerate(rows, start=1):
152
+ for c_idx, cell_text in enumerate(row, start=1):
153
+ if cell_text is None:
154
+ continue
155
+ node_lines.append({
156
+ "page": page_num,
157
+ "type": "table_cell",
158
+ "table_idx": t_idx,
159
+ "table_bbox": t_bbox,
160
+ "row": r_idx,
161
+ "col": c_idx,
162
+ "text": str(cell_text).strip(),
163
+ })
164
+ counts["table_cells"] += 1
165
+
166
+ meta = {
167
+ "_meta": {
168
+ "page": page_num,
169
+ "size": [round(page.rect.width, 2), round(page.rect.height, 2)],
170
+ "blocks": counts["text_blocks"] + counts["image_blocks"],
171
+ "tables": counts["tables"],
172
+ "table_cells": counts["table_cells"],
173
+ }
174
+ }
175
+ lines = [json.dumps(meta, ensure_ascii=False)]
176
+ for n in node_lines:
177
+ lines.append(json.dumps(n, ensure_ascii=False))
178
+ return lines, counts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simplysm/sd-claude",
3
- "version": "14.0.75",
3
+ "version": "14.0.77",
4
4
  "description": "심플리즘 패키지 - Claude Code 셋업",
5
5
  "author": "심플리즘",
6
6
  "license": "Apache-2.0",