@simplysm/sd-claude 14.0.84 → 14.0.86

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 (37) hide show
  1. package/claude/references/sd-simplysm14/README.md +7 -1
  2. package/claude/references/sd-simplysm14/manuals/client-app-structure.md +140 -0
  3. package/claude/references/sd-simplysm14/manuals/client-component.md +6 -5
  4. package/claude/references/sd-simplysm14/manuals/client-orm.md +62 -0
  5. package/claude/references/sd-simplysm14/manuals/client-service.md +96 -0
  6. package/claude/references/sd-simplysm14/manuals/client-shared-data.md +146 -0
  7. package/claude/references/sd-simplysm14/manuals/client-system-log.md +96 -0
  8. package/claude/references/sd-simplysm14/manuals/data-log.md +209 -0
  9. package/claude/references/sd-simplysm14/manuals/event.md +135 -0
  10. package/claude/rules/sd-design-rules.md +8 -0
  11. package/claude/sd-system-prompt.md +35 -14
  12. package/claude/skills/sd-config/SKILL.md +1 -0
  13. package/claude/skills/sd-demo/SKILL.md +1 -1
  14. package/claude/skills/sd-dev/SKILL.md +1 -1
  15. package/claude/skills/sd-docs/SKILL.md +1 -1
  16. package/claude/skills/sd-impl/SKILL.md +15 -10
  17. package/claude/skills/sd-impl/evals/fixtures/case-a-new-screen/.specs/260513120000_warehouse/spec.md +1 -1
  18. package/claude/skills/sd-impl/evals/fixtures/case-b-update-with-demo/.specs/260513120000_warehouse/spec.md +1 -1
  19. package/claude/skills/sd-impl/evals/fixtures/case-c-new-cross/.specs/260513120000_warehouse/spec.md +2 -2
  20. package/claude/skills/sd-impl/evals/fixtures/case-d-spec-modify/.specs/260513120000_warehouse/spec.md +1 -1
  21. package/claude/skills/sd-impl/evals/golden.jsonl +1 -1
  22. package/claude/skills/sd-manual/SKILL.md +51 -0
  23. package/claude/skills/sd-manual/evals/fixtures/new-manual/src/notification.ts +25 -0
  24. package/claude/skills/sd-manual/evals/fixtures/update-manual/.claude/references/sd-simplysm14/manuals/notification.md +14 -0
  25. package/claude/skills/sd-manual/evals/fixtures/update-manual/src/notification.ts +37 -0
  26. package/claude/skills/sd-manual/evals/golden.jsonl +2 -0
  27. package/claude/skills/sd-review/SKILL.md +3 -3
  28. package/claude/skills/sd-skill/SKILL.md +1 -1
  29. package/claude/skills/sd-spec/SKILL.md +65 -67
  30. package/claude/skills/sd-spec/evals/fixtures/case-a-split//355/232/214/354/235/230/353/241/235.md +20 -0
  31. package/claude/skills/sd-spec/evals/fixtures/case-b-detail/.specs/260513120000_warehouse/spec.md +95 -0
  32. package/claude/skills/sd-spec/evals/golden.jsonl +2 -0
  33. package/claude/skills/sd-spec/references/example-spec.md +14 -47
  34. package/claude/skills/sd-unpack/SKILL.md +1 -1
  35. package/claude/skills/sd-use/SKILL.md +1 -0
  36. package/package.json +1 -1
  37. package/claude/references/sd-simplysm14/manuals/client-setup.md +0 -154
