@simplysm/sd-claude 14.0.41 → 14.0.43

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 (67) hide show
  1. package/claude/references/sd-simplysm14/angular/docs/directives.md +74 -3
  2. package/claude/references/sd-simplysm14/angular/docs/features.md +64 -14
  3. package/claude/references/sd-simplysm14/angular/docs/plugins.md +2 -90
  4. package/claude/references/sd-simplysm14/angular/docs/providers.md +2 -2
  5. package/claude/references/sd-simplysm14/angular/docs/type-utilities.md +1 -2
  6. package/claude/references/sd-simplysm14/angular/docs/ui-data.md +103 -23
  7. package/claude/references/sd-simplysm14/angular/docs/ui-form.md +173 -28
  8. package/claude/references/sd-simplysm14/angular/docs/ui-layout.md +19 -4
  9. package/claude/references/sd-simplysm14/angular/docs/ui-navigation.md +20 -2
  10. package/claude/references/sd-simplysm14/angular/docs/ui-overlay.md +23 -14
  11. package/claude/references/sd-simplysm14/angular/docs/ui-visual.md +15 -7
  12. package/claude/references/sd-simplysm14/angular/docs/utils.md +1 -1
  13. package/claude/references/sd-simplysm14/angular/usage.md +16 -15
  14. package/claude/references/sd-simplysm14/capacitor-plugin-auto-update/usage.md +1 -1
  15. package/claude/references/sd-simplysm14/capacitor-plugin-file-system/docs/file-operations.md +154 -0
  16. package/claude/references/sd-simplysm14/capacitor-plugin-file-system/docs/permissions.md +84 -0
  17. package/claude/references/sd-simplysm14/capacitor-plugin-file-system/docs/storage-paths.md +107 -0
  18. package/claude/references/sd-simplysm14/capacitor-plugin-file-system/docs/types.md +83 -0
  19. package/claude/references/sd-simplysm14/capacitor-plugin-file-system/usage.md +83 -128
  20. package/claude/references/sd-simplysm14/capacitor-plugin-usb-storage/usage.md +99 -1
  21. package/claude/references/sd-simplysm14/core-node/docs/child-process.md +182 -0
  22. package/claude/references/sd-simplysm14/core-node/docs/features.md +1 -1
  23. package/claude/references/sd-simplysm14/core-node/docs/file-system.md +509 -0
  24. package/claude/references/sd-simplysm14/core-node/docs/file-watching.md +139 -0
  25. package/claude/references/sd-simplysm14/core-node/docs/logging.md +180 -0
  26. package/claude/references/sd-simplysm14/core-node/docs/path.md +176 -0
  27. package/claude/references/sd-simplysm14/core-node/docs/worker-threads.md +334 -0
  28. package/claude/references/sd-simplysm14/core-node/usage.md +192 -96
  29. package/claude/references/sd-simplysm14/excel/docs/core-classes.md +33 -14
  30. package/claude/references/sd-simplysm14/excel/usage.md +47 -45
  31. package/claude/references/sd-simplysm14/lint/usage.md +3 -2
  32. package/claude/references/sd-simplysm14/orm-common/docs/queryable-executable.md +30 -35
  33. package/claude/references/sd-simplysm14/orm-common/usage.md +9 -8
  34. package/claude/references/sd-simplysm14/sd-claude/docs/assets.md +43 -34
  35. package/claude/references/sd-simplysm14/sd-claude/docs/cli.md +1 -1
  36. package/claude/references/sd-simplysm14/sd-claude/docs/hooks.md +20 -2
  37. package/claude/references/sd-simplysm14/sd-claude/docs/scripts.md +5 -18
  38. package/claude/references/sd-simplysm14/sd-claude/usage.md +6 -5
  39. package/claude/references/sd-simplysm14/sd-cli/usage.md +176 -1
  40. package/claude/references/sd-simplysm14/service-client/usage.md +126 -61
  41. package/claude/references/sd-simplysm14/service-common/usage.md +28 -28
  42. package/claude/references/sd-simplysm14/storage/usage.md +123 -30
  43. package/claude/references/sd-testing.md +100 -4
  44. package/claude/rules/sd-claude-rules.md +19 -4
  45. package/claude/sd-check-write.py +1 -1
  46. package/claude/skills/sd-check/SKILL.md +7 -4
  47. package/claude/skills/sd-claude-docs/SKILL.md +7 -4
  48. package/claude/skills/sd-claude-docs/references/package-doc-gen.md +30 -7
  49. package/claude/skills/sd-commit/SKILL.md +2 -0
  50. package/claude/skills/sd-debug/SKILL.md +1 -1
  51. package/claude/skills/sd-deliverable/SKILL.md +2 -0
  52. package/claude/skills/sd-dev/SKILL.md +1 -1
  53. package/claude/skills/sd-doc-extract/SKILL.md +2 -0
  54. package/claude/{references/sd-debug.md → skills/sd-inner-debug/SKILL.md} +16 -20
  55. package/claude/{references/sd-review.md → skills/sd-inner-review/SKILL.md} +9 -4
  56. package/claude/skills/sd-issue/SKILL.md +2 -0
  57. package/claude/skills/sd-outlook/SKILL.md +2 -0
  58. package/claude/skills/sd-plan/SKILL.md +1 -1
  59. package/claude/skills/sd-prompt/SKILL.md +2 -2
  60. package/claude/skills/sd-refactor/SKILL.md +2 -2
  61. package/claude/skills/sd-review/SKILL.md +1 -1
  62. package/claude/skills/sd-tdd/SKILL.md +7 -7
  63. package/claude/skills/sd-use/SKILL.md +2 -0
  64. package/claude/skills/sd-wbs/SKILL.md +41 -18
  65. package/package.json +1 -1
  66. /package/claude/{rules → references}/sd-simplysm14.md +0 -0
  67. /package/claude/{references → rules}/sd-clarify.md +0 -0
@@ -14,9 +14,9 @@ npm install @simplysm/storage
14
14
 
15
15
  | API | Type | Description |
16
16
  |-----|------|-------------|
