genetics-mcp 2.18.3 → 2.19.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genetics-mcp",
3
- "version": "2.18.3",
3
+ "version": "2.19.2",
4
4
  "description": "문서 포털 발행·세션 이식·사례 기억 MCP — 데이터를 주면 규격 문서가 나온다 (짝인 스킬 동봉)",
5
5
  "author": "주식회사 LIM (LIM Inc.)",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -20,6 +20,7 @@
20
20
  "scripts": {
21
21
  "test": "node smoke.mjs && node notify.test.mjs && bash ../cell/tests/cellcycle.sh",
22
22
  "prepack": "find skills -name __pycache__ -type d -prune -exec rm -rf {} + 2>/dev/null; bash ../cell/build-all.sh",
23
- "prepublishOnly": "npm test"
23
+ "prepublishOnly": "npm test",
24
+ "release": "bash release.sh"
24
25
  }
25
26
  }
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "scripts",
3
+ "lockfileVersion": 3,
4
+ "requires": true,
5
+ "packages": {
6
+ "": {
7
+ "dependencies": {
8
+ "playwright-core": "^1.62.1"
9
+ }
10
+ },
11
+ "node_modules/playwright-core": {
12
+ "version": "1.62.1",
13
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.62.1.tgz",
14
+ "integrity": "sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==",
15
+ "license": "Apache-2.0",
16
+ "bin": {
17
+ "playwright-core": "cli.js"
18
+ },
19
+ "engines": {
20
+ "node": ">=20"
21
+ }
22
+ }
23
+ }
24
+ }
@@ -155,10 +155,10 @@ mail({tenant: "<소속처 slug>", to: "brain", body: "…"}) # 두뇌
155
155
  SendMessage({to: "app-2f", message: "…"}) # 지름길 — 이 기계 Claude Code 끼리만