@@ -0,0 +1,209 @@
1
+ # 데이터 변경 이력 적재·조회
2
+
3
+ CRUD 처리에서 "누가·언제·무엇을 어떻게 바꿨는지" 를 한 모델(`SystemDataLog`)에 모아 적재하고, 목록·단건 화면에서 각 행의 최근/최초 변경 이력을 함께 보여주는 패턴.
4
+
5
+ 핵심은 `Queryable.prototype` 확장 메서드 3개(`insertDataLog`/`joinLastDataLog`/`joinFirstDataLog`)를 만들어, 어떤 모델이든 `db.X().insertDataLog(...)` 처럼 동일하게 쓰는 것. `tableName`·`tableDescription` 은 호출한 테이블 정의에서 자동으로 채워지므로 호출부에서 모델명을 직접 적지 않음.
6
+
7
+ ## 변경 이력 인프라를 프로젝트에 세팅하려면
8
+
9
+ 세 가지를 만들고 연결함: 이력 저장 테이블 + Queryable 확장 + db-context 등록.
10
+
11
+ ### 1. 이력 저장 테이블
12
+
13
+ ```ts
14
+ // tables/system-data-log.ts
15
+ import { Table } from "@simplysm/orm-common";
16
+ import { Employee } from "./employee";
17
+
18
+ export const SystemDataLog = Table("SystemDataLog")
19
+ .columns((c) => ({
20
+ id: c.bigint().autoIncrement(),
21
+ tableName: c.varchar(200),
22
+ tableDescription: c.varchar(200).nullable(),
23
+ action: c.varchar(50),
24
+ itemId: c.bigint().nullable(),
25
+ valueJson: c.text().nullable(),
26
+ dateTime: c.datetime(),
27
+ employeeId: c.bigint().nullable(),
28
+ }))
29
+ .primaryKey("id")
30
+ .indexes((i) => [i.index("tableName", "itemId"), i.index("dateTime").orderBy("DESC")])
31
+ .relations((r) => ({
32
+ employee: r.foreignKey(["employeeId"], () => Employee),
33
+ }));
34
+ ```
35
+
36
+ - `itemId` nullable: 단건 변경은 대상 레코드 PK, 전체 초기화 등 모델 단위 변경은 NULL.
37
+ - `employeeId` nullable: 수행 직원. NULL = 시스템 수행.
38
+ - 인덱스: 조회는 `(tableName, itemId)` 로 격리·`dateTime DESC` 로 최신 정렬하므로 두 인덱스를 둠.
39
+
40
+ ### 2. Queryable 확장 (`*.ext.ts`)
41
+
42
+ `declare module` 로 타입을 보강하고 `Queryable.prototype` 에 런타임 구현을 붙임. `tableName`/`tableDescription` 은 `this.meta.from` 의 `meta.name`/`meta.description` 에서 자동 도출.
43
+
44
+ ```ts
45
+ // system-data-log.ext.ts
46
+ import { DateTime } from "@simplysm/core-common";
47
+ import {
48
+ type DataRecord,
49
+ expr,
50
+ Queryable,
51
+ queryable,
52
+ type TableBuilder,
53
+ } from "@simplysm/orm-common";
54
+ import { SystemDataLog } from "./tables/system-data-log";
55
+
56
+ export interface IDataLogJoinOptions {
57
+ includeActions?: string[];
58
+ excludeActions?: string[];
59
+ }
60
+
61
+ export interface IDataLogJoinResult {
62
+ action?: string;
63
+ dateTime?: DateTime;
64
+ employeeId?: number;
65
+ employeeName?: string;
66
+ }
67
+
68
+ export interface IInsertDataLogParam {
69
+ action: string;
70
+ itemId?: number;
71
+ valueJson?: string;
72
+ employeeId?: number;
73
+ }
74
+
75
+ declare module "@simplysm/orm-common" {
76
+ interface Queryable<TData extends DataRecord, TFrom extends TableBuilder<any, any, any> | never> {
77
+ joinLastDataLog(
78
+ opts?: IDataLogJoinOptions,
79
+ ): Queryable<TData & { lastDataLog?: IDataLogJoinResult }, TFrom>;
80
+ joinFirstDataLog(
81
+ opts?: IDataLogJoinOptions,
82
+ ): Queryable<TData & { firstDataLog?: IDataLogJoinResult }, TFrom>;
83
+ insertDataLog(log: IInsertDataLogParam): Promise<void>;
84
+ }
85
+ }
86
+
87
+ Queryable.prototype.insertDataLog = async function (this: Queryable<any, any>, log) {
88
+ const fromTable = this.meta.from as TableBuilder<any, any, any>;
89
+ const qr = queryable(this.meta.db, SystemDataLog);
90
+ await qr().insert([
91
+ {
92
+ tableName: fromTable.meta.name,
93
+ tableDescription: fromTable.meta.description,
94
+ action: log.action,
95
+ itemId: log.itemId,
96
+ valueJson: log.valueJson,
97
+ dateTime: new DateTime(),
98
+ employeeId: log.employeeId,
99
+ },
100
+ ]);
101
+ };
102
+
103
+ // tableName + 본 행 id 로 격리해 joinSingle 단건 부착. last/first 는 정렬만 다름.
104
+ Queryable.prototype.joinLastDataLog = function (this: Queryable<any, any>, opts) {
105
+ const tableName = (this.meta.from as TableBuilder<any, any, any>).meta.name;
106
+ return this.joinSingle("lastDataLog", (qr, en) =>
107
+ qr
108
+ .from(SystemDataLog)
109
+ .where((dl) => [
110
+ expr.eq(dl.tableName, tableName),
111
+ expr.eq(dl.itemId, en["id"]),
112
+ ...(opts?.includeActions ? [expr.in(dl.action, opts.includeActions)] : []),
113
+ ...(opts?.excludeActions ? [expr.not(expr.in(dl.action, opts.excludeActions))] : []),
114
+ ])
115
+ .orderBy((dl) => dl.dateTime, "DESC")
116
+ .top(1)
117
+ .include((dl) => dl.employee)
118
+ .select((dl) => ({
119
+ action: dl.action,
120
+ dateTime: dl.dateTime,
121
+ employeeId: dl.employeeId,
122
+ employeeName: dl.employee!.name,
123
+ })),
124
+ );
125
+ };
126
+
127
+ // joinFirstDataLog 는 위와 동일하되 키 이름 "firstDataLog" + orderBy 를 "ASC" 로.
128
+ ```
129
+
130
+ `tableDescription` 을 채우려면 테이블에 `.description("역할")` 을 선언해야 함. 미선언 모델은 NULL 로 적재됨.
131
+
132
+ `joinLastDataLog`/`joinFirstDataLog` 는 본 행의 `id` 컬럼을 join 키로 사용함 → 적용 대상 모델은 PK 컬럼명이 `id` 여야 함.
133
+
134
+ ### 3. db-context 등록
135
+
136
+ 확장 메서드는 `*.ext.ts` 가 한 번이라도 로드돼야 prototype 에 붙음. db-context 에서 side-effect import 로 로드를 보장하고, 이력 직접 조회용 queryable 도 등록.
137
+
138
+ ```ts
139
+ // main.db-context.ts
140
+ import "./system-data-log.ext"; // side-effect: Queryable.prototype 확장 로드 보장
141
+ // ...
142
+ export class MainDbContext extends DbContext {
143
+ role = this.queryable(Role);
144
+ // ...
145
+ dataLog = this.queryable(SystemDataLog); // 이력 직접 조회 화면용
146
+ }
147
+ ```
148
+
149
+ ## CRUD 처리에서 변경 이력을 적재하려면
150
+
151
+ 변경을 수행한 모델의 queryable 에서 `insertDataLog` 를 호출. `tableName`·`dateTime` 은 자동이므로 `action` 과 대상 식별 정보만 넘김.
152
+
153
+ ```ts
154
+ // 단건 등록/수정/삭제: itemId 로 대상 레코드 지정
155
+ const [role] = await db.role().insert([{ name: "관리자", isActive: true }], ["id"]);
156
+ await db.role().insertDataLog({ action: "등록", itemId: role.id });
157
+
158
+ // 전체 초기화(엑셀 업로드 등 모델 단위 변경): itemId 생략 → NULL 로 적재
159
+ await db.rolePermission().insertDataLog({ action: "초기화" });
160
+
161
+ // 수행 직원·변경 값 스냅샷까지 남기기
162
+ await db.role().insertDataLog({
163
+ action: "수정",
164
+ itemId: role.id,
165
+ employeeId: ctx.employeeId,
166
+ valueJson: JSON.stringify(changed),
167
+ });
168
+ ```
169
+
170
+ `action` 문자열(`등록`/`수정`/`삭제`/`복구`/`초기화` 등)은 프로젝트 규약으로 통일. 조회 시 `includeActions`/`excludeActions` 가 이 문자열로 필터함.
171
+
172
+ ## 목록·단건에 최근/최초 변경 이력을 함께 표시하려면
173
+
174
+ 본 쿼리 체인에 `joinLastDataLog()`/`joinFirstDataLog()` 를 끼우면 각 행에 `lastDataLog`/`firstDataLog` 단건이 부착됨(없으면 `undefined`). 수행 직원명까지 한 쿼리로 조인됨.
175
+
176
+ ```ts
177
+ const rows = await db
178
+ .role()
179
+ .where((p) => [expr.eq(p.id, role.id)])
180
+ .joinLastDataLog() // 최신 변경 1건 → 행.lastDataLog
181
+ .joinFirstDataLog() // 최초 변경 1건 → 행.firstDataLog
182
+ .execute();
183
+
184
+ rows[0].lastDataLog?.action; // "삭제"
185
+ rows[0].lastDataLog?.dateTime; // DateTime
186
+ rows[0].lastDataLog?.employeeName; // "홍길동" (employee 조인 결과)
187
+ rows[0].firstDataLog?.action; // "등록"
188
+ ```
189
+
190
+ `tableName` + 행 `id` 로 격리되므로 다른 모델의 같은 `itemId` 이력은 섞이지 않음. "최종 수정자/일시", "최초 등록자/일시" 컬럼은 별도 컬럼을 두지 말고 이 조인 결과로 표시.
191
+
192
+ ## 표시 대상 변경 유형을 한정·제외하려면
193
+
194
+ `includeActions`/`excludeActions` 로 어떤 `action` 만 최근/최초로 칠지 조절.
195
+
196
+ ```ts
197
+ // "등록"·"수정"만 대상 → "삭제" 가 최신이어도 제외하고 "수정" 이 lastDataLog
198
+ .joinLastDataLog({ includeActions: ["등록", "수정"] })
199
+
200
+ // "삭제" 만 빼고 최신 → "최종 수정 일시" 컬럼에 적합
201
+ .joinLastDataLog({ excludeActions: ["삭제"] })
202
+ ```
203
+
204
+ ## 지킬 것
205
+
206
+ - 모델 변경을 적재할 땐 변경을 수행한 그 모델의 queryable 에서 `insertDataLog` 호출. `db.dataLog().insert(...)` 로 `tableName` 을 손으로 적지 않음(자동 도출이 깨짐).
207
+ - "최종 수정자/일시"·"최초 등록자/일시" 는 대상 테이블에 별도 컬럼을 추가하지 말고 `joinLastDataLog`/`joinFirstDataLog` 조인으로 표시.
208
+ - 적재와 본 데이터 변경은 같은 트랜잭션 안에서 수행 — 이력만 남고 데이터가 롤백되거나 그 반대가 되지 않게 함.
209
+ - `action` 문자열은 프로젝트 단위로 고정된 집합을 쓰고, 조회 측 `includeActions`/`excludeActions` 와 철자를 일치시킴.
@@ -0,0 +1,135 @@
1
+ # 서비스 이벤트 매뉴얼
2
+
3
+ 클라이언트끼리(또는 서버 → 클라이언트로) 실시간 알림을 주고받으려 할 때 참조. 예: 한 사용자가 주문 상태를 바꾸면 같은 화면을 보는 다른 사용자에게 즉시 반영.
4
+
5
+ `@simplysm/service-common` 의 `defineEvent` 로 이벤트를 정의하고, `ServiceClient`(`@simplysm/service-client`) 또는 서버 `ServiceContext` 로 발생·구독함. WebSocket 위에서 동작하므로 연결된 클라이언트에만 전달됨.
6
+
7
+ 흐름:
8
+
9
+ ```
10
+ [발생측] client.emitEvent(EventDef, selector, data) ← 클라이언트 (가장 흔함)
11
+ ctx.server.emitEvent(EventDef, selector, data) ← 서버
12
+
13
+ ▼ 서버가 selector 로 대상 구독을 추려 라우팅
14
+ [구독측] client.getEvent(EventDef).addListener(info, cb) 로 등록한 콜백 실행
15
+ ```
16
+
17
+ - **구독(리스너 등록)은 클라이언트에만 있음**. 서버는 발생만 가능(구독 불가).
18
+ - **발생은 클라이언트·서버 양쪽 가능**. 화면 동작에서 비롯되는 변경 알림이 대부분이라 클라이언트 발생이 더 흔함.
19
+
20
+ ## 이벤트를 정의하려면
21
+
22
+ `defineEvent<TInfo, TData>(eventName)` 로 정의해 **공통 패키지(`@<workspace>/common`)에서 export**. 발생측·구독측이 같은 정의 객체를 값으로 import 하므로, 서버·클라이언트 양쪽에서 import 가능한 공통 패키지에 둠.
23
+
24
+ ```ts
25
+ // @<workspace>/common 의 events.ts
26
+ import { defineEvent } from "@simplysm/service-common";
27
+
28
+ export const OrderStatusChangedEvent = defineEvent<
29
+ { warehouseId: number }, // TInfo: 구독·필터 기준 메타데이터
30
+ { orderId: number; status: string } // TData: 전달 페이로드
31
+ >("OrderStatusChanged");
32
+ ```
33
+
34
+ - 두 제네릭의 의미:
35
+ - `TInfo` — 구독자가 "무엇을 구독하는지" 식별하는 메타데이터. 발생측이 이 값으로 대상을 골라냄.
36
+ - `TData` — 이벤트가 실어 나르는 페이로드.
37
+ - 인자 `eventName` 은 라우팅 키. 같은 이름이면 같은 이벤트로 취급되므로 앱 내에서 고유하게.
38
+ - **이름·타입의 단일 소스는 이 정의 객체**. 발생·구독 호출 시 정의 객체를 그대로 첫 인자로 넘기면 이름과 타입이 자동 추론됨 — 문자열 이름이나 `<typeof X>` 를 따로 적지 않음.
39
+
40
+ ## 클라이언트에서 이벤트를 구독하려면
41
+
42
+ `client.getEvent(EventDef)` 로 프록시를 얻어 `addListener(info, cb)` 를 호출. 반환된 키는 해제에 사용하므로 보관.
43
+
44
+ ```ts
45
+ const event = client.getEvent(OrderStatusChangedEvent);
46
+
47
+ const listenerKey = await event.addListener(
48
+ { warehouseId: 7 }, // info: 이 구독이 받을 범위
49
+ async (data) => {
50
+ // data 는 { orderId, status } 로 타입 추론됨
51
+ await this.reload();
52
+ },
53
+ );
54
+ ```
55
+
56
+ - `info` 는 정의의 `TInfo` 타입. 발생측의 selector 가 이 값을 보고 전달 여부를 결정(아래 "특정 구독자에게만").
57
+ - 콜백의 `data` 는 정의의 `TData` 로 타입이 잡힘.
58
+ - `addListener` 는 연결이 끊긴 상태에서 호출하면 throw(`서버에 연결되지 않았습니다.`). 부트스트랩의 `connectAsync()` 이후에 등록.
59
+ - AppServiceProvider 에서 이벤트 프록시를 getter 로 노출하는 패턴은 [client-service.md](./client-service.md) 참조.
60
+
61
+ ## 구독을 해제하려면
62
+
63
+ 등록 때 받은 키로 `removeListener` 호출. 화면 컴포넌트면 파기 시점에 해제.
64
+
65
+ ```ts
66
+ await client.removeListener(listenerKey);
67
+ ```
68
+
69
+ - 키 없이 일괄 해제하는 API 는 없음. 등록 시 받은 키를 화면·프로바이더 상태로 들고 있다가 해제.
70
+
71
+ ## 클라이언트에서 이벤트를 발생시키려면
72
+
73
+ 화면 동작으로 생긴 변경을 다른 클라이언트에 알리는 가장 흔한 경우. `client.emitEvent(EventDef, infoSelector, data)` 호출. 프록시의 `.emit()` 도 동일.
74
+
75
+ ```ts
76
+ // 7번 창고를 구독 중인 클라이언트에게만 전달
77
+ await client.emitEvent(
78
+ OrderStatusChangedEvent,
79
+ (info) => info.warehouseId === 7,
80
+ { orderId: 1024, status: "shipped" },
81
+ );
82
+
83
+ // 프록시 형태
84
+ await client.getEvent(OrderStatusChangedEvent).emit(
85
+ (info) => info.warehouseId === 7,
86
+ { orderId: 1024, status: "shipped" },
87
+ );
88
+ ```
89
+
90
+ - `infoSelector` 는 각 구독의 `info` 를 받아 전달 대상인지 판정하는 함수.
91
+ - 자기 자신이 같은 이벤트를 구독 중이고 selector 에 걸리면 자신의 콜백도 실행됨.
92
+
93
+ ## 서버에서 이벤트를 발생시키려면
94
+
95
+ 서버 측 처리(예: 외부 시스템 연동 결과 반영)에서 알릴 때. 서비스 메서드의 `ctx.server` 로 발생.
96
+
97
+ ```ts
98
+ export const OrderService = defineService("Order", (ctx) => ({
99
+ ship: async (orderId: number) => {
100
+ // ... 처리 ...
101
+ await ctx.server.emitEvent(
102
+ OrderStatusChangedEvent,
103
+ (info) => info.warehouseId === 7,
104
+ { orderId, status: "shipped" },
105
+ );
106
+ },
107
+ }));
108
+ ```
109
+
110
+ - 발생 시그니처는 클라이언트와 동일(`emitEvent(EventDef, infoSelector, data)`).
111
+ - `defineService` / `ServiceContext` 의 전반은 서버 패키지 작성 시 참조.
112
+
113
+ ## 특정 구독자에게만 전달하려면
114
+
115
+ 이벤트는 구독자의 `info` 와 발생측의 `infoSelector` 매칭으로 대상을 좁힘. 같은 이벤트라도 selector 가 `true` 를 돌려준 구독만 콜백을 받음.
116
+
117
+ ```ts
118
+ // 구독: 자신이 보는 창고를 info 로 등록
119
+ await event.addListener({ warehouseId: 7 }, cb7);
120
+ await event.addListener({ warehouseId: 9 }, cb9);
121
+
122
+ // 발생: warehouseId === 7 인 구독만 전달 → cb7 만 실행, cb9 는 무시
123
+ await client.emitEvent(OrderStatusChangedEvent, (info) => info.warehouseId === 7, data);
124
+ ```
125
+
126
+ - 전체에게 보내려면 `() => true` 를 selector 로.
127
+ - selector 가 어떤 구독에도 걸리지 않으면 아무 콜백도 실행되지 않음(전송 자체가 생략됨).
128
+
129
+ ## 지킬 것
130
+
131
+ - 이벤트 정의는 공통 패키지에 두고 정의 객체를 그대로 발생·구독에 넘김. 발생·구독 호출부에 이벤트 이름 문자열이나 `<typeof X>` 제네릭을 중복으로 적지 않음.
132
+ - 구독은 클라이언트에서만. 서버는 발생 전용이며 `addListener` 가 없음 — 서버가 다른 서버 동작을 기다려야 한다면 이벤트가 아닌 다른 수단을 사용.
133
+ - `addListener` 로 받은 키는 반드시 보관하고 화면·프로바이더 파기 시 `removeListener` 로 해제. 미해제 리스너는 재연결 때마다 누적됨.
134
+ - 재연결 시 등록된 리스너는 자동으로 재구독되므로, 연결 복구를 감지해 수동으로 다시 `addListener` 하지 않음.
135
+ - 전달은 그 시점에 연결된 클라이언트에만 일어남. 오프라인 클라이언트를 위한 보관·재전송은 없으므로, 놓치면 안 되는 상태는 이벤트가 아니라 조회(재로딩)로 확정.
@@ -33,6 +33,14 @@ Claude 에이전트가 코드 작성·설계·변경 시 따라야 할 행동
33
33
  - silent skip 금지 — 예외를 잡은 후 대안 없이 진행하면 후속 프로세스가 결손된 채 동작함.