17
- | `StorageProtocol` | type | 지원 프로토콜 유니온 타입 (`"ftp" \| "ftps" \| "sftp"`) |
18
- | `StorageConnConfig` | interface | 스토리지 연결 설정 (host, port, user, password) |
19
- | `FileInfo` | interface | 파일/디렉토리 정보 (name, isFile) |
17
+ | `StorageProtocol` | type | 지원 프로토콜 유니온 타입 (`"ftp"` \| `"ftps"` \| `"sftp"`) |
18
+ | `StorageConnConfig` | interface | 스토리지 연결 설정 |
19
+ | `FileInfo` | interface | 파일/디렉토리 정보 |
20
20
  | `StorageClient` | interface | 스토리지 클라이언트 공통 인터페이스 |
21
21
 
22
22
  #### `StorageProtocol`
@@ -25,6 +25,11 @@ npm install @simplysm/storage
25
25
  type StorageProtocol = "ftp" | "ftps" | "sftp";
26
26
  ```
27
27
 
28
+ 지원하는 프로토콜을 나타내는 유니온 타입:
29
+ - `"ftp"`: 일반 FTP 프로토콜
30
+ - `"ftps"`: TLS/SSL 암호화 FTP 프로토콜
31
+ - `"sftp"`: SSH 기반 SFTP 프로토콜
32
+
28
33
  #### `StorageConnConfig`
29
34
 
30
35
  ```typescript
