@simplysm/sd-claude 14.0.87 → 14.0.88

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/references/sd-simplysm14/README.md +17 -18
  2. package/claude/references/sd-simplysm14/apis/angular/README.md +61 -0
  3. package/claude/references/sd-simplysm14/apis/angular/controls.md +119 -0
  4. package/claude/references/sd-simplysm14/apis/angular/crud.md +50 -0
  5. package/claude/references/sd-simplysm14/apis/angular/directives.md +44 -0
  6. package/claude/references/sd-simplysm14/apis/angular/features.md +55 -0
  7. package/claude/references/sd-simplysm14/apis/angular/infra.md +74 -0
  8. package/claude/references/sd-simplysm14/apis/angular/layout.md +55 -0
  9. package/claude/references/sd-simplysm14/apis/angular/overlay.md +115 -0
  10. package/claude/references/sd-simplysm14/apis/angular/routing-appstructure.md +64 -0
  11. package/claude/references/sd-simplysm14/apis/angular/selection-managers.md +43 -0
  12. package/claude/references/sd-simplysm14/apis/angular/shared-data.md +70 -0
  13. package/claude/references/sd-simplysm14/apis/angular/sheet.md +78 -0
  14. package/claude/references/sd-simplysm14/apis/capacitor-plugin-auto-update/README.md +80 -0
  15. package/claude/references/sd-simplysm14/apis/capacitor-plugin-file-system/README.md +66 -0
  16. package/claude/references/sd-simplysm14/apis/capacitor-plugin-intent/README.md +71 -0
  17. package/claude/references/sd-simplysm14/apis/capacitor-plugin-usb-storage/README.md +67 -0
  18. package/claude/references/sd-simplysm14/apis/core-browser/README.md +83 -0
  19. package/claude/references/sd-simplysm14/apis/core-browser/indexed-db.md +79 -0
  20. package/claude/references/sd-simplysm14/apis/core-common/README.md +138 -0
  21. package/claude/references/sd-simplysm14/apis/core-common/array-ext.md +72 -0
  22. package/claude/references/sd-simplysm14/apis/core-common/datetime.md +95 -0
  23. package/claude/references/sd-simplysm14/apis/core-common/json-transfer.md +47 -0
  24. package/claude/references/sd-simplysm14/apis/core-common/obj.md +53 -0
  25. package/claude/references/sd-simplysm14/apis/core-node/README.md +14 -0
  26. package/claude/references/sd-simplysm14/apis/core-node/consola.md +51 -0
  27. package/claude/references/sd-simplysm14/apis/core-node/cpx.md +39 -0
  28. package/claude/references/sd-simplysm14/apis/core-node/fs-watcher.md +38 -0
  29. package/claude/references/sd-simplysm14/apis/core-node/fsx.md +86 -0
  30. package/claude/references/sd-simplysm14/apis/core-node/pathx.md +42 -0
  31. package/claude/references/sd-simplysm14/apis/core-node/worker.md +54 -0
  32. package/claude/references/sd-simplysm14/apis/excel/README.md +43 -0
  33. package/claude/references/sd-simplysm14/apis/excel/cell.md +54 -0
  34. package/claude/references/sd-simplysm14/apis/excel/conditional-format.md +51 -0
  35. package/claude/references/sd-simplysm14/apis/excel/style.md +67 -0
  36. package/claude/references/sd-simplysm14/apis/excel/utils.md +35 -0
  37. package/claude/references/sd-simplysm14/apis/excel/workbook-worksheet.md +97 -0
  38. package/claude/references/sd-simplysm14/apis/excel/wrapper.md +83 -0
  39. package/claude/references/sd-simplysm14/apis/lint/README.md +43 -0
  40. package/claude/references/sd-simplysm14/apis/lint/rules.md +90 -0
  41. package/claude/references/sd-simplysm14/apis/orm-common/README.md +67 -0
  42. package/claude/references/sd-simplysm14/apis/orm-common/db-context.md +80 -0
  43. package/claude/references/sd-simplysm14/apis/orm-common/expr.md +113 -0
  44. package/claude/references/sd-simplysm14/apis/orm-common/query-builder.md +29 -0
  45. package/claude/references/sd-simplysm14/apis/orm-common/queryable.md +111 -0
  46. package/claude/references/sd-simplysm14/apis/orm-common/schema.md +162 -0
  47. package/claude/references/sd-simplysm14/apis/orm-common/types.md +52 -0
  48. package/claude/references/sd-simplysm14/apis/orm-node/README.md +53 -0
  49. package/claude/references/sd-simplysm14/apis/orm-node/db-conn.md +94 -0
  50. package/claude/references/sd-simplysm14/apis/sd-cli/README.md +29 -0
  51. package/claude/references/sd-simplysm14/apis/sd-cli/SdTsCompiler.md +70 -0
  52. package/claude/references/sd-simplysm14/apis/sd-cli/sd-config-types.md +173 -0
  53. package/claude/references/sd-simplysm14/apis/service-client/README.md +152 -0
  54. package/claude/references/sd-simplysm14/apis/service-client/orm.md +45 -0
  55. package/claude/references/sd-simplysm14/apis/service-client/transport.md +36 -0
  56. package/claude/references/sd-simplysm14/apis/service-common/README.md +70 -0
  57. package/claude/references/sd-simplysm14/apis/service-common/app-structure.md +48 -0
  58. package/claude/references/sd-simplysm14/apis/service-common/protocol.md +72 -0
  59. package/claude/references/sd-simplysm14/apis/service-server/README.md +102 -0
  60. package/claude/references/sd-simplysm14/apis/service-server/service-authoring.md +74 -0
  61. package/claude/references/sd-simplysm14/apis/service-server/transport-internals.md +51 -0
  62. package/claude/references/sd-simplysm14/apis/service-server/v1-legacy.md +50 -0
  63. package/claude/references/sd-simplysm14/apis/storage/README.md +114 -0
  64. package/claude/skills/sd-docs/SKILL.md +17 -29
  65. package/claude/skills/sd-docs/references/{subagent-prompt.md → doc-rules.md} +25 -40
  66. package/package.json +1 -1