34
34
  - **자동 복구** (예: 의존성 미설치 → 설치·재시도 = 완전한 동작 회복) 는 silent skip 아님.
35
35
 
36
+ ## 다중 작업 원자성
37
+
38
+ 한 사용자 동작이 여러 하위 작업·여러 항목(루프·배열·다건 처리)으로 구성될 때, 전부 성공 아니면 전부 실패로 처리:
39
+
40
+ - 일부 항목에서 문제 발생 → 이미 처리된 항목까지 취소(롤백)하고 동작 전체를 throw 로 중단.
41
+ - 안티패턴: 문제된 항목만 skip 하고 나머지를 완료 처리 — 데이터가 부분 반영되어 정합성이 깨짐.
42
+ - 부분 성공을 의도적으로 허용하려면(예: 배치 중 실패분만 리포트) 사용자에게 보고 후 합의에 따름.
43
+
36
44
  ## 사용자에게 노출되는 알림 작성 시 심각도 분류
37
45
 
38
46
  사용자에게 노출되는 알림(로그·토스트·다이얼로그 등)의 심각도 분류 기준:
@@ -11,7 +11,12 @@
11
11
  - `/<skill-name>` (예: `/commit`) 은 사용자가 사용자 호출 가능 스킬을 부르는 단축어. 실행되면 전체 프롬프트로 확장됨. 이를 실행하려면 Skill 도구 사용.
