@simplysm/sd-claude 14.0.94 → 14.0.96
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 +6 -2
- package/claude/references/sd-simplysm14/apis/angular/README.md +180 -43
- package/claude/references/sd-simplysm14/apis/angular/controls.md +275 -125
- package/claude/references/sd-simplysm14/apis/angular/crud.md +54 -59
- package/claude/references/sd-simplysm14/apis/angular/directives.md +139 -48
- package/claude/references/sd-simplysm14/apis/angular/features.md +102 -88
- package/claude/references/sd-simplysm14/apis/angular/kanban.md +54 -0
- package/claude/references/sd-simplysm14/apis/angular/layout.md +60 -36
- package/claude/references/sd-simplysm14/apis/angular/overlay.md +127 -75
- package/claude/references/sd-simplysm14/apis/angular/routing-appstructure.md +97 -51
- package/claude/references/sd-simplysm14/apis/angular/shared-data.md +74 -58
- package/claude/references/sd-simplysm14/apis/angular/sheet.md +81 -60
- package/claude/references/sd-simplysm14/apis/excel/README.md +5 -5
- package/claude/references/sd-simplysm14/apis/excel/cell.md +3 -3
- package/claude/references/sd-simplysm14/apis/excel/style.md +2 -2
- package/claude/references/sd-simplysm14/apis/excel/workbook-worksheet.md +5 -4
- package/claude/references/sd-simplysm14/apis/excel/wrapper.md +2 -2
- package/claude/references/sd-simplysm14/manuals/client-app-structure.md +5 -3
- package/claude/references/sd-simplysm14/manuals/client-component.md +31 -26
- package/claude/references/sd-simplysm14/manuals/client-crud.md +154 -4
- package/claude/references/sd-simplysm14/manuals/client-demo.md +5 -18
- package/claude/references/sd-simplysm14/manuals/client-orm.md +3 -12
- package/claude/references/sd-simplysm14/manuals/client-service.md +18 -7
- package/claude/references/sd-simplysm14/manuals/client-shared-data.md +24 -5
- package/claude/references/sd-simplysm14/manuals/data-log.md +1 -1
- package/claude/sd-system-prompt.md +7 -0
- package/claude/skills/sd-debug/SKILL.md +142 -27
- package/claude/skills/sd-review/SKILL.md +158 -20
- package/claude/skills/sd-spec/SKILL.md +53 -61
- package/claude/skills/sd-spec/references/format.md +476 -0
- package/package.json +1 -1
- package/claude/references/sd-simplysm14/apis/angular/infra.md +0 -82
- package/claude/skills/sd-debug/workflow.js +0 -390
- package/claude/skills/sd-review/workflow.js +0 -324
- package/claude/skills/sd-spec/references/format-analyze.md +0 -232
- package/claude/skills/sd-spec/references/format-design.md +0 -248
- package/claude/skills/sd-spec/workflow-analyze.js +0 -615
- package/claude/skills/sd-spec/workflow-design.js +0 -667
|
@@ -1,324 +0,0 @@
|
|
|
1
|
-
export const meta = {
|
|
2
|
-
name: "sd-review",
|
|
3
|
-
description:
|
|
4
|
-
"산출물(코드·문서 등)을 도메인 자동판정 후 적용 룰을 전수·적대적으로 검증해 [자동]/결정 분류로 보고하는 멀티에이전트 리뷰",
|
|
5
|
-
phases: [
|
|
6
|
-
{ title: "Plan", detail: "대상 식별 + 도메인 판정 + 적용 룰 동적 발견 + 리뷰 차원 도출" },
|
|
7
|
-
{ title: "Review", detail: "도출된 차원별 전수 룰 대조 (병렬)" },
|
|
8
|
-
{ title: "Verify", detail: "발견 항목별 적대적 검증 (병렬)" },
|
|
9
|
-
],
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
// ── 입력 ───────────────────────────────────────────────────────
|
|
13
|
-
// args: 경로 문자열 / 경로 배열 / 자연어 대상 설명 무엇이든 허용.
|
|
14
|
-
if (args == null || (typeof args === "string" && args.trim() === "")) {
|
|
15
|
-
throw new Error("리뷰 대상을 args 로 전달하세요 (파일/디렉터리 경로 또는 대상 설명).");
|
|
16
|
-
}
|
|
17
|
-
const targetDesc = typeof args === "string" ? args : JSON.stringify(args);
|
|
18
|
-
|
|
19
|
-
// ── 공통 원칙(모든 단계 주입) ──────────────────────────────────
|
|
20
|
-
const PRINCIPLES = `
|
|
21
|
-
검증 원칙(전수·근거 기반):
|
|
22
|
-
- 표본·대표 패턴만 보지 말고 모든 단위(코드: 함수·라인 / 문서: 섹션·문장)를 인용한 룰 항목 전체에 빠짐없이 대조.
|
|
23
|
-
- 발견 항목은 (위치 + 위반한 룰 원문 인용 + 코드/문장 근거)를 반드시 갖출 것. 근거 없는 추측·일반론·개인 취향 지적 금지.
|
|
24
|
-
- 적용 룰의 출처는 (a) 컨텍스트에 자동 주입된 프로젝트/글로벌 지침(설계룰·행동 규칙 등) 과 (b) 아래에서 발견·전달된 룰 파일 뿐. 그 밖의 임의 기준으로 지적하지 말 것.
|
|
25
|
-
|
|
26
|
-
분류(category) 기준:
|
|
27
|
-
- '자동' = 오타·맞춤법·띄어쓰기·조사 오용·들여쓰기/줄바꿈 통일·trailing whitespace·세미콜론·중복 제거 등 순수 형식 정리, 또는 변경 전후 의미·적용 범위가 동일함이 명백한 표현 정리.
|
|
28
|
-
- '결정' = 위 '자동' 두 종류 어디에도 해당하지 않는, 의미·적용 범위가 조금이라도 변동될 가능성이 있는 모든 항목.
|
|
29
|
-
|
|
30
|
-
심각도(severity): error=동작 결함·데이터 정합성·룰 명백 위반, warn=권고 위반·잠재 위험, info=경미·형식.
|
|
31
|
-
`;
|
|
32
|
-
|
|
33
|
-
// ── fail-fast 가드 ─────────────────────────────────────────────
|
|
34
|
-
// parallel/pipeline 배리어 직후 호출. 결과에 null(에이전트 reject/스킵)이 하나라도 있으면
|
|
35
|
-
// 부분 결과로 진행하지 않고 즉시 throw. 정상이지만 빈 결과(빈 배열 등)는 null 이 아니라 통과.
|
|
36
|
-
function assertNoFailures(results, stage, labels) {
|
|
37
|
-
const failed = results.flatMap((r, i) => (r ? [] : [labels?.[i] ?? `#${i}`]));
|
|
38
|
-
if (failed.length > 0) {
|
|
39
|
-
throw new Error(
|
|
40
|
-
`[${stage}] 에이전트 ${failed.length}/${results.length}건 실행 실패(null) — 부분 결과로 진행 금지(fail-fast). 실패: ${failed.join(", ")}. resume 로 재실행하면 성공분은 캐시됩니다.`,
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// ── 스키마 ─────────────────────────────────────────────────────
|
|
46
|
-
const PLAN_SCHEMA = {
|
|
47
|
-
type: "object",
|
|
48
|
-
additionalProperties: false,
|
|
49
|
-
required: ["units", "dimensions", "strategy", "notes"],
|
|
50
|
-
properties: {
|
|
51
|
-
units: {
|
|
52
|
-
type: "array",
|
|
53
|
-
description: "확정된 리뷰 단위",
|
|
54
|
-
items: {
|
|
55
|
-
type: "object",
|
|
56
|
-
additionalProperties: false,
|
|
57
|
-
required: ["path", "domain"],
|
|
58
|
-
properties: {
|
|
59
|
-
path: { type: "string", description: "Read 도구로 직접 접근 가능한 경로. 사용자가 절대경로(예: 다른 워크스페이스)를 줬으면 절대경로 그대로 보존. 현재 레포 내부 대상일 때만 레포 기준 상대경로 허용." },
|
|
60
|
-
domain: { type: "string", description: "산출물 도메인 판정" },
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
|
-
},
|
|
64
|
-
dimensions: {
|
|
65
|
-
type: "array",
|
|
66
|
-
description: "겹치지 않게 묶은 리뷰 차원",
|
|
67
|
-
items: {
|
|
68
|
-
type: "object",
|
|
69
|
-
additionalProperties: false,
|
|
70
|
-
required: ["key", "title", "ruleSources", "units", "focus"],
|
|
71
|
-
properties: {
|
|
72
|
-
key: { type: "string" },
|
|
73
|
-
title: { type: "string" },
|
|
74
|
-
ruleSources: {
|
|
75
|
-
type: "array",
|
|
76
|
-
items: { type: "string" },
|
|
77
|
-
description: "이 차원이 적용할 룰 소스의 실제 경로 또는 'auto-injected: <지침명>'",
|
|
78
|
-
},
|
|
79
|
-
units: { type: "array", items: { type: "string" }, description: "이 차원이 검사할 단위 경로(비우면 전체)" },
|
|
80
|
-
focus: { type: "string", description: "이 차원의 검사 초점" },
|
|
81
|
-
},
|
|
82
|
-
},
|
|
83
|
-
},
|
|
84
|
-
strategy: { type: "string", description: "선택한 분할 축(룰축/파일축/매트릭스)과 그 이유" },
|
|
85
|
-
notes: { type: "string", description: "도메인 판정·룰 발견 근거 요약" },
|
|
86
|
-
},
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
const FINDINGS_SCHEMA = {
|
|
90
|
-
type: "object",
|
|
91
|
-
additionalProperties: false,
|
|
92
|
-
required: ["findings"],
|
|
93
|
-
properties: {
|
|
94
|
-
findings: {
|
|
95
|
-
type: "array",
|
|
96
|
-
items: {
|
|
97
|
-
type: "object",
|
|
98
|
-
additionalProperties: false,
|
|
99
|
-
required: ["title", "file", "line", "severity", "category", "rule", "evidence", "fix"],
|
|
100
|
-
properties: {
|
|
101
|
-
title: { type: "string", description: "한 줄 요약" },
|
|
102
|
-
file: { type: "string", description: "파일 경로 또는 basename" },
|
|
103
|
-
line: { type: "string", description: "라인/섹션 번호 또는 범위" },
|
|
104
|
-
severity: { type: "string", enum: ["error", "warn", "info"] },
|
|
105
|
-
category: { type: "string", enum: ["자동", "결정"] },
|
|
106
|
-
rule: { type: "string", description: "위반한 룰 출처 + 원문 인용" },
|
|
107
|
-
evidence: { type: "string", description: "코드/문장 근거 인용" },
|
|
108
|
-
fix: { type: "string", description: "제안 수정" },
|
|
109
|
-
},
|
|
110
|
-
},
|
|
111
|
-
},
|
|
112
|
-
},
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
const VERDICT_SCHEMA = {
|
|
116
|
-
type: "object",
|
|
117
|
-
additionalProperties: false,
|
|
118
|
-
required: [
|
|
119
|
-
"verdict",
|
|
120
|
-
"reason",
|
|
121
|
-
"final_severity",
|
|
122
|
-
"final_category",
|
|
123
|
-
"fix_verdict",
|
|
124
|
-
"fix_assessment",
|
|
125
|
-
"fix_revised",
|
|
126
|
-
],
|
|
127
|
-
properties: {
|
|
128
|
-
verdict: { type: "string", enum: ["confirmed", "rejected", "uncertain"], description: "발견(문제) 자체의 진위" },
|
|
129
|
-
reason: { type: "string", description: "룰 원문과 대상을 재확인한 판정 근거" },
|
|
130
|
-
final_severity: { type: "string", enum: ["error", "warn", "info"] },
|
|
131
|
-
final_category: { type: "string", enum: ["자동", "결정"] },
|
|
132
|
-
fix_verdict: {
|
|
133
|
-
type: "string",
|
|
134
|
-
enum: ["sound", "risky", "flawed", "uncertain"],
|
|
135
|
-
description: "제안 해결책의 적대적 검증 결과 (발견이 rejected 면 uncertain)",
|
|
136
|
-
},
|
|
137
|
-
fix_assessment: {
|
|
138
|
-
type: "string",
|
|
139
|
-
description: "해결책이 문제를 실제로 해결하는지, 새 룰 위반·회귀·엣지케이스(결측·동시성·soft delete·권한 등)를 유발하는지 근거",
|
|
140
|
-
},
|
|
141
|
-
fix_revised: {
|
|
142
|
-
type: "string",
|
|
143
|
-
description: "해결책이 flawed/risky 면 교정된 해결책. sound 면 원안을 그대로 다시 기술. 발견 rejected 면 빈 문자열.",
|
|
144
|
-
},
|
|
145
|
-
},
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
// ── [Plan] 대상·도메인·룰·차원 자율 도출 ───────────────────────
|
|
149
|
-
phase("Plan");
|
|
150
|
-
const plan = await agent(
|
|
151
|
-
`리뷰 대상: ${targetDesc}
|
|
152
|
-
|
|
153
|
-
${PRINCIPLES}
|
|
154
|
-
|
|
155
|
-
너의 일 (범용 리뷰 1단계 — 계획 수립). 아래를 스스로 수행:
|
|
156
|
-
|
|
157
|
-
1. 대상 식별: 위 대상을 구체 리뷰 단위로 확정.
|
|
158
|
-
- 경로/디렉터리면 Glob 으로 파일 수집(dist/.back/node_modules/.gitignore 등재 경로 제외).
|
|
159
|
-
- 자연어 설명이면 Grep/Glob 으로 해당 산출물을 찾아 단위 확정.
|
|
160
|
-
- 각 단위의 경로는 Read 로 직접 접근 가능한 형태로 확보. 사용자가 절대경로(예: 다른 워크스페이스)를 줬으면 그 절대경로를 그대로 보존하고 상대경로로 깎지 말 것 — 하위 Review/Verify 단계가 그 경로를 그대로 Read 함. 현재 레포 내부 대상일 때만 레포 기준 상대경로 사용.
|
|
161
|
-
|
|
162
|
-
2. 도메인 판정: 각 단위가 어떤 산출물 도메인인지 판정.
|
|
163
|
-
예: "@simplysm v14 화면 컴포넌트", "@simplysm v14 라이브러리/CLI 코드", "ORM/DB 스키마", "LLM 문서(SKILL.md/CLAUDE.md/.claude/rules)", "사람용 문서", "스킬 정의", "spec.md" 등.
|
|
164
|
-
|
|
165
|
-
3. 적용 룰 동적 발견: 도메인에 맞는 룰 소스를 실제로 찾아 경로를 적음.
|
|
166
|
-
- 컨텍스트에 자동 주입된 프로젝트/글로벌 지침(설계룰·행동 규칙 등) → ruleSources 에 'auto-injected: <지침명>' 으로 표기.
|
|
167
|
-
- Glob ".claude/rules/*.md".
|
|
168
|
-
- 가장 가까운 CLAUDE.md (있으면).
|
|
169
|
-
- 도메인 관련 ".claude/references/**" 매뉴얼 (예: simplysm14 화면이면 references/sd-simplysm14/manuals/client-*.md, orm.md 등; 실제 Glob 으로 존재 확인).
|
|
170
|
-
- 도메인 관련 ".claude/skills/*/SKILL.md" (예: 스킬을 리뷰하면 sd-skill, spec.md 면 sd-spec, 매뉴얼이면 sd-manual).
|
|
171
|
-
- "기존 동종 산출물 패턴" 자체도 룰 소스로 취급(코드베이스 비교) → ruleSources 에 'existing-pattern' 표기.
|
|
172
|
-
|
|
173
|
-
4. 리뷰 차원 도출(분할 축 자율 결정): 대상 규모(단위 수·파일 크기·룰 도메인 수)를 먼저 가늠한 뒤, 가장 효율적인 분할 축을 스스로 택해 차원을 구성한다.
|
|
174
|
-
- 룰축: 파일 적고 룰 도메인 많음 → 차원 = 룰 묶음, units = 전체 단위.
|
|
175
|
-
- 파일축: 파일 많고 룰 도메인 적음 → 차원 = 파일(또는 파일 클러스터), ruleSources = 적용 룰 전체, units = 그 파일들.
|
|
176
|
-
- 매트릭스: 파일도 많고 룰 도메인도 많은 대규모 → 파일 클러스터 × 룰 묶음 조합.
|
|
177
|
-
각 차원에 key, title, ruleSources(실제 경로/표기), units(이 차원이 검사할 단위 경로 — 자율 분할의 핵심이니 반드시 명시), focus.
|
|
178
|
-
불변식: (a) 모든 (단위 × 적용 룰) 조합이 정확히 한 차원에서 빠짐없이 커버될 것 — 누락·중복 금지, (b) 한 차원의 컨텍스트가 과부하되지 않게 적정 크기로 자를 것, (c) 차원 수는 보통 3~8개.
|
|
179
|
-
선택한 분할 축과 이유를 strategy 에, 도메인·룰 발견 근거를 notes 에 적는다.
|
|
180
|
-
|
|
181
|
-
units / dimensions / strategy / notes 를 반환.`,
|
|
182
|
-
{ label: "plan", phase: "Plan", schema: PLAN_SCHEMA },
|
|
183
|
-
);
|
|
184
|
-
|
|
185
|
-
if (!plan) throw new Error("[Plan] 계획 수립 에이전트 실행 실패(null) — 중단.");
|
|
186
|
-
const UNITS = (plan.units ?? []).filter(Boolean);
|
|
187
|
-
const DIMENSIONS = (plan.dimensions ?? []).filter(Boolean);
|
|
188
|
-
if (UNITS.length === 0) throw new Error("리뷰 단위를 식별하지 못했습니다.");
|
|
189
|
-
if (DIMENSIONS.length === 0) throw new Error("리뷰 차원을 도출하지 못했습니다.");
|
|
190
|
-
log(
|
|
191
|
-
`단위 ${UNITS.length}개 / 분할: ${plan.strategy ?? "?"} / 차원 ${DIMENSIONS.length}개: ${DIMENSIONS.map((d) => d.title).join(", ")}`,
|
|
192
|
-
);
|
|
193
|
-
|
|
194
|
-
const ALL_UNIT_PATHS = UNITS.map((u) => u.path);
|
|
195
|
-
|
|
196
|
-
// ── [Review] 전 차원 전수 룰 대조 (배리어: 전량 수집 후 비용 가드) ──
|
|
197
|
-
phase("Review");
|
|
198
|
-
const reviewRaw = (
|
|
199
|
-
await parallel(
|
|
200
|
-
DIMENSIONS.map((d) => () =>
|
|
201
|
-
agent(
|
|
202
|
-
`${PRINCIPLES}
|
|
203
|
-
|
|
204
|
-
리뷰 차원: ${d.title}
|
|
205
|
-
검사 초점: ${d.focus}
|
|
206
|
-
|
|
207
|
-
이 차원에서 적용할 룰 소스(존재하는 파일은 전부 Read; 'auto-injected'/'existing-pattern' 는 컨텍스트·코드베이스 조사로 처리):
|
|
208
|
-
${(d.ruleSources ?? []).map((r) => "- " + r).join("\n") || "- (명시 없음 — 자동 주입 지침 + 코드베이스 패턴 기준)"}
|
|
209
|
-
|
|
210
|
-
리뷰 단위(전부 Read 로 끝까지):
|
|
211
|
-
${((d.units && d.units.length ? d.units : ALL_UNIT_PATHS)).map((p) => "- " + p).join("\n")}
|
|
212
|
-
|
|
213
|
-
위 룰 소스를 단위 전체에 전수 대조해 위반을 findings 로 보고. 각 발견에 category('자동'/'결정')·severity·룰 원문 인용·근거·수정안을 채울 것. 위반 없으면 findings 를 비움.`,
|
|
214
|
-
{ label: `review:${d.key}`, phase: "Review", schema: FINDINGS_SCHEMA },
|
|
215
|
-
).then((res) => ({ dimension: d, res })),
|
|
216
|
-
),
|
|
217
|
-
)
|
|
218
|
-
);
|
|
219
|
-
// fail-fast: 차원이 하나라도 실행 실패(null)면 부분 결과로 진행하지 않고 중단(거짓 통과 방지).
|
|
220
|
-
assertNoFailures(reviewRaw, "Review", DIMENSIONS.map((d) => `review:${d.key}`));
|
|
221
|
-
const malformed = reviewRaw.filter((x) => x.res == null || !Array.isArray(x.res.findings)).map((x) => x.dimension.key);
|
|
222
|
-
if (malformed.length > 0) throw new Error(`[Review] 차원 결과 malformed(findings 부재): ${malformed.join(", ")} — 중단.`);
|
|
223
|
-
const reviewResults = reviewRaw;
|
|
224
|
-
|
|
225
|
-
// ── 비용 상한: verify fan-out 직전, 전체 에이전트 수가 Workflow 런타임 상한(1000) 근접 시 사전 throw ──
|
|
226
|
-
// findings 는 자르지 않는다(누락 방지). 합계가 상한 근접하면 통째 throw 로 보고.
|
|
227
|
-
const TOTAL_FINDINGS = reviewResults.reduce((n, x) => n + (x.res?.findings?.length ?? 0), 0);
|
|
228
|
-
const PLANNED_AGENTS = 1 /* plan */ + DIMENSIONS.length /* review */ + TOTAL_FINDINGS /* verify */;
|
|
229
|
-
const SAFE_AGENT_LIMIT = 900; // Workflow 런타임의 생애 총 에이전트 상한(1000)에 대한 안전 여유
|
|
230
|
-
if (PLANNED_AGENTS > SAFE_AGENT_LIMIT) {
|
|
231
|
-
throw new Error(
|
|
232
|
-
`findings ${TOTAL_FINDINGS}건 → 예상 에이전트 ≈ ${PLANNED_AGENTS}개로 Workflow 런타임 상한(1000) 근접. findings 를 자르면 위반이 누락되므로 리뷰 대상/차원을 좁혀 재호출하세요.`,
|
|
233
|
-
);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// ── [Verify] 발견 항목별 적대적 검증 (배리어 통과분 일괄 fan-out) ──
|
|
237
|
-
phase("Verify");
|
|
238
|
-
const piped = await parallel(
|
|
239
|
-
reviewResults.flatMap(({ dimension: d, res }) =>
|
|
240
|
-
(res?.findings ?? []).map((f) => () =>
|
|
241
|
-
agent(
|
|
242
|
-
`${PRINCIPLES}
|
|
243
|
-
|
|
244
|
-
다음은 리뷰에서 제기된 발견 항목이다. 두 가지를 모두 적대적으로 검증하라.
|
|
245
|
-
|
|
246
|
-
[1] 발견(문제) 검증: 인용된 룰이 실제로 그렇게 규정하는지, 대상이 실제 그 위치에서 그러한지 파일을 직접 Read 하여 확인. 과장·오인·룰 오인용이면 verdict=rejected, 불명확하면 uncertain, 사실이면 confirmed. 의심스러우면 기각 쪽으로 판단. final_severity/final_category 재산정.
|
|
247
|
-
|
|
248
|
-
[2] 해결책(제안 수정) 검증: 제안된 수정을 공격적으로 따져라 —
|
|
249
|
-
- 실제로 그 문제를 해결하는가,
|
|
250
|
-
- 새로운 룰 위반·회귀를 만들지 않는가,
|
|
251
|
-
- 엣지케이스(결측 null/undefined, 동시성/트랜잭션, soft delete 로 인한 동명 레코드 허용, 권한 분기, 타입/스키마 제약 등)에서 깨지지 않는가,
|
|
252
|
-
- 과도하거나(over-engineering) 틀린 접근은 아닌가.
|
|
253
|
-
대상 코드·스키마·룰을 직접 확인해 판정. 결함이 있으면 fix_verdict=flawed/risky 로 두고 fix_revised 에 교정안을 제시. 건전하면 fix_verdict=sound 로 두고 fix_revised 에 원안을 다시 기술.
|
|
254
|
-
(예: "name 컬럼에 유니크 제약 추가" 같은 해결책은 soft delete 로 삭제된 동명 레코드와 충돌하므로 flawed 로 판정해야 함.)
|
|
255
|
-
발견이 rejected 면 [2]는 생략 가능 — fix_verdict=uncertain, fix_revised="".
|
|
256
|
-
|
|
257
|
-
발견 항목:
|
|
258
|
-
- 제목: ${f.title}
|
|
259
|
-
- 위치: ${f.file} (${f.line})
|
|
260
|
-
- 심각도(제안): ${f.severity}
|
|
261
|
-
- 분류(제안): ${f.category}
|
|
262
|
-
- 위반 룰: ${f.rule}
|
|
263
|
-
- 근거: ${f.evidence}
|
|
264
|
-
- 제안 수정: ${f.fix}
|
|
265
|
-
|
|
266
|
-
리뷰 단위 경로:
|
|
267
|
-
${ALL_UNIT_PATHS.map((p) => "- " + p).join("\n")}
|
|
268
|
-
|
|
269
|
-
해당 파일·스키마·룰 소스를 Read 하여 직접 대조 후 판정.`,
|
|
270
|
-
{ label: `verify:${d.key}:${f.file}`, phase: "Verify", schema: VERDICT_SCHEMA },
|
|
271
|
-
).then((v) => ({ dimension: d.key, finding: f, verdict: v })),
|
|
272
|
-
),
|
|
273
|
-
),
|
|
274
|
-
);
|
|
275
|
-
|
|
276
|
-
// fail-fast: verify 에이전트가 하나라도 실패(null)면 중단.
|
|
277
|
-
assertNoFailures(piped, "Verify");
|
|
278
|
-
const all = piped;
|
|
279
|
-
const confirmed = all.filter((x) => x.verdict.verdict === "confirmed");
|
|
280
|
-
const uncertain = all.filter((x) => x.verdict.verdict === "uncertain");
|
|
281
|
-
const rejected = all.filter((x) => x.verdict.verdict === "rejected");
|
|
282
|
-
|
|
283
|
-
// 검증 통과분(확정+불확실)에 검증의 최종 심각도/분류를 반영
|
|
284
|
-
const survived = confirmed.concat(uncertain).map((x) => ({
|
|
285
|
-
dimension: x.dimension,
|
|
286
|
-
title: x.finding.title,
|
|
287
|
-
file: x.finding.file,
|
|
288
|
-
line: x.finding.line,
|
|
289
|
-
severity: x.verdict.final_severity,
|
|
290
|
-
category: x.verdict.verdict === "uncertain" ? "결정" : x.verdict.final_category,
|
|
291
|
-
rule: x.finding.rule,
|
|
292
|
-
evidence: x.finding.evidence,
|
|
293
|
-
fix: x.verdict.fix_revised && x.verdict.fix_revised.trim() !== "" ? x.verdict.fix_revised : x.finding.fix,
|
|
294
|
-
fix_verdict: x.verdict.fix_verdict,
|
|
295
|
-
fix_assessment: x.verdict.fix_assessment,
|
|
296
|
-
verdict: x.verdict.verdict,
|
|
297
|
-
verifyReason: x.verdict.reason,
|
|
298
|
-
}));
|
|
299
|
-
|
|
300
|
-
// ── 결과 반환: 검증 통과분(survived)을 메인 루프(SKILL.md)가 병합·중복제거·[자동]/결정 분류 ──
|
|
301
|
-
return {
|
|
302
|
-
plan: {
|
|
303
|
-
units: UNITS,
|
|
304
|
-
dimensions: DIMENSIONS.map((d) => ({ key: d.key, title: d.title, ruleSources: d.ruleSources })),
|
|
305
|
-
notes: plan.notes,
|
|
306
|
-
},
|
|
307
|
-
summary: {
|
|
308
|
-
units: UNITS.length,
|
|
309
|
-
dimensions: DIMENSIONS.length,
|
|
310
|
-
total: all.length,
|
|
311
|
-
confirmed: confirmed.length,
|
|
312
|
-
uncertain: uncertain.length,
|
|
313
|
-
rejected: rejected.length,
|
|
314
|
-
survived: survived.length,
|
|
315
|
-
},
|
|
316
|
-
survived,
|
|
317
|
-
rejected: rejected.map((x) => ({
|
|
318
|
-
dimension: x.dimension,
|
|
319
|
-
title: x.finding.title,
|
|
320
|
-
file: x.finding.file,
|
|
321
|
-
line: x.finding.line,
|
|
322
|
-
reason: x.verdict.reason,
|
|
323
|
-
})),
|
|
324
|
-
};
|
|
@@ -1,232 +0,0 @@
|
|
|
1
|
-
# sd-spec 분석 작성법 (§1·§2·§3·§7·§8·§9)
|
|
2
|
-
|
|
3
|
-
분석 배치(`workflow-analyze.js`)와 부분 수정(§1~3·§7~9 편집)이 따르는 §별 작성법.
|
|
4
|
-
|
|
5
|
-
- **공유 형식**(섹션 구조·신뢰도 표기·본문 내 참조·sub-section 헤더 레벨·진행 방법)·**골격**·**§10** 은 `SKILL.md` 참조.
|
|
6
|
-
- **설계 작성법**(§4~6)은 `format-design.md`.
|
|
7
|
-
- §7·§8·§9 는 분석이 주로 생성하지만 설계 배치도 보강하므로 여기 둠 — 설계 시 §7~9 작성법은 본 파일을 참조.
|
|
8
|
-
|
|
9
|
-
## §1 개요
|
|
10
|
-
|
|
11
|
-
**작성 단위**: §1.1·§1.2·§1.3·§1.4. 단, §1.4 는 ASCII 구성도 + 장치 목록의 2 부분.
|
|
12
|
-
|
|
13
|
-
**모범**: example-spec.md §1.1~§1.4.
|
|
14
|
-
|
|
15
|
-
### §1.1 핵심 목적
|
|
16
|
-
|
|
17
|
-
- 한 줄 동사형의 큰 단위로 작성.
|
|
18
|
-
- 분석 단계의 도메인 관계도를 기반으로 사이클·관계를 표현.
|
|
19
|
-
- §2.x 의 동사구 항목 나열 금지 (해당 표현은 §1.2 의 역할).
|
|
20
|
-
|
|
21
|
-
### §1.2 주요 목표
|
|
22
|
-
|
|
23
|
-
- 단위: 최종 사용자의 업무 흐름 1건 (화면·기능 단위가 아님).
|
|
24
|
-
- 표현: 한 줄 동사구. 최종 사용자 도메인 어휘 사용.
|
|
25
|
-
- §2 업무 프로세스 헤더와 1:1 매핑되는 것이 일반적.
|
|
26
|
-
- §2.x 본문의 핵심 항목은 한 줄에 모두 담아야 하며, 임의 축약·누락 금지.
|
|
27
|
-
|
|
28
|
-
### §1.3 최종 사용자/이해관계자
|
|
29
|
-
|
|
30
|
-
- 실제 담당자의 이름 사용 금지.
|
|
31
|
-
- 역할로 일반화하여 표현.
|
|
32
|
-
|
|
33
|
-
### §1.4 환경/장치
|
|
34
|
-
|
|
35
|
-
분석 단계에서 작성한 뒤, 설계 단계 진행 중에도 보강 가능 (다른 섹션 반영거리 — 부분 수정으로 처리).
|
|
36
|
-
|
|
37
|
-
구성도와 장치 목록은 목적이 별개이므로 각각 독립적으로 작성.
|
|
38
|
-
|
|
39
|
-
- **ASCII 구성도**: 현장 물리 구성 시각화 — 이해당사자 간 그림 맞추기.
|
|
40
|
-
- 포함 대상: 솔루션 노드 (서버·DB·클라이언트) + 노드 간 연결선 + 물리 위치 그룹 (IDC·사무실·창고) + 자동 연동되는 외부 시스템.
|
|
41
|
-
- 제외 대상: Actor, 외부 관계자 (인적·조직), 수동 채널 (메일·종이), 구체 제품명 (MySQL)·통신 프로토콜 (HTTPS), 장치 사양 (OS·해상도).
|
|
42
|
-
- **장치 목록**: 솔루션이 대상으로 하는 장치·환경 스펙 — 서버·DB·클라이언트 등. OS·버전·제품명·모델·해상도·하드웨어.
|
|
43
|
-
- 명시한 스펙에 맞게 개발하며, 그 외 스펙 환경에서의 동작은 책임 범위 외.
|
|
44
|
-
|
|
45
|
-
## §2 업무 프로세스
|
|
46
|
-
|
|
47
|
-
**작성 단위**: §2.x 1개당 위→아래 순서로:
|
|
48
|
-
1. BPMN (mermaid 형식으로 저장).
|
|
49
|
-
2. 흐름 설명 bullet.
|
|
50
|
-
|
|
51
|
-
**모범**: example-spec.md §2.1.
|
|
52
|
-
|
|
53
|
-
### 분할 단위 = BPMN end-to-end process
|
|
54
|
-
|
|
55
|
-
§2.x 1건 = BPMN end-to-end process 1건 (= Value Stream L1). start event ~ end event 의 풀 사이클. 사이클 내부의 handoff (역할 전환·시간 지연·외부 채널·동일인의 다른 시점) 도 포함 — BPMN swim lane 또는 노드 분기로 표현. **handoff 는 §2.x 분리 사유가 아님**.
|
|
56
|
-
|
|
57
|
-
- **start event** (BPMN 시작 트리거):
|
|
58
|
-
- Message — 외부 actor 의 메시지·자료가 도착 (구매처 발주·고객 주문·거래처 EDI).
|
|
59
|
-
- Timer — 시간이 도래 (세무 신고일·정산 마감일).
|
|
60
|
-
- Signal — 외부 조건 신호.
|
|
61
|
-
- **end event** (BPMN 종결 outcome):
|
|
62
|
-
- 외부 가치 전달 또는 외부 자료 송신 (출하 완료·세금 제출·EDI 송신).
|
|
63
|
-
- **분리 기준 = pivotal event** (Event Storming 의 pivotal event 개념):
|
|
64
|
-
1. start event 가 별개 (입고 start ↔ 출고 start).
|
|
65
|
-
2. end event 가 별개.
|
|
66
|
-
3. 도메인 어휘 자체의 전환점 (DDD 경계).
|
|
67
|
-
|
|
68
|
-
### 분할 절차 (분석)
|
|
69
|
-
|
|
70
|
-
분석 배치에서 §2/§3 분할을 동시에 진행. 절차는 4단계 (워크플로 reduce 단계가 pivotal event 로 §2/§3 분할 결정).
|
|
71
|
-
|
|
72
|
-
1. **domain event 추출** (Event Storming 의 "chaotic events" 단계) — Requirement Source 에서 동사 과거형 사건을 모두 평면적으로 추출. 원본 구조 (메일·슬라이드·문서) 와는 무관.
|
|
73
|
-
2. **timeline 정렬** (Event Storming 의 "Enforce timeline" 단계) — 비즈니스 시간 순으로 정렬. 시점이 모호한 항목은 인접 후보로 보류.
|
|
74
|
-
3. **pivotal event 식별** — start/end/도메인 어휘 전환점을 마킹. 외부 관계자 평면 (구매처·공급처·고객·하청·감독기관) 이 이 단계에서 자연스럽게 도출됨 — 이 결과가 §1 작성용 도메인 관계도 자료가 됨.
|
|
75
|
-
4. **bounded subdomain 그룹화** — 한 pivotal trigger ~ 다음 pivotal outcome 까지가 1 사이클. 사이클 내부의 handoff 도 포함.
|
|
76
|
-
|
|
77
|
-
#### 분할 결과 매핑
|
|
78
|
-
|
|
79
|
-
3가지로 분류:
|
|
80
|
-
- **사이클** (multi-event handoff 포함) → §2.x.
|
|
81
|
-
- **단일 user goal 완결** → §3.x.
|
|
82
|
-
- **사이클도 user goal 단위도 아닌 것** (도메인 룰·계산식·기존 화면 미세 변경·자료 해석 파라미터·기타 규칙) → **미분류**.
|
|
83
|
-
|
|
84
|
-
미분류 항목 중 근거가 자료에서 인용 불가능한 것(As-Is 답습·답변 범위 흡수)은 [OPEN] 으로 보고 (작성 원칙 참조).
|
|
85
|
-
|
|
86
|
-
### 헤더 명칭
|
|
87
|
-
|
|
88
|
-
- 외부 관계자 평면 기반의 도메인 업무 동사구 사용.
|
|
89
|
-
- 시스템·문서·메뉴·양식의 명칭을 그대로 사용 금지.
|
|
90
|
-
- ❌ `발주서 변경 요청` (발주서 = 시스템 문서).
|
|
91
|
-
- ✅ `구매처 발주 변경 요청`.
|
|
92
|
-
|
|
93
|
-
### 본문 구조
|
|
94
|
-
|
|
95
|
-
sub-section 헤더 레벨은 `## spec.md 형식` 의 "sub-section 헤더 레벨" 표 참조.
|
|
96
|
-
|
|
97
|
-
**평문 sub-section** (§2.x 헤더 바로 아래에 위→아래 순서로 작성):
|
|
98
|
-
|
|
99
|
-
- **BPMN** (mermaid fence):
|
|
100
|
-
- mermaid 형식으로 저장.
|
|
101
|
-
- 노드 = 최종 사용자 액션 또는 시스템 핵심 트랜잭션 (1행 동사구). 줄바꿈 `<br/>` 사용 금지.
|
|
102
|
-
- Actor·장치·매체 (PDA·PC·종이) 는 액션 컨텍스트로 노드 안에 명시 허용 (예: `창고 작업자: PDA 박스 바코드 스캔`). OS·해상도·API·DB 등 시스템 내부 디테일은 제외.
|
|
103
|
-
- 외부 채널 송신 (메일·파일 다운로드) 과 외부 응답 수신 (다음 사이클 자료 도착) 도 노드로 표현. 처리 노드는 사각 (`T[...]`), 이벤트 노드는 둥근 (`E([...])`).
|
|
104
|
-
- 분기는 사용자의 의사결정 분기만 표현.
|
|
105
|
-
- **흐름 설명 bullet** (BPMN 아래, 관련 섹션 위에 자유롭게 나열):
|
|
106
|
-
- 노드 동사구만으로는 표현되지 않는 룰·조건·계산식·외부 약속·자료 출처 인용 등을 한 줄씩 기재.
|
|
107
|
-
- §4/§5/§6 본문 작성 시 이 bullet 들을 근거로 매핑하여 누락 방지.
|
|
108
|
-
- 단, §4/§5/§6 자체의 디테일 (필드·UI·파일 확장자) 과 §7/§8/§9 의 정형 명세는 여기에 작성 금지 — 각 § 본문에서는 이름으로만 참조.
|
|
109
|
-
- **관련 섹션** (자동 누적): "본문 내 참조" 절의 룰 따름.
|
|
110
|
-
|
|
111
|
-
### 안티패턴 (분할)
|
|
112
|
-
|
|
113
|
-
- ❌ 같은 사이클의 A→B→C 단계별로 담당자·산출물이 다르다는 이유로 §2.A·§2.B·§2.C 로 쪼개는 행위.
|
|
114
|
-
- ❌ 메뉴 그룹·기존 화면 폴더·코드 As-Is 구조를 기준으로 묶거나 쪼개는 행위.
|
|
115
|
-
|
|
116
|
-
## §3 기타 요구사항
|
|
117
|
-
|
|
118
|
-
**작성 단위**: §3.x 1개당 본문이 짧으면 전체를 한 단위로. 본문이 길어지면 "요구 의도" 와 "관련 섹션" 으로 분리.
|
|
119
|
-
|
|
120
|
-
**모범**: example-spec.md §3.1.
|
|
121
|
-
|
|
122
|
-
### 분할 단위 = Cockburn sea level use case
|
|
123
|
-
|
|
124
|
-
- Cockburn 의 정의: "primary actor go away happy after having done this in a single sitting?".
|
|
125
|
-
- 행위자가 1회 작업으로 끝내고 만족하여 떠나는 단일 user goal.
|
|
126
|
-
- 사이클·handoff·지연이 없음.
|
|
127
|
-
- 정형 분해 (화면 / 자동 처리 / 공통·기반 기능) 를 동반하는, 사용자의 직접 요구.
|
|
128
|
-
- 시스템 전반 자동 룰 (예: "모든 데이터 수정 시 변경 이력 기록") 과 프레임워크 운영 기반 (앱 구조·접근 권한·부트스트랩 등) 도 §3 에 포함 — 그에 대한 설계는 §6 공통·기반 기능에서 수행.
|
|
129
|
-
|
|
130
|
-
### 본문 구조
|
|
131
|
-
|
|
132
|
-
- 요구 의도 (한 줄 또는 한 문단).
|
|
133
|
-
- 관련 섹션 한 줄 ("본문 내 참조" 절 룰 따름).
|
|
134
|
-
- 구현 디테일은 §4 화면 / §5 자동 처리 / §6 공통·기반 기능 본문에 작성.
|
|
135
|
-
|
|
136
|
-
## §7 공통 정의
|
|
137
|
-
|
|
138
|
-
작성 중 자연스럽게 도출됨 (다른 섹션 반영거리). 새로운 도메인 어휘·시스템 규격을 발견하면 추가.
|
|
139
|
-
|
|
140
|
-
**모범**: example-spec.md §7.1~§7.4.
|
|
141
|
-
|
|
142
|
-
### §7.1 용어 사전 (고정)
|
|
143
|
-
|
|
144
|
-
도메인 어휘·약어·시스템 내 의미를 정의.
|
|
145
|
-
|
|
146
|
-
### §7.2~ 기타 공통 규격
|
|
147
|
-
|
|
148
|
-
시스템 전반 규격 (바코드 형식·라벨·공통 정책 등) 작성.
|
|
149
|
-
|
|
150
|
-
특정 §2/§3 본문 단계에서 자연스럽게 포함되는 규칙 (업로드 자료 컬럼 매핑·자료 해석 규칙 등) 은 §7 에 두지 않음 → 사용처 본문에 둠.
|
|
151
|
-
|
|
152
|
-
### 외부 자료 명세
|
|
153
|
-
|
|
154
|
-
§7 의 외부 자료 = **시스템 외부에서 정의되어 도메인 모델로 자명하지 않은** 자료 (예: 외부 시스템 송신 파일, 거래처 표준 양식, 정해진 외부 규격). 도메인 모델의 자체 엑셀 업로드·다운로드 양식은 §7 가 아님 — §8 도메인 모델 + §4/§5/§6 의 양식 매핑으로 자명.
|
|
155
|
-
|
|
156
|
-
자료별로 표가 필수. 외부 자료의 모든 컬럼·필드를 1행 1정의로 작성 (자료 자체의 객관 명세).
|
|
157
|
-
|
|
158
|
-
**자료-레벨 메타** (송신 측·자료 라이프사이클·갱신 주기·식별 키 구조 등 자료 *전체* 에 적용되는 객관 명세):
|
|
159
|
-
- 근거가 있을 때만 컬럼 표 위에 자유 작성 (불릿·단락·`라벨: 값` 형식 모두 허용).
|
|
160
|
-
- 컬럼 표 안에 억지로 담지 않음.
|
|
161
|
-
|
|
162
|
-
**컬럼 표**:
|
|
163
|
-
- 외부 자료가 여러 개 (`SAP.XML`·`D1.CSV` 등) 이면 자료별로 별도의 표 작성.
|
|
164
|
-
- 컬럼 구성은 자료 성격에 따라 LLM 이 도출. 예: `컬럼명/식별자 | 필수/선택 | 설명/규칙`.
|
|
165
|
-
- 모든 컬럼에 대한 행을 유지.
|
|
166
|
-
- "설명/규칙" 류 컬럼에는 **그 컬럼이 무엇인지** (데이터 정의: 형식·값 범위·의미·제약) 만 작성. **용도** (어디에 어떻게 쓰이는지) 는 작성 금지 — 용도·매핑은 사용처 (§4/§5/§6 의 양식 매핑) 에서 작성.
|
|
167
|
-
- 각 셀 값은 근거로 채택 가능한 자료가 있을 때만 작성. 추정·임의 작성 금지 → 근거 없으면 [OPEN].
|
|
168
|
-
- "원본 따름"·"전체 명세는 X.xlsx 참조" 등 외부 참조로 본문 정의를 대체 금지.
|
|
169
|
-
- 원본 자료에 LLM 이 접근 불가하거나 누락·모호 → 임의 처리 금지. [OPEN] 으로 표기하고 검토 패키지에서 자료·명세 정보를 요청.
|
|
170
|
-
|
|
171
|
-
## §8 도메인 모델
|
|
172
|
-
|
|
173
|
-
작성 중 자연스럽게 도출됨. 새로운 엔티티를 발견하면 추가.
|
|
174
|
-
|
|
175
|
-
**모범**: example-spec.md §8.1~§8.5.
|
|
176
|
-
|
|
177
|
-
섹션 구조: `필드:` 표 + `키/제약:` 불릿.
|
|
178
|
-
|
|
179
|
-
### 필드 표
|
|
180
|
-
|
|
181
|
-
컬럼 구성: `필드 | 타입 | 필수 | 비고`.
|
|
182
|
-
|
|
183
|
-
- 비고에는 **그 필드가 무엇인지** (데이터 정의: 형식·값 범위·의미·제약·예시) 만 작성. **용도·출처·외부 양식·외부 시스템과의 매핑** 은 작성 금지 — §4/§5/§6 의 양식 매핑·모델 매핑 / §9 의 자료 매핑에서 작성.
|
|
184
|
-
|
|
185
|
-
### 키/제약 불릿
|
|
186
|
-
|
|
187
|
-
- **식별 키**: 모든 엔티티에 `ID` 필드 (숫자, 자동 부여) 를 명시.
|
|
188
|
-
- **비즈니스 키** (코드 등): 별도로 명시. 수정 가능 (PK 가 아님).
|
|
189
|
-
- **활성/비활성 boolean** = 소프트 삭제 플래그. 마스터에는 완전 삭제가 없음. UI 의 "삭제/복구" 액션과 매핑됨 → §7.1 용어 사전에 한 줄로 명문화.
|
|
190
|
-
|
|
191
|
-
## §9 외부 인터페이스
|
|
192
|
-
|
|
193
|
-
**작성 단위** (한 §9.x 당):
|
|
194
|
-
1. 기본 정보 + 자료 매핑.
|
|
195
|
-
2. 예외 처리.
|
|
196
|
-
|
|
197
|
-
작성 중 자연스럽게 도출됨. 외부 시스템 호출을 발견하면 추가.
|
|
198
|
-
|
|
199
|
-
**모범**: example-spec.md §9.1.
|
|
200
|
-
|
|
201
|
-
### 정의
|
|
202
|
-
|
|
203
|
-
§9 = 상대 시스템 고유의 인터페이스 약속 (시스템마다 자료 매핑·방식이 달라 협상이 필요한 것).
|
|
204
|
-
|
|
205
|
-
- 포함 예시: 상대 ERP·거래처가 자체 정의한 API·EDI 규격, 외부 시스템의 webhook 페이로드.
|
|
206
|
-
- 제외 예시: 표준 프로토콜·채널 (메일 SMTP·IMAP·Exchange·Graph, FTP, 표준 OAuth 등). 시스템별 협상이 없음 → §5 자동 처리에서 트리거·처리·양식 매핑으로 처리.
|
|
207
|
-
|
|
208
|
-
### 디테일 범위
|
|
209
|
-
|
|
210
|
-
도메인 약속 레벨까지만 작성. 구체 endpoint URL·HTTP method·query 파라미터·인증 흐름 단계 (grant_type·scope·token endpoint 등) ·SDK 메소드명 등 구현 디테일은 제외 — 구현 단계에서 처리. (§2 BPMN·§4 와이어·§8 도메인 모델과 동일한 원칙).
|
|
211
|
-
|
|
212
|
-
### 본문 구조
|
|
213
|
-
|
|
214
|
-
sub-section 헤더 레벨은 `## spec.md 형식` 의 "sub-section 헤더 레벨" 표 참조.
|
|
215
|
-
|
|
216
|
-
**평문 sub-section** (§9.x 헤더 바로 아래에 위→아래 순서로 작성):
|
|
217
|
-
|
|
218
|
-
- **기본 정보** (라벨 bullet 3건):
|
|
219
|
-
- `상대 시스템: <시스템명>`.
|
|
220
|
-
- `방향: <방향 표기>`.
|
|
221
|
-
- `전송 방식: <방식>`.
|
|
222
|
-
- **관련 섹션** (자동 누적): "본문 내 참조" 절 룰 따름. 호출자 등 참조·의존 섹션 포함.
|
|
223
|
-
|
|
224
|
-
**h4 sub-section** (평문 아래에 고정 순서로 위→아래 작성):
|
|
225
|
-
|
|
226
|
-
#### 자료 매핑
|
|
227
|
-
|
|
228
|
-
표 형식 — 상대 시스템의 필드 ↔ 본 시스템의 출처.
|
|
229
|
-
|
|
230
|
-
#### 예외 처리
|
|
231
|
-
|
|
232
|
-
실패 케이스별 위험·대처·재시도 한계 작성.
|