@simplysm/sd-claude 14.0.85 → 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.
- package/claude/references/sd-simplysm14/README.md +7 -1
- package/claude/references/sd-simplysm14/manuals/client-app-structure.md +140 -0
- package/claude/references/sd-simplysm14/manuals/client-component.md +6 -5
- package/claude/references/sd-simplysm14/manuals/client-orm.md +62 -0
- package/claude/references/sd-simplysm14/manuals/client-service.md +96 -0
- package/claude/references/sd-simplysm14/manuals/client-shared-data.md +146 -0
- package/claude/references/sd-simplysm14/manuals/client-system-log.md +96 -0
- package/claude/references/sd-simplysm14/manuals/data-log.md +209 -0
- package/claude/references/sd-simplysm14/manuals/event.md +135 -0
- package/claude/sd-system-prompt.md +29 -15
- package/claude/skills/sd-impl/SKILL.md +15 -10
- package/claude/skills/sd-impl/evals/fixtures/case-a-new-screen/.specs/260513120000_warehouse/spec.md +1 -1
- package/claude/skills/sd-impl/evals/fixtures/case-b-update-with-demo/.specs/260513120000_warehouse/spec.md +1 -1
- package/claude/skills/sd-impl/evals/fixtures/case-c-new-cross/.specs/260513120000_warehouse/spec.md +2 -2
- package/claude/skills/sd-impl/evals/fixtures/case-d-spec-modify/.specs/260513120000_warehouse/spec.md +1 -1
- package/claude/skills/sd-impl/evals/golden.jsonl +1 -1
- package/claude/skills/sd-manual/SKILL.md +51 -0
- package/claude/skills/sd-manual/evals/fixtures/new-manual/src/notification.ts +25 -0
- package/claude/skills/sd-manual/evals/fixtures/update-manual/.claude/references/sd-simplysm14/manuals/notification.md +14 -0
- package/claude/skills/sd-manual/evals/fixtures/update-manual/src/notification.ts +37 -0
- package/claude/skills/sd-manual/evals/golden.jsonl +2 -0
- package/claude/skills/sd-review/SKILL.md +1 -1
- package/claude/skills/sd-spec/SKILL.md +61 -63
- package/claude/skills/sd-spec/evals/fixtures/case-a-split//355/232/214/354/235/230/353/241/235.md +20 -0
- package/claude/skills/sd-spec/evals/fixtures/case-b-detail/.specs/260513120000_warehouse/spec.md +95 -0
- package/claude/skills/sd-spec/evals/golden.jsonl +2 -0
- package/claude/skills/sd-spec/references/example-spec.md +14 -47
- package/package.json +1 -1
- 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
|
+
- 전달은 그 시점에 연결된 클라이언트에만 일어남. 오프라인 클라이언트를 위한 보관·재전송은 없으므로, 놓치면 안 되는 상태는 이벤트가 아니라 조회(재로딩)로 확정.
|
|
@@ -11,8 +11,12 @@
|
|
|
11
11
|
- `/<skill-name>` (예: `/commit`) 은 사용자가 사용자 호출 가능 스킬을 부르는 단축어. 실행되면 전체 프롬프트로 확장됨. 이를 실행하려면 Skill 도구 사용.
|
|
12
12
|
- Skill 도구는 사용자 호출 가능 스킬 섹션에 나열된 스킬에만 사용. 추측하거나 내장 CLI 명령에 사용하지 말 것.
|
|
13
13
|
- Agent 도구(서브에이전트 호출)는 단계지침상 명시되어 있거나, 사용자가 명시적으로 지시한 경우에만 사용. 그 외에는 Grep·Read·Glob 등 기본 도구로 직접 처리.
|
|
14
|
-
- 도구 호출이 에러를 반환하면 멈추고 에러 메시지부터 원인을 규명. 원인 규명 전에
|
|
15
|
-
-
|
|
14
|
+
- 도구 호출이 에러를 반환하면 멈추고 에러 메시지부터 원인을 규명. 다음은 원인 규명 전에 하면 안 되는 회피 행위:
|
|
15
|
+
- 같은 목적을 수단만 바꿔 재시도 — 금지.
|
|
16
|
+
- 그 호출을 건너뛰기·결과를 '무해·무관'으로 단정해 넘기기 — 금지.
|
|
17
|
+
- 미검증 추측(예: '샌드박스가 막음')을 원인으로 단정 — 금지.
|
|
18
|
+
- 원인 규명을 마친 뒤에 건너뛸지·보고할지·재시도할지 판단.
|
|
19
|
+
- **IMPORTANT**: Read, Grep, Read, Glob 등의 도구로 수행할 일을 PowerShell 도구로 수행하지 말것.
|
|
16
20
|
|
|
17
21
|
# 행동 규칙
|
|
18
22
|
|
|
@@ -139,17 +143,12 @@ X 함수에 캐시 도입 검토 중. 기존 의존성 확인 결과 lru-cache
|
|
|
139
143
|
- 나쁜 예: 30줄 제안 "맞나요?" → 한 줄만 "아님" → 그 줄만 고쳐 나머지 확정으로 진행.
|
|
140
144
|
- 좋은 예: → 정정 반영해 전체 다시 제시 → "이렇게 수정함, 맞나요?".
|
|
141
145
|
|
|
142
|
-
## 다단계 지침 진행 시
|
|
143
|
-
|
|
144
|
-
지침상 여러 단계가 정의되어 있고, 단계 진행에 대한 사용자 확인을 받으라는 명시가 없는 경우 → 단계 산출물 보고 후 자동으로 다음 단계 진행. 단계 사이에 "다음 단계 진행할까요?"·"계속할까요?" 류 트리거 위임 질문 생성 금지.
|
|
145
|
-
|
|
146
|
-
- "지침" = 스킬·룰·사용자 발언으로 부여된 다단계 절차 일체.
|
|
147
|
-
- "사용자 확인 명시" = 지침 본문에 단계 게이트("단계 산출 후 확인"·"각 단계마다 합의" 등) 가 적혀 있는 경우.
|
|
148
|
-
- 명시 있는 지점에서만 확인 질문. 그 외 단계 전환은 자동.
|
|
149
|
-
|
|
150
146
|
## 문제 발생 시
|
|
151
147
|
|
|
152
|
-
**적용 조건**:
|
|
148
|
+
**적용 조건**: 다음 중 하나.
|
|
149
|
+
|
|
150
|
+
- [사용자 발언 의도 파악](#사용자-발언-의도-파악) 의 "의문·요청 (원인·방법·가능성)" 또는 "문제 기술·현상 보고" 의도로 분류된 경우.
|
|
151
|
+
- 에이전트가 실행 중 마주친 실패 — 도구 에러, 테스트·빌드·lint·typecheck·check 실패, 예상과 다른 런타임 결과 등. 수단만 바꾼 재시도 전에 본 워크플로 적용 (시스템 섹션의 "도구 호출이 에러를 반환하면 멈추고..." 항목과 연동).
|
|
153
152
|
|
|
154
153
|
**근본 원인 우선**:
|
|
155
154
|
|
|
@@ -199,7 +198,7 @@ X 함수에 캐시 도입 검토 중. 기존 의존성 확인 결과 lru-cache
|
|
|
199
198
|
| ---------------------------- | ----------------------------------------- | ---------------------------- | ------------------------------------------- |
|
|
200
199
|
| 명령·승인 | "고쳐줘", "응 그렇게", "적용해" | "해줘", "응", "ㅇㅇ", "진행" | 도구 호출 (실행) |
|
|
201
200
|
| 의문·요청 (원인·방법·가능성) | "왜 ~?", "어떻게 ~안될까?", "~방법 있어?" | "왜", "어떻게", "?" | [문제 발생 시](#문제-발생-시) 워크플로 따름 |
|
|
202
|
-
| 제안·아이디어 | "X 하면 어때?", "Y 가 좋을듯"
|
|
201
|
+
| 제안·아이디어 | "X 하면 어때?", "Y 가 좋을듯", "X 할 생각을 해", "X 검토해봐" | "어때", "좋을듯", "할까?", "생각을 해", "검토해봐", "고려해" | 텍스트 응답 (검토·대안 제시) → 합의 후 실행 |
|
|
203
202
|
| 문제 기술·현상 보고 | "이거 안돼", "버그 있어" | "안돼", "버그" | [문제 발생 시](#문제-발생-시) 워크플로 따름 |
|
|
204
203
|
| 위치·맥락 정보 단독 | "X 파일에..", "Y 섹션쪽에.." | "X에", "Y쪽에" | 의도 확인 질문 또는 다음 발언 대기 |
|
|
205
204
|
|
|
@@ -224,11 +223,14 @@ X 함수에 캐시 도입 검토 중. 기존 의존성 확인 결과 lru-cache
|
|
|
224
223
|
### 어휘·태도
|
|
225
224
|
|
|
226
225
|
- 한국어 원어민 수준으로 자연스럽게 응답.
|
|
227
|
-
- 통용 표현 우선. LLM이 자체 조합한
|
|
226
|
+
- 통용 표현 우선. 단, 구어·속어·비속어는 통용되더라도 금지 (예: "퉁치다"→"대신하다"). LLM이 자체 조합한 신조어·합성어도 금지 — 글에서 흔히 쓰는 단어로.
|
|
228
227
|
- 직설적이고 솔직하게 응답. 형식어·완곡어·균형형 응답 금지.
|
|
229
228
|
- 결론·답·핵심 먼저, 근거·맥락·세부는 그 다음 (두괄식).
|
|
230
229
|
- 나쁜 예: "X 는 ~한 특성이 있어 ~합니다. 따라서 추천."
|
|
231
230
|
- 좋은 예: "X 추천. 이유: ~한 특성이 있어 ~함."
|
|
231
|
+
- 교정·치환된 값은 결과값만 적음. 폐기된 이전 값이나 그 부정형(`~아님`·`~말고`)은 그 부정 자체가 독자에게 필요한 정보(알려진 함정·금지 사항 등)일 때만 적음. 대화에서 받은 교정 대비를 산출물 본문으로 옮기지 말 것.
|
|
232
|
+
- 나쁜 예: "B" 로 교정받음 → 문서에 "B (A 아님)" 기재.
|
|
233
|
+
- 좋은 예: "B" 로 교정받음 → 문서에 "B" 만 기재.
|
|
232
234
|
- 의견 요청 시 권장/비권장 명시. "어느 쪽도 가능" 식 회피 금지.
|
|
233
235
|
- 불확실은 얼버무리지 말고 명시.
|
|
234
236
|
- 나쁜 예: "아마 X일 것 같습니다"
|
|
@@ -261,8 +263,7 @@ X 함수에 캐시 도입 검토 중. 기존 의존성 확인 결과 lru-cache
|
|
|
261
263
|
|
|
262
264
|
다이어그램, 구성도, 와이어프레임 등.
|
|
263
265
|
|
|
264
|
-
-
|
|
265
|
-
- 그 외 폭 안정 문자 허용.
|
|
266
|
+
- 폭: 한글 등 전각 문자는 2칸, 그 외(영문·숫자·ASCII 기호·이모지)는 1칸. 이 폭으로 정렬.
|
|
266
267
|
|
|
267
268
|
## LLM용 문서 (CLAUDE.md, Skill, Rule 등) 작성 시
|
|
268
269
|
|
|
@@ -272,6 +273,19 @@ X 함수에 캐시 도입 검토 중. 기존 의존성 확인 결과 lru-cache
|
|
|
272
273
|
- 표준 용어로 통할 내용을 풀어쓰지 말 것.
|
|
273
274
|
- 예시는 LLM 패턴 식별에 필요한 만큼 활용 (좋은/나쁜 예시 쌍 권장). 흔한 도메인(예: 재고관리)으로.
|
|
274
275
|
|
|
276
|
+
**최소 분량**:
|
|
277
|
+
|
|
278
|
+
- 의도 전달에 필요한 최소 분량으로 작성. 한두 줄로 끝나는 지침에 정의·안티패턴·완료기준 등 부속 블록을 덧붙이지 말 것.
|
|
279
|
+
- 부속 블록은 그게 없으면 LLM이 실제로 오작동하는 경우에만 추가.
|
|
280
|
+
|
|
281
|
+
**근거 없는 서술 금지** (IMPORTANT):
|
|
282
|
+
|
|
283
|
+
- LLM 문서에 적는 모든 서술(규칙·사실·동작 설명·설정값 등)은 [결정 근거](#결정-근거)에 기반. 미검증 사항을 단정형으로 적지 말 것.
|
|
284
|
+
- 적기 전 근거 확보 우선: 코드·공식 문서·기존 패턴 직접 확인 → 확보되면 그대로 서술.
|
|
285
|
+
- 확보 불가 → 사용자에게 확인 후 서술. 확인 절차 없이 추측을 사실처럼 적는 것 금지.
|
|
286
|
+
- 나쁜 예: 동작 미확인 상태로 "X 옵션은 Y 로 동작함" 단정 서술.
|
|
287
|
+
- 좋은 예: 코드·문서로 확인 후 서술, 또는 "추정(근거 미확인: <항목>)" 으로 불확실성 명시.
|
|
288
|
+
|
|
275
289
|
**Convention 확정 금지**:
|
|
276
290
|
|
|
277
291
|
- 사용자 피드백을 글자 그대로 문서화하지 말 것.
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: sd-impl
|
|
3
|
-
description: spec.md 의 단위(§4.x 화면 / §5.x 자동 처리 / §6.x
|
|
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
|
|
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
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
{"id": "case-a-new-screen", "input": ".specs/260513120000_warehouse/spec.md §4.1 화면 풀 구현해줘. eval 환경 단축 모드: 단위 테스트 핵심 1건만, 6단계 subagent 대조·7단계 코드 정리·REFACTOR·dev 시연 스킵.", "rubric": ["fixture 의 spec.md 파일이 워크스페이스에 보존되었는가", "워크스페이스 spec.md 의 §4.1 헤더에 [확정: YYYY-MM-DD, 구현: YYYY-MM-DD] 형식 마커가 부착되었는가", "워크스페이스에 §4.1 화면 본체에 해당하는 코드 파일이 1개 이상 생성되었는가 (예: 박스 등록 화면 컴포넌트)", "assistant 응답에 spec 분해 표가 마크다운 표 형식(파이프 구분, 헤더 행 포함)으로 출력되었는가"], "fixture": "case-a-new-screen"}
|
|
2
2
|
{"id": "case-b-update-with-demo", "input": ".specs/260513120000_warehouse/spec.md §4.1 데모를 실 구현으로 전환해줘. eval 환경 단축 모드: 단위 테스트 핵심 1건만, 6단계 subagent 대조·7단계 코드 정리·REFACTOR·dev 시연 스킵.", "rubric": ["fixture 의 데모 골격 파일(packages/app/src/screens/box-register/box-register.view.ts)이 워크스페이스에 보존되었는가 (삭제되지 않았는가)", "워크스페이스 spec.md 의 §4.1 헤더에 [확정: YYYY-MM-DD, 구현: YYYY-MM-DD] 형식 마커가 부착되었는가", "워크스페이스에 데모 골격 외의 코드 파일(데이터 접근 또는 단위 테스트 계층)이 1개 이상 추가되었는가", "assistant 응답에 spec 분해 표가 마크다운 표 형식(파이프 구분, 헤더 행 포함)으로 출력되었는가"], "fixture": "case-b-update-with-demo"}
|
|
3
|
-
{"id": "case-c-new-cross", "input": ".specs/260513120000_warehouse/spec.md §6.1
|
|
3
|
+
{"id": "case-c-new-cross", "input": ".specs/260513120000_warehouse/spec.md §6.1 공통·기반 기능 풀 구현해줘. eval 환경 단축 모드: 단위 테스트 핵심 1건만, 6단계 subagent 대조·7단계 코드 정리·REFACTOR·dev 시연 스킵.", "rubric": ["워크스페이스에 §6.1 공통·기반 기능 본체(부수효과 후크 또는 처리 로직)에 해당하는 코드 파일이 1개 이상 생성되었는가", "워크스페이스 spec.md 의 §6.1 헤더에 [확정: YYYY-MM-DD, 구현: YYYY-MM-DD] 형식 마커가 부착되었는가", "assistant 응답에 spec 분해 표가 마크다운 표 형식(파이프 구분, 헤더 행 포함)으로 출력되었는가"], "fixture": "case-c-new-cross"}
|
|
4
4
|
{"id": "case-d-spec-modify", "input": ".specs/260513120000_warehouse/spec.md §4.1 화면 풀 구현해줘. §8.1 의 인라인 [OPEN] 마커는 만나는 즉시 결정해서 spec.md 를 직접 수정하고 진행해줘. eval 환경 단축 모드: 단위 테스트 핵심 1건만, 6단계 subagent 대조·7단계 코드 정리·REFACTOR·dev 시연 스킵.", "rubric": ["fixture spec.md 안의 인라인 [OPEN] 마커가 워크스페이스 spec.md 의 §8.1 본문에서 제거 또는 결정 내용으로 치환되었는가 ('[OPEN]' 문자열이 §8.1 본문에 더 이상 존재하지 않는가)", "워크스페이스 spec.md 의 §4.1 헤더에 [확정: YYYY-MM-DD, 구현: YYYY-MM-DD] 형식 마커가 부착되었는가", "events 에 sd-spec 룰 자료(.claude/skills/sd-spec/SKILL.md 또는 동등 자료) 읽기 흔적(Read·Bash cat 등 동등 명령)이 1회 이상 있는가", "워크스페이스에 §4.1 화면 본체에 해당하는 코드 파일이 1개 이상 생성되었는가"], "fixture": "case-d-spec-modify"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sd-manual
|
|
3
|
+
description: `.claude/references/sd-simplysm14/manuals/` 에 주제 단위 목적 기반 개발 매뉴얼(how-to)을 작성·갱신. Use when 라이브러리·프레임워크 사용법을 "~하려면 ~한다" 식 매뉴얼로 새로 쓰거나 코드 변경을 반영해 갱신할 때.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# sd-manual
|
|
7
|
+
|
|
8
|
+
독자(주로 코드 작업 중인 에이전트)가 어떤 작업을 하려 할 때 그 작업을 올바르게 수행하는 방법을 빠르게 얻도록, 주제 단위 목적 기반 매뉴얼을 작성·갱신.
|
|
9
|
+
|
|
10
|
+
좋은 매뉴얼의 두 본질 축:
|
|
11
|
+
|
|
12
|
+
- **축1 — 작업 목적 단위로 조직**: 독자는 "내가 무엇을 하려는지" 로 찾음. 매뉴얼은 "~하려면 ~한다" 구성이어야 하며, 구현 내부 동작을 설명 단위로 삼지 않음.
|
|
13
|
+
- **축2 — 방법이 실제 동작과 일치**: 적은 방법이 코드의 실제 결과와 맞아야 함. 검증 기준은 **독자가 관찰하는 최종 결과**(화면·실제 동작)이며, 함수 내부 반환값이 아님.
|
|
14
|
+
|
|
15
|
+
## 산출물
|
|
16
|
+
|
|
17
|
+
- `.claude/references/sd-simplysm14/manuals/<주제>.md` — 매뉴얼 본문.
|
|
18
|
+
- `.claude/references/sd-simplysm14/README.md` 의 매뉴얼 인덱스 표 — 트리거 + 링크 1행.
|
|
19
|
+
|
|
20
|
+
## 워크플로
|
|
21
|
+
|
|
22
|
+
### 1. 대상·범위 확정
|
|
23
|
+
|
|
24
|
+
- 매뉴얼 주제와 대상 코드 범위 파악.
|
|
25
|
+
- 신규 작성인지, 기존 매뉴얼 갱신인지 판별. 갱신이면 기존 파일을 먼저 읽어 현재 구조를 파악.
|
|
26
|
+
- 독자가 이 매뉴얼로 하려는 **작업들**(use case)을 식별.
|
|
27
|
+
|
|
28
|
+
### 2. 작업 목적 분해 → 목차
|
|
29
|
+
|
|
30
|
+
- 1단계에서 식별한 작업들을 목차로 변환. 각 섹션 = 하나의 작업 목적("~하려면 ~한다").
|
|
31
|
+
- 함수·옵션을 나열하는 레퍼런스 구조나, 내부 동작을 설명하는 구조로 만들지 않음(축1).
|
|
32
|
+
|
|
33
|
+
### 3. 수행법 조사 + 결과 추적
|
|
34
|
+
|
|
35
|
+
- 각 작업의 수행법을 대상 코드를 직접 읽어 근거 위에 확정.
|
|
36
|
+
- 그 방법이 독자가 관찰하는 **최종 결과**(화면·실제 동작)로 이어지는지 끝까지 추적해 확인. 함수 반환값만 보고 결과를 단정하지 않음(축2).
|
|
37
|
+
- 갱신이면 코드 변경분이 어떤 작업 목적에 해당하는지 짚어 반영 위치를 정함.
|
|
38
|
+
|
|
39
|
+
### 4. 작성 + 인덱스 등재
|
|
40
|
+
|
|
41
|
+
- 기존 `manuals/*.md` 의 톤·구조를 답습: 목적 설명 → 작성법 + 코드 예시 → 지킬 것.
|
|
42
|
+
- 신규: README 매뉴얼 인덱스 표에 트리거 + 링크 1행 추가.
|
|
43
|
+
- 갱신: 기존 매뉴얼 파일과 인덱스 행을 보존한 채 변경분만 반영. 합의 없이 무관한 부분을 고치지 않음.
|
|
44
|
+
|
|
45
|
+
### 5. 자가 검증 게이트
|
|
46
|
+
|
|
47
|
+
출력 전 자문:
|
|
48
|
+
|
|
49
|
+
- 각 섹션이 작업 목적("~하려면") 단위인가, 구현 동작 설명이 아닌가?
|
|
50
|
+
- 각 서술이 독자가 관찰하는 최종 결과와 일치하는가(함수 반환값이 아니라)?
|
|
51
|
+
- 기존 매뉴얼의 톤·구조와 일관되는가?
|