@@ -0,0 +1,102 @@
1
+ # @simplysm/service-server
2
+
3
+ Fastify 기반 서비스 서버. WebSocket(v2)/HTTP RPC, JWT 인증, 정적 파일·업로드, 서버→클라이언트 이벤트 푸시를 한 서버로 제공한다. 클라이언트는 `defineService` 로 정의한 서비스의 메서드를 원격 호출한다.
4
+
5
+ ## 사용 트리거 인덱스
6
+
7
+ - **createServiceServer / ServiceServer / ServiceServerOptions / ServerEventProxy** — 서버를 부팅(listen)·종료하고, 토큰을 서명/검증하고, 클라이언트로 이벤트를 푸시할 때.
8
+ - **defineService / auth / ServiceContext / ServiceMethods / ServiceDefinition** — RPC 서비스(메서드 묶음)를 정의하고 인증을 걸 때. 서비스 작성 시 항상 함께 읽힘. 자세히: [service-authoring.md](./service-authoring.md)
9
+ - **signJwt / verifyJwt / decodeJwt / AuthTokenPayload** — JWT 토큰을 서버 밖에서 직접 서명·검증·디코드할 때(서버의 `signAuthToken`/`verifyAuthToken` 으로 충분하면 불필요).
10
+ - **OrmService / AutoUpdateService** — DB 원격 실행·앱 자동업데이트를 `services` 에 끼워 넣을 때.
11
+ - **getConfig** — `.config.json` 을 캐시·워치와 함께 직접 읽을 때(보통 `ctx.getConfig` 로 간접 사용).
12
+ - **전송·프로토콜 내부 (WebSocketHandler, ServiceSocket, handleHttpRequest, handleUpload, handleStaticFile, ServerProtocolWrapper)** — ServiceServer 내부에서만 쓰는 저수준 핸들러. 직접 서버를 조립·진단할 때만. 자세히: [transport-internals.md](./transport-internals.md)
13
+ - **V1 레거시 (handleV1Connection, V1RequestHandler 등)** — ver≠2 구버전 클라이언트(자동업데이트)를 받을 때. 자세히: [v1-legacy.md](./v1-legacy.md)
14
+
15
+ ## 서버 부팅 (createServiceServer / ServiceServer)
16
+
17
+ `createServiceServer<TAuthInfo>(options): ServiceServer<TAuthInfo>` — `new ServiceServer(options)` 의 래퍼. `TAuthInfo` 는 `ctx.authInfo` 및 JWT 페이로드 `data` 의 타입.
18
+
19
+ `class ServiceServer<TAuthInfo> extends EventEmitter<{ ready: void; close: void }>`:
20
+
21
+ - `options: ServiceServerOptions` — 생성자에 넘긴 옵션(readonly).
22
+ - `isOpen: boolean` — listen 성공 후 true, close 후 false. 가동 여부 판단에 사용.
23
+ - `fastify: FastifyInstance` — 내부 Fastify 인스턴스. 임의 포트 조회(`fastify.server.address()`)·직접 라우트 추가에 사용.
24
+ - `listen(): Promise<void>` — 플러그인 등록 후 `0.0.0.0:options.port` 수신 시작. 완료 시 `ready` emit. `auth` 미설정인데 auth 요구 서비스가 있으면 throw. listen 중 SIGINT/SIGTERM graceful shutdown(10초 타임아웃 후 강제 종료) 1회 등록.
25
+ - `close(): Promise<void>` — 모든 WebSocket 연결을 닫고 Fastify 종료. `close` emit.
26
+ - `getEvent<TEventDef>(eventDef): ServerEventProxy<TEventDef>` — eventDef 를 바인딩한 emit 전용 프록시 획득. 같은 이벤트를 반복 emit 할 때 편의.
27
+ - `emitEvent<TEventDef>(eventDef, infoSelector, data): Promise<void>` — `infoSelector(info) === true` 인 리스너에게만 `data` 전송. `infoSelector` = 리스너 등록 시의 info 로 수신 대상 선별.
28
+ - `signAuthToken(payload: AuthTokenPayload<TAuthInfo>): Promise<string>` — JWT 발급. `auth` 미설정 시 throw. 로그인 처리에 사용.
29
+ - `verifyAuthToken(token): Promise<AuthTokenPayload<TAuthInfo>>` — JWT 검증·페이로드 반환. `auth` 미설정 시 throw.
30
+
31
+ `ServerEventProxy<TEventDef>`:
32
+
33
+ - `emit(infoSelector: (info: TEventDef["$info"]) => boolean, data: TEventDef["$data"]): Promise<void>` — `getEvent` 가 반환하는 emit 헬퍼. `emitEvent` 와 동작 동일하되 eventDef 가 이미 바인딩됨.
34
+
35
+ ```ts
36
+ const server = createServiceServer<MyAuth>({
37
+ rootPath: process.cwd(),
38
+ port: 50080,
39
+ auth: { jwtSecret: env("JWT_SECRET")! },
40
+ services: [OrmService, AutoUpdateService, MyService],
41
+ });
42
+ await server.listen();
43
+ const token = await server.signAuthToken({ roles: ["admin"], data: authInfo });
44
+ await server.getEvent(MyEventDef).emit((info) => info.room === "lobby", payload);
45
+ ```
46
+
47
+ ## ServiceServerOptions
48
+
49
+ - `rootPath: string` — 서버 루트. 정적파일은 `<rootPath>/www`, 업로드는 `<rootPath>/www/uploads`, 설정은 `<rootPath>/.config.json` 및 `www/<client>/.config.json` 기준.
50
+ - `port: number` — 수신 포트. `0` 이면 OS가 임의 할당(테스트용, `fastify.server.address()` 로 확인).
51
+ - `ssl?: { pfxBytes: Uint8Array; passphrase: string }` — HTTPS 설정. `pfxBytes` = PFX 인증서 바이트, `passphrase` = 암호. 설정 시 HSTS·COOP 켜짐, 미설정 시 HTTP(`upgrade-insecure-requests` 해제).
52
+ - `auth?: { jwtSecret: string } | false` — 인증 모드. `{ jwtSecret }` = JWT 활성(서명/검증 키), `false` = auth 요구 서비스를 두되 인증 검사를 의도적으로 스킵, `undefined`(미지정) = 인증 비활성이되 auth 요구 서비스가 있으면 `listen()` 에서 throw.
53
+ - `services: ServiceDefinition[]` — 등록할 서비스 목록(`defineService` 산출물).
54
+ - `legacyV1Handlers?: V1RequestHandler[]` — ver≠2 구버전 클라이언트용 커스텀 핸들러. 미지정 시 빈 배열.
55
+
56
+ ## 인증 토큰 (AuthTokenPayload / signJwt / verifyJwt / decodeJwt)
57
+
58
+ `ServiceServer.signAuthToken`/`verifyAuthToken` 의 저수준 구현. 서버 밖에서 토큰을 직접 다룰 때 사용. HS256, 발급시각 자동, 만료 12시간 고정.
59
+
60
+ `interface AuthTokenPayload<TAuthInfo> extends jose.JWTPayload`:
61
+
62
+ - `roles: string[]` — 권한 역할 목록. `auth(["admin"], ...)` 의 권한 검사 대상.
63
+ - `data: TAuthInfo` — 애플리케이션 인증 정보. `ctx.authInfo` 로 노출됨.
64
+
65
+ - `signJwt<TAuthInfo>(jwtSecret, payload): Promise<string>` — HS256·발급시각 자동·만료 12h 로 서명.
66
+ - `verifyJwt<TAuthInfo>(jwtSecret, token): Promise<AuthTokenPayload<TAuthInfo>>` — 검증·디코드. 만료 시 "토큰이 만료되었습니다.", 그 외 실패 시 "유효하지 않은 토큰입니다." throw.
67
+ - `decodeJwt<TAuthInfo>(token): AuthTokenPayload<TAuthInfo>` — 서명 검증 없이 페이로드만 디코드.
68
+
69
+ ```ts
70
+ const token = await signJwt(secret, { roles: ["admin"], data: { userId: "u1" } });
71
+ const payload = await verifyJwt<MyAuth>(secret, token); // payload.data, payload.roles
72
+ ```
73
+
74
+ ## 빌트인 서비스 (OrmService / AutoUpdateService)
75
+
76
+ `services` 배열에 넣어 등록. 각 서비스는 별칭 2개로 노출된다.
77
+
78
+ `OrmService` (이름 `"Orm"`, `"SdOrmService"`) — `auth()` 래핑(로그인 필요). WebSocket 전용(HTTP 호출 시 throw). 소켓별 DB 연결을 `connId` 로 관리하며 소켓 종료 시 자동 정리. 연결 설정은 `ctx.getConfig("orm")[configName]` 에서 읽음. 메서드:
79
+
80
+ - `getInfo(opt): Promise<{ dialect; database?; schema? }>` — 설정의 dialect·DB·스키마 조회(`mssql-azure` → `mssql` 로 표준화). `opt` = `DbConnOptions & { configName }`.
81
+ - `connect(opt): Promise<number>` — DB 연결 후 `connId` 반환. 첫 연결 시 소켓 close 훅 등록(누수 방지).
82
+ - `close(connId): Promise<void>` — 연결 종료. 종료 중 에러는 warn 로그만 남기고 무시.
83
+ - `beginTransaction(connId, isolationLevel?): Promise<void>` — 트랜잭션 시작. `isolationLevel` = 격리수준(미지정 시 드라이버 기본).
84
+ - `commitTransaction(connId)` / `rollbackTransaction(connId): Promise<void>` — 커밋/롤백.
85
+ - `executeParametrized(connId, query, params?): Promise<unknown[][]>` — 파라미터 바인딩 SQL 실행.
86
+ - `executeDefs(connId, defs, options?): Promise<unknown[][]>` — 쿼리 정의 배열 실행. `options` 가 모두 null 이면 묶음 실행 후 빈 결과, 아니면 정의별 결과셋을 `options[i]`(ResultMeta) 로 파싱.
87
+ - `bulkInsert(connId, tableName, columnDefs, records): Promise<void>` — 대량 삽입. `columnDefs` = 컬럼별 메타, `records` = 행 객체 배열.
88
+ - `type OrmServiceType = ServiceMethods<typeof OrmService>` — 클라이언트 타입 공유용.
89
+
90
+ `AutoUpdateService` (이름 `"AutoUpdate"`, `"SdAutoUpdateService"`) — 인증 없음.
91
+
92
+ - `getLastVersion(platform): Promise<{ version; downloadPath } | undefined>` — `www/<client>/<platform>/updates` 에서 최신 semver 산출물(`platform === "android"` 면 `.apk`, 그 외 `.exe`)을 찾아 버전·다운로드 경로 반환. 디렉토리/산출물 없으면 undefined. clientPath 없으면 throw.
93
+ - `type AutoUpdateServiceType = ServiceMethods<typeof AutoUpdateService>` — 클라이언트 타입 공유용.
94
+
95
+ ```ts
96
+ services: [OrmService, AutoUpdateService]
97
+ // 클라이언트: client.getService<OrmServiceType>("Orm")
98
+ ```
99
+
100
+ ## 설정 읽기 (getConfig)
101
+
102
+ `getConfig<TConfig>(filePath): Promise<TConfig | undefined>` — JSON 설정 파일 로드. 1시간 만료 캐시 + FsWatcher 로 파일 변경 시 자동 리로드, 삭제 시 캐시 무효화. 파일 없으면 undefined. 보통 직접 호출하지 않고 `ctx.getConfig(section)`(루트 + 클라이언트 설정 merge 후 섹션 추출)으로 사용.
@@ -0,0 +1,74 @@
1
+ # @simplysm/service-server — service-authoring
2
+
3
+ RPC 서비스(클라이언트가 원격 호출할 메서드 묶음)를 정의하고 인증을 거는 묶음. `defineService`·`auth`·`ServiceContext`·`ServiceMethods` 가 서비스 작성 시 항상 함께 읽힌다. `defineService` 산출물을 `ServiceServerOptions.services` 에 등록한다.
4
+
5
+ ## defineService
6
+
7
+ `defineService<TMethods>(name: string | string[], factory: (ctx: ServiceContext) => TMethods): ServiceDefinition<TMethods>` — 이름과 팩토리로 서비스를 정의.
8
+
9
+ - `name: string | string[]` — 서비스 이름. 배열이면 다중 별칭(첫 요소가 primary `name`, 전체가 호출 매칭용 `names`). 클라이언트는 `names` 중 아무 이름으로나 호출 가능. 빈 배열이면 "서비스 이름은 하나 이상 필요합니다." throw.
10
+ - `factory: (ctx) => TMethods` — 컨텍스트를 받아 메서드 객체 반환. 메서드 호출마다 새 ctx 로 1회 실행됨. `auth(...)` 로 감싸면 서비스 전체에 인증 부여.
11
+
12
+ ```ts
13
+ export const HealthService = defineService("Health", (ctx) => ({
14
+ check: () => ({ status: "ok" }),
15
+ }));
16
+ ```
17
+
18
+ ## ServiceDefinition
19
+
20
+ `interface ServiceDefinition<TMethods>` — `defineService` 산출물.
21
+
22
+ - `name: string` — 대표 이름(`names[0]`).
23
+ - `names: string[]` — 호출 매칭에 쓰이는 전체 이름 목록(별칭 포함).
24
+ - `factory: (ctx: ServiceContext) => TMethods` — 메서드 생성 팩토리.
25
+ - `authPermissions?: string[]` — 서비스 수준 인증 권한. 팩토리를 `auth` 로 감쌌을 때만 존재(빈 배열 = 로그인만 요구, undefined = 인증 없음).
26
+
27
+ ## auth
28
+
29
+ 인증 래퍼. 서비스 팩토리 또는 개별 메서드를 감싼다. 호출 동작은 보존하고 권한 메타데이터만 심볼로 부착하며, 메서드 수준 권한이 서비스 수준보다 우선한다.
30
+
31
+ - `auth<TFn>(fn): TFn` — 로그인만 요구(권한 무관). 토큰 없으면 "로그인이 필요합니다." throw.
32
+ - `auth<TFn>(permissions: string[], fn): TFn` — `roles` 에 `permissions` 중 하나라도 있어야 통과. 빈 배열이면 로그인만 요구. 권한 부족 시 "권한이 부족합니다." throw.
33
+ - 적용 위치: 팩토리 전체를 감싸면(서비스 수준) 모든 메서드에, 메서드 1개를 감싸면(메서드 수준) 그 메서드에만 적용.
34
+ - 특수 상황: 서버 옵션 `auth === false` 면 검사 스킵(인증 의도적 비활성), `auth` 미설정(null)인데 auth 메서드 호출 시 "auth 설정이 필요합니다." 설정오류 throw.
35
+
36
+ ```ts
37
+ export const UserService = defineService("User", auth((ctx) => ({
38
+ getProfile: () => ctx.authInfo,
39
+ removeAll: auth(["admin"], () => repo.removeAll()),
40
+ })));
41
+ ```
42
+
43
+ ## ServiceContext
44
+
45
+ `interface ServiceContext<TAuthInfo>` — 팩토리·메서드 안에서 호출 맥락에 접근.
46
+
47
+ - `server: ServiceServer<TAuthInfo>` — 현재 서버 인스턴스(이벤트 emit·옵션 접근 등).
48
+ - `socket?: ServiceSocket` — WebSocket 호출일 때의 소켓. HTTP/레거시 호출이면 undefined(소켓 의존 서비스는 이걸로 분기).
49
+ - `http?: { clientName: string; authTokenPayload?: AuthTokenPayload<TAuthInfo> }` — HTTP 호출 컨텍스트.
50
+ - `legacy?: { clientName?: string }` — V1 레거시 호출 컨텍스트.
51
+ - `authInfo: TAuthInfo | undefined` (getter) — 인증 페이로드의 `data`(socket 우선, 없으면 http). 미인증이면 undefined. 결측을 `?? ""` 등으로 치환하지 말고 그대로 전파.
52
+ - `clientName: string | undefined` (getter) — 클라이언트 이름(socket→http→legacy 순). 빈문자·`..`·`/`·`\` 포함 시 "유효하지 않은 클라이언트 이름" throw(경로탐색 가드).
53
+ - `clientPath: string | undefined` (getter) — `<rootPath>/www/<clientName>` 절대경로. clientName 없으면 undefined.
54
+ - `getConfig<T>(section): Promise<T>` — 루트 `.config.json` 과 클라이언트별 `.config.json` 을 merge 후 `section` 값 반환. 섹션 없으면 "설정 섹션을 찾을 수 없습니다" throw.
55
+
56
+ ## ServiceMethods
57
+
58
+ `type ServiceMethods<TDefinition> = TDefinition extends ServiceDefinition<infer M> ? M : never` — `ServiceDefinition` 에서 메서드 시그니처 타입만 추출. 클라이언트 측 타입 공유에 사용.
59
+
60
+ ```ts
61
+ export type UserServiceType = ServiceMethods<typeof UserService>;
62
+ // 클라이언트: client.getService<UserServiceType>("User");
63
+ ```
64
+
65
+ ## 저수준 (직접 서버 조립·디스패치 시에만)
66
+
67
+ - `getServiceAuthPermissions(fn): string[] | undefined` — 함수가 `auth()` 래핑됐으면 권한배열, 아니면 undefined.
68
+ - `createServiceContext(server, socket?, http?, legacy?): ServiceContext` — 컨텍스트 수동 생성(전송 핸들러용). 인자별로 socket/http/legacy 출처를 지정.
69
+ - `executeServiceMethod(server, { serviceName, methodName, params, socket?, http? }): Promise<unknown>` — 서비스 검색→clientName 가드→컨텍스트 생성→팩토리 호출→메서드 검색→인증 검사→실행. 전송 계층이 RPC 를 실제 디스패치하는 진입점. 서비스/메서드 미존재 시 throw.
70
+
71
+ ## 주의사항
72
+
73
+ - `factory` 는 호출마다 실행되므로 호출 간 공유 상태(예: 소켓별 DB 연결)는 팩토리 외부 `WeakMap` 등에 둔다(OrmService 참고).
74
+ - 결측(authInfo/clientName)은 undefined 로 끝까지 전파. `?? ""` 등으로 치환 금지.
@@ -0,0 +1,51 @@
1
+ # @simplysm/service-server — transport-internals
2
+
3
+ `ServiceServer.listen()` 이 내부적으로 등록하는 저수준 전송·프로토콜 핸들러. 보통 직접 호출하지 않으며, 커스텀 서버를 손수 조립하거나 동작을 디버깅할 때만 참조한다.
4
+
5
+ ## WebSocketHandler
6
+
7
+ `createWebSocketHandler(runMethod, jwtSecret): WebSocketHandler` — 다중 WebSocket 연결을 `clientId` 단위로 관리하고 메시지를 `runMethod`(보통 `executeServiceMethod` 바인딩)로 라우팅·이벤트 브로드캐스트.
8
+
9
+ - `runMethod(def): Promise<unknown>` — `{ serviceName, methodName, params, socket? }` 받아 RPC 결과 반환.
10
+ - `jwtSecret: string | undefined` — `"auth"` 메시지로 들어온 토큰 검증용. 없으면 auth 메시지에서 "JWT Secret이 정의되지 않았습니다." throw.
11
+
12
+ `WebSocketHandler`:
13
+
14
+ - `addSocket(socket, clientId, clientName, connReq): void` — 연결 등록. 같은 `clientId` 의 기존 연결은 닫고 교체(동일 clientId 다중 동시 연결 불가).
15
+ - `closeAll(): void` — 전 연결 종료.
16
+ - `emit<TEventDef>(eventName, infoSelector, data): Promise<void>` — `infoSelector(info) === true` 인 리스너에 `evt:on` 푸시. `infoSelector` = 리스너 info 로 수신 대상 선별.
17
+
18
+ 처리 메시지: `"<Service>.<method>"`(RPC), `evt:add`/`evt:remove`/`evt:gets`/`evt:emit`(이벤트 리스너 등록·해제·조회·브로드캐스트), `auth`(토큰 등록). 미지원 메시지는 code `BAD_MESSAGE`, 처리 중 예외는 `INTERNAL_ERROR` 응답(`DEV` env 시 stack 포함).
19
+
20
+ ## ServiceSocket
21
+
22
+ `createServiceSocket(socket, clientId, clientName, connReq): ServiceSocket` — 단일 WebSocket 연결 래퍼. 프로토콜 인코딩/디코딩, 5초 ping/pong keep-alive(무응답 시 terminate), 이벤트 리스너 추적.
23
+
24
+ - `connectedAtDateTime: DateTime` / `clientName: string` / `connReq: FastifyRequest` — 연결 메타(readonly).
25
+ - `authTokenPayload?: AuthTokenPayload` — `auth` 메시지로 세팅되는 인증 페이로드. 읽기/쓰기 가능.
26
+ - `close(): void` — 연결 terminate.
27
+ - `send(uuid, msg): Promise<number>` — 메시지 인코딩 후 전송, 전송 바이트수 반환(소켓 미개방 시 0).
28
+ - `addListener(key, eventName, info): void` — key/이벤트명/info 로 리스너 등록.
29
+ - `removeListener(key): void` — key 로 리스너 제거.
30
+ - `getEventListeners(eventName): Array<{ key; info }>` — 해당 이벤트명 리스너 전체 조회.
31
+ - `filterEventTargetKeys(targetKeys): string[]` — 이 소켓에 존재하는 대상 key 만 필터.
32
+ - `on("error"|"close"|"message", handler): void` — 핸들러 등록. `error` → `(err)`, `close` → `(code)`, `message` → `({ uuid, msg })`.
33
+
34
+ ## HTTP 핸들러
35
+
36
+ - `handleHttpRequest(req, reply, jwtSecret, runMethod): Promise<void>` — `/api/:service/:method` 처리. `x-sd-client-name` 헤더 필수(없으면 throw). `Authorization: Bearer <t>` 있으면 검증(실패 시 401). GET 은 `?json=` 쿼리, POST 는 배열 본문에서 params 추출(POST 비배열 400, 그 외 메서드 405).
37
+ - `handleUpload(req, reply, rootPath, jwtSecret): Promise<void>` — `/upload` multipart 처리. 인증 필수(토큰 없음·검증 실패 시 401). 파일을 `www/uploads/<uuid><ext>` 로 저장하고 `ServiceUploadResult[]`(path/filename/size) 반환. 멀티파트 아니면 400. 도중 실패 시 이미 저장한 파일·불완전 파일 전부 삭제 후 500(원자성).
38
+ - `handleStaticFile(req, reply, rootPath, urlPath): Promise<void>` — `www` 하위 정적 파일 서빙. `www` 밖 경로는 "접근이 거부되었습니다" throw(경로탐색 가드). 디렉토리는 슬래시 리다이렉트 후 `index.html`. `.` 시작 파일은 403. 없으면 404, 그 외 500(HTML 에러 페이지).
39
+
40
+ ## ServerProtocolWrapper
41
+
42
+ `createServerProtocolWrapper(): ServerProtocolWrapper` — 무거운 인코딩/디코딩을 공유 worker 스레드(지연 싱글턴)에 자동 위임, 가벼운 건 메인 스레드.
43
+
44
+ - `encode(uuid, message): Promise<{ chunks; totalSize }>` — body 가 `Uint8Array`(또는 Uint8Array 포함 배열)면 worker, 아니면 메인.
45
+ - `decode(bytes): Promise<ServiceMessageDecodeResult>` — 청크 재조립(stateful)은 항상 메인 단일 누적기, 재조립 완료 후 30KB 초과 JSON 파싱만 worker(청크 분산 재조립 버그 #35 방지).
46
+ - `dispose(): void` — 프로토콜 리소스 해제.
47
+
48
+ ## 주의사항
49
+
50
+ - `decode` 의 청크 재조립을 worker 로 분기하면 한 메시지의 청크가 서로 다른 누적기로 흩어져 재조립이 완성되지 못함 — 메인 스레드 누적기 유지가 필수.
51
+ - 같은 `clientId` 재연결 시 이전 소켓이 강제 종료되므로 동일 `clientId` 다중 동시 연결은 불가.
@@ -0,0 +1,50 @@
1
+ # @simplysm/service-server — v1-legacy
2
+
3
+ ver≠2(구버전) WebSocket 클라이언트를 받기 위한 레거시 핸들러. 주로 구버전 앱의 자동업데이트(`SdAutoUpdateService.getLastVersion`) 요청을 처리한다. `ServiceServer` 는 ver=2 가 아닌 연결을 자동으로 이 핸들러로 넘긴다(`AutoUpdate` 서비스나 `legacyV1Handlers` 가 있을 때만, 둘 다 없으면 연결 거부).
4
+
5
+ ## handleV1Connection
6
+
7
+ - `handleV1Connection(socket, autoUpdateMethods: V1AutoUpdateMethods, clientNameSetter?): void`
8
+ - `handleV1Connection(socket, options: V1ConnectionOptions): void`
9
+
10
+ 연결 즉시 `{ name: "connected" }` 전송 후, JSON 메시지(`V1Request`)를 받아 처리:
11
+
12
+ 1. 등록된 `handlers` 를 순서대로 실행, `handled: true` 면 그 결과로 응답.
13
+ 2. 미처리이고 command 가 `"SdAutoUpdateService.getLastVersion"` 이면 자동업데이트 fallback 실행.
14
+ 3. 그 외엔 `{ message: "앱 업그레이드가 필요합니다.", code: "UPGRADE_REQUIRED" }` 에러 응답.
15
+
16
+ ## 타입
17
+
18
+ - `V1Request` — `{ uuid: string; command: string; params: unknown[]; clientName?: string }`. 클라이언트 요청.
19
+ - `V1Response` — `{ name: "response"; reqUuid: string; state: "success" | "error"; body: unknown }`. 서버 응답 형식. `state` = 처리 결과("success" = 정상, "error" = 실패).
20
+ - `V1AutoUpdateMethods` — `{ getLastVersion(platform): Promise<unknown> | unknown }`. 자동업데이트 fallback 구현.
21
+ - `V1RequestHandlerResult` — `{ handled: true; state?: "success"|"error"; body }`(처리함, state 기본 success) 또는 `{ handled: false }`(다음 핸들러로 넘김).
22
+ - `V1RequestHandlerContext` — `{ request: V1Request; serviceContext: ServiceContext }`. 핸들러 인자.
23
+ - `V1RequestHandler` — `(ctx: V1RequestHandlerContext) => V1RequestHandlerResult | Promise<V1RequestHandlerResult>`. 커스텀 처리 함수. `ServiceServerOptions.legacyV1Handlers` 에 등록.
24
+ - `V1ConnectionOptions`:
25
+ - `serviceContext?: ServiceContext` — 핸들러에 넘길 고정 컨텍스트.
26
+ - `serviceContextFactory?: (request) => ServiceContext` — 요청별 컨텍스트 생성(고정 대신 요청마다 만들 때).
27
+ - `handlers?: V1RequestHandler[]` — 커스텀 핸들러 체인.
28
+ - `autoUpdateMethods?: V1AutoUpdateMethods` — 자동업데이트 fallback 고정 구현.
29
+ - `autoUpdateMethodsFactory?: (ctx) => V1AutoUpdateMethods` — 요청별 fallback 생성.
30
+ - `clientNameSetter?: (clientName) => void` — 메시지마다 clientName 통지 콜백.
31
+
32
+ ## 사용 예
33
+
34
+ ```ts
35
+ createServiceServer({
36
+ // ...
37
+ services: [AutoUpdateService], // ver≠2 연결의 getLastVersion fallback 자동 연결
38
+ legacyV1Handlers: [
39
+ (ctx) =>
40
+ ctx.request.command === "Legacy.ping"
41
+ ? { handled: true, body: "pong" }
42
+ : { handled: false },
43
+ ],
44
+ });
45
+ ```
46
+
47
+ ## 주의사항
48
+
49
+ - `handlers` 가 있는데 `serviceContext`(또는 factory)가 없으면 핸들러 실행 시 "serviceContext가 필요합니다." throw — 핸들러를 쓰려면 컨텍스트를 함께 제공.
50
+ - 메시지 처리 중 예외는 warn 로그만 남기고 응답하지 않음(레거시 한정 동작).
@@ -0,0 +1,114 @@
1
+ # @simplysm/storage
2
+
3
+ FTP/FTPS/SFTP 원격 스토리지에 연결해 파일·디렉토리를 업로드·다운로드·조회·삭제하는 Node 전용 클라이언트. 프로토콜별 구현을 동일 인터페이스(`StorageClient`)로 통일하고, 팩토리(`StorageFactory.connect`)로 연결/종료를 자동 관리.
4
+
5
+ ## 사용 트리거 인덱스
6
+
7
+ - **StorageFactory.connect** — 원격 스토리지에 접속해 파일 작업을 한 뒤 자동으로 연결을 닫고 싶을 때. 권장 진입점.
8
+ - **StorageClient** — `connect` 콜백 안에서 받는 파일 조작 인터페이스. mkdir/list/readFile/put/remove 등 호출 시 참조.
9
+ - **FileInfo** — `list()` 가 돌려주는 항목 구조(이름·파일여부)를 확인할 때.
10
+ - **StorageConnConfig** — 접속 호스트/계정/비밀번호를 구성할 때.
11
+ - **StorageProtocol** — 프로토콜 종류(`ftp`/`ftps`/`sftp`)를 지정할 때.
12
+ - **FtpStorageClient / SftpStorageClient** — 팩토리 없이 클라이언트를 직접 인스턴스화·재연결 제어해야 할 때(비권장).
13
+
14
+ ## StorageProtocol
15
+
16
+ ```ts
17
+ type StorageProtocol = "ftp" | "ftps" | "sftp";
18
+ ```
19
+
20
+ - `"ftp"` — 평문 FTP. 보안 채널 없이 접속(`basic-ftp` `secure: false`). 내부망·테스트 환경에서만 권장.
21
+ - `"ftps"` — TLS 로 암호화된 FTP(`basic-ftp` `secure: true`). 외부망 FTP 접속 시.
22
+ - `"sftp"` — SSH 기반 SFTP. password 미지정 시 `~/.ssh/id_ed25519` 키 + SSH agent 로 인증.
23
+
24
+ ## StorageConnConfig
25
+
26
+ ```ts
27
+ interface StorageConnConfig { host: string; port?: number; user?: string; password?: string; }
28
+ ```
29
+
30
+ - `host: string` — 접속 대상 서버 호스트명 또는 IP. 필수.
31
+ - `port?: number` — 접속 포트. 미지정 시 각 라이브러리 기본값(FTP 21, SFTP 22) 사용.
32
+ - `user?: string` — 로그인 사용자명. 미지정 시 익명/기본 사용자.
33
+ - `password?: string` — 로그인 비밀번호. **SFTP 에서 이 값이 `null`/미지정이면** password 인증 대신 `~/.ssh/id_ed25519` 개인키와 SSH agent(`SSH_AUTH_SOCK`) 로 인증 시도하고, 키 파싱 실패(암호화 키 등) 시 agent 단독으로 재시도.
34
+
35
+ ## StorageFactory
36
+
37
+ 스토리지 접속 진입점. 연결 생성 → 콜백 실행 → 자동 종료를 묶어 처리한다.
38
+
39
+ ```ts
40
+ static connect<R>(type: StorageProtocol, config: StorageConnConfig, fn: (storage: StorageClient) => R | Promise<R>): Promise<R>
41
+ ```
42
+
43
+ - `type: StorageProtocol` — 사용할 프로토콜. `"sftp"` → `SftpStorageClient`, `"ftps"` → `FtpStorageClient(secure=true)`, `"ftp"` → `FtpStorageClient(secure=false)` 를 내부 생성.
44
+ - `config: StorageConnConfig` — 접속 설정.
45
+ - `fn` — 연결된 `StorageClient` 를 받아 파일 작업을 수행하는 콜백. 반환값이 그대로 `connect` 의 결과(`Promise<R>`) 가 됨.
46
+ - 동작: `client.connect()` 후 `fn` 실행, `finally` 에서 `client.close()` 호출하며 종료 오류는 무시. 콜백에서 예외가 나도 연결은 반드시 닫히고 예외는 그대로 전파됨. 직접 클라이언트를 다루는 것보다 권장.
47
+
48
+ ```ts
49
+ const names = await StorageFactory.connect("sftp", { host: "10.0.0.1", user: "u", password: "p" }, async (s) => {
50
+ await s.mkdir("/upload");
51
+ await s.put(buffer, "/upload/a.txt");
52
+ return (await s.list("/upload")).map((f) => f.name);
53
+ });
54
+ ```
55
+
56
+ ## StorageClient
57
+
58
+ `connect` 콜백 안에서 받는 파일 조작 인터페이스. `FtpStorageClient`·`SftpStorageClient` 가 구현한다. 모든 메서드는 `Promise` 반환.
59
+
60
+ ```ts
61
+ interface StorageClient {
62
+ connect(config: StorageConnConfig): Promise<void>;
63
+ mkdir(dirPath: string): Promise<void>;
64
+ rename(fromPath: string, toPath: string): Promise<void>;
65
+ list(dirPath: string): Promise<FileInfo[]>;
66
+ readFile(filePath: string): Promise<Bytes>;
67
+ exists(filePath: string): Promise<boolean>;
68
+ put(localPathOrBuffer: string | Bytes, storageFilePath: string): Promise<void>;
69
+ uploadDir(fromPath: string, toPath: string): Promise<void>;
70
+ remove(filePath: string): Promise<void>;
71
+ close(): Promise<void>;
72
+ }
73
+ ```
74
+
75
+ - `connect(config)` — 서버에 연결. 이미 연결된 인스턴스에서 재호출하면 `SdError` throw(먼저 `close()` 필요). `StorageFactory.connect` 사용 시 직접 호출 불필요.
76
+ - `mkdir(dirPath)` — 디렉토리 생성. 부모 디렉토리가 없으면 함께 생성(FTP `ensureDir`, SFTP 재귀 `mkdir`).
77
+ - `rename(fromPath, toPath)` — 파일/디렉토리 경로 이동·이름 변경.
78
+ - `list(dirPath)` — 디렉토리 내 항목을 `FileInfo[]` 로 반환.
79
+ - `readFile(filePath)` — 원격 파일 전체를 `Bytes`(Uint8Array) 로 다운로드. SFTP 는 응답이 예상 타입(Buffer/string) 이 아니면 `SdError` throw.
80
+ - `exists(filePath)` — 파일/디렉토리 존재 여부. **모든 예외(부모 없음·권한·네트워크 오류 포함) 에 대해 `false` 반환** — true/false 외 throw 없음. FTP 는 `size()` 로 파일을 O(1) 확인 후 실패 시 부모 디렉토리 목록으로 디렉토리 확인(슬래시 없는 경로는 루트 `/` 기준).
81
+ - `put(localPathOrBuffer, storageFilePath)` — 업로드. 첫 인자가 `string` 이면 로컬 파일 경로, `Bytes` 면 메모리 바이트를 업로드 대상으로 사용.
82
+ - `uploadDir(fromPath, toPath)` — 로컬 디렉토리 전체를 원격 디렉토리로 업로드.
83
+ - `remove(filePath)` — 원격 파일 삭제.
84
+ - `close()` — 연결 종료. 이미 종료된 상태에서 호출해도 오류 없음. 종료 후 같은 인스턴스에서 `connect()` 로 재연결 가능.
85
+
86
+ 미연결 상태에서 작업 메서드를 호출하면 모든 구현체가 `SdError`("연결되어 있지 않습니다") throw.
87
+
88
+ ## FileInfo
89
+
90
+ ```ts
91
+ interface FileInfo { name: string; isFile: boolean; }
92
+ ```
93
+
94
+ - `name: string` — 항목 이름(파일명 또는 디렉토리명).
95
+ - `isFile: boolean` — 파일이면 `true`, 디렉토리면 `false`. SFTP 는 항목 type 이 `"-"` 인 경우만 `true`(디렉토리·심볼릭 링크는 `false`).
96
+
97
+ ## FtpStorageClient / SftpStorageClient (직접 사용, 비권장)
98
+
99
+ `StorageClient` 직접 구현체. 보통은 `StorageFactory.connect` 로 충분하며, 연결 수명을 수동으로 다뤄야 할 때만 직접 생성한다.
100
+
101
+ ```ts
102
+ new FtpStorageClient(secure?: boolean) // secure=true → FTPS, 생략/false → 평문 FTP
103
+ new SftpStorageClient()
104
+ ```
105
+
106
+ - `FtpStorageClient` 의 `secure` 생성자 인자 — `true` 면 TLS(FTPS), 생략/`false` 면 평문 FTP. (팩토리는 `ftps`→`true`, `ftp`→`false` 로 매핑.)
107
+ - `SftpStorageClient` 는 생성자 인자 없음. password 미지정 시 키/agent 인증 경로를 탄다(StorageConnConfig 의 `password` 풀이 참조).
108
+ - 직접 사용 시 `connect()` → 작업 → `close()` 순으로 호출하고 호출 측이 종료를 책임져야 함(연결 누수 주의).
109
+
110
+ ```ts
111
+ const client = new SftpStorageClient();
112
+ await client.connect({ host: "10.0.0.1", user: "u", password: "p" });
113
+ try { await client.put(buf, "/x.txt"); } finally { await client.close(); }
114
+ ```
@@ -6,53 +6,41 @@ model: haiku
6
6
 
7
7
  # sd-docs
8
8
 
9
- `@simplysm/*` 라이브러리 패키지의 API 문서를 코드를 근거로 작성·갱신. 메인 에이전트가 패키지 목록 추출과 상위 README 인덱스 갱신을 담당하고, 패키지별 문서 작성은 패키지 1개당 subagent 1개를 호출해 병렬 위임.
9
+ `@simplysm/*` 라이브러리 패키지의 API 문서를 코드만을 진실 근거로 재작성하고, 상위 README 패키지 인덱스를 최신 상태로 맞춤.
10
10
 
11
11
  ## 산출물 위치
12
12
 
13
13
  - `.claude/references/sd-simplysm14/apis/<패키지명>/README.md` — 패키지당 1개, 필수.
14
14
  - `.claude/references/sd-simplysm14/apis/<패키지명>/<군명>.md` — 사용 트리거 군이 커서 README 한 장에 풀어쓰면 같은 README 의 다른 군 정보까지 함께 읽혀 부담이 커지는 경우에만 분할 산출.
15
- - `.claude/references/sd-simplysm14/README.md` 의 "패키지 인덱스" 섹션 — 메인 에이전트가 갱신.
15
+ - `.claude/references/sd-simplysm14/README.md` 의 "패키지 인덱스" 섹션.
16
16
 
17
- ## 워크플로
17
+ 여기서 `<패키지명>` = `@simplysm/` 접두사 제외한 짧은 이름.
18
18
 
19
- ### 1. 패키지 목록 추출
19
+ ## 대상
20
20
 
21
- 워크스페이스 루트의 `packages/*/package.json` 모두 읽어 다음 리스트를 만듦.
21
+ - **public**: `packages/*/package.json` `private: true` 아닌 패키지. 문서·인덱스 대상.
22
+ - **private**: `private: true` 인 패키지. 인덱스에서 제외.
22
23
 
23
- - **public 리스트**: `private: true` 가 아닌 패키지. 각 항목 = `{ name, dir }`.
24
- - **private 리스트**: `private: true` 인 패키지. 인덱스에서 제외하기 위해 보관.
24
+ ## 오케스트레이션
25
25
 
26
- ### 2. 패키지별 subagent 병렬 호출
26
+ Workflow 도구로 처리. 실행 형태(parallel/pipeline/schema/동시성)는 스크립트 작성 시 최적으로 선택하되, 다음 정합성 제약만 충족:
27
27
 
28
- `public 리스트` 의 패키지 수만큼 `general-purpose` subagent 호출을 **단일 메시지 안에서 병렬**로 보냄. 호출 프롬프트는 [references/subagent-prompt.md](references/subagent-prompt.md) 양식의 "프롬프트" 마커 아래 본문을 그대로 사용하고, `<PACKAGE_NAME>` `<PACKAGE_DIR>` 치환.
28
+ - 팬아웃 단위 = public 패키지 1개.agent [references/doc-rules.md](references/doc-rules.md) 작성 규칙을 따라 해당 `apis/<패키지명>/` 자리만 산출하고, doc-rules.md "8. 구조화 결과 반환" 형태로 결과를 돌려줌.
29
+ - 상위 README "패키지 인덱스" 섹션 갱신은 전 패키지 결과가 모두 모인 뒤 1회 처리 (전체 알파벳 리스트라 취합 필요). 개별 agent 는 상위 README 를 건드리지 않음.
29
30
 
30
- subagent 산출 (풀 재작성 모드 — 기존 파일 참고 없이 처음부터 작성):
31
+ ## 패키지 인덱스 갱신 규칙
31
32
 
32
- - `apis/<패키지명>/README.md` 재작성.
33
- - 필요 시 `apis/<패키지명>/<군명>.md` 재작성. 코드에서 사라진 군의 파일은 삭제.
34
- - 결과 보고 1단락 (산출 파일 목록, 분할 발생 여부, 한 줄 트리거 요약).
35
-
36
- **범위 한정**: subagent 는 `apis/<패키지명>/` 위치만 다룸. 상위 `.claude/references/sd-simplysm14/README.md` 는 건드리지 않음 (다음 단계의 "패키지 인덱스 섹션 갱신" 에서 메인 에이전트가 처리).
37
-
38
- ### 3. 상위 README 의 "패키지 인덱스" 섹션 갱신 (파일 보존 + 섹션 내 항목만 갱신)
39
-
40
- `.claude/references/sd-simplysm14/README.md` 는 **풀 재작성 대상 아님**. 파일 본문·다른 섹션은 그대로 보존, "패키지 인덱스" 섹션의 항목 리스트만 재구성함.
41
-
42
- 모든 subagent 완료 후:
33
+ `.claude/references/sd-simplysm14/README.md` 는 **풀 재작성 대상 아님**. "패키지 인덱스" 섹션의 항목 리스트만 재구성.
43
34
 
44
35
  - **갱신 대상**: "패키지 인덱스" 섹션 본문(항목 리스트)만.
45
36
  - **건드리지 않음**: 섹션 머리(`## 패키지 인덱스`), 다른 모든 섹션, 파일 상단/하단 텍스트.
46
- - **항목 형식**: `- **<패키지명>** — <한 줄 트리거 요약>. 자세히: [apis/<패키지명>/README.md](./apis/<패키지명>/README.md)`.
37
+ - **항목 형식**: `- **<패키지명>** — <triggerSummary>. 자세히: [apis/<패키지명>/README.md](./apis/<패키지명>/README.md)`.
47
38
  - **순서**: 패키지명 알파벳순.
48
- - **포함**: `public 리스트` 만.
49
- - **제외**: `private 리스트` 의 패키지, 코드베이스에 더 이상 존재하지 않는 패키지.
50
-
51
- ### 4. 사용자 보고
39
+ - **포함**: public 만. **제외**: private, 코드베이스에 더 이상 존재하지 않는 패키지.
52
40
 
53
- 다음 항목을 짧게 정리해 출력함.
41
+ ## 사용자 보고
54
42
 
55
- - 재작성된 패키지 목록 (= `public 리스트` 전체. 풀 재작성 모드 — 매번 모든 패키지 산출).
56
- - 분할 발생(`<군명>.md` 생긴) 패키지 목록.
43
+ - 재작성된 패키지 목록 (= public 전체. 풀 재작성 모드 — 매번 모든 패키지 산출).
44
+ - 분할(`<군명>.md`) 발생 패키지 목록.
57
45
  - 삭제된 분할 파일 (코드에서 사라진 군, 있다면).
58
46
  - 인덱스에서 제거된 항목 (있다면).
@@ -1,31 +1,25 @@
1
- # subagent 호출 프롬프트 (sd-docs)
1
+ # API 문서 작성 규칙 (sd-docs)
2
2
 
3
- 메인 에이전트가 패키지 1개당 1번씩 `general-purpose` subagent 호출할 쓰는 프롬프트 양식. `<PACKAGE_NAME>` `<PACKAGE_DIR>` 자리만 치환.
3
+ 패키지 1개의 API 문서를 작성·갱신할 때 따르는 규칙. 산출 자리는 `.claude/references/sd-simplysm14/apis/<패키지명>/` (`<패키지명>` = `@simplysm/` 접두사 제외한 짧은 이름. 예: `@simplysm/foo` `apis/foo/`).
4
4
 
5
- ---
6
-
7
- ## 프롬프트 (이 마커 아래 전부를 그대로 subagent 에 전달)
8
-
9
- 너는 `<PACKAGE_NAME>` 패키지(소스 위치: `<PACKAGE_DIR>/src/`)의 API 문서를 작성·갱신하는 subagent. 산출 자리는 `.claude/references/sd-simplysm14/apis/<PACKAGE_NAME 의 `@simplysm/` 접두사를 제외한 짧은 이름>/` (예: `@simplysm/foo` → `apis/foo/`). 아래 규칙을 끝까지 따름.
10
-
11
- ### 1. 입력 분석
5
+ ## 1. 입력 분석
12
6
 
13
7
  다음만 진실 근거로 사용 — 외부 자료·과거 git 기록·다른 패키지의 사용처는 참조 금지.
14
8
 
15
- - `<PACKAGE_DIR>/src/index.ts` 의 export — **entry 시작점**. 여기서 노출된 심볼만 문서 대상.
9
+ - `<패키지 디렉토리>/src/index.ts` 의 export — **entry 시작점**. 여기서 노출된 심볼만 문서 대상.
16
10
  - 위 entry 가 재노출하는 각 심볼의 정의 파일(타입 시그니처 + 본문).
17
11
  - 위 정의 파일 또는 동일 패키지 안의 다른 파일에 달린 JSDoc 주석.
18
12
  - `tests/` 디렉토리 중 해당 패키지를 import 해 검증하는 테스트 코드 (존재할 경우에만).
19
13
 
20
14
  타입 시그니처에서 직접 드러나지 않는 사용 패턴을 보강해야 하면 위 4개 소스 안에서만 추론.
21
15
 
22
- ### 2. 사용 트리거 군 분류
16
+ ## 2. 사용 트리거 군 분류
23
17
 
24
18
  entry 의 export 심볼들을 "한 작업 컨텍스트에서 함께 참조될 군"으로 묶음. 예: 에러 처리 군, 값 타입 군, 큐/이벤트 군. 분류 기준은 폴더 구조나 심볼 종류가 아니라 **사용 시점**(언제 같이 읽힐 것인가) 기준.
25
19
 
26
20
  군이 모호하거나 작아 트리거가 1~2개뿐인 심볼은 별도 군을 만들지 말고 README 의 공통 인라인 섹션에 배치.
27
21
 
28
- ### 3. 산출 단위 판정
22
+ ## 3. 산출 단위 판정
29
23
 
30
24
  기본은 `README.md` 1장. 다음 조건을 **모두** 만족하는 군만 별도 `<군명>.md` 로 분할.
31
25
 
@@ -34,19 +28,17 @@ entry 의 export 심볼들을 "한 작업 컨텍스트에서 함께 참조될
34
28
 
35
29
  위 조건을 만족하지 않으면 README 안에 유지.
36
30
 
37
- ### 4. 풀 재작성 모드 (범위: `apis/<패키지명>/` 자리만)
31
+ ## 4. 풀 재작성 모드 (범위: `apis/<패키지명>/` 자리만)
38
32
 
39
- - 기존 `apis/<패키지명>/README.md` 와 분할 `<군명>.md` 가 있어도 **참고하지 않고 처음부터 작성**. 코드 + 본 프롬프트의 형식 (아래 README 형식 섹션·분할 .md 형식 섹션·작성 원칙 섹션) 만을 진실 근거로 삼음.
40
- - 기존 파일 내용·표현 보존 시도 금지. 변경(예: 식별자 풀이 의무) 이 자동 반영되도록 호출마다 패키지별 산출물(README + 분할 `<군명>.md`) 을 다시 작성.
33
+ - 기존 `apis/<패키지명>/README.md` 와 분할 `<군명>.md` 가 있어도 **참고하지 않고 처음부터 작성**. 코드 + 본 문서의 형식 규칙(아래 README 형식·분할 .md 형식·작성 원칙) 만을 진실 근거로 삼음.
34
+ - 기존 파일 내용·표현 보존 시도 금지. 규칙 변경(예: 식별자 풀이 의무) 이 자동 반영되도록 매번 패키지별 산출물(README + 분할 `<군명>.md`) 을 다시 작성.
41
35
  - 결과적으로 코드가 변경되지 않은 패키지도 표현이 달라질 수 있음 — 정상.
42
36
  - 코드에서 사라진 군의 분할 파일은 삭제.
43
- - **범위 밖**: 상위 `.claude/references/sd-simplysm14/README.md` 는 건드리지 말 것 (메인 에이전트가 인덱스 섹션 항목만 갱신, subagent 와 무관).
37
+ - **범위 밖**: 상위 `.claude/references/sd-simplysm14/README.md` 는 건드리지 말 것 (인덱스 섹션은 취합 단계에서 별도 처리).
44
38
 
45
- ### 5. README.md 형식
39
+ ## 5. README.md 형식
46
40
 
47
- 각 패키지의 `apis/<패키지명>/README.md` 는 다음 구조를 따름.
48
-
49
- H1 은 `# @simplysm/<short-name>` 형식으로 고정 (예: `# @simplysm/foo`).
41
+ 각 패키지의 `apis/<패키지명>/README.md` 는 다음 구조를 따름. H1 은 `# @simplysm/<short-name>` 형식으로 고정.
50
42
 
51
43
  ```markdown
52
44
  # @simplysm/<short-name>
@@ -61,6 +53,7 @@ H1 은 `# @simplysm/<short-name>` 형식으로 고정 (예: `# @simplysm/foo`).
61
53
  ## <인라인 군 이름 1>
62
54
 
63
55
  각 심볼별로 짧은 시그니처 + 옵션·prop·필드·enum literal(열거형 리터럴 값) 1줄 풀이 + 즉시 사용 가능한 사용법을 작성. 본질적으로 큰 군이라 분할된 경우 이 섹션은 두지 않고, 위 인덱스에서 `자세히:` 링크로 안내.
56
+ ```
64
57
 
65
58
  **식별자 풀이 의무**: 시그니처에 나오는 옵션·prop·필드·enum literal 각각에 1줄 풀이 부착. 풀이 구성 = `<무엇을 함>` + (enum/boolean 인 경우) `<값별 동작 차이>` + `<언제 쓰는지 단서 1조각>`. 이름만 나열하는 형식 금지.
66
59
 
@@ -77,15 +70,9 @@ inputs: selectMode, autoSelect: "click"|"focus", useAutoSort, focusMode: "row"|"
77
70
  - focusMode: "row"|"cell" — 키보드 포커스 단위. "row" = 행 전체 이동, "cell" = 셀 단위 이동. 셀 편집·복사 화면이면 "cell".
78
71
  ```
79
72
 
80
- ## <인라인 이름 2>
81
- ...
82
- ```
83
-
84
- ### 6. 분할 .md 형식
85
-
86
- `<군명>.md` 는 다음 구조를 따름.
73
+ ## 6. 분할 .md 형식
87
74
 
88
- 분할 `.md` H1 동일 형식 + 군명을 붙임: `# @simplysm/<short-name> — <군명>`.
75
+ `<군명>.md` 다음 구조를 따름. H1 `# @simplysm/<short-name> — <군명>` 형식.
89
76
 
90
77
  ```markdown
91
78
  # @simplysm/<short-name> — <군명>
@@ -94,25 +81,23 @@ inputs: selectMode, autoSelect: "click"|"focus", useAutoSort, focusMode: "row"|"
94
81
 
95
82
  ## <심볼>
96
83
 
97
- 시그니처, 옵션·prop·필드·enum literal 1줄 풀이(위 README 형식 섹션의 "식별자 풀이 의무" 와 동일 적용), 사용 예, 주의사항.
98
-
99
- ## <심볼>
100
- ...
84
+ 시그니처, 옵션·prop·필드·enum literal 1줄 풀이(위 "식별자 풀이 의무" 와 동일 적용), 사용 예, 주의사항.
101
85
  ```
102
86
 
103
- ### 7. 작성 원칙
87
+ ## 7. 작성 원칙
104
88
 
105
89
  - 산출물의 소비자는 Claude 에이전트. 사람 가독성보다 에이전트가 즉시 따를 수 있는 간결·명확성이 우선.
106
90
  - **코드 본문에 드러난 동작은 JSDoc 이 없어도 기재**. 옵션·prop·필드·enum literal 의 동작을 본문(분기·기본값·사용처) 에서 추론해 풀이. 단, 본문에서 확인 불가한 외부 추측·미검증 동작은 기재 금지.
107
- - **식별자만 나열 금지** — 위 README 형식 섹션·분할 .md 형식 섹션의 "식별자 풀이 의무" 강제. 이름을 콤마로 나열하고 끝내지 말 것. 자명해 보여도 enum literal 의 값별 동작, boolean 의 토글 효과, 함수형 prop 의 호출 시점은 반드시 풀이.
91
+ - **식별자만 나열 금지** — 위 "식별자 풀이 의무" 강제. 이름을 콤마로 나열하고 끝내지 말 것. 자명해 보여도 enum literal 의 값별 동작, boolean 의 토글 효과, 함수형 prop 의 호출 시점은 반드시 풀이.
108
92
  - 사용 예는 실제 호출 코드 형태로 1~3줄 분량, 군마다 1개 가량.
109
93
  - 분량을 늘리기 위한 부연·중복·꾸밈 금지. 단, 식별자 풀이는 "분량 늘리기" 가 아니라 필수 정보로 취급 — 풀이 생략을 통한 분량 절약 금지.
110
94
 
111
- ### 8. 산출 보고
95
+ ## 8. 구조화 결과 반환
112
96
 
113
- 작성·갱신을 마치면 호출자(메인 에이전트) 에게 다음 항목을 1단락으로 보고.
97
+ 작성·갱신을 마치면 다음 항목을 구조화해 반환 (상위 README 인덱스 취합·사용자 보고에 사용).
114
98
 
115
- - 신규 작성인지 갱신인지 구분.
116
- - 산출·갱신한 파일 목록 (README 와 분할 `.md` 모두 포함).
117
- - 패키지의 트리거 요약 (메인 에이전트가 상위 README 인덱스에 옮겨 적을 문장).
118
- - 삭제한 파일이 있으면 목록.
99
+ - `shortName`: `@simplysm/` 제외한 짧은 이름.
100
+ - `mode`: 신규 작성인지 갱신인지.
101
+ - `writtenFiles`: 산출·갱신한 파일 목록 (README + 분할 `.md` 모두).
102
+ - `deletedFiles`: 삭제한 파일 목록 (코드에서 사라진 군, 없으면 빈 목록).
103
+ - `triggerSummary`: 패키지의 한 줄 트리거 요약 (상위 README 인덱스에 옮겨 적을 문장).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simplysm/sd-claude",
3
- "version": "14.0.87",
3
+ "version": "14.0.88",
4
4
  "description": "심플리즘 패키지 - Claude Code 셋업",
5
5
  "author": "심플리즘",
6
6
  "license": "Apache-2.0",