@simplysm/sd-claude 14.0.91 → 14.0.92
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 -6
- package/claude/references/sd-simplysm14/apis/angular/README.md +59 -39
- package/claude/references/sd-simplysm14/apis/angular/controls.md +119 -186
- package/claude/references/sd-simplysm14/apis/angular/crud.md +70 -31
- package/claude/references/sd-simplysm14/apis/angular/directives.md +55 -57
- package/claude/references/sd-simplysm14/apis/angular/features.md +86 -105
- package/claude/references/sd-simplysm14/apis/angular/infra.md +48 -57
- package/claude/references/sd-simplysm14/apis/angular/layout.md +37 -47
- package/claude/references/sd-simplysm14/apis/angular/overlay.md +82 -74
- package/claude/references/sd-simplysm14/apis/angular/routing-appstructure.md +61 -50
- package/claude/references/sd-simplysm14/apis/angular/shared-data.md +74 -57
- package/claude/references/sd-simplysm14/apis/angular/sheet.md +63 -72
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-auto-update/README.md +23 -18
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-file-system/README.md +21 -19
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-intent/README.md +23 -18
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-usb-storage/README.md +72 -32
- package/claude/references/sd-simplysm14/apis/core-browser/README.md +18 -18
- package/claude/references/sd-simplysm14/apis/core-browser/dom-element.md +29 -29
- package/claude/references/sd-simplysm14/apis/core-browser/indexed-db.md +41 -41
- package/claude/references/sd-simplysm14/apis/core-common/README.md +97 -90
- package/claude/references/sd-simplysm14/apis/core-common/async-runtime.md +75 -51
- package/claude/references/sd-simplysm14/apis/core-common/collection-ext.md +81 -0
- package/claude/references/sd-simplysm14/apis/core-common/errors.md +27 -29
- package/claude/references/sd-simplysm14/apis/core-common/obj.md +44 -45
- package/claude/references/sd-simplysm14/apis/core-common/serialization.md +34 -33
- package/claude/references/sd-simplysm14/apis/core-common/value-types.md +86 -0
- package/claude/references/sd-simplysm14/apis/core-node/README.md +6 -6
- package/claude/references/sd-simplysm14/apis/core-node/consola.md +3 -0
- package/claude/references/sd-simplysm14/apis/core-node/cpx.md +2 -2
- package/claude/references/sd-simplysm14/apis/core-node/fs-watcher.md +1 -1
- package/claude/references/sd-simplysm14/apis/core-node/fsx.md +2 -2
- package/claude/references/sd-simplysm14/apis/core-node/worker.md +6 -3
- package/claude/references/sd-simplysm14/apis/excel/README.md +10 -10
- package/claude/references/sd-simplysm14/apis/excel/conditional-format.md +4 -2
- package/claude/references/sd-simplysm14/apis/excel/utils.md +1 -1
- package/claude/references/sd-simplysm14/apis/excel/workbook-worksheet.md +6 -6
- package/claude/references/sd-simplysm14/apis/lint/README.md +6 -32
- package/claude/references/sd-simplysm14/apis/lint/recommended.md +60 -0
- package/claude/references/sd-simplysm14/apis/lint/rules.md +17 -17
- package/claude/references/sd-simplysm14/apis/orm-common/README.md +15 -6
- package/claude/references/sd-simplysm14/apis/orm-common/db-context.md +68 -102
- package/claude/references/sd-simplysm14/apis/orm-common/expr.md +75 -89
- package/claude/references/sd-simplysm14/apis/orm-common/queryable.md +87 -99
- package/claude/references/sd-simplysm14/apis/orm-common/schema.md +110 -147
- package/claude/references/sd-simplysm14/apis/orm-common/types.md +48 -51
- package/claude/references/sd-simplysm14/apis/orm-node/README.md +8 -13
- package/claude/references/sd-simplysm14/apis/orm-node/db-conn.md +5 -5
- package/claude/references/sd-simplysm14/apis/sd-cli/README.md +9 -6
- package/claude/references/sd-simplysm14/apis/sd-cli/SdTsCompiler.md +9 -8
- package/claude/references/sd-simplysm14/apis/sd-cli/sd-config-types.md +23 -19
- package/claude/references/sd-simplysm14/apis/service-client/README.md +20 -12
- package/claude/references/sd-simplysm14/apis/service-client/orm.md +6 -6
- package/claude/references/sd-simplysm14/apis/service-client/transport.md +1 -1
- package/claude/references/sd-simplysm14/apis/service-common/README.md +35 -32
- package/claude/references/sd-simplysm14/apis/service-common/app-structure.md +23 -22
- package/claude/references/sd-simplysm14/apis/service-common/protocol.md +23 -23
- package/claude/references/sd-simplysm14/apis/service-server/README.md +51 -43
- package/claude/references/sd-simplysm14/apis/service-server/service-authoring.md +6 -6
- package/claude/references/sd-simplysm14/apis/service-server/transport-internals.md +31 -21
- package/claude/references/sd-simplysm14/apis/service-server/v1-legacy.md +8 -8
- package/claude/references/sd-simplysm14/apis/storage/README.md +55 -49
- package/claude/references/sd-simplysm14/manuals/client-component.md +843 -740
- package/claude/references/sd-simplysm14/manuals/client-crud.md +8 -0
- package/claude/references/sd-simplysm14/manuals/client-demo.md +6 -16
- package/claude/references/sd-simplysm14/manuals/client-shared-data.md +26 -0
- package/claude/references/sd-simplysm14/manuals/logging.md +1 -1
- package/claude/references/sd-simplysm14/manuals/orm.md +15 -1
- package/claude/rules/sd-design-rules.md +7 -0
- package/claude/sd-system-prompt.md +5 -8
- package/claude/skills/sd-debug/SKILL.md +43 -0
- package/claude/skills/sd-debug/workflow.js +390 -0
- package/claude/skills/sd-demo/SKILL.md +18 -20
- package/claude/skills/sd-dev/SKILL.md +127 -24
- package/claude/skills/sd-docs/SKILL.md +5 -3
- package/claude/skills/sd-docs/references/subagent-prompt.md +2 -3
- package/claude/skills/sd-impl/SKILL.md +18 -18
- package/claude/skills/sd-manual/SKILL.md +1 -0
- package/claude/skills/sd-review/SKILL.md +24 -18
- package/claude/skills/sd-review/workflow.js +324 -0
- package/claude/skills/sd-spec/SKILL.md +96 -679
- package/claude/skills/sd-spec/references/example-spec.md +28 -50
- package/claude/skills/sd-spec/references/format-analyze.md +232 -0
- package/claude/skills/sd-spec/references/format-design.md +248 -0
- package/claude/skills/sd-spec/workflow-analyze.js +615 -0
- package/claude/skills/sd-spec/workflow-design.js +667 -0
- package/claude/skills/sd-unpack/scripts/handlers/office_com.py +5 -1
- package/package.json +1 -1
- package/scripts/postinstall.mjs +157 -18
- package/claude/references/sd-simplysm14/apis/angular/selection-managers.md +0 -68
- package/claude/references/sd-simplysm14/apis/core-common/array-ext.md +0 -77
- package/claude/references/sd-simplysm14/apis/core-common/datetime.md +0 -86
- package/claude/skills/sd-skill/SKILL.md +0 -245
- package/claude/skills/sd-skill/scripts/run_eval.py +0 -380
|
@@ -56,6 +56,12 @@
|
|
|
56
56
|
- **`'control'`** — view 안에 임베드. 명령 영역에 저장 버튼.
|
|
57
57
|
- **`'modal'`** — 모달. `selectMode` 와 함께 쓰면 close 시 `{ selectedKeys }` 페이로드를 자동 전달.
|
|
58
58
|
|
|
59
|
+
### 편집 방식 (`inlineEdit`, 기본 `true`)
|
|
60
|
+
|
|
61
|
+
- `true` — 시트를 `<sd-form>` 으로 감싸 셀 인라인 편집 + 저장 버튼/CTRL+S + per-row 삭제 컬럼 제공.
|
|
62
|
+
- `false` — 인라인 편집 chrome 제거. 시트는 조회·선택 전용이며, 편집은 호스트가 `selectedKeys`(또는 별도 진입)로 상세/모달을 열어 처리. 등록·선택 삭제·복구·필터·페이징은 그대로 유지. 이 모드에선 `submit` 출력이 발화하지 않음.
|
|
63
|
+
- `readonly` 와 직교: `readonly=true` 면 편집 자체 불가, `readonly=false` + `inlineEdit=false` 면 편집은 가능하되 인라인이 아님(외부 모달·상세).
|
|
64
|
+
|
|
59
65
|
### 모달 선택 모드
|
|
60
66
|
|
|
61
67
|
`viewType="modal"` + `selectMode` 지정 시:
|
|
@@ -65,6 +71,8 @@
|
|
|
65
71
|
|
|
66
72
|
호출측은 `_sdModal.showAsync(...)` 결과로 `{ selectedKeys }` 페이로드 수신.
|
|
67
73
|
|
|
74
|
+
`selectMode` 는 `readonly` 와 독립 — selectMode 지정만으로는 편집이 막히지 않음. 등록·인라인 편집은 그대로 유지되고, `single` 일 때 "선택 삭제/복구" 버튼만 숨김. 읽기 전용이 필요하면 `readonly=true` 를 별도로 전달. `sd-shared-data-select-list` 가 모달을 띄울 때도 `selectMode="single"` 만 주입하므로 모달 내용은 편집 가능 상태로 유지됨.
|
|
75
|
+
|
|
68
76
|
## `sd-crud-detail`
|
|
69
77
|
|
|
70
78
|
단일 레코드 편집 화면의 표준 골격. 다음 기능을 일괄 제공: 폼 래핑, CTRL+S 단축키 저장, 저장 버튼, 모달의 "확인" 버튼 자동 처리.
|
|
@@ -10,11 +10,13 @@
|
|
|
10
10
|
| 단건 입력 폼 | `<domain>.detail.ts` |
|
|
11
11
|
| 좌 목록 + 우 단건 | `<domain>.view.ts` + `.list.ts` + `.detail.ts` |
|
|
12
12
|
| 좌 헤더 목록 + 우(헤더 정보 + 라인 시트) 마스터-라인 | `<domain>.view.ts` + `.list.ts` + `.detail.ts` — 우 라인 영역은 `.detail.ts` (헤더 단건 + 라인) |
|
|
13
|
-
| 모달
|
|
13
|
+
| 모달 전용 비-CRUD 화면 (도구·검색·설정 등) | `<domain>.modal.ts` |
|
|
14
14
|
| 프린트 양식 | `<domain>.print-template.ts` |
|
|
15
15
|
|
|
16
16
|
`<domain>` 은 화면명을 dash-case 영문으로 음역한 슬러그. 같은 도메인 폴더에 같은 역할의 파일이 2개 이상이면 `<domain>-<갈래>.<역할>.ts` 형식 사용.
|
|
17
17
|
|
|
18
|
+
동작 섹션의 `→ [화면.X] 을 모달로 띄움` 표기는 표시 방식일 뿐 파일 역할이 아님. 화면.X 가 단건 편집이면 `.detail.ts` 를 `showAsync` 로 띄우고(= 위 "단건 입력 폼" 행), 모달 전용 비-CRUD UI 일 때만 `.modal.ts`. 판별 기준은 [client-component.md "detail 과 modal 구분"](./client-component.md) 참조.
|
|
19
|
+
|
|
18
20
|
## 와이어프레임 기준
|
|
19
21
|
|
|
20
22
|
화면 정의 섹션(4번 섹션) 의 와이어프레임이 모든 시각 요소(버튼·필터·시트·탭·검색) 의 **존재·영역·순서** 결정의 1순위. 표준 슬롯이나 기본 UI(사용자 인터페이스) 와 충돌하면 와이어프레임에 맞춰 슬롯을 비우거나 컴포넌트를 교체. 줄 수·픽셀 좌표는 기준이 아님 (폼 inline 자동 wrap 등의 표현 한계 때문).
|
|
@@ -69,9 +71,9 @@ if (!result) return;
|
|
|
69
71
|
// sd-demo: 미구현 — 동작 자리
|
|
70
72
|
```
|
|
71
73
|
|
|
72
|
-
영역 한정 호출(`→ [화면.Y] 의 <영역> — 선택 전용` 등) 은 모달의 입력 시그널(`selectMode` 등) 로 전달.
|
|
74
|
+
영역 한정 호출(`→ [화면.Y] 의 <영역> — 선택 전용` 등) 은 모달의 입력 시그널(`selectMode` 등) 로 전달. spec 마커 매핑: "선택 전용"·multiselect 는 `selectMode`(`single`/`multi`) 로, "편집 가능 여부" 는 `readonly` 로 따로 전달. "선택 전용" 은 선택 목적을 뜻할 뿐 편집을 막지 않으므로(readonly 아님), 편집까지 차단하려면 `readonly=true` 를 함께 줄 것.
|
|
73
75
|
|
|
74
|
-
피호출
|
|
76
|
+
단건 편집을 모달로 띄우는 경우 피호출 화면은 `.detail.ts`(`<sd-crud-detail>` 루트, `viewType='modal'` 자동 주입)이며 모달 표시용 별도 `.modal.ts` 를 만들지 않음. 모달 전용 비-CRUD 화면(`.modal.ts`)은 `sd-crud-detail` 대신 `sd-busy-container` 등으로 자체 구성. 어느 경우든 임의 `close` output 규약을 만들지 말 것 — `_sdModal.showAsync` 의 페이로드 반환 규약만 사용.
|
|
75
77
|
|
|
76
78
|
**동반 모달**: 동작 섹션에 `→ [화면.Y] 을 모달로 띄움` 으로 등장하는 모든 모달은 같은 호출에서 함께 생성. 이미 존재하면 재사용.
|
|
77
79
|
|
|
@@ -109,19 +111,7 @@ state = signal<"draft" | "confirmed">("draft");
|
|
|
109
111
|
|
|
110
112
|
## "선택하세요." 빈 화면
|
|
111
113
|
|
|
112
|
-
list 와 detail 의 합성 view 에서 항목 미선택 빈 상태는
|
|
113
|
-
|
|
114
|
-
```html
|
|
115
|
-
<div
|
|
116
|
-
class="flex-fill tx-theme-gray-default p-xxl"
|
|
117
|
-
style="font-size: 48px; line-height: 1.5em"
|
|
118
|
-
>
|
|
119
|
-
<ng-icon [svg]="tablerArrowLeft" />
|
|
120
|
-
선택하세요.
|
|
121
|
-
</div>
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
[client-component.md "list + detail 합성"](./client-component.md) 의 단순 `<div class="flex-fill p-default">선택하세요.</div>` 코드를 위 사례로 대체.
|
|
114
|
+
list 와 detail 의 합성 view 에서 항목 미선택 빈 상태는 [client-component.md "list + detail 합성"](./client-component.md) 의 빈 상태 규약(`p-xxl` + `font-size: 48px` + `tablerArrowLeft` 아이콘) 을 그대로 따름.
|
|
125
115
|
|
|
126
116
|
## 라우팅·메뉴 따라가기
|
|
127
117
|
|
|
@@ -139,6 +139,32 @@ sharedProducts = useSharedSignal("품목");
|
|
|
139
139
|
|
|
140
140
|
- `register` 에 쓴 이름 문자열을 그대로 넘기면 `TAppSharedData` 에서 타입이 추론됨.
|
|
141
141
|
|
|
142
|
+
## 선택 컨트롤에서 관리·선택 모달 띄우기
|
|
143
|
+
|
|
144
|
+
공유데이터 선택 컨트롤(`sd-shared-data-select` · `sd-shared-data-select-list`)은 그 자리에서 해당 마스터를 관리·선택하는 모달을 여는 입력을 가짐. 마스터 목록 화면(`sd-crud-list` 기반)을 모달로 재사용해, 선택 컨트롤 옆에서 등록·수정·선택을 끝낼 수 있음.
|
|
145
|
+
|
|
146
|
+
| 입력 | 컨트롤 | 동작 |
|
|
147
|
+
| ------------ | ------------------- | -------------------------------------------------------------------------------------------------- |
|
|
148
|
+
| `[modal]` | select · select-list | 선택형 모달. 모달에 `selectMode: "single"` 과 현재 선택 키가 주입되어 열리고, 닫힘 결과의 첫 키로 선택을 갱신. |
|
|
149
|
+
| `[editModal]` | select | 관리 전용 모달(선택을 바꾸지 않음). 닫혀도 현재 선택은 그대로 유지. |
|
|
150
|
+
|
|
151
|
+
```html
|
|
152
|
+
<sd-shared-data-select-list
|
|
153
|
+
[items]="sharedRoles.items()"
|
|
154
|
+
[(selectedItem)]="selectedRole"
|
|
155
|
+
[modal]="{ type: RoleList, title: '역할', inputs: {} }"
|
|
156
|
+
>
|
|
157
|
+
<ng-template [itemOf]="sharedRoles.items()" let-item="item">{{ item.name }}</ng-template>
|
|
158
|
+
</sd-shared-data-select-list>
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
- `[modal]` 값은 `{ type, title, inputs }` (모달 호출과 동일 형태, [client-component.md](./client-component.md) 의 '모달 호출' 참조).
|
|
162
|
+
- `[modal]` 로 띄울 목록 컴포넌트는 **선택 모달 계약**을 구현해야 함:
|
|
163
|
+
- `selectMode` input + `selectedKeys` model 보유.
|
|
164
|
+
- `SdModalContentDef<SelectModalOutputResult<TKey> | undefined>` 구현 (close 페이로드로 `{ selectedKeys }` 전달).
|
|
165
|
+
- 이 계약은 `sd-crud-list` 의 모달 선택 모드와 동일 ([client-crud.md](./client-crud.md) 참조). 즉 목록 화면 하나가 일반 페이지·선택 모달 양쪽으로 재사용됨.
|
|
166
|
+
- 선택 컨트롤이 띄울 때는 항상 `selectMode: "single"` 로 주입되므로, 목록은 단건 선택 모드로 동작함.
|
|
167
|
+
|
|
142
168
|
## 지킬 것
|
|
143
169
|
|
|
144
170
|
- 항목 추가 시 세 곳(`register` · `TAppSharedData` · 인터페이스)을 모두 갱신. 하나라도 빠지면 타입 불일치 또는 미등록 데이터가 됨.
|
|
@@ -44,7 +44,7 @@ console.error("[X] 실패:", err);
|
|
|
44
44
|
|
|
45
45
|
## 환경별 셋업
|
|
46
46
|
|
|
47
|
-
- **Node 진입점(서버·CLI)**: 진입점에서 `setupConsola()` 를 1회 호출.
|
|
47
|
+
- **Node 진입점(서버·CLI)**: 진입점에서 `setupConsola()` 를 1회 호출.
|
|
48
48
|
- **Browser·Capacitor 진입점**: `setupConsola` 호출 금지 (Node 전용 API). consola 기본 reporter 가 브라우저 콘솔로 출력하며, tag·level·호출 방식의 일관성은 그대로 충족.
|
|
49
49
|
|
|
50
50
|
## 모듈-레벨 logger 주의
|
|
@@ -27,6 +27,20 @@ WHERE 와 SELECT 양쪽에서 동일 도출 산식을 쓰겠다고 `buildDerived
|
|
|
27
27
|
|
|
28
28
|
## 안티패턴
|
|
29
29
|
|
|
30
|
+
### execute 결과를 코드에서 후처리 금지
|
|
31
|
+
|
|
32
|
+
가져올 데이터는 DB단에서 최소화함. 중복 제거·필터·정렬·집계·페이징은 ORM 절로 처리하고, `execute()` 로 받은 배열을 코드에서 가공하지 않음. 전건을 메모리로 끌어온 뒤 코드에서 거르면 네트워크·직렬화·메모리 비용이 행 수에 비례해 커짐.
|
|
33
|
+
|
|
34
|
+
| 코드 후처리 (나쁜 예) | ORM 절 (좋은 예) |
|
|
35
|
+
| --------------------------------------------- | ----------------------------------------- |
|
|
36
|
+
| `(await q.execute())` 후 `.distinct()` | `.distinct().execute()` (count 시 `.distinct().wrap().count()`) |
|
|
37
|
+
| 받은 배열을 `.filter(...)` | `.where((r) => [...])` |
|
|
38
|
+
| 받은 배열을 `.sort(...)` | `.orderBy((r) => ..., "ASC")` |
|
|
39
|
+
| 받은 배열을 `.slice(page*size, ...)` | `.limit(page * size, size)` |
|
|
40
|
+
| 받은 배열로 `.reduce((sum, ...) => ...)` | `.select((r) => ({ sum: expr.sum(...) }))` (집계는 `joinSingle` 부착) |
|
|
41
|
+
|
|
42
|
+
이종 엔티티를 합쳐야 할 때도 코드 merge 대신 DB단 UNION — predicate pushdown 으로 각 소스에서 먼저 행을 줄임 ([orm-union.md](./orm-union.md)).
|
|
43
|
+
|
|
30
44
|
### SELECT 절 내부에 `expr.subquery` / `expr.exists` 사용 금지
|
|
31
45
|
|
|
32
46
|
도메인 boolean(`isCompleted`, `hasAny` 등)이나 집계(`SUM`, `COUNT`, `MAX`)가 필요하면 `joinSingle` 안에서 `from + where + select(aggregate)` 로 묶어 outer 행에 컬럼으로 부착함. SELECT 컬럼에 subquery / exists 를 넣으면 outer 행마다 inner 쿼리가 N 회 실행됨.
|
|
@@ -77,7 +91,7 @@ WHERE 와 SELECT 양쪽에서 동일 도출 산식을 쓰겠다고 `buildDerived
|
|
|
77
91
|
|
|
78
92
|
`expr.val` 은 `select` 콜백에서 리터럴 상수 컬럼을 만들 때처럼 `ExprUnit` 이 요구되는 자리에서만 사용.
|
|
79
93
|
|
|
80
|
-
##
|
|
94
|
+
## 컬럼 정책 (nullable / default)
|
|
81
95
|
|
|
82
96
|
컬럼은 `NOT NULL` 기본. `.nullable()` / `.default(...)` 는 도메인 근거가 있을 때만 사용.
|
|
83
97
|
|
|
@@ -62,6 +62,13 @@ API·함수가 단순 입력(리터럴·기본값·직접 인자)을 그대로
|
|
|
62
62
|
- 안티패턴: 문제된 항목만 skip 하고 나머지를 완료 처리 — 데이터가 부분 반영되어 정합성이 깨짐.
|
|
63
63
|
- 부분 성공을 의도적으로 허용하려면(예: 배치 중 실패분만 리포트) 사용자에게 보고 후 합의에 따름.
|
|
64
64
|
|
|
65
|
+
## 변수 명명 시
|
|
66
|
+
|
|
67
|
+
식별자(param·var·const·let) 전체가 `name` 인 것 금지. 무엇의 이름인지 드러내는 식별자로 대체. `name` 을 일부로 포함하는 식별자(`fileName`·`userName` 등)는 허용.
|
|
68
|
+
|
|
69
|
+
- 나쁜 예: `const name = ...`, `function f(name: string)`.
|
|
70
|
+
- 좋은 예: `const fileName = ...`, `function f(userName: string)`.
|
|
71
|
+
|
|
65
72
|
## 사용자에게 노출되는 알림 작성 시 심각도 분류
|
|
66
73
|
|
|
67
74
|
사용자에게 노출되는 알림(로그·토스트·다이얼로그 등)의 심각도 분류 기준:
|
|
@@ -66,8 +66,9 @@ Claude 에이전트가 반드시 지켜야 할 행동 지침.
|
|
|
66
66
|
- 결정 대상 처리 후 남은 결정 대상이 있으면 멈추지 말고 다음 결정 대상으로 진행.
|
|
67
67
|
- 질문 구조: 맥락 + 질문 + 선택지(번호) + 추천.
|
|
68
68
|
- 여러 제안이 있을 경우 모든 제안이 선택지에 포함.
|
|
69
|
-
- 질문당 결정 대상 1
|
|
70
|
-
-
|
|
69
|
+
- 질문당 결정 대상 1건 (사전 차단):
|
|
70
|
+
- 한 응답의 "옵션" 블록은 결정 대상 1건에 대해서만 출력. 결정 대상 2건 이상이면 첫 1건만 옵션 제시, 나머지는 이 응답에 적지 않음 (개수·이름 나열도 금지).
|
|
71
|
+
- 금지 표현: "확인할 결정 (N건)", "답을 한 번에", 여러 결정 대상의 번호 나열, "모두 확정", "큰 그림 채택 → 다항목 자동 진행".
|
|
71
72
|
- 답변 받은 뒤 다음 결정 대상으로 이동.
|
|
72
73
|
- `AskUserQuestion` 도구 사용 금지.
|
|
73
74
|
|
|
@@ -84,11 +85,6 @@ Claude 에이전트가 반드시 지켜야 할 행동 지침.
|
|
|
84
85
|
- 좋은 예: 묻지 않고 Grep·Read 수행 → 결과 기반 다음 행동 결정.
|
|
85
86
|
- 판정 기준: "사용자만 답할 수 있는가?" 가 아니라 "에이전트 도구로 답이 나오는가?" 를 먼저 확인. 도구로 나오면 묻지 않음.
|
|
86
87
|
|
|
87
|
-
**응답 전송 직전 자가 점검**:
|
|
88
|
-
|
|
89
|
-
- 마지막 문장에 우선순위·순서·그룹화·진행 트리거를 사용자에게 위임하는 표현이 있는가?
|
|
90
|
-
- Yes → 첫 결정 대상 1건만 옵션 제시 질문으로 재작성 후 전송.
|
|
91
|
-
|
|
92
88
|
**예시** (시나리오: 어떤 함수에 캐시 도입. 결정 대상 3건 — 라이브러리·TTL·무효화 전략):
|
|
93
89
|
|
|
94
90
|
❌ 나쁜 예 (한 번에 묶기 + 후보 누락 + 추천 없음 + 점검 생략):
|
|
@@ -170,6 +166,7 @@ X 함수에 캐시 도입 검토 중. 기존 의존성 확인 결과 lru-cache
|
|
|
170
166
|
|
|
171
167
|
- 가설1: <[결정 근거](#결정-근거) 인용> → <채택/기각>
|
|
172
168
|
- 가설2: ...
|
|
169
|
+
- 채택 가설 정합성 검증: 그 원인이 참이라 가정하면 보고된 증상이 반드시 도출되는가 + 이를 반박할 증거는 없는가 → <통과 / 실패 시 가설 재검토>
|
|
173
170
|
|
|
174
171
|
해결책:
|
|
175
172
|
|
|
@@ -222,7 +219,7 @@ X 함수에 캐시 도입 검토 중. 기존 의존성 확인 결과 lru-cache
|
|
|
222
219
|
|
|
223
220
|
### 어휘·태도
|
|
224
221
|
|
|
225
|
-
-
|
|
222
|
+
- **한국어** 원어민 수준으로 자연스럽게 응답.
|
|
226
223
|
- 통용 표현 우선. 단, 구어·속어·비속어는 통용되더라도 금지 (예: "퉁치다"→"대신하다"). LLM이 자체 조합한 신조어·합성어도 금지 — 글에서 흔히 쓰는 단어로.
|
|
227
224
|
- 직설적이고 솔직하게 응답. 형식어·완곡어·균형형 응답 금지.
|
|
228
225
|
- 결론·답·핵심 먼저, 근거·맥락·세부는 그 다음 (두괄식).
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sd-debug
|
|
3
|
+
description: 버그·실패·예외의 원인을 다관점으로 발굴하고 가설별 검증·해결책·적대검증을 거쳐 검증된 해결책을 제안하는 멀티에이전트 디버깅. Use when 버그·예외·실패의 근본 원인 분석과 해결책이 필요할 때.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# sd-debug
|
|
7
|
+
|
|
8
|
+
디버깅 요청 시 멀티에이전트 워크플로(`workflow.js`)에 원인 발굴·검증·적대검증을 위임하고, 그 결과를 메인 루프가 사용자에게 제시·결정 처리.
|
|
9
|
+
|
|
10
|
+
## 절차
|
|
11
|
+
|
|
12
|
+
1. **문제 확정** — 증상·기대동작·관찰결과를 확정.
|
|
13
|
+
- 대화 중 오류를 논의하다 진입했으면, 그때까지의 맥락(증상·에러·스택·시도·관찰)을 요약해 문제 설명으로 합성.
|
|
14
|
+
- 에러 메시지·스택·재현조건·관련 코드 경로·환경은 있으면 함께 모음(선택).
|
|
15
|
+
- 문제 설명조차 불명확하면 사용자에게 묻기.
|
|
16
|
+
|
|
17
|
+
2. **워크플로 실행** — Workflow 도구로 이 스킬의 `workflow.js` 실행:
|
|
18
|
+
- `Workflow({ scriptPath: ".claude/skills/sd-debug/workflow.js", args: <문제 설명> })`.
|
|
19
|
+
- `args` 는 1단계의 문제 설명(자연어 문자열 또는 `{ problem, error, repro, paths, env }` 객체).
|
|
20
|
+
- 관점 도출·가설 발굴·검증·해결책 탐색·적대검증·병합은 워크플로가 자율 수행.
|
|
21
|
+
|
|
22
|
+
3. **병합·우선순위화** — 반환값 `survived[]` 를 메인이 직접 정리:
|
|
23
|
+
- 같은 근본 원인의 가설이 중복되면 병합.
|
|
24
|
+
- 각 가설의 해결책 중 `passed: true` 인 것을 채택하되 `revisions`(교정)를 반영하고 `risks` 는 잔존 리스크로 보존.
|
|
25
|
+
- (검증 confidence: confirmed>uncertain) + (적대검증 통과 강도 `votes`) + (원인-증상 직접성)으로 우선순위 정렬.
|
|
26
|
+
|
|
27
|
+
4. **결과 렌더** — 행동 규칙 "문제 발생 시" 의 3블록으로 제시:
|
|
28
|
+
- 원인 가설: `survived[].hypothesis`·`cause` (+ `dropped[]` 로 기각된 가설과 사유).
|
|
29
|
+
- 검증: 각 항목의 `verdict`·`verifyReason` (uncertain 은 "근거 약함" 으로 표시).
|
|
30
|
+
- 해결책: 채택한 해결책(교정 반영) + 잔존 리스크.
|
|
31
|
+
|
|
32
|
+
5. **결정 진행** — 채택 해결책 후보가 1건 이상이면 행동 규칙 "사용자 질의 시" 의 결정 진행 모드로 전환(우선순위 순). 사용자가 고른 해결책만 실제 수정에 착수.
|
|
33
|
+
|
|
34
|
+
6. **미해결 보고** — `summary.noSolution` 이면(검증된 해결책 0건) `survived[]` 의 가설·탈락 사유와 `dropped[]` 를 제시해 다음 수동 디버깅의 출발점으로 삼게 함.
|
|
35
|
+
|
|
36
|
+
## 워크플로 반환 구조
|
|
37
|
+
|
|
38
|
+
- `survived[]`: `{ hypothesis, cause, perspective, verdict, verifyReason, solutions }` — 검증 통과 가설.
|
|
39
|
+
- `solutions[]`: `{ approach, mechanism, changeScope, passed, vetoed, votes, risks, revisions }` — `passed: true` 가 채택 후보.
|
|
40
|
+
- `dropped[]`: `{ hypothesis, cause, reason }` — 검증에서 기각된 가설과 사유.
|
|
41
|
+
- `summary`: 집계(가설 수·confirmed/uncertain·dropped·solutionsPassed·noSolution).
|
|
42
|
+
- `perspectives`: 사용한 의심 관점 목록.
|
|
43
|
+
- `problem`: 입력 문제 요약.
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
export const meta = {
|
|
2
|
+
name: "sd-debug",
|
|
3
|
+
description:
|
|
4
|
+
"버그·실패·예외의 원인 가설을 다관점으로 발굴하고 가설별로 검증→해결책→적대검증을 거쳐 검증된 해결책을 구조화 보고하는 멀티에이전트 디버깅",
|
|
5
|
+
phases: [
|
|
6
|
+
{ title: "Hypothesize", detail: "관점 동적 생성 + 관점별 병렬 추출 + 자유탐색 + 의미 중복제거" },
|
|
7
|
+
{ title: "Verify", detail: "가설별 검증→해결책 통합 (명백 오류·근거 전무 기각, 통과 시 근본 우선 해결책 ≤2, 무배리어)" },
|
|
8
|
+
{ title: "Adversarial", detail: "가설+해결책 쌍별 4관점 적대검증 veto+다수결 (무배리어)" },
|
|
9
|
+
],
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// ── 입력 ───────────────────────────────────────────────────────
|
|
13
|
+
// args: 문제 설명(필수). 에러·스택·재현조건·코드경로·환경은 선택.
|
|
14
|
+
// 대화 맥락에서 호출되면 메인 루프가 위 정보를 요약해 args 로 전달.
|
|
15
|
+
if (args == null || (typeof args === "string" && args.trim() === "")) {
|
|
16
|
+
throw new Error(
|
|
17
|
+
"디버깅할 문제 설명을 args 로 전달하세요 (증상·기대동작·관찰결과 등; 에러/재현조건/코드경로는 선택).",
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
const problem = typeof args === "string" ? args : JSON.stringify(args);
|
|
21
|
+
|
|
22
|
+
// ── 공통 원칙(모든 단계 주입) ──────────────────────────────────
|
|
23
|
+
const PRINCIPLES = `
|
|
24
|
+
디버깅 원칙:
|
|
25
|
+
- 모든 판정은 실제 코드/설정을 Read 하여 확인. 근거 없는 추측·일반론 금지(추측은 '가설'로만 등록, 사실로 단정 금지).
|
|
26
|
+
- 현재 워킹트리만 기준. git log/diff/show/blame 등 과거 조회 금지. .back/ 및 .gitignore 등재 경로(node_modules·dist·.tmp 등) 읽지 말 것.
|
|
27
|
+
- 결측(null/undefined)은 결측대로 다룰 것. 빈 값을 추측으로 채우지 말 것.
|
|
28
|
+
- 입력 정보가 부족하면 Grep/Glob/Read 로 코드베이스를 직접 조사해 보강.
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
// ── fail-fast 가드 ─────────────────────────────────────────────
|
|
32
|
+
// parallel/pipeline 배리어 직후 호출. 결과에 null(에이전트 reject/스킵)이 하나라도 있으면
|
|
33
|
+
// 부분 결과로 진행하지 않고 즉시 throw. 정상이지만 빈 결과(빈 배열 등)는 null 이 아니라 통과.
|
|
34
|
+
function assertNoFailures(results, stage, labels) {
|
|
35
|
+
const failed = results.flatMap((r, i) => (r ? [] : [labels?.[i] ?? `#${i}`]));
|
|
36
|
+
if (failed.length > 0) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`[${stage}] 에이전트 ${failed.length}/${results.length}건 실행 실패(null) — 부분 결과로 진행 금지(fail-fast). 실패: ${failed.join(", ")}. resume 로 재실행하면 성공분은 캐시됩니다.`,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ── 적대검증 관점 4개 ──────────────────────────────────────────
|
|
44
|
+
const LENSES = [
|
|
45
|
+
{ key: "causal", title: "인과", focus: "해결책이 이 가설의 원인을 실제로 제거하는가. 원인-증상 인과가 성립하는가." },
|
|
46
|
+
{
|
|
47
|
+
key: "regression",
|
|
48
|
+
title: "회귀·부작용",
|
|
49
|
+
focus:
|
|
50
|
+
"해결책이 새 버그·룰 위반·엣지케이스(결측 null/undefined·동시성/트랜잭션·soft delete 동명 레코드·권한 분기·타입/스키마 제약)를 유발하는가.",
|
|
51
|
+
},
|
|
52
|
+
{ key: "evidence", title: "증거 정합", focus: "가설의 근거가 실제 코드/스택과 일치하는가. 오인·과장은 없는가." },
|
|
53
|
+
{ key: "alternative", title: "대안 원인", focus: "이 가설 말고 다른 원인이 진짜일 가능성은 없는가." },
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
// ── 의심 범주 예시(단일 소스 — 스키마 설명·관점 도출 프롬프트가 공유) ──
|
|
57
|
+
const PERSPECTIVE_EXAMPLES =
|
|
58
|
+
"동시성·타이밍, 데이터·결측, 타입·계약, 로직·경계조건, 외부의존·환경·설정, 상태·생명주기";
|
|
59
|
+
|
|
60
|
+
// ── 스키마 ─────────────────────────────────────────────────────
|
|
61
|
+
const PERSPECTIVES_SCHEMA = {
|
|
62
|
+
type: "object",
|
|
63
|
+
additionalProperties: false,
|
|
64
|
+
required: ["perspectives", "notes"],
|
|
65
|
+
properties: {
|
|
66
|
+
perspectives: {
|
|
67
|
+
type: "array",
|
|
68
|
+
items: {
|
|
69
|
+
type: "object",
|
|
70
|
+
additionalProperties: false,
|
|
71
|
+
required: ["key", "title", "focus"],
|
|
72
|
+
properties: {
|
|
73
|
+
key: { type: "string" },
|
|
74
|
+
title: { type: "string", description: `의심 범주 이름(예: ${PERSPECTIVE_EXAMPLES})` },
|
|
75
|
+
focus: { type: "string", description: "이 관점이 의심하는 구체 지점" },
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
notes: { type: "string", description: "증상에서 이 관점들을 고른 근거" },
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const HYPOTHESES_SCHEMA = {
|
|
84
|
+
type: "object",
|
|
85
|
+
additionalProperties: false,
|
|
86
|
+
required: ["hypotheses"],
|
|
87
|
+
properties: {
|
|
88
|
+
hypotheses: {
|
|
89
|
+
type: "array",
|
|
90
|
+
items: {
|
|
91
|
+
type: "object",
|
|
92
|
+
additionalProperties: false,
|
|
93
|
+
required: ["title", "cause", "perspective", "evidenceExpected", "refuteSignal"],
|
|
94
|
+
properties: {
|
|
95
|
+
title: { type: "string", description: "가설 한 줄 요약" },
|
|
96
|
+
cause: { type: "string", description: "원인 가설 상세(무엇이 어떻게 증상을 일으키는가)" },
|
|
97
|
+
perspective: { type: "string", description: "이 가설이 나온 관점(자유탐색이면 'free')" },
|
|
98
|
+
evidenceExpected: { type: "string", description: "이 가설이 맞다면 코드/로그에서 보일 근거" },
|
|
99
|
+
refuteSignal: { type: "string", description: "이 가설이 틀렸다면 보일 반증 신호" },
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const VERIFY_SOLVE_SCHEMA = {
|
|
107
|
+
type: "object",
|
|
108
|
+
additionalProperties: false,
|
|
109
|
+
required: ["verdict", "reason", "refinedCause", "solutions"],
|
|
110
|
+
properties: {
|
|
111
|
+
verdict: {
|
|
112
|
+
type: "string",
|
|
113
|
+
enum: ["confirmed", "uncertain", "rejected"],
|
|
114
|
+
description: "confirmed=근거 확인, uncertain=일부라도 코드로 뒷받침(통과), rejected=명백히 틀림 또는 근거 전무+반증 우세",
|
|
115
|
+
},
|
|
116
|
+
reason: { type: "string", description: "코드를 Read 해 확인한 판정 근거(근거 인용)" },
|
|
117
|
+
refinedCause: { type: "string", description: "검증으로 구체화된 원인(통과 시). rejected 면 빈 문자열" },
|
|
118
|
+
solutions: {
|
|
119
|
+
type: "array",
|
|
120
|
+
description: "통과(confirmed/uncertain) 시 근본 원인 직접 제거 정도가 높은 순으로 정렬한 해결책 후보(최대 2). rejected 면 빈 배열",
|
|
121
|
+
items: {
|
|
122
|
+
type: "object",
|
|
123
|
+
additionalProperties: false,
|
|
124
|
+
required: ["approach", "mechanism", "changeScope"],
|
|
125
|
+
properties: {
|
|
126
|
+
approach: { type: "string", description: "해결 접근 한 줄" },
|
|
127
|
+
mechanism: { type: "string", description: "이 접근이 원인을 어떻게 제거하는가" },
|
|
128
|
+
changeScope: { type: "string", description: "대략의 수정 범위(파일·함수)" },
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const ADVERSARIAL_SCHEMA = {
|
|
136
|
+
type: "object",
|
|
137
|
+
additionalProperties: false,
|
|
138
|
+
required: ["lenses"],
|
|
139
|
+
properties: {
|
|
140
|
+
lenses: {
|
|
141
|
+
type: "array",
|
|
142
|
+
minItems: LENSES.length,
|
|
143
|
+
description: "각 적대검증 관점의 판정 (LENSES 4개 전부, 관점마다 1개 항목)",
|
|
144
|
+
items: {
|
|
145
|
+
type: "object",
|
|
146
|
+
additionalProperties: false,
|
|
147
|
+
required: ["lens", "pass", "critical", "reason", "revisedNote"],
|
|
148
|
+
properties: {
|
|
149
|
+
lens: { type: "string", description: "이 판정의 관점 이름" },
|
|
150
|
+
pass: { type: "boolean", description: "이 관점에서 해결책이 통과하는가(true=문제없음)" },
|
|
151
|
+
critical: {
|
|
152
|
+
type: "boolean",
|
|
153
|
+
description: "발견한 결함이 치명적인가(회귀 유발·인과 불성립은 true). pass=true 면 false",
|
|
154
|
+
},
|
|
155
|
+
reason: { type: "string", description: "코드 확인 기반 판정 근거" },
|
|
156
|
+
revisedNote: { type: "string", description: "결함 있으면 교정 제안. 없으면 빈 문자열" },
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// ── 단계별 프롬프트 ────────────────────────────────────────────
|
|
164
|
+
const verifySolvePrompt = (h) => `문제: ${problem}
|
|
165
|
+
|
|
166
|
+
${PRINCIPLES}
|
|
167
|
+
|
|
168
|
+
너의 일: 아래 원인 가설을 실제 코드를 Read 해 검증하고(관대 기준), 통과하면 같은 코드 근거 위에서 해결책까지 도출하라.
|
|
169
|
+
|
|
170
|
+
1) 검증:
|
|
171
|
+
- confirmed: 코드에서 근거를 확인함.
|
|
172
|
+
- uncertain: 근거가 부분적·애매하지만 코드에서 일부라도 뒷받침됨 — 통과시킴(이후 적대검증이 거른다).
|
|
173
|
+
- rejected: 코드로 보아 '명백히 틀린' 경우, 또는 코드에서 근거가 전혀 확인되지 않고 반증신호가 우세한 경우. 단 코드에서 일부라도 뒷받침되면 기각하지 말고 uncertain(진짜 원인이면 코드에 근거가 남으므로, '근거 전무'만 기각해 누락을 막는다).
|
|
174
|
+
- 판정 근거(reason)에 확인한 코드 위치·내용을 인용. 통과면 refinedCause 에 구체화된 원인을 적을 것.
|
|
175
|
+
|
|
176
|
+
2) 해결책(rejected 가 아닐 때만):
|
|
177
|
+
- 검증하며 확인한 코드 근거 위에서, 근본 원인을 가장 직접적으로 제거하는 정도가 높은 순으로 정렬해 해결책 후보를 최대 2개, 서로 접근이 다르게.
|
|
178
|
+
- 각 후보에 approach·mechanism(원인을 어떻게 제거하나)·changeScope(수정 범위) 채움.
|
|
179
|
+
- 과도한 설계(over-engineering)·증상만 가리는 임시방편은 피할 것.
|
|
180
|
+
- rejected 면 solutions 를 빈 배열로 둘 것.
|
|
181
|
+
|
|
182
|
+
가설:
|
|
183
|
+
- 제목: ${h.title}
|
|
184
|
+
- 원인: ${h.cause}
|
|
185
|
+
- 관점: ${h.perspective}
|
|
186
|
+
- 예상 근거: ${h.evidenceExpected}
|
|
187
|
+
- 반증 신호: ${h.refuteSignal}`;
|
|
188
|
+
|
|
189
|
+
const adversarialPrompt = (h, verdict, sol) => `문제: ${problem}
|
|
190
|
+
|
|
191
|
+
${PRINCIPLES}
|
|
192
|
+
|
|
193
|
+
너의 일: 아래 (원인 가설 + 해결책) 쌍을 다음 4개 관점 각각에서 적대적으로 공격하라. 기본 입장은 '이 해결책은 결함이 있다'로 두고 약점을 찾을 것. 각 관점을 서로 끌려가지 말고 독립적으로 판정해 lenses 배열로 반환(관점마다 1개 항목).
|
|
194
|
+
|
|
195
|
+
관점:
|
|
196
|
+
${LENSES.map((l) => `- ${l.title}: ${l.focus}`).join("\n")}
|
|
197
|
+
|
|
198
|
+
각 관점 판정:
|
|
199
|
+
- pass=false 로 둘 결함을 찾으면 reason 에 코드 근거와 함께 적고, 치명적(회귀 유발·인과 불성립 등)이면 critical=true. 교정안이 있으면 revisedNote 에.
|
|
200
|
+
- 그 관점에서 결함이 없으면 pass=true, critical=false, revisedNote="".
|
|
201
|
+
|
|
202
|
+
원인 가설: ${h.title} — ${verdict?.refinedCause || h.cause}
|
|
203
|
+
해결책: ${sol.approach} / ${sol.mechanism} (수정 범위: ${sol.changeScope})`;
|
|
204
|
+
|
|
205
|
+
// ── [Hypothesize] 관점 생성 → 관점별 병렬 추출 + 자유탐색 → 중복제거 ──
|
|
206
|
+
phase("Hypothesize");
|
|
207
|
+
|
|
208
|
+
const perspectivePlan = await agent(
|
|
209
|
+
`문제: ${problem}
|
|
210
|
+
|
|
211
|
+
${PRINCIPLES}
|
|
212
|
+
|
|
213
|
+
너의 일 (디버깅 1단계 — 의심 관점 도출). 위 증상을 보고, 원인을 찾을 때 서로 겹치지 않는 '의심 관점(범주)'을 도출하라.
|
|
214
|
+
- 증상 성격에 맞춰 동적으로 고를 것. 예시 범주: ${PERSPECTIVE_EXAMPLES}. (예시일 뿐 — 증상에 맞게 가감)
|
|
215
|
+
- 각 관점에 key, title, focus(이 관점이 의심하는 구체 지점)를 채울 것.
|
|
216
|
+
- 보통 3~6개. 증상을 좁게 가리키면 적게, 막연하면 넓게.
|
|
217
|
+
- 관점 선정 근거를 notes 에.`,
|
|
218
|
+
{ label: "perspectives", phase: "Hypothesize", schema: PERSPECTIVES_SCHEMA },
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
if (!perspectivePlan) throw new Error("[Hypothesize/perspectives] 관점 도출 에이전트 실행 실패(null) — 중단.");
|
|
222
|
+
const PERSPECTIVES = (perspectivePlan.perspectives ?? []).filter(Boolean);
|
|
223
|
+
if (PERSPECTIVES.length === 0) throw new Error("의심 관점을 도출하지 못했습니다.");
|
|
224
|
+
log(`관점 ${PERSPECTIVES.length}개: ${PERSPECTIVES.map((p) => p.title).join(", ")}`);
|
|
225
|
+
|
|
226
|
+
const extractTasks = PERSPECTIVES.map((p) => () =>
|
|
227
|
+
agent(
|
|
228
|
+
`문제: ${problem}
|
|
229
|
+
|
|
230
|
+
${PRINCIPLES}
|
|
231
|
+
|
|
232
|
+
너의 일: 오직 '${p.title}' 관점에서만 원인 가설을 발굴하라. 이 관점의 의심 지점: ${p.focus}
|
|
233
|
+
- 코드베이스를 Grep/Glob/Read 로 직접 조사해 이 관점에 해당하는 원인 후보를 가능한 한 빠짐없이 뽑을 것(재현율 우선).
|
|
234
|
+
- 다른 관점의 원인은 무시(중복은 이후 단계가 정리).
|
|
235
|
+
- 각 가설에 title·cause·perspective('${p.title}')·evidenceExpected·refuteSignal 을 채울 것.
|
|
236
|
+
- 해당 관점에서 원인이 안 보이면 hypotheses 를 비울 것(억지 생성 금지).`,
|
|
237
|
+
{ label: `extract:${p.key}`, phase: "Hypothesize", schema: HYPOTHESES_SCHEMA },
|
|
238
|
+
),
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
const freeTask = () =>
|
|
242
|
+
agent(
|
|
243
|
+
`문제: ${problem}
|
|
244
|
+
|
|
245
|
+
${PRINCIPLES}
|
|
246
|
+
|
|
247
|
+
너의 일: 어떤 정해진 관점에도 매이지 말고 자유롭게 원인 가설을 발굴하라(사각지대 안전망).
|
|
248
|
+
- 앞서 정한 관점 목록(${PERSPECTIVES.map((p) => p.title).join(", ")})에 잘 안 들어가는 원인일수록 가치가 크다.
|
|
249
|
+
- 코드베이스를 직접 조사해 근거 기반으로 뽑을 것. 각 가설에 title·cause·perspective('free')·evidenceExpected·refuteSignal 채움.`,
|
|
250
|
+
{ label: "extract:free", phase: "Hypothesize", schema: HYPOTHESES_SCHEMA },
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
const extracted = await parallel([...extractTasks, freeTask]);
|
|
254
|
+
assertNoFailures(extracted, "Hypothesize/extract", [...PERSPECTIVES.map((p) => `extract:${p.key}`), "extract:free"]);
|
|
255
|
+
const rawHypotheses = extracted.flatMap((r) => r.hypotheses ?? []);
|
|
256
|
+
if (rawHypotheses.length === 0) {
|
|
257
|
+
throw new Error("원인 가설을 하나도 도출하지 못했습니다. 문제 설명을 보강해 재호출하세요.");
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const dedup = await agent(
|
|
261
|
+
`아래는 여러 관점에서 발굴된 원인 가설들이다(JSON). 의미 기준으로 병합·중복제거하라.
|
|
262
|
+
|
|
263
|
+
병합 규칙:
|
|
264
|
+
- '같은 근본 원인'을 가리키는 가설끼리만 하나로 합칠 것(표현만 다른 중복 제거). 근본 원인이 다르면 절대 합치지 말 것(서로 다른 원인을 뭉개면 검증에서 통째 탈락한다).
|
|
265
|
+
- 병합 시 evidenceExpected·refuteSignal 은 합쳐 보존. perspective 는 합쳐진 관점들을 표기.
|
|
266
|
+
- 개수를 인위적으로 줄이지 말 것(재현율 우선). 진짜 중복만 제거.
|
|
267
|
+
|
|
268
|
+
가설들(JSON):
|
|
269
|
+
${JSON.stringify(rawHypotheses)}`,
|
|
270
|
+
{ label: "dedup", phase: "Hypothesize", schema: HYPOTHESES_SCHEMA },
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
if (!dedup) throw new Error("[Hypothesize/dedup] 중복제거 에이전트 실행 실패(null) — 중단.");
|
|
274
|
+
const HYPOTHESES = (dedup.hypotheses ?? []).filter(Boolean);
|
|
275
|
+
if (HYPOTHESES.length === 0) throw new Error("중복제거 후 남은 가설이 없습니다.");
|
|
276
|
+
log(`가설 ${HYPOTHESES.length}개 (원시 ${rawHypotheses.length}개에서 병합)`);
|
|
277
|
+
|
|
278
|
+
// ── 비용 상한: 곱셈이 시스템 상한(1000) 근접 시 사전 throw ───────
|
|
279
|
+
// 가설은 안 자른다(누락 방지). 전체 곱셈이 상한 근접하면 통째 throw 로 보고.
|
|
280
|
+
const EST_PER_HYP = 1 /*verify+solve 통합*/ + 2 /*adversarial: 해결책 후보당 1(4관점 통합)*/;
|
|
281
|
+
const EST_AGENTS = HYPOTHESES.length * EST_PER_HYP;
|
|
282
|
+
const SAFE_AGENT_LIMIT = 900;
|
|
283
|
+
if (EST_AGENTS > SAFE_AGENT_LIMIT) {
|
|
284
|
+
throw new Error(
|
|
285
|
+
`가설 ${HYPOTHESES.length}개 × 단계 ≈ ${EST_AGENTS} 에이전트로 시스템 상한(1000) 근접. 가설을 자르면 중요한 원인이 누락될 수 있으니, 문제 설명을 더 좁혀 재호출하세요.`,
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// ── [Verify(+Solve)]→[Adversarial] 가설별 파이프라인 (무배리어) ──
|
|
290
|
+
phase("Verify");
|
|
291
|
+
const piped = await pipeline(
|
|
292
|
+
HYPOTHESES,
|
|
293
|
+
// stage1: 검증+해결책 통합 (한 에이전트가 검증하며 읽은 코드로 바로 해결책까지)
|
|
294
|
+
(h, _h, i) =>
|
|
295
|
+
agent(verifySolvePrompt(h), { label: `diagnose:${i}`, phase: "Verify", schema: VERIFY_SOLVE_SCHEMA }).then((v) => ({
|
|
296
|
+
index: i,
|
|
297
|
+
hypothesis: h,
|
|
298
|
+
verdict: { verdict: v.verdict, reason: v.reason, refinedCause: v.refinedCause },
|
|
299
|
+
solutions: v.verdict === "rejected" ? [] : (v.solutions ?? []).slice(0, 2),
|
|
300
|
+
})),
|
|
301
|
+
// stage2: 적대검증 (해결책별 fan-out, 4관점은 단일 에이전트가 1회 통합 판정)
|
|
302
|
+
(prev, h, i) => {
|
|
303
|
+
// stage1 실패(null)는 복구하지 말고 그대로 전파 → line 320 assertNoFailures(piped) 가 포착(fail-fast).
|
|
304
|
+
if (!prev) return null;
|
|
305
|
+
// 여기부터 prev 는 비-null 보장 → verdict 보존됨(정상 무해결책만 빈 judged 로 통과).
|
|
306
|
+
if (!prev.solutions || prev.solutions.length === 0) return Promise.resolve({ ...prev, judged: [] });
|
|
307
|
+
return parallel(
|
|
308
|
+
prev.solutions.map((sol, si) => () =>
|
|
309
|
+
agent(adversarialPrompt(h, prev.verdict, sol), {
|
|
310
|
+
label: `adv:${i}:${si}`,
|
|
311
|
+
phase: "Adversarial",
|
|
312
|
+
schema: ADVERSARIAL_SCHEMA,
|
|
313
|
+
}).then((v) => ({ solution: sol, lensResults: (v?.lenses ?? []).filter(Boolean) })),
|
|
314
|
+
),
|
|
315
|
+
).then((judged) => {
|
|
316
|
+
assertNoFailures(judged, `Adversarial(가설#${i})`, prev.solutions.map((_, si) => `adv:${i}:${si}`));
|
|
317
|
+
return { ...prev, judged };
|
|
318
|
+
});
|
|
319
|
+
},
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
// ── 집계: 검증 기각 분리 + 해결책 veto+다수결 판정 ──────────────
|
|
323
|
+
// fail-fast: stage1(diagnose) reject 또는 stage2(adversarial) 실패로 null 이 된 가설이 하나라도 있으면 중단.
|
|
324
|
+
assertNoFailures(piped, "Verify/Adversarial", HYPOTHESES.map((h) => h.title));
|
|
325
|
+
const results = piped;
|
|
326
|
+
const droppedH = results.filter((x) => x.verdict?.verdict === "rejected");
|
|
327
|
+
const survivedH = results.filter((x) => x.verdict && x.verdict.verdict !== "rejected");
|
|
328
|
+
|
|
329
|
+
function judgeSolution(j) {
|
|
330
|
+
const lr = (j.lensResults ?? []).filter(Boolean);
|
|
331
|
+
const veto = lr.some((l) => l.critical === true && l.pass === false); // 치명결함 1표면 기각
|
|
332
|
+
const passVotes = lr.filter((l) => l.pass === true).length;
|
|
333
|
+
const total = lr.length;
|
|
334
|
+
const passed = !veto && total > 0 && passVotes > total / 2; // veto 없으면 통과 다수결
|
|
335
|
+
const risks = lr.filter((l) => l.pass === false).map((l) => `[${l.lens}] ${l.reason}`);
|
|
336
|
+
const revisions = lr.filter((l) => l.revisedNote && l.revisedNote.trim() !== "").map((l) => `[${l.lens}] ${l.revisedNote}`);
|
|
337
|
+
return { passed, veto, passVotes, total, risks, revisions };
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const survivedDetailed = survivedH.map((x) => {
|
|
341
|
+
const sols = (x.judged ?? []).map((j) => ({ solution: j.solution, ...judgeSolution(j) }));
|
|
342
|
+
return {
|
|
343
|
+
hypothesis: x.hypothesis,
|
|
344
|
+
verdict: x.verdict.verdict,
|
|
345
|
+
verifyReason: x.verdict.reason,
|
|
346
|
+
refinedCause: x.verdict.refinedCause,
|
|
347
|
+
solutions: sols,
|
|
348
|
+
passedSolutions: sols.filter((s) => s.passed),
|
|
349
|
+
};
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
const solutionsPassed = survivedDetailed.reduce((n, x) => n + x.passedSolutions.length, 0);
|
|
353
|
+
const noSolution = solutionsPassed === 0;
|
|
354
|
+
|
|
355
|
+
// 병합·우선순위화·렌더·결정 진행은 호출측(SKILL.md/메인 루프)이 수행.
|
|
356
|
+
// 워크플로는 검증·적대검증 완료된 가설+해결책(평탄화) + 기각 가설을 구조화 반환.
|
|
357
|
+
return {
|
|
358
|
+
problem,
|
|
359
|
+
perspectives: PERSPECTIVES.map((p) => p.title),
|
|
360
|
+
summary: {
|
|
361
|
+
hypotheses: HYPOTHESES.length,
|
|
362
|
+
confirmed: survivedH.filter((x) => x.verdict.verdict === "confirmed").length,
|
|
363
|
+
uncertain: survivedH.filter((x) => x.verdict.verdict === "uncertain").length,
|
|
364
|
+
dropped: droppedH.length,
|
|
365
|
+
solutionsPassed,
|
|
366
|
+
noSolution,
|
|
367
|
+
},
|
|
368
|
+
survived: survivedDetailed.map((x) => ({
|
|
369
|
+
hypothesis: x.hypothesis.title,
|
|
370
|
+
cause: x.refinedCause || x.hypothesis.cause,
|
|
371
|
+
perspective: x.hypothesis.perspective,
|
|
372
|
+
verdict: x.verdict,
|
|
373
|
+
verifyReason: x.verifyReason,
|
|
374
|
+
solutions: x.solutions.map((s) => ({
|
|
375
|
+
approach: s.solution.approach,
|
|
376
|
+
mechanism: s.solution.mechanism,
|
|
377
|
+
changeScope: s.solution.changeScope,
|
|
378
|
+
passed: s.passed,
|
|
379
|
+
vetoed: s.veto,
|
|
380
|
+
votes: `${s.passVotes}/${s.total}`,
|
|
381
|
+
risks: s.risks,
|
|
382
|
+
revisions: s.revisions,
|
|
383
|
+
})),
|
|
384
|
+
})),
|
|
385
|
+
dropped: droppedH.map((x) => ({
|
|
386
|
+
hypothesis: x.hypothesis.title,
|
|
387
|
+
cause: x.hypothesis.cause,
|
|
388
|
+
reason: x.verdict.reason,
|
|
389
|
+
})),
|
|
390
|
+
};
|