12
12
  - Skill 도구는 사용자 호출 가능 스킬 섹션에 나열된 스킬에만 사용. 추측하거나 내장 CLI 명령에 사용하지 말 것.
13
13
  - Agent 도구(서브에이전트 호출)는 단계지침상 명시되어 있거나, 사용자가 명시적으로 지시한 경우에만 사용. 그 외에는 Grep·Read·Glob 등 기본 도구로 직접 처리.
14
- - 도구 호출이 에러를 반환하면 멈추고 에러 메시지부터 원인을 규명. 원인 규명 전에 같은 목적을 수단만 바꿔 재시도하지 말 것.
14
+ - 도구 호출이 에러를 반환하면 멈추고 에러 메시지부터 원인을 규명. 다음은 원인 규명 전에 하면 되는 회피 행위:
15
+ - 같은 목적을 수단만 바꿔 재시도 — 금지.
16
+ - 그 호출을 건너뛰기·결과를 '무해·무관'으로 단정해 넘기기 — 금지.
17
+ - 미검증 추측(예: '샌드박스가 막음')을 원인으로 단정 — 금지.
18
+ - 원인 규명을 마친 뒤에 건너뛸지·보고할지·재시도할지 판단.
19
+ - **IMPORTANT**: Read, Grep, Read, Glob 등의 도구로 수행할 일을 PowerShell 도구로 수행하지 말것.
15
20
 