156
156
  ```
157
157
 
158
- 받은 것에 답할 때 `mail` 은 `thread` 를 쓰고, SendMessage 지름길을 탔으면 그 메시지의 `from` 을
159
- 그대로 `to` 에 씁니다.
158
+ 받은 것에 답할 때 `mail` 은 받은 글의 `· 실:` 줄에 찍힌 32자리 값을 `thread` 에 그대로 쓰고(그래야 같은 실로
159
+ 이어집니다), SendMessage 지름길을 탔으면 그 메시지의 `from` 을 그대로 `to` 에 씁니다.
160
160
 
161
- `mail` 의 `to` 에 올 수 있는 것은 **셋뿐**입니다. **지어내지 마세요** — 셋 중 하나가 아니면
161
+ `mail` 의 `to` 에 올 수 있는 것은 **넷뿐**입니다. **지어내지 마세요** — 넷 중 하나가 아니면
162
162
  사용자에게 묻습니다. 소속처 값(slug — 이름도 받는다)을 모르면 `list_my_tenants` 로 먼저 확인합니다.
163
163
 
164
164
  | `to` | 언제 쓰나 |
@@ -166,6 +166,7 @@ SendMessage({to: "app-2f", message: "…"}) # 지름길 —
166
166
  | **세션 uuid** | **확실히 닿아야 할 때.** 하네스가 재사용하지 않는 유일한 열쇠입니다 |
167
167
  | 세포 이름 (`app-2f`) | 이 기계 안에서 사람이 부를 때. **불안정한 별칭입니다 — 아래** |
168
168
  | `brain` | 두뇌에게. 주소가 필요 없습니다 |
169
+ | 담당의 이메일 | 사람에게. **그 소속처 담당만** 받고 포털 알림이 남습니다. 모르면 사용자에게 묻습니다 |
169
170
 
170
171
  **이름은 유일하지 않습니다.** 하네스가 짧은 이름을 프로젝트·시간을 넘어 재사용하므로 **같은
171
172
  이름을 쥔 산 세션이 따로 있을 수 있습니다.** 그러면 나중에 등록하는 쪽은 이름 없이(받기만) 서고,
@@ -210,7 +211,7 @@ HEAD 사본 검사)은 `genetics-sync` 스킬이 정본입니다.
210
211
  | 세포는 이 기계만 본다 | 다른 기계를 **보는** 것은 `world`, **말 거는** 것은 `mail` |
211
212
  | `ListAgents` 에 없어서 안 닿는다 했다 | 그 목록은 Claude Code 만 본다. 발견은 `agents`, 말걸기는 `mail` |
212
213
  | `SendMessage` 로 Cursor·다른 기계에 보냈다 | **안 간다.** 지름길은 이 기계 Claude Code 끼리만 — `mail` 을 쓴다 |
213
- | 왕복이 길어진다 | 상한이 있다 — **사람이 낀 실 5회, 기계끼리 3회.** 넘으면 사람에게 올라간다 |
214
+ | 왕복이 길어진다 | 한 실에 상한이 있다 — **왕복(보내고 답하면 1왕복) 사람이 낀 실 5회·기계끼리 3회, 답 없이 연달아 3통.** 넘으면 409 로 거절되고 **사람에게 저절로 알리는 기능은 아직 없다** — 사람의 판단이 필요하면 이 세션의 사용자에게 말하거나 담당의 이메일로 thread 없이 보낸다 |
214
215
  | 이름으로 부쳤는데 딴 세션이 받았다 | 같은 이름의 **산 세션**이 있다. 이름은 별칭이니 **uuid** 로 부친다 |
215
216
  | 보냈는데 답이 없다 | 받는 쪽 꼬리는 **그쪽이 도구를 불러야** 붙는다 — 폴 60초 + 그 간격이다(실측 47·39초) |
216
217
  | `status` 에 내 줄이 표시되지 않는다 | 내 uuid 는 전사 파일 이름에서 얻는다 |
@@ -0,0 +1,223 @@
1
+ ---
2
+ name: genetics-define
3
+ description: |
4
+ 일감의 항목 하나를 **누가 짜도 같은 물건이 나오는 결정 묶음**으로 만듭니다 — 발주 원문 대조 →
5
+ 코드 계약 지도 → 설계안·반박 → 「안 정해진 자리」를 새 세션 셋으로 실측 → 기술 자리는 구현 계약으로,
6
+ 사업 자리는 시각표 질문으로 오너에게 → 결정문 → 갈림 0 이 될 때까지 재측정.
7
+ CPO(무엇을 어떻게 정할지) + CTO(어떻게 지을지) 순환이고, 결정 자체는 오너가 합니다.
8
+ 다음과 같은 요청 시 사용하세요:
9
+ - "W-02 정의해" / "다음 항목 잡자" / "이거 기획된 거야?" / "세션 닫혀도 같은 로직 나오게 해"
10
+ - "안 정해진 게 뭐야" / "내가 정할 게 뭔데" / "코딩 전에 확인만 해봐"
11
+ - 항목 하나를 착수하려는데 결정 장이 발주 한 줄뿐일 때(사람이 안 시켜도)
12
+ [책임 경계] vs genetics-sync: 저 스킬=일하기 전에 남이 정한 것을 읽는 절차, 이 스킬=아직 안 정해진 것을 정하게 만드는 절차.
13
+ [책임 경계] vs genetics-report: 저 스킬=고객에게 낼 문서, 이 스킬=구현자에게 남길 결정.
14
+ [책임 경계] vs genetics-cell-id: 저 스킬=지금 이 기계의 사람·세션 지형, 이 스킬=항목의 정의 완결.
15
+ version: "1.0.0"
16
+ ---
17
+
18
+ # 항목을 정의한다 (genetics-define)
19
+
20
+ > **보기는 전부 지어낸 것이다.** 이 문서의 항목 번호(W-·M-·R-)는 회차를 가리키는 **가린 표지**이고,
21
+ > 저장소·화면·필드 이름은 **보기로 지어냈다** — 어느 고객사의 것도 아니다. 회차와 갈림 수는 실제로 잰 값이라
22
+ > 그대로 두었다(그 숫자가 이 순환이 수렴한다는 유일한 증거다). 네 항목에 쓸 때는 보기 자리를 **네 실물**로 바꾼다.
23
+
24
+ ## 왜 이 스킬이 있는가
25
+
26
+ 어느 항목(보기로 부르면 W-01 — 「고정 큐 둘을 종류별 동적 큐로」)을 하루 종일 정의했다. 결정문 셋을 적고
27
+ 「끝났다」고 믿은 뒤 오너가 물었다 — **「세션 닫혀도 genetics 만 보고 톳씨없이 같은 로직이 나오나?」** 재 보니
28
+ **아니었다**: 이 대화를 모르는 세션 셋이 18자리 중 5자리를 서로 다르게 정했고, 그중 4자리는 내
29
+ 기획과도 달랐다. 갈린 이유는 셋이었다 — 내가 알면서 안 적은 것, 문서가 두 가지로 읽히는 것,
30
+ 그리고 **오너가 아직 정하지 않은 것**. 그리고 측정이 내 기획의 오류 하나(별칭 버킷)도 잡았다.
31
+
32
+ 같은 일감에 항목이 14개 더 남아 있었다. 이 순환을 매번 다시 발명하지 않으려고 적는다.
33
+
34
+ **한 줄 정의**: 「정의됐다」= *이 대화를 모르는 세션 셋이 genetics + 코드만 읽고 같은 로직을 낸다.*
35
+ 그 전엔 정의된 것이 아니다. 자기 확신은 표본이 아니다.
36
+
37
+ ## 절차 — 항목 하나당
38
+
39
+ ```
40
+ 0 잡기 works(claim) · 항목 장 get_case · list_sessions(같은 항목을 잡은 살아 있는 세션)
41
+ 1 발주 원문 일감 창구(Asana 등)의 notes 를 항목 장에 **그대로** 싣는다(요약 금지)
42
+ 2 계약 지도 저장소 여럿이면 병렬로 읽기·쓰기·모양·암묵 가정·blast (references/contract-map.js)
43
+ 3 설계·반박 설계 판단이 있으면 서로 다른 축의 안 둘셋 → 안마다 실물 줄로 깨뜨리는 검토
44
+ 4 실측 새 세션 셋(최소변경·정석·빠른구현)이 genetics+코드만 읽고 고정 물음에 답 → 대조 (references/fresh-reader-test.js)
45
+ 5 갈래 처리 기술 자리 → 구현 계약 장 / 사업 자리 → 시각표 질문 → 오너 답을 결정문 하나씩
46
+ 6 항목 장 확정 장을 읽는 순서로 링크, 「무슨 조건이면 다시 여나」= 지금 막는 것 없음
47
+ 7 재측정 4 를 다시. 갈림 0 이면 끝. 아니면 5 로
48
+ ```
49
+
50
+ **항목 장이 없다고 바로 쓰지 말 것.** 어느 회차(W-05)에서 5분 차로 세션 둘이 같은 항목을 잡았고, 앞 세션이 쓴
51
+ 항목 장을 뒤 세션이 search_cases 로 못 보고(굽기 전) 다시 쓸 뻔했다. search_cases 가 비어 있어도 `list_sessions` 로
52
+ 살아 있는 세션 제목을 본다 — 같은 항목이 있으면 그 장 위에서 이어 가고, 새 장은 새 제목(설계·계약)으로만 낸다.
53
+
54
+ **오너에게 묻기 전에 상대 세션의 최신 장을 다시 읽는다.** 한 회차(R-06)에서 옆 항목(R-03)의 결정문을 인용해
55
+ 「그 규칙 그대로」라는 선택지를 냈는데, 그 결정문은 30분 전에 그쪽 세션이 이미 뒤집어 둔 것이었다. 오너는 겉으로
56
+ 일관된 선택지를 골랐고 두 세션의 결정문이 반대로 남았다. 다른 세션이 살아서 같은 자리를 만지고 있으면 `get_case` 로
57
+ 그 장의 `상태:` 줄을 질문 직전에 확인하고, 상충이 보이면 「어느 쪽이 맞나」로 묻지 말고 오너가 지정하는 세션에 넘긴다.
58
+
59
+ ### 1. 발주 원문을 그대로 싣는다
60
+
61
+ 항목 장의 「왜」에 일감 창구의 `notes` 원문을 코드블록으로 **통째로** 넣는다. 첫 회차에서 결정 장에는
62
+ 내 요약 한 문장만 있었고, 설계가 오너에게 물으려던 질문 넷 중 셋의 답이 발주 원문에 이미 있었다.
63
+ 오너가 「그 낱말이 무슨 뜻이지?」라고 되물었다면 이미 늦은 것이다.
64
+
65
+ ```
66
+ search_tasks(text:"W-02", opt_fields:"name,notes") # 일감 창구의 MCP — notes 가 스펙이다
67
+ ```
68
+
69
+ ### 2. 계약 지도 — 저장소 여럿에 파급할 때만
70
+
71
+ 저장소마다 에이전트 하나: 읽는 자리·쓰는 자리(파일:줄)·데이터 모양(원문)·**암묵 가정**(큐가 둘, 한 사람은
72
+ 한 큐, 연락처가 유일키…)·바꾸면 무엇이 깨지나. 관여 안 하면 `touches:false` 로 정직하게.
73
+ 저장소 하나면 grep 으로 족하다 — Workflow 를 억지로 쓰지 않는다.
74
+
75
+ 가장 값진 산출은 「가장 깊이 박힌 가정이 무엇인가」다. 첫 회차의 그것은 「큐가 둘」이 아니라 「연락처 하나 =
76
+ 엔티티 하나」였고, 그것이 설계안 셋 중 하나를 골랐다.
77
+
78
+ ### 3. 설계안은 서로 다른 축으로, 반박은 실물 줄로
79
+
80
+ 안 둘셋은 **다른 축**이어야 한다(최소 변경 / 저장소를 옮김 / 정체를 바꿈). 반박 검토는 통과가 아니라
81
+ 깨뜨리기가 목표이고, **파일:줄을 인용해서만** 깬다. 결과는 「반려 / 조건부 재심 / 통과」로 가른다.
82
+ 첫 회차: A·B 는 반려(이력 소실, write-skew), C 는 조건부(필수 수정 넷).
83
+
84
+ ### 4. 실측 — 이 스킬의 관문
85
+
86
+ ```
87
+ 새 세션 셋: 「너는 오늘 처음 이어받았다. 읽어도 되는 것: genetics(tenant) + 코드. 읽으면 안 되는 것:
88
+ 이 기계의 대화 전사·에이전트 메모리 폴더, 임시 폴더, 어떤 대화 기록도. 아래 물음마다 구현할 로직과
89
+ source(genetics|code|guess)를 적어라. 애매하면 guess 다.」
90
+ 대조자: 물음마다 셋이 같은가 · 기획과 같은가 · 갭 종류(적히지 않음 / 문서가 애매함 / 문서끼리 어긋남 / 오너 미결)
91
+ ```
92
+
93
+ **「같은 로직」의 자**: 표현·키 이름 차이는 같음. 조건·기본값·저장 위치·계산 주체가 다르면 다름.
94
+
95
+ **물음표는 항목마다 새로 짠다.** 여섯 갈래에서 뽑으면 10~20개가 나온다:
96
+
97
+ | 갈래 | 보기(대기열 항목이면) |
98
+ |---|---|
99
+ | 데이터 | 엔티티 몇 개, 어느 키·필드에 |
100
+ | 규칙 | 순번 점수, 기본값, 전환 스위치 |
101
+ | 상호작용 | 누가 계산하나, 누가 알리나, 잠금 |
102
+ | 호환 | 옛 클라이언트에 무엇을 계속 내보내나, 어느 버킷에 |
103
+ | 배포 | 순서, 동시인가 순차인가, 게이트 |
104
+ | 예외 | 되돌리기, 실패, 거부 시 |
105
+
106
+ `references/fresh-reader-test.js` 의 `QUESTIONS` 와 `args.reference` 를 항목에 맞게 바꿔 띄운다.
107
+ **읽기 세션이 셋 미만이면 측정이 아니다** — 어느 1차에서 셋 다 「session limit」로
108
+ 죽었는데 대조자가 빈 입력으로 돌아 13자리 전부 「판정 불가」를 false 로 적었다. 스크립트가 `got.length < 3`
109
+ 이면 대조를 건너뛰고 `failed` 로 끝나게 해 두었다. 한도(5시간 창)가 풀린 뒤 **새로** 띄운다.
110
+ **재측정은 캐시를 안 탄다** — 같은 프롬프트면 옛 답을 돌려주므로 프롬프트에 `[2차 측정]` 표지를 넣고
111
+ `resumeFromRunId` 없이 새로 띄운다.
112
+
113
+ ### 5. 갈래별 처리
114
+
115
+ | 갭 종류 | 누가 닫나 | 어디에 |
116
+ |---|---|---|
117
+ | 적히지 않음(세션은 안다) | 세션 | 구현 계약 장 |
118
+ | 문서가 애매함 · 문서끼리 어긋남 | 세션 — 그 문장을 고쳐 쓴다 | 해당 장 덮어쓰기 |
119
+ | 오너 미결 | **오너** | 시각표 질문 → 결정문 하나씩 |
120
+
121
+ **시각표 질문의 꼴** — 등장인물 A·B·C 와 시각, 각 시점의 줄 상태 표, 선택지 × 결과 × **누가 억울한가**,
122
+ 권고를 첫 선택지에. 오너가 「그래 앞으로 이렇게 얘기해야 돼」라고 한 꼴이다.
123
+
124
+ **질문은 발주 범위 안의 명사로만.** 첫 회차에서 발주에 없던 개념(정원)을 끌어와 물었다가 「너의 휴리스틱임」으로
125
+ 반려됐다 — 발주가 든 예시에는 정원이라는 개념이 아예 없었다. 물음의 명사가 전부
126
+ 발주 원문에 있는지 먼저 본다. 없으면 그 물음은 내 설계 가정이지 오너 결정 사항이 아니다.
127
+
128
+ ### 6. 결정문 — remember_decision 의 규율
129
+
130
+ - **글자 상한은 장 전체(제목+what+why+who+reopen 합산)다, why 만이 아니다.** 어느 회차에서 why 3809자가
131
+ 「4522자」로 거절됐다. 상한은 포털의 `WIKI_DECISION_MAX_CHARS` — 결정 장은 10000 으로 올라갔고 사례 장(remember_case)은 4000 그대로다.
132
+ 거절되면 실물 줄 인용을 지우지 말고 절 단위로 장을 가른다(I-A/I-B 처럼).
133
+ - **같은 제목 = 덮어쓰기.** 서로 다른 결정은 장을 가른다(사업 규칙 하나당 장 하나).
134
+ - **`work:` 는 항목 장에만.** 설계·계약·사업 규칙 장에 붙이면 항목이 늘어난다.
135
+ - **링크 경로는 저장 결과에서 받는다.** 제목→경로 변환(«·» 삭제, 공백→붙임표)을 추측하지 말 것.
136
+ - 항목 장의 「무슨 조건이면 다시 여나」는 두뇌가 **막는 것**으로 읽는다 — 정의가 끝나면 「지금 막는
137
+ 것 없음」으로 시작하고, 뒤집힘 조건은 그 다음에.
138
+ - **저장 결과 「남겼습니다」는 본문이 실렸다는 뜻이 아니다.** 어느 회차에서 why 에 머리글만 넣고 계약 본문(S1~S7)을
139
+ 빠뜨린 채 셋을 저장했다 — 도구는 그대로 받는다. 저장 직후 `get_case` 로 한 번 읽어 본문·링크가 있는지 본다.
140
+ - **4000자 예산은 why ≈ 3200 으로 잡는다.** 제목+what+who+reopen+태그·틀이 700~750자를 먹는다(실측: why 3536 → 「4253자」,
141
+ why 3314 → 「4031자」). 넘치면 머리글(왜)을 「항목 장 참조」 한 줄로 줄이는 것이 실물 줄 인용을 지우는 것보다 낫다.
142
+ - 두뇌가 구운 개념 장은 결정 **전** 굽기일 수 있다. 결정문과 어긋나면 결정문이 정본이고 다음 굽기가 닫는다.
143
+
144
+ ## 실측 기록 — 이 순환이 수렴하나
145
+
146
+ 아래 표의 항목 번호는 **가린 표지**이고 갈래(내용)는 한 줄로만 적는다. 회차·갈림 수·갭 종류는 실제로 잰 값이다.
147
+
148
+ | 회차 | 셋 갈림 | 기획과 다름 | 갭 종류 |
149
+ |---|---|---|---|
150
+ | W-01 1차(결정 3장) | 5 / 18 | 4 / 18 | 적히지 않음 3 · 애매 1 · 오너 미결 6(그중 실제 갈림 1) |
151
+ | W-01 2차(계약 1장 + 오너 결정 4장 추가) | **0 / 18** | **0 / 18** | 54칸 전부 genetics 출처. 남은 것은 로직이 아닌 **이름 셋**(칼럼명·캐시 키·순위 필드명) — 셋 다 「내가 정함」 표시 → 계약 장에 한 줄씩 못 박아 닫음 |
152
+ | W-05 1차(설계 1장 + 계약 1장) | 0 / 18 | 0 / 18 | guess 3칸 전부 한 자리(스위치를 끄는 순간의 기존 대기자 — 오너 미결). 「내가 정함」 셋(검사 위치·부수 앱의 변수명·전환 로그) → 계약에 못 박음 |
153
+ | W-05 2차(오너 결정 1장 추가) | **0 / 18** | **0 / 18** | 54칸 전부 genetics. 남은 「내가 정함」 넷(응답 reason 문구·초기값 판정·특정 값일 때의 흐림 처리·소켓 서버 무검증) → 계약에 못 박음. **읽기 세션이 계약 문구의 결함을 잡았다** — `Number(v) !== 0 이면 ON` 은 Number(null)===0 이라 「NULL=ON」과 모순 → 문구 수정 |
154
+ | M-02 1차(범위·계약 각 1장) | 2 / 13 | 0 / 13 | 둘 다 「문서가 애매함」 — 읽기 세션이 플러그인 소스에서 그 API 가 예외를 안 던짐을 확인(계약의 「예외면 토스트」가 틀림), 빌드 번호는 빌드 스크립트 하드코딩을 계약이 몰랐음 |
155
+ | M-02 2차(감지 선검사·빌드 두 파일 보탬) | **0 / 13** | **0 / 13** | 39칸 전부 genetics, 「내가 정함」 0 |
156
+ | M-01 1차(배치 결정 1장 + 계약 1장) | 1 / 13 | 0 / 13 | 세로 탭의 선택 기록 여부(적히지 않음) + 계약 줄 범위 낡음·옆 항목 계약에 탭·뱃지 규격 없음(어긋남) |
157
+ | M-01 2차 | 1 / 13 | 0 / 13 | 상세 pane 의 메모 입력 컨트롤러 수명(모달과 달리 pane 은 계속 살아 소켓 갱신 때 입력이 날아감) — 「적히지 않음」이 2차에서 새로 드러남 |
158
+ | M-01 3차 | **0 / 13** | **0 / 13** | 39칸 genetics. 남은 것은 태블릿 판정의 꼴·엔티티 id 비교 기준 → 계약에 못 박음. **회차마다 새 자리가 하나씩 드러난다** — 한 번에 0 이 안 나오면 세 번까지 돈다 |
159
+ | R-07 1차(설계 1장 + 계약 2장) | 2 / 13 | 2 / 13 | 일괄 처리 API 에도 훅을 다나·주문 검사의 조인/행 없음/잠금 규칙(적히지 않음). **읽기 세션 셋이 서버 결함을 잡았다** — 발송 함수의 `return` 이 반복문 **안**이라 지점 전체 발송이 첫 기기 한 대에만 기록됨 |
160
+ | R-07 2차(계약을 서버 전파·주문 검사·기기 셋으로 가르고 보탬) | **0 / 13** | **0 / 13** | 39칸 genetics. 남은 것은 변수명·null 가드·같은 이름의 프로세스가 이 기계에 둘이라 경로를 박은 것 |
161
+ | R-06 1차(계약 1장) | **0 / 9** | **0 / 9** | 27칸 genetics. 대조자가 **항목 간 어긋남**을 잡았다 — 옆 항목 결정문의 한 조항이 같은 결정문이 기각한 억울함(다음 손님이 앞 손님 팝업을 봄)을 되살림 → 오너에게 물어 「3분 뒤 닫는다」로 확정 |
162
+ | R-08 1차(값·설계 각 1장 + 계약 3장) | 2 / 13 | 0 / 13 | 문구 저장의 전송 형식(반복 필드 vs JSON 문자열)·상태 재처리·탭 카운트의 출처 — 계약이 「무엇」만 적고 「어느 응답·어느 꼴」을 안 적은 자리. 새 클래스 이름이 기존 파일과 충돌·발주 문구 vs 코드 실측 낱말 차이도 대조자가 잡음 |
163
+ | R-06 2차(오너 결정 1장 추가) | **0 / 9** | 2 / 9(기대 동작만) | 27칸 genetics, 「내가 정함」 0. 코드는 셋이 같은데 **같은 날 오너 결정 둘이 상충** — 옆 세션이 그 사이 규칙을 뒤집었고, 내 질문은 옛 규칙을 「그대로」라고 잘못 전제했다. 오너: 「그 세션에서 정의하고 이어받아라」 → 대기. **다른 세션이 같은 자리를 만지면 질문 전에 그 세션의 최신 장을 다시 읽는다** |
164
+ | R-06 종결(재측정 없음) | — | — | 남은 갈림 하나(팝업 3분 기대값)는 코드가 아니라 문서끼리 어긋남이었고, 오너 지시대로 옆 세션이 자기 장을 복구하자 닫혔다. **코드가 어느 결정이든 같으면 재측정 대신 완료 조건만 고친다** — 측정은 로직 갈림을 재는 것이지 결정 대기를 재는 것이 아니다 |
165
+ | R-08 2차(계약 셋 보탬) | 2 / 13 | 2 / 13 | 1차 갈림은 전부 닫혔고 새 둘은 관리자 **화면 꼴**(탭 라벨을 기존 서식대로 그리나 계약 문구 「(N)」대로 그리나 · 뱃지를 같은 프레임 안에 두나 별 타일로 두나) — 계약이 「무엇」만 적고 「기존 화면과 같은 꼴인가」를 안 적은 자리. 항목이 클수록 회차마다 한 층씩 더 드러난다 |
166
+ | R-08 3차(2차 갈림 열한 자리 못 박음) | 1 / 13 | 0 / 13 | 39칸 genetics. 남은 하나는 뱃지 타일의 **부모 컨테이너**(형제인가 안인가 — 탭 동작이 달라진다): 계약이 「:158-166 옆에」라고 썼는데 그 줄 범위가 뷰 하나만 가리켰다. 줄 범위 인용은 **컨테이너 전체**로 하고 「안/형제」를 말로 적는다. 이름 갈림은 메서드명·콜백명·컨테이너 id 셋 |
167
+ | R-08 4차(부모 컨테이너·이름 셋·두뇌 지적 둘 못 박음) | **0 / 13** | **0 / 13** | 39칸 genetics. 남은 「내가 정함」은 정석 세션의 DDL 타입·뷰 id 같은 세부뿐 — 로직 아님. 저장소 셋·화면 여섯짜리 항목은 **4회차**가 들었고 회차마다 한 층(값 → 구조 → 렌더 꼴 → 컨테이너)씩 드러났다. 대조자가 「(내가 정함)」 옆에 「단 개념 장에 이미 있음」을 붙여 준 것도 값 — 두뇌가 구운 개념 장과 계약 장이 흩어져 있으면 계약에 합친다 |
168
+ | **W-02**(작은 항목: 2·3 생략, grep 지도) 1차 | **0 / 13** | **0 / 13** | 39칸 전부 genetics. 그러나 문서가 안 적은 자리를 셋이 **코드를 읽고 같은 쪽으로** 메운 흔적 7곳(적히지 않음 3 · 이름만 2 · 애매 1 · 항목 장 요약과 계약 장 어긋남 1) → 계약 장에 보탬 |
169
+ | W-02 2차(7자리 보탠 뒤) | 1 / 13 | 0 / 13 | 「내가 정함」 0. 갈린 하나 = 계약이 부모 레이아웃 폭 규칙을 파일 하나에만 적음 → 세션 하나는 코드 보고 넷 다, 둘은 문서대로 하나만. 한 줄 보탬 → 3차 |
170
+ | W-02 3차 | 2 / 13 | 0 / 13 | 같은 종류가 **한 층 더 위**에서 — 옛 화면 세로 부모의 End 제약·모달 Column Expanded. 회차마다 한 층씩 파는 꼴 → 구체 둘 + **일반 규칙**(「그 텍스트에서 화면까지 조상 폭 전부 유한」) 보탬 → 4차 |
171
+ | **W-02 4차** | **0 / 13** | **0 / 13** | 39칸 genetics. 남은 건 갈림 아닌 빈칸 둘(플랫폼별 구분자 View·버튼 묶음 id 이름) → 레이아웃 절을 별도 장으로 갈라 적음. **정의됨.** 한도 실패 2회 포함 6회 띄움 |
172
+ | **W-04**(안정화 항목: 지도 3저장소 → 안 A/B/C 반박 → 종합안 D 렌즈 셋 재반박(필수 수정 27) → 장 넷) 1차 | **0 / 17** | **0 / 17** | 51칸 중 48 genetics · 3 code(전부 한 물음의 포트 해석 — 문서에 없어 셋이 코드를 읽고 같은 쪽). 「내가 정함」 하나(타임스탬프 값·단위) → 둘 다 계약에 한 줄씩 못 박고 2차 |
173
+ | W-04 2차(타임스탬프 값·포트 해석 한 줄씩 보탠 뒤) | **0 / 17** | **0 / 17** | 51칸 전부 genetics · 「내가 정함」 0. 대조자가 짚은 세부 하나(지점 전환 때 세대 번호 「올림」 vs 「비움」 — 관측 결과 같음) → 계약 한 단어 보강. **정의됨.** 에이전트 4 × 2회, 한도 실패 0 |
174
+ | W-04 3차(오너 확정 셋을 결정문으로 남기고 계약 두 절을 고친 뒤) | **0 / 17** | **0 / 17** | 51칸 전부 genetics. 오너 결정 장 셋이 항목 장 링크를 타고 세 세션에 그대로 읽힘. **오너 확정까지 정의됨.** 옛 결정문 하나는 경로에 든 ①②③ 문자 때문에 `get_case` 가 거부해 뒤집힘 표시를 못 붙임 — 포털 화면에서 손으로 |
175
+ | W-04 현장 대조(정의 뒤, 오너가 서버 키를 등록해 줌) | — | — | 원인 지도의 경로 중 **실제로 도는 것**(재연결: 하루 끊김 610)과 **코드엔 있으나 현장 근거 0**(서버 예외 ack 미응답)을 갈랐고, 지도에 없던 원인 하나(없는 키를 읽으면 빈 엔티티가 truthy 로 → 유령 문서 54건)를 잡아 계약 한 절을 보탬. **정의가 끝나도 현장 대조는 따로** — 문서 수렴은 설계가 맞다는 증거가 아니다 |
176
+ | **R-04**(grep 지도, 오너 결정 1 = 범위, 계약 둘) 1차 | 1 / 15 | 0 / 15 | 45칸 genetics. 갈림 1 = 등록 폼도 1장 제한인데 계약이 수정 폼만 적음. **읽기 세션이 계약 결함 셋을 잡았다** — 모델 함수명이 다른 용도였음, 옛 API 컨트롤러가 필드를 손으로 골라 모델만 고치면 JSON 누락, 「X 는 그대로」의 전제가 실제 화면과 다름 → 정정 후 2차 |
177
+ | R-04 2차 | 1 / 15 | 0 / 15 | 45칸 genetics. 갈림 1 = 0장 가드를 서버 상태 조회 **전**에 두나 **후**에 두나(계약이 안 적음). 문서끼리 어긋남 2 = 항목 장 요약이 옛 문구를 들고 있었고, 계약의 「잠정」 표기가 낡음 → 셋 정정 후 3차 |
178
+ | **R-04 3차** | **0 / 15** | **0 / 15** | 45칸 genetics. 이름만 갈림 2(버튼 필드명·탭 View id) → 계약에 보탬. **정의됨.** 오너 결정 1(범위), 계약 둘, 3회 띄움(한도 실패 0) |
179
+ | **R-03**(단일 저장소, 반박 워크플로 3렌즈+합침 먼저 → 실측) 1차 | 1 / 18 | 0 / 18 | 54칸 전부 genetics. 갈림 1 은 문서에 **있는** 화면 하나를 한 세션이 놓친 것 → 그 절을 「화면 집합 셋」 한 줄 목록으로 다시 씀 |
180
+ | R-03 2차 | **0 / 18** | **0 / 18** | 이름 갈림 0. 오너 미결 5(잠정값을 계약에 적어 두니 셋이 같은 잠정값으로 읽음) |
181
+ | R-03 3차(규칙 뒤집힘 뒤 축소 5문항) | **0 / 5** | **0 / 5** | 뒤집힌 옛 장에 「상태: 뒤집힘」이 있어 셋 다 새 장을 따름 — 뒤집을 땐 옛 장 상태를 반드시 표시 |
182
+
183
+ **UI 말줄임류의 교훈**: 「부모 폭」을 파일 하나로 적으면 회차마다 한 층씩 올라간다. 처음부터 **조상 사슬 전부**를
184
+ 규칙 한 줄 + 실측 목록으로 적는다.
185
+
186
+ R-03 의 교훈 둘째: **오너에게 묻기 전에 두 가지 — 같은 자리를 다른 세션이 이미 물었는지 genetics 를 검색하고, 인과를 끝까지
187
+ 따져 선택지의 결과가 정말 갈리는지 본다.** 그 규칙을 옆 세션이 먼저 물어 답을 받아 두었는데, 나는 발주
188
+ 원문만 보고 한쪽 프레이밍으로 다시 물어 오너가 반대로 답하게 만들었다(보는 사람이 없는 화면의 갱신은
189
+ 결과가 안 갈린다). 세 번째로 「최종은?」을 묻자 「인과관계 대충 보고 보고하는 게 근본
190
+ 원인」이라는 지적을 받았다. 결과가 안 갈리면 질문이 아니라 오류다. 그리고 물음의 명사는 손님이 보는 화면으로 —
191
+ 내부 클래스 이름으로 물어 「이해가 어려움」을 받았고, 「09:59, 09:58 … 줄어드는 화면」으로 다시 물어 답을 받았다.
192
+
193
+ R-03 의 교훈: **실측 전에 반박 워크플로(렌즈 셋: 플랫폼 창·생명주기 / 발주 원문 대조 / 기존 동작 충돌)를 먼저 돌리면** 1차 갈림이
194
+ 5 → 1 로 준다 — 초안의 이중 정의·팝업 수명·알림 삼킴 등 필수 4·조건부 8 을 실측이 아니라 반박이 잡았다.
195
+ 그리고 **문서에 적혀 있어도 목록에 묻힌 항목은 놓친다** — 예외 화면 같은 집합은 「화면 집합 셋」처럼 한 절에 한 줄씩 열거한다.
196
+
197
+ W-02 의 교훈: **로직 갈림 0 이어도 「내가 정함」·code 출처가 남으면 문서가 덜 적은 것이다.** 셋이 우연히 같은
198
+ 쪽을 고른 것이지 문서가 정한 것이 아니다. 특히 「손댈 자리」는 항목 장 요약과 계약 장이 다르게 적히기 쉽다 —
199
+ 항목 장은 색인만, 상세는 계약 장 한 곳에.
200
+
201
+ 첫 항목의 두 회차 다 에이전트 4(읽기 3 + 대조 1), 오류 0. 2차가 1차보다 빨랐다(6.8분 vs 10.7분) — 읽을 장이
202
+ 늘었는데도 guess 가 없어 코드를 덜 뒤졌다. **「내가 정함」 표시는 로직이 아니라 이름에도 붙는다** —
203
+ 대조자가 「같음」이라 해도 그 표시가 남아 있으면 이름 갈림이고, 그것도 문서에 못 박는다.
204
+
205
+ 1차에서 오너 미결 6자리 중 5자리는 셋이 **같은 기본값으로 수렴**했다(잠금 거부·큐별 전부·필터 없음·
206
+ 지점 단위 지표). 수렴은 결정이 아니다 — 오너에게 물었고 넷을 확정했으며, 그중 하나(인원 필터)는
207
+ 질문 자체가 반려됐다.
208
+
209
+ ## 하지 않는 것
210
+
211
+ - 할지 말지, 항목 순서를 정하지 않는다 — 오너의 것.
212
+ - 발주에 없는 요구를 질문으로 만들어 내지 않는다.
213
+ - 「같은 로직이 나온다」를 표본 없이 말하지 않는다.
214
+ - 계약 지도(2)·설계안(3) 워크플로를 단일 판단 항목에 쓰지 않는다 — grep 으로 지도가 되는 항목(작은 표시 항목,
215
+ 이식 항목 같은 것)은 2·3 을 빼고 **1 → grep → 4 → 5 → 6 → 7** 로 돈다. 실측(4·7)은 빼지 않는다 —
216
+ 작은 항목일수록 「당연하다」고 안 적은 자리가 갈린다.
217
+ - **이 스킬의 보기에 실제 고객사 이름·저장소 경로·소속처 슬러그·장 경로를 적지 않는다.** 이 파일은 패키지로
218
+ 남의 기계에 배급된다 — 보기는 지어낸 것으로 두고, 실물은 부를 때 인자로 넘긴다.
219
+
220
+ ## references/
221
+
222
+ - `contract-map.js` — 저장소별 계약 지도 → 설계안 셋 → 반박. `REPOS`·`ROOT` 를 항목에 맞게.
223
+ - `fresh-reader-test.js` — 새 세션 셋 실측 → 대조. `QUESTIONS`·`args.reference`·`args.tenant`·`args.item_path` 를 항목에 맞게.
@@ -0,0 +1,134 @@
1
+ /*
2
+ * 계약 지도 → 설계안 → 반박, 한 벌.
3
+ *
4
+ * ★ 아래 내용은 **지어낸 보기**다. 저장소 이름·필드 이름·항목 번호는 아무 고객사와도 무관하고,
5
+ * 이 파일을 쓸 때는 그 자리를 **네 항목의 실물**로 바꾼다. 보기를 그대로 돌리면 아무것도 안 나온다.
6
+ *
7
+ * 보기로 쓰는 항목(가상): 「대기줄이 소형·대형 **두 개 고정 큐**로 박혀 있다. 이것을 종류별 동적 큐 N개 +
8
+ * 한 사람이 여러 큐에 동시 대기 + 한 곳에서 배정되면 나머지 큐에서 자동 제거로 바꾼다.」
9
+ * 저장소 여섯에 파급하고, 옛 클라이언트가 고정 큐 두 개를 직접 읽고 있다 — 계약 지도가 필요한 꼴이다.
10
+ */
11
+ export const meta = {
12
+ name: 'define-contract-map',
13
+ description: '보기: 종류별 동적 대기열 — 저장소 여섯의 고정 큐 계약을 병렬로 뜨고 데이터 모델 안을 세운다',
14
+ phases: [
15
+ { title: '계약 지도', detail: '저장소마다 대기열을 읽고 쓰는 자리 전수' },
16
+ { title: '설계안', detail: '복수 대기 데이터 모델 후보 셋 — 파급·이관·충돌 규칙' },
17
+ { title: '반박', detail: '각 안을 깨는 시나리오 찾기' },
18
+ ],
19
+ }
20
+
21
+ // ★ 네 기계의 실제 경로로 바꾼다. 여기에 절대경로를 **박아 두지 않는다** — 이 파일은 패키지로 배급된다.
22
+ const ROOT = process.env.DEFINE_PROJECT_ROOT || '<이 항목이 사는 프로젝트 뿌리의 절대경로>'
23
+ const REPOS = [
24
+ { key: 'queue', path: 'queue-core', role: '대기열 SSOT(인메모리 정렬 집합) · 알림 발송 · 소켓 브로드캐스트' },
25
+ { key: 'api', path: 'api-server', role: '접수/알림 API · 손님용 대기현황 웹' },
26
+ { key: 'kiosk', path: 'kiosk-app', role: '접수 키오스크 — 고정 큐 두 개를 직접 소비하는 클라이언트' },
27
+ { key: 'staff', path: 'staff-app', role: '직원 앱 — 대기 목록 조회·호출·부재 처리' },
28
+ { key: 'legacy', path: 'legacy-admin', role: '옛 관리자 태블릿 — 대기 목록(아직 살아 있다)' },
29
+ { key: 'member', path: 'member-app', role: '부수 표면 — 대기 관련 노출이 있으면' },
30
+ ]
31
+
32
+ const CONTRACT = {
33
+ type: 'object',
34
+ required: ['repo', 'touches', 'reads', 'writes', 'shapes', 'assumptions', 'blast', 'confidence'],
35
+ properties: {
36
+ repo: { type: 'string' },
37
+ touches: { type: 'boolean', description: '이 저장소가 대기열에 관여하는가' },
38
+ reads: { type: 'array', items: { type: 'object', required: ['file', 'line', 'what'], properties: { file: {type:'string'}, line: {type:'integer'}, what: {type:'string'} } }, description: '대기열을 읽는 자리 전수 — 파일:줄:무엇을' },
39
+ writes: { type: 'array', items: { type: 'object', required: ['file', 'line', 'what'], properties: { file: {type:'string'}, line: {type:'integer'}, what: {type:'string'} } }, description: '대기열을 쓰는/바꾸는 자리 전수' },
40
+ shapes: { type: 'array', items: { type: 'string' }, description: '데이터 모양 — 키 이름·정렬 점수·JSON 필드·DB 칼럼·API 응답 형태. 원문 그대로' },
41
+ assumptions: { type: 'array', items: { type: 'string' }, description: '「큐가 둘뿐」·「한 사람은 한 큐」·「연락처가 유일키」 같은 암묵 가정 — 코드 위치와 함께' },
42
+ blast: { type: 'string', description: '종류별 N개 큐 + 한 사람 복수 대기로 바꾸면 이 저장소에서 무엇이 깨지나 — 구체적으로' },
43
+ confidence: { type: 'string', enum: ['확실', '그럴듯함', '약함'] },
44
+ },
45
+ }
46
+
47
+ phase('계약 지도')
48
+ const maps = await parallel(REPOS.map((r) => () => agent(
49
+ `너는 저장소 하나를 읽고 **대기열(대기·waiting·queue·line) 계약**을 전수로 뜨는 탐색자다.
50
+
51
+ 저장소: ${ROOT}/${r.path}
52
+ 이 저장소의 알려진 역할: ${r.role}
53
+
54
+ 배경 — 이 항목: 지금 대기열은 line_small(소형)·line_large(대형) **두 개 고정 큐**로 하드코딩돼 있다. 이것을 **종류별 동적 큐 N개 + 한 사람이 복수 큐에 동시 대기 + 배정되면 나머지 큐에서 자동 제거**로 바꿔야 한다. 세션 첫머리 실측으로 이미 아는 것을 여기에 적어 둔다(보기): 대기열 서버의 브로드캐스트 함수에 party_size ≤ 4 / ≥ 5 분기, API 서버의 접수 경로가 연락처 하나로 단건 upsert(복수 대기와 정면 충돌), 키오스크가 응답의 line_small 키를 직접 소비.
55
+
56
+ 해야 할 것(very thorough):
57
+ 1. grep 으로 큐 이름·waiting·대기·queue·정렬 집합 명령·연락처 필드·인원수 필드·호출(call)·부재(absent) 를 전수 검색해라. 대소문자 무시.
58
+ 2. 읽는 자리와 쓰는 자리를 **파일:줄** 로 전부 적어라. 추측하지 말고 실제 줄 번호.
59
+ 3. 데이터 모양을 원문 그대로 적어라 — 키 이름, 정렬 점수가 무엇인지, JSON 응답 필드, DB 칼럼, 클라이언트 모델 필드.
60
+ 4. **암묵 가정**을 찾아라: 큐가 정확히 둘이라는 가정, 한 사람=한 큐, 연락처가 유일키, 순번 계산이 큐 하나 안에서만 성립, 알림 문구에 「소형」「대형」이 박혀 있음 등. 각각 코드 위치와 함께.
61
+ 5. 이 저장소가 대기열에 **전혀 관여하지 않으면** touches=false 로 정직하게 답하고 reads/writes 를 비워라 — 지어내지 마라.
62
+ 6. 마지막으로 blast: 큐가 N개가 되고 한 사람이 여러 큐에 서면 이 저장소에서 구체적으로 무엇이 깨지는지.
63
+
64
+ JSON 스키마대로만 답하라. 근거 없는 줄 번호를 적지 마라.`,
65
+ { label: `map:${r.key}`, phase: '계약 지도', schema: CONTRACT }
66
+ )))
67
+ const found = maps.filter(Boolean)
68
+ log(`계약 지도: ${found.length}/${REPOS.length} 저장소 · 관여 ${found.filter(m => m.touches).length}곳`)
69
+
70
+ phase('설계안')
71
+ const DESIGNS = {
72
+ type: 'object',
73
+ required: ['options', 'recommendation', 'migration', 'open_questions'],
74
+ properties: {
75
+ options: { type: 'array', minItems: 2, maxItems: 3, items: { type: 'object', required: ['name', 'model', 'pros', 'cons', 'touched_repos', 'multi_wait_rule', 'auto_remove_rule'], properties: {
76
+ name: {type:'string'}, model: {type:'string', description:'데이터 모델 — 키 설계·DB 스키마·API 계약을 구체적으로'},
77
+ pros: {type:'array', items:{type:'string'}}, cons: {type:'array', items:{type:'string'}},
78
+ touched_repos: {type:'array', items:{type:'string'}},
79
+ multi_wait_rule: {type:'string', description:'한 사람이 여러 큐에 설 때 순번·호출·동시배정 충돌을 어떻게 막나'},
80
+ auto_remove_rule: {type:'string', description:'배정 뒤 나머지 큐에서 빼는 원자성 — 어디서 누가'},
81
+ } } },
82
+ recommendation: { type: 'string', description: '어느 안을 왜 — 오너가 고를 수 있게 한 문단' },
83
+ migration: { type: 'string', description: '지금 고정 큐 데이터·클라이언트를 어떻게 넘기나 — 무중단인가, 동시 배포가 필요한가' },
84
+ open_questions: { type: 'array', items: {type:'string'}, description: '오너가 정해야 하는 것만 — 기술로 못 정하는 것' },
85
+ },
86
+ }
87
+ const design = await agent(
88
+ `너는 이 항목(종류별 동적 대기열·복수 대기·배정 후 자동 제거)의 **데이터 모델 설계자**다.
89
+
90
+ 아래는 저장소 여섯의 대기열 계약 지도다(병렬 탐색 결과, JSON):
91
+ ${JSON.stringify(found, null, 1)}
92
+
93
+ 이 지도 위에서 설계안을 **둘 또는 셋** 세워라. 각 안은 서로 다른 축을 골라야 한다 — 예: (A) 캐시 키를 종류별로 늘리고 클라이언트 계약을 배열로 바꾸는 최소 변경, (B) 대기 엔티티를 DB 로 옮기고 캐시는 파생, (C) 사람 단위 대기표(ticket)를 1급으로 두고 큐 소속을 그 아래 N:M 으로.
94
+
95
+ 반드시 다룰 것:
96
+ · 접수 경로의 연락처 단건 upsert — 복수 대기와 정면 충돌한다. 각 안에서 그 줄이 어떻게 되나.
97
+ · 키오스크가 고정 큐 키를 직접 읽는다 — 계약을 바꾸면 클라이언트 넷이 **동시 배포**여야 하는가, 아니면 호환 계층을 둘 수 있는가.
98
+ · 한 사람이 큐 A·B 에 동시에 서 있다가 A 에서 호출됐다. B 에서 빼는 것은 누가 언제 — 원자적인가. 그 사이 B 에서 호출이 나가면.
99
+ · 순번(몇 번째)은 큐 안에서만 뜻이 있다. 복수 큐면 사람에게 무엇을 보여 주나.
100
+ · 알림 문구에 「소형」「대형」이 박혀 있으면 어디를 고쳐야 하나.
101
+ · 옛 관리자 태블릿은 살아 있는 화면이다 — 그것도 새 계약을 받아야 하는가.
102
+
103
+ migration 은 정직하게: 무중단이 되는지, 안 되면 어느 창에 무엇을 동시에 내려야 하는지.
104
+ open_questions 에는 **기술로 못 정하는 것만** — 예: 큐 종류의 정의를 누가 어디서 관리하나, 한 사람이 최대 몇 큐까지, 배정 뒤 자동 제거를 손님에게 알리나.
105
+
106
+ JSON 스키마대로만 답하라.`,
107
+ { label: 'design:item', phase: '설계안', schema: DESIGNS, effort: 'high' }
108
+ )
109
+
110
+ phase('반박')
111
+ const VERDICT = { type: 'object', required: ['option', 'breaks', 'severity', 'fix_or_reject'], properties: {
112
+ option: {type:'string'}, breaks: {type:'array', items:{type:'string'}, description:'이 안이 깨지는 구체 시나리오 — 계약 지도의 파일:줄을 근거로'},
113
+ severity: {type:'string', enum:['치명','중대','경미','없음']}, fix_or_reject: {type:'string'},
114
+ } }
115
+ const attacks = await parallel(design.options.map((o) => () => agent(
116
+ `너는 회의적인 검토자다. 아래 설계안 하나를 **깨뜨려라.** 통과시키는 것이 목표가 아니다.
117
+
118
+ 설계안:
119
+ ${JSON.stringify(o, null, 1)}
120
+
121
+ 저장소 계약 지도(근거로 써라 — 파일:줄 인용):
122
+ ${JSON.stringify(found.filter(m => m.touches), null, 1)}
123
+
124
+ 찾을 것: 경쟁 조건(동시 배정·이중 호출), 데이터 정합성(캐시↔DB 갈라짐), 클라이언트 호환(옛 키오스크가 새 응답을 받으면), 운영 중 마이그레이션 실패 모드, 알림 오발송, 순번 표시 모순. 하나라도 실제로 깨지면 severity 를 정직하게 매겨라. 못 깨면 '없음'이라 하되 무엇을 시도했는지 적어라.
125
+
126
+ JSON 스키마대로만 답하라.`,
127
+ { label: `attack:${o.name.slice(0, 20)}`, phase: '반박', schema: VERDICT, effort: 'high' }
128
+ )))
129
+
130
+ return {
131
+ contract_map: found,
132
+ designs: design,
133
+ attacks: attacks.filter(Boolean),
134
+ }
@@ -0,0 +1,128 @@
1
+ /*
2
+ * 실측 — 이 대화를 모르는 세션 셋이 genetics + 코드만 읽고 같은 로직을 내는가. 그리고 대조.
3
+ *
4
+ * ★ 아래 QUESTIONS·PERSONAS·프롬프트의 내용은 **지어낸 보기**다(고정 큐 둘 → 종류별 동적 큐 N개).
5
+ * `QUESTIONS` 와 `args.reference` 는 **항목마다 새로 짠다** — 보기를 그대로 돌리면 남의 물음에 답하게 된다.
6
+ * ★ 소속처 슬러그와 장 경로는 **네 항목의 실물**로 바꾼다. 여기에 박아 두지 않는다 — 이 파일은 패키지로 배급된다.
7
+ */
8
+ export const meta = {
9
+ name: 'define-fresh-reader-test',
10
+ description: '2차 측정 — 결정문과 구현 계약을 적은 뒤, genetics 만 읽은 새 세션 셋이 같은 로직을 내나',
11
+ phases: [
12
+ { title: '새 세션 읽기(2차)', detail: '이 대화를 모르는 구현자 셋이 genetics + 코드만으로 항목별 로직을 쓴다' },
13
+ { title: '대조(2차)', detail: '셋이 갈리는 자리 · 세션 기획과 다른 자리 · 아직 미결인 자리' },
14
+ ],
15
+ }
16
+
17
+ /* 이 항목을 가리키는 값 — **부를 때 넘긴다**(args). 기본값은 자리를 보이기 위한 빈 표지일 뿐이다. */
18
+ const TENANT = args.tenant || '<소속처-슬러그>'
19
+ const ITEM_PATH = args.item_path || '<항목 장 경로 — 저장 결과가 준 그대로>'
20
+ const ITEM_TITLE = args.item_title || '<항목 제목>'
21
+ const CODE_ROOT = args.code_root || process.env.DEFINE_PROJECT_ROOT || '<이 항목이 사는 프로젝트 뿌리의 절대경로>'
22
+
23
+ const QUESTIONS = [
24
+ ['Q1_entity_model', '손님 한 명이 큐 여럿에 설 때 대기 엔티티는 몇 개인가, 큐 소속은 어디에 저장하나'],
25
+ ['Q2_membership_store', '키·필드 이름 — 큐별 정렬 집합, 큐 종류 등록부, 지점 설정, 엔티티에 추가되는 필드'],
26
+ ['Q3_initial_score', '접수 때 손님이 고른 큐들에서 정렬 점수(순번 기준)는 무엇인가'],
27
+ ['Q4_late_attach_score', '접수 뒤 직원이 큐 종류를 추가로 체크해 줄 때 그 큐에서의 점수는 무엇인가 — 기본값과 전환 가능 여부'],
28
+ ['Q5_switch_storage', 'Q4 의 전환 스위치는 어디에 저장되고(기기/서버) 어느 화면·API 로 바꾸나'],
29
+ ['Q6_call_lock', '손님이 큐 A 에서 호출 중일 때 큐 B 의 직원이 같은 손님을 호출할 수 있나'],
30
+ ['Q7_assign_removal', '큐 A 에서 배정 처리되면 나머지 큐에서 제거하는 것은 누가·어디서·어떻게 원자성을 보장하나'],
31
+ ['Q8_revert', '배정 처리를 되돌리면 손님은 어느 큐로, 어느 점수로 복귀하나'],
32
+ ['Q9_ahead_count', '손님 화면·알림의 「앞에 N팀」은 어느 큐 기준인가 — 누가 계산하나'],
33
+ ['Q10_party_filter', '인원수를 입력한 뒤 큐 선택 화면에서 인원수로 후보를 걸러 주나, 큐마다 정원이 있나'],
34
+ ['Q11_legacy_alias', '옛 클라이언트용 고정 큐 두 개의 별칭을 계속 내보내나 — 여러 큐에 선 티켓은 어느 버킷에, 몇 번 접히나, 그 규칙의 근거는'],
35
+ ['Q12_transition_table', '상태 전이 허용/거부 표 — 호출·배정·취소·되돌리기가 각각 어느 현재 상태에서 거부되나'],
36
+ ['Q13_payload_v2', '새 페이로드 모양 — 큐별 목록의 키, 순번(몇 번째)을 서버가 주나 클라이언트가 세나'],
37
+ ['Q14_whole_doc_save', '문서 전체 저장(fetch→save)을 계속 쓰나, 금지하나 — 금지하면 어느 경로들이 대상이고 어디 하나만 남기나'],
38
+ ['Q15_rollout', '배포 순서 — 서버와 클라이언트 넷을 동시에 내리나 순차인가, 새 큐 종류는 언제 켜나'],
39
+ ['Q16_wait_metrics', '동적 대기시간 지표를 큐 종류별로 쪼개나, 지점 단위로 두나'],
40
+ ['Q17_seed', '기존 고정 큐 둘은 어떻게 되나 — 큐 종류인가 아닌가, 키 이름은, 선택 화면에 보이나'],
41
+ ['Q18_notify_autocancel', '배정 뒤 나머지 큐에서 자동 삭제된 사실을 손님에게 따로 알리나'],
42
+ ]
43
+
44
+ const ANSWER = {
45
+ type: 'object',
46
+ required: ['persona', 'answers'],
47
+ properties: {
48
+ persona: { type: 'string' },
49
+ answers: { type: 'array', minItems: 18, maxItems: 18, items: { type: 'object',
50
+ required: ['q', 'value', 'source', 'where'],
51
+ properties: {
52
+ q: { type: 'string' },
53
+ value: { type: 'string', description: '구현할 로직 — 한두 문장, 구체적으로(키 이름·조건·기본값)' },
54
+ source: { type: 'string', enum: ['genetics', 'code', 'guess'], description: 'genetics 문서가 명시적으로 정해 줬나 · 코드에서 읽어 정했나 · 어디에도 없어 내가 정했나' },
55
+ where: { type: 'string', description: 'genetics 면 장 경로와 절 이름, code 면 파일:줄, guess 면 「없음」 또는 「암시: …」' },
56
+ } } },
57
+ },
58
+ }
59
+
60
+ const PERSONAS = [
61
+ '새로 합류한 백엔드 개발자. 최소 변경을 선호한다 — 문서에 없는 것은 지금 코드가 하는 대로 둔다.',
62
+ '정석을 좋아하는 설계자. 문서에 없는 것은 가장 보수적이고 안전한 쪽으로 정한다(잠금·거부·알림 추가).',
63
+ '빠른 구현을 좋아하는 개발자. 문서에 없는 것은 구현이 제일 간단한 쪽으로 정한다(잠금 없음·별칭 최소·클라이언트 계산).',
64
+ ]
65
+
66
+ phase('새 세션 읽기(2차)')
67
+ const readings = await parallel(PERSONAS.map((p, i) => () => agent(
68
+ `[2차 측정] 너는 과업 «${ITEM_TITLE}» 을 **오늘 처음** 이어받은 세션이다. 이전 대화 기억은 없다.
69
+ 너의 성향: ${p}
70
+
71
+ **읽어도 되는 것(이것뿐):**
72
+ 1. genetics 팀 기억 — ToolSearch 로 genetics 의 works, get_case, search_cases 를 불러 써라. tenant 는 "${TENANT}".
73
+ · get_case(tenant:"${TENANT}", path:"${ITEM_PATH}") 부터 읽어라 — 항목 장이고, 거기 링크된 결정 장을 **전부 get_case 로 전문을 읽어라**(설계 · 구현 계약 · 사업 규칙).
74
+ · search_cases(tenant:"${TENANT}", q:"<이 항목의 낱말 대여섯>") 로 빠진 장이 없는지 한 번 더 훑어라.
75
+ 2. 소스 — ${CODE_ROOT} 아래. 필요한 만큼 읽어라.
76
+
77
+ **읽으면 안 되는 것:** 이 기계의 대화 전사·에이전트 메모리 폴더 전부, 임시 폴더 전부, 어떤 대화 기록도. 읽으면 이 시험이 무효다.
78
+
79
+ 할 일: 아래 18개 물음에 **네가 실제로 구현할 로직**을 적어라. source 는 정직하게 — genetics 장이 **명시적으로** 정해 줬으면 genetics(장 경로+절), 코드를 읽고 네가 정했으면 code(파일:줄), 어디에도 없어 네 성향대로 정했으면 guess. 문서가 암시만 했으면 guess + 「암시: …」. 이 시험의 값은 guess 의 정직함에 있다.
80
+
81
+ 물음:
82
+ ${QUESTIONS.map(([k, t]) => `- ${k}: ${t}`).join('\n')}
83
+
84
+ JSON 스키마대로 18개를 순서대로 답하라.`,
85
+ { label: `read2:${['최소변경', '정석', '빠른구현'][i]}`, phase: '새 세션 읽기(2차)', schema: ANSWER }
86
+ )))
87
+ const got = readings.filter(Boolean)
88
+ if (got.length < 3) {
89
+ log(`읽기 세션 ${got.length}/3 — 셋 미만이면 대조를 돌리지 않는다(빈 입력 대조는 측정이 아니다)`)
90
+ return { readings: got, diff: null, failed: `읽기 세션 ${got.length}/3 — 한도·오류로 죽었다. 다시 띄워라` }
91
+ }
92
+ log(`2차: 새 세션 ${got.length}/3 이 답을 냈다`)
93
+
94
+ phase('대조(2차)')
95
+ const REF = args.reference
96
+ const DIFF = {
97
+ type: 'object',
98
+ required: ['rows', 'verdict'],
99
+ properties: {
100
+ rows: { type: 'array', minItems: 18, maxItems: 18, items: { type: 'object',
101
+ required: ['q', 'agree', 'sources', 'matches_ref', 'gap_kind', 'note'],
102
+ properties: {
103
+ q: { type: 'string' },
104
+ agree: { type: 'boolean', description: '세 세션의 value 가 로직으로 같은가(표현·이름 차이는 같음)' },
105
+ sources: { type: 'string', description: '세 세션의 source 를 순서대로, 예 "genetics/genetics/guess"' },
106
+ matches_ref: { type: 'boolean', description: '세 세션 다수의 로직이 reference 와 같은가' },
107
+ gap_kind: { type: 'string', enum: ['없음', '적히지 않음', '문서가 애매함', '문서끼리 어긋남'], description: '없음=genetics 가 정했고 셋이 같다 · 적히지 않음=genetics 에 없어 갈렸다 · 애매함=적혔는데 셋이 다르게 읽었다 · 어긋남=장끼리 서로 다른 말을 해서 갈렸다' },
108
+ note: { type: 'string', description: '갈렸으면 값들을 한 줄로 — 「최소변경: … / 정석: … / 빠른구현: …」. 같으면 「같음」' },
109
+ } } },
110
+ verdict: { type: 'string', description: '한 문단 — 1차와 비교해 지금은 몇 개가 갈리고 어떤 종류인가. 0 이면 0 이라 말하라' },
111
+ },
112
+ }
113
+ const diff = await agent(
114
+ `너는 대조자다. 같은 문서(genetics ${TENANT})만 읽은 세 세션이 이 항목을 같은 로직으로 구현하려 했는지 잰다. 이것은 2차 측정이다 — 1차에서 갈린 자리(저장 구조·별칭 버킷·원자 단위·자동취소 알림·되돌리기 등)를 문서로 닫은 뒤 다시 재는 것.
115
+
116
+ 세 세션의 답(JSON):
117
+ ${JSON.stringify(got, null, 1)}
118
+
119
+ reference — 원 세션이 문서에 적었다고 믿는 값(세 세션은 이것을 못 봤다):
120
+ ${JSON.stringify(REF, null, 1)}
121
+
122
+ 물음마다: (1) 세 value 가 **로직으로** 같은가 — 표현·키 이름 차이는 같음, 조건·기본값·저장 위치·계산 주체가 다르면 다름. (2) 다수 로직이 reference 와 같은가. (3) gap_kind 를 정직하게. 셋이 같고 reference 와도 같으면 「없음」. 특히 **문서끼리 어긋남**(설계 장과 구현 계약 장과 사업 규칙 장이 서로 다른 말)을 찾아라 — 세션들의 where 가 서로 다른 장을 가리키며 값이 다르면 그것이다.
123
+
124
+ JSON 스키마대로만.`,
125
+ { label: 'diff2', phase: '대조(2차)', schema: DIFF, effort: 'high' }
126
+ )
127
+
128
+ return { readings: got, diff }