@@ -39,9 +44,9 @@ interface StorageConnConfig {
39
44
  | Field | Type | Description |
40
45
  |-------|------|-------------|
41
46
  | `host` | `string` | 서버 호스트 주소 |
42
- | `port` | `number \| undefined` | 포트 번호 (생략 시 프로토콜 기본값 사용) |
47
+ | `port` | `number \| undefined` | 포트 번호. 생략 시 프로토콜 기본값 사용 (FTP: 21, FTPS: 21, SFTP: 22) |
43
48
  | `user` | `string \| undefined` | 사용자 이름 |
44
- | `password` | `string \| undefined` | 비밀번호. SFTP에서 생략하면 SSH agent + `~/.ssh/id_ed25519` 키 파일 인증 시도 |
49
+ | `password` | `string \| undefined` | 비밀번호. SFTP에서 생략하면 SSH agent (`SSH_AUTH_SOCK` 환경변수) + `~/.ssh/id_ed25519` 키 파일 인증을 순서대로 시도 |
45
50
 
46
51
  #### `FileInfo`
47
52
 
@@ -54,7 +59,7 @@ interface FileInfo {
54
59
 
55
60
  | Field | Type | Description |
56
61
  |-------|------|-------------|
57
- | `name` | `string` | 파일 또는 디렉토리 이름 |
62
+ | `name` | `string` | 파일 또는 디렉토리의 이름 |
58
63
  | `isFile` | `boolean` | `true`이면 파일, `false`이면 디렉토리 |
59
64
 
60
65
  #### `StorageClient`
@@ -78,23 +83,23 @@ interface StorageClient {
78
83
 
79
84
  | Method | Parameters | Return | Description |
80
85
  |--------|-----------|--------|-------------|
81
- | `connect` | `config: StorageConnConfig` | `Promise<void>` | 서버에 연결 |
82
- | `mkdir` | `dirPath: string` | `Promise<void>` | 디렉토리 생성 (부모 디렉토리 자동 생성) |
83
- | `rename` | `fromPath: string, toPath: string` | `Promise<void>` | 파일/디렉토리 이름 변경 |
86
+ | `connect` | `config: StorageConnConfig` | `Promise<void>` | 스토리지 서버에 연결. 연결 해제 전 한 번만 호출 가능 |
87
+ | `mkdir` | `dirPath: string` | `Promise<void>` | 디렉토리 생성. 부모 디렉토리가 없으면 함께 생성 |
88
+ | `rename` | `fromPath: string, toPath: string` | `Promise<void>` | 파일/디렉토리 이름 변경 또는 이동 |
84
89
  | `list` | `dirPath: string` | `Promise<FileInfo[]>` | 디렉토리 내 파일/디렉토리 목록 조회 |
85
90
  | `readFile` | `filePath: string` | `Promise<Bytes>` | 파일 내용을 `Bytes`(`Uint8Array`)로 읽기 |
86
- | `exists` | `filePath: string` | `Promise<boolean>` | 파일/디렉토리 존재 여부 확인 |
87
- | `put` | `localPathOrBuffer: string \| Bytes, storageFilePath: string` | `Promise<void>` | 로컬 파일 경로 또는 바이트 데이터를 원격에 업로드 |
88
- | `uploadDir` | `fromPath: string, toPath: string` | `Promise<void>` | 로컬 디렉토리 전체를 원격에 업로드 |
91
+ | `exists` | `filePath: string` | `Promise<boolean>` | 파일/디렉토리 존재 여부 확인. 모든 예외는 `false` 반환 |
92
+ | `put` | `localPathOrBuffer: string \| Bytes, storageFilePath: string` | `Promise<void>` | 로컬 파일 경로 또는 바이트 데이터를 원격 경로에 업로드 |
93
+ | `uploadDir` | `fromPath: string, toPath: string` | `Promise<void>` | 로컬 디렉토리 전체를 원격 경로에 업로드 |
89
94
  | `remove` | `filePath: string` | `Promise<void>` | 파일 삭제 |
90
- | `close` | (없음) | `Promise<void>` | 연결 종료 |
95
+ | `close` | 없음 | `Promise<void>` | 연결 종료. 이미 종료된 상태에서 호출해도 안전 |
91
96
 
92
97
  ### Clients
93
98
 
94
99
  | API | Type | Description |
95
100
  |-----|------|-------------|
96
- | `FtpStorageClient` | class | FTP/FTPS 프로토콜 스토리지 클라이언트 (`basic-ftp` 기반) |
97
- | `SftpStorageClient` | class | SFTP 프로토콜 스토리지 클라이언트 (`ssh2-sftp-client` 기반) |
101
+ | `FtpStorageClient` | class | FTP/FTPS 프로토콜 스토리지 클라이언트 (`basic-ftp` 라이브러리 기반) |
102
+ | `SftpStorageClient` | class | SFTP 프로토콜 스토리지 클라이언트 (`ssh2-sftp-client` 라이브러리 기반) |
98
103
 
99
104
  #### `FtpStorageClient`
100
105
 
@@ -106,10 +111,14 @@ class FtpStorageClient implements StorageClient {
106
111
  }
107
112
  ```
108
113
 
109
- - `_secure` 생성자 매개변수: `true`이면 FTPS, `false`이면 FTP 사용
110
- - 미연결 상태에서 메서드를 호출하면 `SdError`를 던진다
111
- - `exists()`는 먼저 `size()` 명령으로 파일을 O(1) 확인하고, 실패 시 부모 디렉토리 목록을 조회하여 디렉토리 존재 여부를 확인한다. 모든 예외에 대해 `false`를 반환한다
112
- - `close()`는 이미 종료된 상태에서 호출해도 안전하다
114
+ **생성자**:
115
+ - `_secure`: `true`이면 FTPS (TLS/SSL 암호화), `false`이면 FTP (기본값)
116
+
117
+ **특징**:
118
+ - `connect()`로 연결 후 메서드 호출. 미연결 상태에서 호출하면 `SdError` 발생
119
+ - `exists()`는 먼저 `size()` 명령으로 파일을 O(1) 성능으로 확인하고, 실패 시 부모 디렉토리 목록을 조회하여 디렉토리 존재 여부 확인. 모든 예외는 `false` 반환
120
+ - 슬래시가 없는 경로(예: `file.txt`)는 루트 디렉토리(`/`)에서 검색
121
+ - `close()`는 이미 종료된 상태에서 호출해도 안전 (오류 미발생)
113
122
 
114
123
  #### `SftpStorageClient`
115
124
 
@@ -121,16 +130,23 @@ class SftpStorageClient implements StorageClient {
121
130
  }
122
131
  ```
123
132
 
124
- - `password`가 있으면 패스워드 인증, 없으면 SSH agent(`SSH_AUTH_SOCK`) + `~/.ssh/id_ed25519` 키 파일 인증을 순서대로 시도한다
125
- - 미연결 상태에서 메서드를 호출하면 `SdError`를 던진다
126
- - `exists()`는 `ssh2-sftp-client`의 `exists()` 반환값(`false | 'd' | '-' | 'l'`)으로 판단한다
127
- - `close()`는 이미 종료된 상태에서 호출해도 안전하다
133
+ **인증 메커니즘**:
134
+ - `password`가 있으면 패스워드 인증 사용
135
+ - `password`가 없으면 다음 순서대로 시도:
136
+ 1. SSH agent (`SSH_AUTH_SOCK` 환경변수 설정 시)
137
+ 2. `~/.ssh/id_ed25519` 개인키 파일 인증
138
+ 3. privateKey 파싱 실패 시 (암호화된 키 등) agent만으로 재시도
139
+
140
+ **특징**:
141
+ - `connect()`로 연결 후 메서드 호출. 미연결 상태에서 호출하면 `SdError` 발생
142
+ - `exists()`는 `ssh2-sftp-client`의 `exists()` 반환값 (`false | 'd' | '-' | 'l'`)을 검사하여 존재 여부 판단. 모든 예외는 `false` 반환
143
+ - `close()`는 이미 종료된 상태에서 호출해도 안전 (오류 미발생)
128
144
 
129
145
  ### Factory
130
146
 
131
147
  | API | Type | Description |
132
148
  |-----|------|-------------|
133
- | `StorageFactory` | class | 프로토콜에 따른 클라이언트 생성 및 연결 생명주기 관리 팩토리 |
149
+ | `StorageFactory` | class | 프로토콜별 클라이언트 생성 및 연결 생명주기 자동 관리 |
134
150
 
135
151
  #### `StorageFactory`
136
152
 
@@ -146,17 +162,25 @@ class StorageFactory {
146
162
  }
147
163
  ```
148
164
 
165
+ **메서드**:
166
+ - `StorageFactory.connect<R>()` (정적 메서드)
167
+
168
+ **파라미터**:
169
+
149
170
  | Parameter | Type | Description |
150
171
  |-----------|------|-------------|
151
- | `type` | `StorageProtocol` | 프로토콜 (`"ftp"`, `"ftps"`, `"sftp"`) |
172
+ | `type` | `StorageProtocol` | 프로토콜 타입 (`"ftp"`, `"ftps"`, `"sftp"`) |
152
173
  | `config` | `StorageConnConfig` | 연결 설정 |
153
- | `fn` | `(storage: StorageClient) => R \| Promise<R>` | 연결된 클라이언트를 받아 작업을 수행하는 콜백 |
174
+ | `fn` | `(storage: StorageClient) => R \| Promise<R>` | 연결된 클라이언트를 받아 작업을 수행하는 콜백. 반환값은 `Promise<R>` 형태로 래핑되어 반환됨 |
154
175
 
155
- 콜백 완료 또는 예외 발생 시 연결이 자동으로 종료된다.
176
+ **동작**:
177
+ - `fn` 콜백이 완료되거나 예외를 발생시키면 연결이 자동으로 종료됨
178
+ - `fn`에서 발생한 예외는 그대로 전파됨
179
+ - 반환값: 콜백의 반환값
156
180
 
157
181
  ## Usage Examples
158
182
 
159
- ### StorageFactory.connect로 파일 업로드
183
+ ### SFTP로 파일 업로드 (StorageFactory 사용 권장)
160
184
 
161
185
  ```typescript
162
186
  import { StorageFactory } from "@simplysm/storage";
@@ -171,7 +195,9 @@ await StorageFactory.connect(
171
195
  );
172
196
  ```
173
197
 
174
- ### 파일 목록 조회 다운로드
198
+ 콜백이 완료되거나 예외가 발생하면 자동으로 연결이 종료된다.
199
+
200
+ ### FTP로 파일 목록 조회 및 다운로드
175
201
 
176
202
  ```typescript
177
203
  import { StorageFactory } from "@simplysm/storage";
@@ -184,18 +210,38 @@ const files = await StorageFactory.connect(
184
210
  for (const file of list.filter((f) => f.isFile)) {
185
211
  const content = await storage.readFile(`/data/${file.name}`);
186
212
  // content는 Bytes (Uint8Array)
213
+ console.log(`Read ${file.name}: ${content.length} bytes`);
187
214
  }
188
215
  return list;
189
216
  },
190
217
  );
191
218
  ```
192
219
 
220
+ ### FTPS로 파일 이름 변경
221
+
222
+ ```typescript
223
+ import { StorageFactory } from "@simplysm/storage";
224
+
225
+ await StorageFactory.connect(
226
+ "ftps",
227
+ { host: "ftps.example.com", user: "user", password: "pass" },
228
+ async (storage) => {
229
+ const exists = await storage.exists("/remote/file.txt");
230
+ if (exists) {
231
+ await storage.rename("/remote/file.txt", "/remote/backup.txt");
232
+ }
233
+ },
234
+ );
235
+ ```
236
+
193
237
  ### 클라이언트 직접 사용 (수동 생명주기 관리)
194
238
 
239
+ 클라이언트를 직접 인스턴스화하여 수동으로 연결 생명주기를 관리할 수 있다. 하지만 연결 누수 위험이 있으므로 `StorageFactory.connect` 사용을 권장한다.
240
+
195
241
  ```typescript
196
242
  import { FtpStorageClient } from "@simplysm/storage";
197
243
 
198
- const client = new FtpStorageClient(true); // FTPS
244
+ const client = new FtpStorageClient(true); // FTPS 사용
199
245
  try {
200
246
  await client.connect({ host: "ftps.example.com", user: "user", password: "pass" });
201
247
  const exists = await client.exists("/remote/file.txt");
@@ -206,3 +252,50 @@ try {
206
252
  await client.close();
207
253
  }
208
254
  ```
255
+
256
+ ### SSH 키 인증으로 SFTP 연결 (비밀번호 생략)
257
+
258
+ `password`를 생략하면 SSH agent와 `~/.ssh/id_ed25519` 키 파일을 사용하여 인증한다.
259
+
260
+ ```typescript
261
+ import { StorageFactory } from "@simplysm/storage";
262
+
263
+ await StorageFactory.connect(
264
+ "sftp",
265
+ { host: "sftp.example.com", user: "user" }, // password 생략
266
+ async (storage) => {
267
+ const list = await storage.list("/home/user");
268
+ console.log(`Files: ${list.map((f) => f.name).join(", ")}`);
269
+ },
270
+ );
271
+ ```
272
+
273
+ ### 바이트 데이터로 파일 업로드
274
+
275
+ ```typescript
276
+ import { StorageFactory } from "@simplysm/storage";
277
+
278
+ const data = new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f]); // "Hello"
279
+
280
+ await StorageFactory.connect(
281
+ "sftp",
282
+ { host: "sftp.example.com", user: "user", password: "pass" },
283
+ async (storage) => {
284
+ await storage.put(data, "/remote/file.bin");
285
+ },
286
+ );
287
+ ```
288
+
289
+ ### 로컬 디렉토리 전체 업로드
290
+
291
+ ```typescript
292
+ import { StorageFactory } from "@simplysm/storage";
293
+
294
+ await StorageFactory.connect(
295
+ "ftp",
296
+ { host: "ftp.example.com", user: "user", password: "pass" },
297
+ async (storage) => {
298
+ await storage.uploadDir("/local/folder", "/remote/backup");
299
+ },
300
+ );
301
+ ```
@@ -2,11 +2,107 @@
2
2
 
3
3
  ## 모킹 원칙
4
4
 
5
- - **CRITICAL: 모든 코드는 기본적으로 실제로 실행시킨다.** 테스트 환경에서 물리적으로 실행 불가능한 것(실제 네트워크 서버, 하드웨어 장치, 사용자 상호작용 UI 등)만 어쩔 수 없이 모킹한다.
6
- - 모킹 여부를 판단할 때 "이걸 모킹하면 편하다"가 아니라 **"이걸 모킹 안 하면 테스트가 물리적으로 돌아갈 수 없는가?"** 를 기준으로 한다. 답이 "돌아간다"이면 모킹하지 않는다.
5
+ **CRITICAL: 모든 코드는 기본적으로 실제로 실행시킨다. mock은 최후의 수단이다.**
6
+
7
+ ### 모킹 결정 플로우차트
8
+
9
+ mock을 작성하기 **전에** 반드시 아래 순서로 판단한다:
10
+
11
+ 1. **이 의존성을 실제로 실행하면 테스트가 물리적으로 돌아가는가?**
12
+ - 돌아간다 → **모킹 금지. 실제 코드를 실행한다.**
13
+ - 돌아가지 않는다 → 2번으로
14
+ 2. **왜 물리적으로 불가능한가? 아래 화이트리스트에 해당하는가?**
15
+ - 해당한다 → 모킹 허용 (최소 범위로)
16
+ - 해당하지 않는다 → **모킹 금지. 테스트 설계를 재고한다.**
17
+
18
+ ### 모킹 허용 화이트리스트
19
+
20
+ 아래 항목**만** 모킹이 허용된다. 이 목록에 없으면 모킹하지 않는다:
21
+
22
+ | 허용 대상 | 예시 | 이유 |
23
+ |-----------|------|------|
24
+ | 외부 네트워크 요청 | HTTP API 호출, WebSocket 서버 연결 | 외부 서버가 테스트 환경에 없음 |
25
+ | 데이터베이스 연결 | DB 커넥션, 쿼리 실행 | DB 인스턴스가 테스트 환경에 없을 때만 (Docker로 띄울 수 있으면 실제 DB 사용) |
26
+ | 하드웨어/OS 의존 | USB 장치, 파일시스템의 특수 권한, 네이티브 플러그인 | 물리 장치가 없음 |
27
+ | 타이머/시간 | `Date.now()`, `setTimeout` | `vi.useFakeTimers()`로 제어 (이것은 mock이 아닌 테스트 유틸리티) |
28
+
29
+ **다음은 모킹 대상이 아니다:**
30
+ - 같은 패키지의 다른 모듈/클래스 → 실제 인스턴스를 생성해서 사용
31
+ - 유틸리티 함수, 순수 함수 → 그냥 실행
32
+ - 설정/config 객체 → 테스트용 실제 값을 만들어서 주입
33
+ - 에러 핸들링 로직 → 실제 에러를 발생시켜서 테스트
34
+ - 이벤트 핸들러/콜백 → 실제로 이벤트를 발생시켜서 테스트
35
+
36
+ ### 모킹 세부 규칙
37
+
7
38
  - `vi.mock()` 하나가 모듈 전체를 대체하므로, 순수 함수까지 함께 가짜로 바뀐다. 혼합 모듈은 `importOriginal`로 실제 구현을 최대한 살린다.
8
- - **모킹이 실제 로직을 복제하면 모킹이 불필요하다는 증거다.** 예: `err: { message: (e) => e?.message ?? String(e) }` — 원본과 동일한 코드를 모킹에 작성하고 있다면 실제 모듈을 그대로 쓴다.
9
- - **호출 여부만 확인하려면 `vi.mock()`이 아니라 `vi.spyOn()`을 쓴다.** mock은 구현을 가짜로 대체하고, spy는 실제 코드를 실행하면서 호출을 추적한다. 실제 동작을 유지한 채 "호출됐는가"만 검증하려면 항상 spy를 쓴다.
39
+ - **모킹이 실제 로직을 복제하면 모킹이 불필요하다는 증거다.** 모킹 코드가 원본 코드와 동일하거나 유사하면, 모킹을 삭제하고 실제 모듈을 쓴다.
40
+ - **호출 여부만 확인하려면 `vi.mock()`이 아니라 `vi.spyOn()`을 쓴다.** mock은 구현을 가짜로 대체하고, spy는 실제 코드를 실행하면서 호출을 추적한다.
41
+
42
+ ### Bad/Good 예시
43
+
44
+ #### 예시 1: 같은 패키지의 유틸리티 함수
45
+
46
+ ```typescript
47
+ // ❌ BAD: 실행 가능한 유틸 함수를 모킹
48
+ vi.mock("../utils/string-utils", () => ({
49
+ formatName: vi.fn().mockReturnValue("formatted"),
50
+ }));
51
+
52
+ test("이름을 포맷한다", () => {
53
+ const result = service.process("raw");
54
+ expect(formatName).toHaveBeenCalledWith("raw"); // 구현 결합
55
+ expect(result).toBe("formatted");
56
+ });
57
+ ```
58
+
59
+ ```typescript
60
+ // ✅ GOOD: 실제 유틸 함수를 실행
61
+ import { formatName } from "../utils/string-utils";
62
+
63
+ test("이름을 포맷한다", () => {
64
+ const result = service.process("raw");
65
+ expect(result).toBe("Raw"); // 실제 결과를 검증
66
+ });
67
+ ```
68
+
69
+ #### 예시 2: 클래스 의존성
70
+
71
+ ```typescript
72
+ // ❌ BAD: 같은 패키지의 클래스를 통째로 모킹
73
+ const mockParser = {
74
+ parse: vi.fn().mockReturnValue({ type: "text", value: "hello" }),
75
+ validate: vi.fn().mockReturnValue(true),
76
+ };
77
+
78
+ test("파서를 사용해 변환한다", () => {
79
+ const converter = new Converter(mockParser as any);
80
+ converter.convert("hello");
81
+ expect(mockParser.parse).toHaveBeenCalled(); // 구현 결합
82
+ });
83
+ ```
84
+
85
+ ```typescript
86
+ // ✅ GOOD: 실제 파서 인스턴스를 사용
87
+ test("파서를 사용해 변환한다", () => {
88
+ const parser = new Parser();
89
+ const converter = new Converter(parser);
90
+ const result = converter.convert("hello");
91
+ expect(result).toEqual({ type: "text", value: "hello" }); // 동작 결과를 검증
92
+ });
93
+ ```
94
+
95
+ #### 예시 3: 외부 API (모킹이 정당한 경우)
96
+
97
+ ```typescript
98
+ // ✅ OK: 외부 HTTP 서버는 테스트 환경에 없으므로 모킹 허용
99
+ vi.spyOn(httpClient, "get").mockResolvedValue({ status: 200, data: { id: 1 } });
100
+
101
+ test("사용자 정보를 조회한다", async () => {
102
+ const user = await userService.getUser(1);
103
+ expect(user.id).toBe(1); // 반환값을 검증 (호출 여부가 아님)
104
+ });
105
+ ```
10
106
 
11
107
  ## 검증 원칙
12
108
 
@@ -3,7 +3,15 @@
3
3
  - 어떠한 경우에도 지침을 무시하고 건너뛰지 않는다. 혼자만의 판단으로 무단 진행 절대(NEVER) 금지
4
4
  - 지침이 충돌등의 이유로 애매하면 사용자에게 질문한다.
5
5
  - 근거없는 답변 금지. 답변에는 항상 그 근거가 포함되어야 함.
6
- - 문제의 원인을 지멋대로 추측하지 말것. 코드 문제면 코드베이스 검토 반드시 수행해야함.
6
+ - 코드를 수정할 때, 코드가 문제인지 근거를 먼저 확인하고 수정한다. "일단 바꿔보고 되면 넘어가자" 식의 추측성 시행착오를 절대(NEVER) 금지한다.
7
+
8
+ # CRITICAL: 변경사항 되돌리기 금지
9
+
10
+ **git diff에 나타나는 변경사항을 임의로 되돌리지 않는다(NEVER).**
11
+
12
+ - `git diff`에 보이는 변경은 사용자가 직접 수정한 것일 수 있다. subagent가 만든 변경인지, 사용자가 직접 한 변경인지 구분할 수 없으므로, 어떤 변경이든 되돌리기 전에 반드시 사용자에게 확인한다.
13
+ - 특히 현재 작업 범위 밖의 파일 변경을 발견해도, "의도하지 않은 변경"이라고 단정짓지 않는다. 사용자가 별도로 수정한 코드일 수 있다.
14
+ - 되돌려야 할 명확한 근거(사용자의 명시적 요청, 빌드/테스트 실패 등)가 없으면 절대 되돌리지 않는다.
7
15
 
8
16
  # Compaction Rules
9
17
 
@@ -33,7 +41,7 @@
33
41
 
34
42
  - 사용자의 질문에 답변만 하라. 절대 임의로 다음단계(특히, 코드변경)로 넘어가지 않는다(NEVER). 답변만 하고 사용자의 명시적 요청을 기다린다.
35
43
  - 사용자의 질문은 동의를 구하는것이 아니다. 무조건적 동의하려하지 말고, 비판적으로 사고하여 답변한다.
36
- - 사용자의 요청에 대해 `.claude/references/sd-clarify.md`를 읽고, 사용자의 의도를 명확화 한다. (절대 추측하지 않는다.)
44
+ - 사용자의 요청에 대해 `.claude/rules/sd-clarify.md` 지침에 따라, 사용자의 의도를 명확화 한다. (절대 추측하지 않는다.)
37
45
 
38
46
  # Playwright
39
47
 
@@ -46,7 +54,7 @@
46
54
  - `@angular/*` 패키지를 사용할 때 `angular-cli` mcp를 활용하여, 표준 사용법을 확인하여 따른다.
47
55
  - 테스트 작성 시 `.claude/references/sd-testing.md`를 읽고 따른다.
48
56
  - 프론트엔드 UI 코드 작성·수정 시 `.claude/references/sd-frontend-design.md`를 읽고 따른다.
49
- - 디버깅 시 `.claude/references/sd-debug.md`를 읽고 따른다.
57
+ - 디버깅 시 `/sd-inner-debug` 스킬을 호출한다.
50
58
  - 코딩을 하거나 코드예제를 출력할때는, 반드시 코드베이스의 기존 패턴을 확인하여 통일성있게 안내한다.
51
59
  - 코드를 수정할 경우 수정에 의한 사이드이펙트를 항상 고려한다. (예, html구조가 바뀌면 css의 selector도 바뀌어야함)
52
60
  - 함수 작성 혹은 함수내 기능 추가시 단일 책임 원칙을 따른다. (함수가 이름에서 드러나지 않는 일을 몰래 해선 안됨)
@@ -54,9 +62,15 @@
54
62
  - **barrel export 금지**: `src/` 루트의 `index.ts`를 제외하고, 하위 폴더에 re-export 전용 `index.ts`를 만들지 않는다. 패키지 루트 `index.ts`에서 개별 파일 경로를 직접 export한다.
55
63
  - 다른 패키지의 타입등 re-export 금지.
56
64
  - **dynamic import (`import()`) 사용 금지**: 조건부 peer dependency 로딩, 외부 ts 파일 읽기 등 정적 import가 불가능한 경우를 제외하고 `import()` 사용 금지. 정적 `import` 문을 사용한다.
65
+ - **null/undefined 비교 규칙**: `===`/`!==` 사용이 기본이지만 **null/undefined 비교만 예외**이다. 일반 값 비교는 `===`/`!==`, null/undefined 검사는 `== null`/`!= null`을 사용한다. `=== null`, `!== null`, `=== undefined`, `!== undefined`는 lint 에러이다.
66
+ - `value === "hello"` ○ (일반 값 비교 → `===`)
67
+ - `value == null` ○ (null/undefined 검사 → `==`)
68
+ - `value === null` ✕ (lint 에러)
69
+ - `value === undefined` ✕ (lint 에러)
57
70
 
58
71
  ## 자주 하는 실수
59
72
 
73
+ - **import 경로에 `.js` 확장자 금지**: 내부 모듈 import 시 `.js` 확장자를 붙이지 않는다. `from "./foo"` ○, `from "./foo.js"` ✕. 이 프로젝트는 번들러(esbuild/Vite)가 확장자를 해석하므로 `.js`를 붙이면 안 된다.
60
74
  - **`as any[]` 캐스팅 후 `??` 방어**: `value as any[]`로 캐스팅하면 TypeScript는 nullable이 아니라고 판단하여 `?? []`에 lint 에러 발생. `value as any[] | undefined`로 캐스팅해야 한다
61
75
  - **타입 추론 해제 금지**: 타입 추론을 해제하는 방식의 수정은 절대 금지한다.
62
76
  - **불필요한 `as` 캐스팅**: 가드(`target !== "client"` 등)로 타입이 좁혀진 후에는 `as SdClientPackageConfig` 같은 캐스팅 불필요. lint 에러 `no-unnecessary-type-assertion` 발생
@@ -67,7 +81,8 @@
67
81
 
68
82
  # @simplysm 패키지 참조
69
83
 
70
- - `@simplysm/*` 패키지 사용 시, `.claude/references/sd-simplysm{메이저버전}.md`를 읽고 해당 패키지의 문서 경로를 찾아 읽는다
84
+ - `@simplysm/*` 패키지 사용 시, `.claude/references/sd-simplysm{메이저버전}.md`를 읽고 해당 패키지의 문서 경로를 찾아 읽는다.
85
+ - 주의사항: 해당 패키지의 `CLAUDE.md`를 읽는것이 아니다.
71
86
  - simplysm 패키지의 경우 context7은 구버전일 수 있으니 사용을 지양한다
72
87
 
73
88
  # 프로젝트 경계
@@ -3,5 +3,5 @@ import json, os, sys
3
3
  data = json.load(sys.stdin)
4
4
  file_path = data["tool_input"]["file_path"]
5
5
  if os.path.isfile(file_path):
6
- print(f"File already exists. Use the Edit tool instead of overwriting: {file_path}", file=sys.stderr)
6
+ print(f"CRITICAL: This file already exists. NEVER delete/rm the file and retry with Write. You MUST use the Edit tool instead: {file_path}", file=sys.stderr)
7
7
  sys.exit(2)
@@ -9,21 +9,21 @@ description: typecheck, lint, test를 실행하고 에러 발생시 사용자
9
9
 
10
10
  ### 에러 분석 및 수정
11
11
 
12
- 에러 분석: @.claude/references/sd-debug.md
12
+ 에러 분석: `/sd-inner-debug` 스킬을 호출한다.
13
13
 
14
14
  #### 에러 처리 범위
15
15
 
16
16
  이 **대화(conversation)** 에서, sd-check 호출 전에 Claude가 코드를 수정한 것.
17
17
  - git status의 미커밋 변경이나 과거 커밋 변경을 말하는것이 아님.
18
18
  - sd-check 내부(typecheck/lint/test 단계)에서의 수정을 말하는것이 아님.
19
- - sd-check 단독 실행시 발견된 모든 에러를 수정해야함
19
+ - **CRITICAL**: sd-check 단독 실행시, 발견된 모든 에러를 수정해야함 (예: test 에러 발견시, typecheck/lint의 수정과는 별개로 모두 수정)
20
20
  - sd-check호출전 대화내 수정이 있었던 경우, 해당 수정과 관련된 에러만 수정 대상으로 봄
21
21
 
22
22
  #### 에스컬레이션 규칙
23
23
 
24
24
  **CRITICAL: 동일 에러가 2회 반복되면 즉시 수정을 중단하고 사용자에게 보고한다.**
25
25
 
26
- - 1회차: sd-debug.md에 따라 근본 원인을 분석하고 수정을 시도한다.
26
+ - 1회차: `/sd-inner-debug` 스킬을 호출하여 근본 원인을 분석하고 수정을 시도한다.
27
27
  - 2회차(동일/유사 에러 재발): 수정을 중단하고, 지금까지의 분석 결과와 시도한 수정 내용을 사용자에게 보고한 뒤 판단을 요청한다.
28
28
  - 원인을 특정할 수 없는 경우에도 즉시 사용자에게 보고한다. 추측으로 수정 시도 금지.
29
29
 
@@ -63,13 +63,16 @@ Bash 출력이 길면 잘리므로 **반드시 파일로 리다이렉트**한
63
63
  | 3 | lint, eslint | 린트 |
64
64
  | 4 | test, jest, vitest, mocha | 테스트 |
65
65
 
66
+ **typecheck와 lint는 동시수행할 수 있는 `script`가 있다면 하나로 묶어 동시수행**
67
+ - Step 2 + Step 3가 하나의 Step으로 병합됨 (예: `pnpm check`)
68
+
66
69
  ### 1-3. 탐지 결과 표시
67
70
 
68
71
  ```
69
72
  탐지된 check 스크립트:
70
73
  1. typecheck → pnpm run typecheck
71
74
  2. lint → pnpm run lint
72
- 3. test → pnpm run test
75
+ 3. test → pnpm vitest run
73
76
  ```
74
77
 
75
78
  탐지된 스크립트가 없으면 오류 메시지를 출력하고 종료한다.
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  name: sd-claude-docs
3
3
  description: 프로젝트 분석을 통해 CLAUDE.md와 LLM용 usage 문서를 동시 생성하는 스킬. "init", "CLAUDE.md 생성", "usage 문서 생성", "LLM 문서 만들어줘", "패키지 문서 생성" 등을 요청할 때 사용한다.
4
+ model: sonnet[1m]
5
+ effort: low
4
6
  ---
5
7
 
6
8
  # sd-claude-docs: CLAUDE.md + usage 문서 통합 생성
@@ -67,7 +69,7 @@ description: 프로젝트 분석을 통해 CLAUDE.md와 LLM용 usage 문서를
67
69
  1. 루트 `package.json`의 `name`에서 라이브러리명을 추출한다
68
70
  2. 루트 `package.json`의 `version`에서 메이저 버전을 추출한다
69
71
  3. usage 문서 경로: `.claude/references/sd-{name}{majorVersion}/` (예: `sd-simplysm14/`)
70
- 4. 인덱스 파일 경로: `.claude/rules/sd-{name}{majorVersion}.md` (예: `sd-simplysm14.md`)
72
+ 4. 인덱스 파일 경로: `.claude/references/sd-{name}{majorVersion}.md` (예: `sd-simplysm14.md`)
71
73
 
72
74
  ## Step 2: 분기
73
75
 
@@ -89,7 +91,8 @@ root 문서는 생성·변경하지 않는다.
89
91
 
90
92
  ## Step 3: 패키지별 문서 생성 (모노레포)
91
93
 
92
- 각 패키지에 대해 **Agent 도구로 subagent를 병렬 실행**한다. 하나의 메시지에서 모든 패키지의 Agent 호출을 동시에 보낸다.
94
+ 각 패키지에 대해 **Agent 도구로 subagent(model: `sonnet`, effort: `low`)를 병렬 실행**한다.
95
+ 하나의 메시지에서 모든 패키지의 Agent 호출을 동시에 보낸다.
93
96
 
94
97
  ### subagent 프롬프트
95
98
 
@@ -172,7 +175,7 @@ pnpm watch [targets..] # 라이브러리 패키지를 watch
172
175
  ### 코드 품질
173
176
 
174
177
  ```bash
175
- pnpm check [targets..] # 전체 검사 (typecheck + lint + test 병렬)
178
+ pnpm check [targets..] # 전체 검사 (typecheck + lint 병렬)
176
179
  pnpm typecheck [targets..] # TypeScript 타입 체크
177
180
  ```
178
181
 
@@ -194,7 +197,7 @@ UI: angular (Angular)
194
197
 
195
198
  ### sd-{name}{ver}.md (라이브러리 프로젝트만)
196
199
 
197
- 라이브러리 프로젝트인 경우 `.claude/rules/sd-{name}{ver}.md` 인덱스 파일을 생성/갱신한다.
200
+ 라이브러리 프로젝트인 경우 `.claude/references/sd-{name}{ver}.md` 인덱스 파일을 생성/갱신한다.
198
201
  소비앱인 경우 이 단계를 건너뛴다.
199
202
 
200
203
  #### 포함할 내용
@@ -83,6 +83,7 @@ region 주석이 없으면, re-export되는 파일의 디렉토리 구조를 카
83
83
 
84
84
  - **대화언어로 작성**한다
85
85
  - **소스에서 읽은 내용만** 문서화한다 — 시그니처는 직접 복사하고, 존재하지 않는 파라미터·반환 타입·동작을 만들어내지 않는다
86
+ - **기존 문서의 시그니처를 신뢰하지 않는다** — 기존 docs/*.md의 코드블록(시그니처·멤버 이름·타입·required 여부 등)을 그대로 재사용하지 않는다. 반드시 소스 파일을 Read하여 확인한 내용만 작성한다. 단, 소스 코드와 무관한 내용(사용 가이드, 주의사항, 규칙 등)은 그대로 보존한다
86
87
  - **모든 export를 빠짐없이 문서화한다** — Step 2에서 수집한 export 목록의 모든 항목이 문서에 포함되어야 한다. "덜 중요하다"는 이유로 생략하지 않는다
87
88
  - **interface/type은 필드별 설명 테이블을 포함한다** — 시그니처만 나열하지 않고, 각 필드의 타입과 설명을 테이블로 작성한다. 소스에 필드가 있는 interface를 빈 `{}`로 표시하는 것은 금지한다 — 필드가 많더라도 모든 필드를 테이블로 나열한다
88
89
  - **union type은 discriminant와 각 variant를 설명한다** — discriminated union인 경우, 어떤 필드로 분기되는지와 각 variant를 나열한다
@@ -194,23 +195,45 @@ Step 2B에서 스타일 항목이 수집된 경우, `{출력 경로}/docs/stylin
194
195
  | `flex-direction` | `@mixin flex-direction($dir)` | {설명} |
195
196
  ```
196
197
 
197
- ### Step 4-4: 완전성 검증
198
+ ### Step 4-4: 완전성 및 정확성 검증
198
199
 
199
- 문서 생성 후, Step 2에서 수집한 export 목록과 생성된 문서를 대조한다:
200
+ 문서 생성 후, Step 2에서 수집한 export 목록과 생성된 문서를 대조한다.
201
+
202
+ #### 완전성 검증
200
203
 
201
204
  1. export 목록의 각 항목이 usage.md 또는 docs/*.md에 존재하는지 확인한다
202
205
  2. 누락된 항목이 있으면 해당 API를 문서에 추가한다
203
- 3. 검증 결과를 표시한다:
206
+
207
+ #### 정확성 검증
208
+
209
+ 문서의 각 API 항목에 대해, 해당 소스 파일을 Read로 다시 읽어 아래 항목을 대조한다:
210
+
211
+ | 검증 항목 | 확인 내용 |
212
+ |-----------|-----------|
213
+ | 클래스/함수명 | 제네릭 파라미터 포함 일치 여부 |
214
+ | 멤버 이름 | input/output/model/signal/computed/method 이름 일치 여부 |
215
+ | 멤버 종류 | `input()` vs `model()` vs `output()` vs `signal()` 등 구분 정확성 |
216
+ | 타입 | 파라미터 타입, 반환 타입 일치 여부 |
217
+ | required 여부 | `input()` vs `input.required()` 구분 정확성 |
218
+ | 기본값 | 기본값이 있는 경우 정확한 값 |
219
+
220
+ 불일치가 발견되면 **소스 코드를 기준으로** 문서를 수정한다.
221
+
222
+ 파일 수가 많으면(20개 이상) Agent 도구로 파일 그룹별 병렬 검증을 수행한다.
223
+
224
+ #### 검증 결과 표시
204
225
 
205
226
  ```
206
227
  완전성 검증: 52/52 API 문서화됨
228
+ 정확성 검증: 52/52 API 시그니처 일치
207
229
  ```
208
230
 
209
- 누락이 있는 경우:
231
+ 불일치가 있는 경우:
210
232
 
211
233
  ```
212
- 완전성 검증: 50/52 API 문서화됨
213
- 누락: MissingType, MissingFunction
214
- 누락된 API를 문서에 추가합니다.
234
+ 완전성 검증: 52/52 API 문서화됨
235
+ 정확성 검증: 50/52 API 시그니처 일치
236
+ 불일치: SdPermissionTable (itemspermissions, value→permRecord), SdBarcode (value: required→optional)
237
+ → 소스 기준으로 문서를 수정합니다.
215
238
  ```
216
239
 
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  name: sd-commit
3
3
  description: 전체 변경사항에 대한 단일 커밋을 생성하는 스킬. "커밋", "commit", "변경사항 커밋" 등을 요청할 때 사용한다.
4
+ model: sonnet
5
+ effort: low
4
6
  ---
5
7
 
6
8
  # sd-commit: 그룹별 커밋
@@ -7,7 +7,7 @@ description: 버그·동작 이상의 근본 원인 분석 및 해결책을 제
7
7
 
8
8
  ## Step 1: 근본 원인 분석
9
9
 
10
- @.claude/references/sd-debug.md 읽고 따른다.
10
+ `/sd-inner-debug` 스킬을 호출한다.
11
11
 
12
12
  ## Step 2: 문서 기록
13
13
 
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  name: sd-deliverable
3
3
  description: 코드 분석 기반으로 사용자 매뉴얼(md)과 SIT 문서(md)를 생성·업데이트하는 스킬. "매뉴얼 만들어줘", "SIT 문서 만들어줘", "산출물 생성", "매뉴얼 업데이트" 등을 요청할 때 사용한다.
4
+ model: sonnet
5
+ effort: low
4
6
  ---
5
7
 
6
8
  # sd-deliverable: 매뉴얼 & SIT 생성
@@ -42,7 +42,7 @@ sd-wbs → sd-plan → sd-tdd → sd-check → sd-review를 순차 진행하는
42
42
 
43
43
  ## Step 6: sd-review
44
44
 
45
- `.claude/references/sd-review.md` 읽고, 발견사항에 대해 수정한다.
45
+ `/sd-inner-review` 스킬을 호출하고, 발견사항에 대해 수정한다.
46
46
 
47
47
  - wbs/feature 문서를 읽고 잘 구현되었는지 함께 검토한다.
48
48
  - 수정사항이 있는 경우, `/sd-check` 스킬을 재 수행한다.
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  name: sd-doc-extract
3
3
  description: 문서 파일(docx, xlsx, xlsb, pptx, pdf, eml, msg)에서 텍스트, 이미지, 임베디드 파일을 추출하는 스킬. "문서 추출", "문서 분해", "docx 분석", "PDF 내용 뽑아줘", "eml 파일 추출" 등을 요청할 때 사용한다.
4
+ model: sonnet
5
+ effort: low
4
6
  ---
5
7
 
6
8
  # sd-doc-extract: 문서 분해/추출