16
21
  # 행동 규칙
17
22
 
@@ -132,17 +137,18 @@ X 함수에 캐시 도입 검토 중. 기존 의존성 확인 결과 lru-cache
132
137
  추천: 1 — 단일 인스턴스 환경, TTL 요구사항 단순.
133
138
  ```
134
139
 
135
- ## 다단계 지침 진행
140
+ ## 사용자 답변
136
141
 
137
- 지침상 여러 단계가 정의되어 있고, 단계 진행에 대한 사용자 확인을 받으라는 명시가 없는 경우 단계 산출물 보고 자동으로 다음 단계 진행. 단계 사이에 "다음 단계 진행할까요?"·"계속할까요?" 류 트리거 위임 질문 생성 금지.
138
-
139
- - "지침" = 스킬·룰·사용자 발언으로 부여된 다단계 절차 일체.
140
- - "사용자 확인 명시" = 지침 본문에 단계 게이트("단계 산출 후 확인"·"각 단계마다 합의" 등) 가 적혀 있는 경우.
141
- - 명시 있는 지점에서만 확인 질문. 그 외 단계 전환은 자동.
142
+ - 제시한 다항목·장문에 사용자가 일부만 이견을 내면, 나머지는 미확정 이견 반영해 전체를 다시 제시·재확인. 이견 없는 부분을 확정으로 굳히고 진행 금지.
143
+ - 나쁜 예: 30줄 제안 "맞나요?" → 한 줄만 "아님" → 그 줄만 고쳐 나머지 확정으로 진행.
144
+ - 좋은 예: 정정 반영해 전체 다시 제시 → "이렇게 수정함, 맞나요?".
142
145
 
143
146
  ## 문제 발생 시
144
147
 
145
- **적용 조건**: [사용자 발언 의도 파악](#사용자-발언-의도-파악) 의 "의문·요청 (원인·방법·가능성)" 또는 "문제 기술·현상 보고" 의도로 분류된 경우.
148
+ **적용 조건**: 다음 하나.
149
+
150
+ - [사용자 발언 의도 파악](#사용자-발언-의도-파악) 의 "의문·요청 (원인·방법·가능성)" 또는 "문제 기술·현상 보고" 의도로 분류된 경우.
151
+ - 에이전트가 실행 중 마주친 실패 — 도구 에러, 테스트·빌드·lint·typecheck·check 실패, 예상과 다른 런타임 결과 등. 수단만 바꾼 재시도 전에 본 워크플로 적용 (시스템 섹션의 "도구 호출이 에러를 반환하면 멈추고..." 항목과 연동).
146
152
 
147
153
  **근본 원인 우선**:
148
154
 
@@ -192,7 +198,7 @@ X 함수에 캐시 도입 검토 중. 기존 의존성 확인 결과 lru-cache
192
198
  | ---------------------------- | ----------------------------------------- | ---------------------------- | ------------------------------------------- |
193
199
  | 명령·승인 | "고쳐줘", "응 그렇게", "적용해" | "해줘", "응", "ㅇㅇ", "진행" | 도구 호출 (실행) |
194
200
  | 의문·요청 (원인·방법·가능성) | "왜 ~?", "어떻게 ~안될까?", "~방법 있어?" | "왜", "어떻게", "?" | [문제 발생 시](#문제-발생-시) 워크플로 따름 |
195
- | 제안·아이디어 | "X 하면 어때?", "Y 가 좋을듯" | "어때", "좋을듯", "할까?" | 텍스트 응답 (검토·대안 제시) → 합의 후 실행 |
201
+ | 제안·아이디어 | "X 하면 어때?", "Y 가 좋을듯", "X 할 생각을 해", "X 검토해봐" | "어때", "좋을듯", "할까?", "생각을 해", "검토해봐", "고려해" | 텍스트 응답 (검토·대안 제시) → 합의 후 실행 |
196
202
  | 문제 기술·현상 보고 | "이거 안돼", "버그 있어" | "안돼", "버그" | [문제 발생 시](#문제-발생-시) 워크플로 따름 |
197
203
  | 위치·맥락 정보 단독 | "X 파일에..", "Y 섹션쪽에.." | "X에", "Y쪽에" | 의도 확인 질문 또는 다음 발언 대기 |
198
204
 
@@ -212,16 +218,19 @@ X 함수에 캐시 도입 검토 중. 기존 의존성 확인 결과 lru-cache
212
218
 
213
219
  ## 응답 톤·표현
214
220
 
215
- **적용**: 모든 응답 + 산출물(spec.md·문서·코드 주석 등 Write/Edit 본문).
221
+ **적용**: 모든 응답 + 산출물(spec.md·문서·발표자료·코드 주석 등).
216
222
 
217
223
  ### 어휘·태도
218
224
 
219
225
  - 한국어 원어민 수준으로 자연스럽게 응답.
220
- - 통용 표현 우선. LLM이 자체 조합한 신조어·합성어 사용 금지 — 사람들이 흔히 쓰는 단어로.
226
+ - 통용 표현 우선. 단, 구어·속어·비속어는 통용되더라도 금지 (예: "퉁치다"→"대신하다"). LLM이 자체 조합한 신조어·합성어도 금지 — 글에서 흔히 쓰는 단어로.
221
227
  - 직설적이고 솔직하게 응답. 형식어·완곡어·균형형 응답 금지.
222
228
  - 결론·답·핵심 먼저, 근거·맥락·세부는 그 다음 (두괄식).
223
229
  - 나쁜 예: "X 는 ~한 특성이 있어 ~합니다. 따라서 추천."
224
230
  - 좋은 예: "X 추천. 이유: ~한 특성이 있어 ~함."
231
+ - 교정·치환된 값은 결과값만 적음. 폐기된 이전 값이나 그 부정형(`~아님`·`~말고`)은 그 부정 자체가 독자에게 필요한 정보(알려진 함정·금지 사항 등)일 때만 적음. 대화에서 받은 교정 대비를 산출물 본문으로 옮기지 말 것.
232
+ - 나쁜 예: "B" 로 교정받음 → 문서에 "B (A 아님)" 기재.
233
+ - 좋은 예: "B" 로 교정받음 → 문서에 "B" 만 기재.
225
234
  - 의견 요청 시 권장/비권장 명시. "어느 쪽도 가능" 식 회피 금지.
226
235
  - 불확실은 얼버무리지 말고 명시.
227
236
  - 나쁜 예: "아마 X일 것 같습니다"
@@ -254,8 +263,7 @@ X 함수에 캐시 도입 검토 중. 기존 의존성 확인 결과 lru-cache
254
263
 
255
264
  다이어그램, 구성도, 와이어프레임 등.
256
265
 
257
- - 이모지(✏ 등) 금지: 렌더러별 1칸·2칸 변동되어 정렬이 깨짐.
258
- - 그 외 폭 안정 문자 허용.
266
+ - 폭: 한글 전각 문자는 2칸, 외(영문·숫자·ASCII 기호·이모지)는 1칸. 폭으로 정렬.
259
267
 
260
268
  ## LLM용 문서 (CLAUDE.md, Skill, Rule 등) 작성 시
261
269
 
@@ -265,6 +273,19 @@ X 함수에 캐시 도입 검토 중. 기존 의존성 확인 결과 lru-cache
265
273
  - 표준 용어로 통할 내용을 풀어쓰지 말 것.
266
274
  - 예시는 LLM 패턴 식별에 필요한 만큼 활용 (좋은/나쁜 예시 쌍 권장). 흔한 도메인(예: 재고관리)으로.
267
275
 
276
+ **최소 분량**:
277
+
278
+ - 의도 전달에 필요한 최소 분량으로 작성. 한두 줄로 끝나는 지침에 정의·안티패턴·완료기준 등 부속 블록을 덧붙이지 말 것.
279
+ - 부속 블록은 그게 없으면 LLM이 실제로 오작동하는 경우에만 추가.
280
+
281
+ **근거 없는 서술 금지** (IMPORTANT):
282
+
283
+ - LLM 문서에 적는 모든 서술(규칙·사실·동작 설명·설정값 등)은 [결정 근거](#결정-근거)에 기반. 미검증 사항을 단정형으로 적지 말 것.
284
+ - 적기 전 근거 확보 우선: 코드·공식 문서·기존 패턴 직접 확인 → 확보되면 그대로 서술.
285
+ - 확보 불가 → 사용자에게 확인 후 서술. 확인 절차 없이 추측을 사실처럼 적는 것 금지.
286
+ - 나쁜 예: 동작 미확인 상태로 "X 옵션은 Y 로 동작함" 단정 서술.
287
+ - 좋은 예: 코드·문서로 확인 후 서술, 또는 "추정(근거 미확인: <항목>)" 으로 불확실성 명시.
288
+
268
289
  **Convention 확정 금지**:
269
290
 
270
291
  - 사용자 피드백을 글자 그대로 문서화하지 말 것.
@@ -290,7 +311,7 @@ X 함수에 캐시 도입 검토 중. 기존 의존성 확인 결과 lru-cache
290
311
 
291
312
  **상위 룰 중복 금지**:
292
313
 
293
- - 자동 로드되는 상위 룰(예: `sd-base-rules.md`)에 이미 명시된 내용을 하위 지침 문서(`CLAUDE.md`·스킬 SKILL.md·참고 자료 등)에 다시 옮기지 말 것.
314
+ - 자동 로드되는 상위 룰(예: `sd-design-rules.md`)에 이미 명시된 내용을 하위 지침 문서(`CLAUDE.md`·스킬 SKILL.md·참고 자료 등)에 다시 옮기지 말 것.
294
315
  - 하위 문서는 해당 스코프 고유 내용만 작성.
295
316
 
296
317
  **산문 종결**:
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  name: sd-config
3
3
  description: sd-* 스킬 사용에 필요/권장되는 프로젝트 설정을 항목별로 사용자와 확인하며 적용하는 스킬. Use when sd-* 를 쓰기 전 환경을 점검하거나 누락 항목을 채울 때.
4
+ model: haiku
4
5
  ---
5
6
 
6
7
  각 항목을 차례로 점검. 이미 값이 있으면 변경할지 묻고, 비어 있으면 추가 여부를 물음. 동의하면 항목 설명에 기본값이 있는 경우 그 값을 제안하고, 없으면 사용자에게 값을 물어서 해당 항목의 대상 파일에 기록. 대상 파일이 없으면 새로 만듦.
@@ -129,4 +129,4 @@ LLM 단독 판단의 한계: 동일 구성 화면이 한 곳도 없다는 이유
129
129
 
130
130
  ## 운용
131
131
 
132
- - 결정 근거: `sd-base-rules.md` "결정 근거" 적용.
132
+ - 결정 근거: 행동 규칙 "결정 근거" 적용.
@@ -17,7 +17,7 @@ spec.md 없이 진행하는 가벼운 코드 작업. 의도 합의 후 워크플
17
17
  - 대상 파일·모듈·패키지.
18
18
  - 변경의 입출력·동작 — 이름·시그니처·예외 동작 등.
19
19
 
20
- 근거 충분(사용자 발화 명시 + 기존 코드 패턴으로 추론 가능)이면 묻지 않고 진행. 부족분은 1건씩 질문 (sd-base-rules "사용자 질의 시"). 외부 자료(예: 디자인 시안·샘플 데이터·접근 자격증명)가 필요하면 이 단계에서 사용자에게 요청.
20
+ 근거 충분(사용자 발화 명시 + 기존 코드 패턴으로 추론 가능)이면 묻지 않고 진행. 부족분은 1건씩 질문 (행동 규칙 "사용자 질의 시"). 외부 자료(예: 디자인 시안·샘플 데이터·접근 자격증명)가 필요하면 이 단계에서 사용자에게 요청.
21
21
 
22
22
  ### 2단계: 분석
23
23
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: sd-docs
3
3
  description: `@simplysm/*` 라이브러리 패키지의 API 문서를 `.claude/references/sd-simplysm14/apis/<패키지명>/` 위치에 사용 트리거 기준으로 작성·갱신. Use when 라이브러리 API 문서를 새로 작성하거나 코드 변경을 반영해 갱신할 때.
4
- effort: "low"
4
+ model: haiku
5
5
  ---
6
6
 
7
7
  # sd-docs
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: sd-impl
3
- description: spec.md 의 단위(§4.x 화면 / §5.x 자동 처리 / §6.x 횡단 처리) 1개를 현재 코드베이스 상태와 비교해 차이만큼 풀 구현(테스트·시연 포함). Use when "화면 풀 구현", "데모를 실 구현으로", "화면 실가동", "자동 처리 풀 구현", "횡단 처리 풀 구현" 을 요청할 때.
3
+ description: spec.md 의 단위(§4.x 화면 / §5.x 자동 처리 / §6.x 공통·기반 기능) 1개를 현재 코드베이스 상태와 비교해 차이만큼 풀 구현(테스트·시연 포함). Use when "화면 풀 구현", "데모를 실 구현으로", "화면 실가동", "자동 처리 풀 구현", "공통·기반 기능 풀 구현" 을 요청할 때.
4
4
  ---
5
5
 
6
6
  # sd-impl
7
7
 
8
- spec.md 단위(§4.x 화면 / §5.x 자동 처리 / §6.x 횡단 처리) 1개를 현재 코드베이스 상태와 비교해 빠지거나 어긋난 부분만큼 풀 구현. 데모 골격이 있으면 보존. 끝에 사용자 시연으로 회귀 점검.
8
+ spec.md 단위(§4.x 화면 / §5.x 자동 처리 / §6.x 공통·기반 기능) 1개를 현재 코드베이스 상태와 비교해 빠지거나 어긋난 부분만큼 풀 구현. 데모 골격이 있으면 보존. 끝에 사용자 시연으로 회귀 점검.
9
9
 
10
10
  호출 시나리오 2종 — 동일 워크플로:
11
11
 
@@ -17,6 +17,7 @@ spec.md 단위(§4.x 화면 / §5.x 자동 처리 / §6.x 횡단 처리) 1개를
17
17
  워크플로 전 단계 적용:
18
18
 
19
19
  - **임의 채움 금지**: spec 본문에 명시가 없거나 모호한 분기를 발견하면 즉시 멈추고 사용자에게 질문. 자체 판단으로 채우지 않음.
20
+ - **YAGNI**: spec 에 없는 기능·옵션·추상화·방어코드 추가 금지. 필요해질 때까지 만들지 않음 (작성 시점부터 적용).
20
21
  - **사용자 보고 어휘**: 사용자에게 보고하는 본문에 코드 식별자(변수명·SQL 함수명·타입 키워드 등) 노출 금지. 도메인 어휘로 풀어 보고.
21
22
 
22
23
  ## 워크플로
@@ -24,9 +25,9 @@ spec.md 단위(§4.x 화면 / §5.x 자동 처리 / §6.x 횡단 처리) 1개를
24
25
  ### 1단계: 입력 확보
25
26
 
26
27
  - 대상 spec.md 경로.
27
- - 구현할 단위 식별자 — `§4.x` (화면) / `§5.x` (자동 처리) / `§6.x` (횡단 처리).
28
+ - 구현할 단위 식별자 — `§4.x` (화면) / `§5.x` (자동 처리) / `§6.x` (공통·기반 기능).
28
29
 
29
- 없으면 묻기. spec.md 에서 식별자 매칭이 안 되면 §4 화면 목록 표 / §5 자동 처리 / §6 횡단 처리 헤더 목록에서 후보를 제시 후 확정.
30
+ 없으면 묻기. spec.md 에서 식별자 매칭이 안 되면 §4 화면 목록 표 / §5 자동 처리 / §6 공통·기반 기능 헤더 목록에서 후보를 제시 후 확정.
30
31
 
31
32
  ### 2단계: 외부 입력 점검
32
33
 
@@ -64,11 +65,11 @@ spec 본문 수정이 필요한 경우(인라인 `[OPEN]` 해소·헤더 `[OPEN]
64
65
 
65
66
  **4계층**:
66
67
 
67
- | 계층 | §4.x (화면) | §5.x (자동 처리) | §6.x (횡단 처리) |
68
+ | 계층 | §4.x (화면) | §5.x (자동 처리) | §6.x (공통·기반 기능) |
68
69
  | ----------- | ------------- | ----------------------- | ------------------------------ |
69
70
  | 도메인 모델 | 엔티티·필드 | 동일 | 동일 |
70
71
  | 데이터 접근 | 쿼리·서비스 | 동일 | 동일 |
71
- | 본체 | 화면 컴포넌트 | 자동 처리 로직 + 트리거 | 횡단 처리 로직 + 부수효과 후크 |
72
+ | 본체 | 화면 컴포넌트 | 자동 처리 로직 + 트리거 | 부수효과 후크 또는 설정·부트스트랩 구성 |
72
73
  | 테스트 | 단위 테스트 | 동일 | 동일 |
73
74
 
74
75
  **기준 단위 선정** (같은 종류 §4.x↔§4.x, §5.x↔§5.x, §6.x↔§6.x 안에서):
@@ -138,11 +139,17 @@ spec 본문 수정이 필요한 경우(인라인 `[OPEN]` 해소·헤더 `[OPEN]
138
139
  | 테스트 가능 | 도메인 로직·계산·변환·서버 함수·데이터 접근 | RED → GREEN → REFACTOR |
139
140
  | 테스트 불가·비효율 | UI 시각·인터랙션·외부 서비스 실호출·환경 의존 | 구현 → REFACTOR |
140
141
 
142
+ **테스트 단위 = spec 이 요구한 산출 단위. 내부 구현을 쪼개지 말 것**:
143
+
144
+ - 위 "테스트 가능" 분류는 그 처리가 **spec 이 독립 산출물로 요구한 단위**(공개 함수·API·계약)일 때 적용 — 그 경우 그 단위를 그대로 테스트.
145
+ - 요구사항이 "동작"(화면·자동 처리·공통·기반 기능·초기화 등)이고 변환·매핑·계산이 그 동작의 **내부 구현 조각**일 뿐이면, 동작 전체를 테스트 단위로 봄. 동작이 통째로 테스트 가능하면 그 단위로, 아니면(외부 I/O·환경 의존) "테스트 불가·비효율" 로 분류.
146
+ - **내부 구현 조각을 테스트하려고 별도 함수·파일·인터페이스로 분리·추상화하지 말 것.** spec 이 그 조각을 산출물로 요구하지 않았다면, 테스트는 그 조각을 떼낼 이유가 되지 못함.
147
+
141
148
  **사이클 단계 정의**:
142
149
 
143
150
  - **RED**: 테스트만 작성 → 러너 실행 → 실패 확인. **구현 파일은 손대지 않음.**
144
151
  - **GREEN**: 테스트를 통과하는 **최소** 구현 → 러너 통과 확인.
145
- - **REFACTOR**: 중복·가독성·기존 패턴 정합 정리 → 러너 통과 유지. 정리할 것이 없으면 스킵.
152
+ - **REFACTOR**: 중복·가독성·기존 패턴 정합 정리 → 러너 통과 유지.
146
153
 
147
154
  작업 사이클 중 spec 미정의·모호 분기를 만나면 2단계로 회귀.
148
155
 
@@ -225,8 +232,6 @@ subagent 출력을 받은 후 분류별로 처리:
225
232
 
226
233
  ### 7단계: 코드 정리
227
234
 
228
- spec 에 없는 기능·옵션·추상화 추가 금지 (YAGNI 원칙: 필요해질 때까지 만들지 않음).
229
-
230
235
  - **횡단 중복**: 작업 사이클 사이에 흩어진 동일 로직 합치기.
231
236
  - **계층별 정합 점검**: 3-B 의 기준 단위 + 보강 출처 파일을 다시 Read 하여 4계층별로 실제 작성분과 비교. 한 계층이라도 채택 패턴과 어긋나면(임의 변형·새 발명) 수정.
232
237
  - 도메인 모델: 필드·타입·제약·네이밍 패턴 일치.
@@ -249,7 +254,7 @@ spec 에 없는 기능·옵션·추상화 추가 금지 (YAGNI 원칙: 필요해
249
254
  - 기능 개요·동작의 사용자 흐름을 1회씩 수행.
250
255
  - 화면 항목·와이어프레임 위치와 실제 렌더 결과를 비교.
251
256
  - §5.x (자동 처리) 는 트리거 발생 → 결과 확인.
252
- - §6.x (횡단 처리) 부수효과 트리거 동작 발동 → 결과·기록 확인.
257
+ - §6.x (공통·기반 기능): 부수효과 동작은 트리거 발동 → 결과·기록 확인, 상시 구성은 앱 부팅 후 구성 반영 확인.
253
258
 
254
259
  회귀 발견 시 5단계로 회귀 → 수정 → 재시연.
255
260
 
@@ -75,7 +75,7 @@ Actor: 창고 작업자
75
75
 
76
76
  ## 5. 자동 처리
77
77
 
78
- ## 6. 횡단 처리
78
+ ## 6. 공통·기반 기능
79
79
 
80
80
  ## 7. 공통 정의
81
81
 
@@ -75,7 +75,7 @@ Actor: 창고 작업자
75
75
 
76
76
  ## 5. 자동 처리
77
77
 
78
- ## 6. 횡단 처리
78
+ ## 6. 공통·기반 기능
79
79
 
80
80
  ## 7. 공통 정의
81
81
 
@@ -38,13 +38,13 @@
38
38
 
39
39
  모든 도메인 모델 변경분을 변경 로그에 기록. 감사·복구 목적.
40
40
 
41
- 관련 섹션: [횡단 처리.데이터 변경 로그]
41
+ 관련 섹션: [기반.데이터 변경 로그]
42
42
 
43
43
  ## 4. 화면
44
44
 
45
45
  ## 5. 자동 처리
46
46
 
47
- ## 6. 횡단 처리
47
+ ## 6. 공통·기반 기능
48
48
 
49
49
  ### 6.1 데이터 변경 로그 [확정: 2026-05-20]
50
50
 
@@ -75,7 +75,7 @@ Actor: 창고 작업자
75
75
 
76
76
  ## 5. 자동 처리
77
77
 
78
- ## 6. 횡단 처리
78
+ ## 6. 공통·기반 기능
79
79
 
80
80
  ## 7. 공통 정의
81
81