@kaydash9999/banker-plugins 0.5.0 → 0.7.0

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.
@@ -0,0 +1,266 @@
1
+ ---
2
+ name: interval-report
3
+ description: "(banker) 장기 수행의 중간 보고를 docs/intermission.md 로 만들고 계획된 작업이 끝날 때까지 갱신. 시작 시각은 date 로 실측해 파일에 영속하고, ETA 는 단일 숫자 대신 범위+확신 라벨로 낸다. 'interval-report'/'중간 보고'/'진행 상황 보고'/'intermission' 시 사용."
4
+ ---
5
+
6
+ # interval-report: 장기 수행 중간 보고 (docs/intermission.md)
7
+
8
+ 장기 수행은 사람에게 보이지 않는다. 사용자는 지시를 넣고 나면 지금 어디쯤인지, 언제 끝나는지, 그동안 무슨 일이 있었는지 알 방법이 없다.
9
+ 이 스킬은 그 공백을 `docs/intermission.md` 파일 하나로 메우고, **계획된 작업이 끝날 때까지 그 파일을 갱신한다.**
10
+ 읽는 사람이 전체 trajectory 를 한 번에 파악할 수 있어야 한다. 그게 이 파일의 유일한 존재 이유다.
11
+
12
+ 여기서 오작동의 대가는 느린 진행이 아니라 **거짓 보고**다. 시각을 추측하면 elapsed 가 허구가 되고, 자신감 있는 단일 숫자 ETA 를 뱉으면 사용자가 그걸 믿고 계획을 세운다.
13
+ 그래서 아래의 실측 규칙과 ETA 정직성 규칙은 보고의 매끄러움보다 우선한다. 답변은 한글(기술 토큰·경로·명령은 영문).
14
+
15
+ **런타임:** 외부 도구·프레임워크 의존이 없다. 필요한 것은 `date` 와 파일 쓰기뿐이라 Claude Code(OMC)·Codex CLI(OMX) 양쪽에서 동일하게 동작한다.
16
+
17
+ - **시각의 출처는 `date` 하나다.** 두 런타임 모두 컨텍스트에 시:분 시각이 없다. 이건 직접 관측된 사실이고, 매번 셸에서 실측하는 **유일한 근거**다.
18
+ - **ultragoal 이 있으면 읽고, 없어도 동작한다**(아래 "ultragoal 관계").
19
+ - **자가 트리거는 설치하지 않는다.** 이 스킬은 호출되면 동작한다(아래 "When NOT to use").
20
+
21
+ ## When to use
22
+ - 사용자 지시 하나가 여러 단계·긴 시간으로 늘어나, 중간에 사람이 상태를 볼 수 없는 수행.
23
+ - 자리를 비운 사용자가 돌아와 그동안의 trajectory 를 한 번에 파악해야 할 때.
24
+ - 진행 중 사용자가 알아야 할 일이 생겼는데(가정이 깨짐, 외부 대기, 예상 밖 발견) 지금 당장 물을 만큼은 아닐 때.
25
+ - 사용자가 `interval-report`/`중간 보고`/`진행 상황 보고`/`intermission` 이라고 할 때.
26
+
27
+ ## When NOT to use
28
+ - **단발 작업.** 한 번에 끝나는 일에 보고 파일을 세우면 파일 관리가 작업보다 비싸다.
29
+ - **상태의 원본 저장소로.** 이 스킬은 렌더러지 원장이 아니다. 진행 상태의 원본이 필요하면 `ultragoal` 이다.
30
+ - **컨텍스트 임계 감지·자동 발화.** statusLine·hook 으로 스스로 발화하려 들지 마라. **그 영역은 `smart-compact` 소유다.** 트리거 메커니즘을 복제하지도, 설치하지도 않는다. interval-report 는 호출되면 동작하고, 갱신 시점은 아래 "3. 갱신 리듬" 이 정한다.
31
+ - **compaction 직전 준비.** 그건 `ready-compact` 다. 이름이 비슷해 헷갈리지만 하는 일이 다르다.
32
+
33
+ | | interval-report | ready-compact |
34
+ |---|---|---|
35
+ | 계기 | **연속**(작업이 끝날 때까지 갱신) | 이벤트 1회성(compaction 직전) |
36
+ | 독자 | **사람** | 다음 세션의 에이전트 |
37
+ | 산출물 | `docs/intermission.md`(자급식 보고서) | 붙여넣기용 resume 프롬프트 |
38
+ | 담는 것 | trajectory 전체(시작·경과·잔여·이벤트) | 휘발성 맥락(다음 한 수) |
39
+ | 시간 | **핵심**(실측·영속·ETA) | 범위 밖 |
40
+
41
+ 둘은 같은 세션에서 공존한다. 경쟁하지 않는다. 보고서가 있다고 resume 프롬프트를 생략하지 말고, 그 반대도 마찬가지다.
42
+
43
+ ## 측정 범위 (여기가 틀리면 나머지 숫자가 전부 틀린다)
44
+
45
+ 측정 대상은 세션 전체가 아니라 **마지막 사용자 지시로 시작된 현재 수행분**이다.
46
+
47
+ > **시작 시각 = 마지막으로 사용자가 대화·지시한 시점.**
48
+
49
+ - 세션은 며칠을 살아 있을 수 있고 그중 대부분은 사용자가 자리에 없던 시간이다. 세션 시작을 기준으로 잡으면 elapsed 는 "이 지시가 얼마나 걸리고 있나" 가 아니라 "이 터미널이 얼마나 오래 열려 있나" 를 재게 된다. 후자는 아무도 궁금해하지 않는다.
50
+ - 사용자가 **새 지시**를 넣으면 그것이 새 수행분이다. 시작 시각을 새로 찍고 보고서를 새로 연다.
51
+ - 사용자의 개입이 지시가 아니라 **승인·답변**(예: "응", "그렇게 해", "1번으로")이면 수행분은 이어진다. 시작 시각을 옮기지 마라.
52
+ - 애매하면 **옮기지 않는 쪽**을 택한다. 두 실수의 대가가 대칭이 아니다. 옮기는 실수는 elapsed 를 낙관 쪽으로 붕괴시키고, 안 옮기는 실수는 비관 쪽으로 어긋난다. 보고서에서는 낙관 편향이 더 나쁘다.
53
+
54
+ ## 시간 메커닉
55
+
56
+ ### 실측
57
+ ```bash
58
+ date +%s # epoch 초. 산술은 이 값으로만.
59
+ date '+%Y-%m-%d %H:%M %Z' # 사람이 읽는 표기. 보고서에 그대로 싣는다.
60
+ ```
61
+
62
+ 시각을 추측하거나 대화 내용에서 역산하지 마라. 두 런타임 모두 컨텍스트에 시:분 시각이 없다.
63
+ 이 문장이 `date` 를 부르는 근거의 전부다. 다른 근거를 덧붙이지 마라.
64
+
65
+ ### READ-BACK 불변 규칙 (이 스킬의 핵심 안전장치)
66
+
67
+ > **파일에 이미 시작 시각이 있으면 절대 덮어쓰지 않는다.**
68
+
69
+ 갱신할 때는 언제나 파일을 먼저 읽는다. 계산은 그다음이다.
70
+
71
+ ```bash
72
+ F=docs/intermission.md
73
+ if [ -f "$F" ] && grep -q '^started_epoch:' "$F"; then
74
+ START=$(grep -m1 '^started_epoch:' "$F" | tr -cd '0-9') # 파일이 정본
75
+ else
76
+ START=$(date +%s) # 최초 1회만
77
+ fi
78
+ NOW=$(date +%s)
79
+ ELAPSED=$(( NOW - START ))
80
+ printf 'start=%s elapsed=%dh %02dm\n' "$START" $(( ELAPSED / 3600 )) $(( ELAPSED % 3600 / 60 ))
81
+ ```
82
+
83
+ - **왜 영속이 필수인가:** compaction·resume 후에는 모델이 시작 시각을 기억하지 못한다. 그때 `now` 로 재스탬프하면 elapsed 가 0 으로 붕괴한다. 붕괴는 조용하고, 하필 **긴 작업일수록 잘 일어난다**(길수록 compaction 을 겪으니까). 즉 리포트가 가장 필요한 순간에 리포트가 가장 낙관적으로 거짓말한다. 이 규칙은 편의보다 우선한다.
84
+ - **정수 산술만 쓴다.** `date -d @...`(GNU)와 `date -v`(BSD)는 문법이 갈린다. `date +%s` 로 epoch 만 얻고 뺄셈은 셸에 맡기면 분기가 사라진다.
85
+ - **사람이 읽는 표기도 같이 영속한다.** epoch 만 두면 파일을 연 사람이 값을 검증할 수 없다.
86
+
87
+ ## ETA 정직성
88
+
89
+ ETA 는 **실측량에서만** 유도한다: 경과 시간, 완료 수, 진행중 수, 대기 수. 이 넷 말고 다른 입력을 지어내지 마라.
90
+
91
+ **규칙 1. 단일 숫자 금지. 범위 + 확신 라벨.**
92
+ - 나쁨: `예상 종료: 17:30`
93
+ - 좋음: `예상 잔여: 40m ~ 2h (확신: 낮음)` / `예상 종료: 17:00 ~ 18:20 KST`
94
+ - 확신 라벨은 3단이면 충분하다: 높음 / 보통 / 낮음. 소수점 확신도가 필요하면 그건 `curation` 의 형식이지 여기가 아니다.
95
+
96
+ **규칙 2. 가정을 인라인에 적는다.**
97
+ ETA 바로 옆이나 바로 아래에 무엇을 가정했는지 쓴다. 별도 문서로 미루지 마라. 읽는 사람이 ETA 를 보는 **그 자리에서** 가정을 봐야 정정할 수 있다.
98
+
99
+ > 예상 잔여: 40m ~ 2h (확신: 낮음)
100
+ > 가정: 남은 3건이 끝난 2건과 비슷한 크기다(확인된 바 없음). 5번이 사용자 승인 대기라 상한이 열려 있다.
101
+
102
+ **규칙 3. 90% 천장을 의식한다.**
103
+ 카운트가 9/10 을 가리킬 때가 **남은 노력이 가장 클 수 있는 지점**이다. 마지막 항목은 대개 통합·검증·사용자 확인이고, 그것들은 앞의 9개와 크기가 다르다.
104
+ 선형 외삽(경과 ÷ 완료 × 대기)은 초반에 그럭저럭 맞고 **꼬리에서 무너진다.** 꼬리에서 성립하는 척하지 마라.
105
+ - 카운트가 올라갈수록 범위의 **상한을 좁히지 마라.** 좁히고 싶은 충동이 정확히 그 편향이다.
106
+ - 남은 항목의 성격이 앞의 항목과 다르면(검증·리뷰·사용자 결정 대기) 그 사실을 가정에 적고 확신을 낮춘다.
107
+ - 진행률 퍼센트 하나로 요약하지 마라. 작업 표의 카운트가 이미 그 정보를 편향 없이 담고 있다.
108
+
109
+ **규칙 4. 자기모순 금지.**
110
+ "정직한 ETA" 를 표방하면서 자신감 있는 단일 숫자를 뱉으면 자기모순이다. 그건 ETA 를 아예 안 내는 것보다 **나쁘다.** 사용자가 정직성 표방을 믿고 그 숫자를 더 신뢰하기 때문이다.
111
+ 범위를 못 내겠으면 **"모른다" 고 적어라.** 그건 허용된 답이다. 근거 없는 숫자를 채워 넣는 것만 금지다.
112
+
113
+ ## ultragoal 관계 (렌더러지 원장이 아니다)
114
+
115
+ `ultragoal` 은 목표 목록·상태·타임스탬프를 append-only 원장으로 **양 런타임에 이미 영속화**한다. 이 스킬은 그것을 복제하지 않는다.
116
+
117
+ - **있으면 읽어서 렌더한다.** 작업 표의 순서·작업·상태를 원장에서 채우고, 보고서에 원장 경로를 출처로 적는다.
118
+ - Claude(OMC): `.omc/ultragoal/`
119
+ - Codex(OMX): `.omx/ultragoal/`
120
+ - **없어도 동작한다.** ultragoal 은 opt-in 이라 대부분의 세션에 없다. 없으면 현재 지시의 작업 목록에서 표를 직접 만든다. **ultragoal 을 요구하지도, 대신 켜지도 마라.**
121
+ - **상태를 복제하지 않는다.** 보고서는 원장의 파생물이다. 둘이 어긋나면 원장이 맞다. 보고서를 고쳐 원장을 반영하지, 원장을 보고서에 맞추지 않는다.
122
+ - **왜 그 위에 얹는가:** ultragoal 에는 시간·ETA 개념이 없고 산출물이 기계 포맷이다. 이 스킬이 더하는 것은 딱 두 가지다. 얇은 시간 레이어(실측·영속·경과·ETA), 그리고 사람용 렌더러. 그 둘을 넘어가면 ultragoal 을 다시 구현하는 것이다.
123
+
124
+ ## 워크플로
125
+
126
+ ### 0. READ-BACK (추정 금지)
127
+ 언제나 여기서 시작한다. 파일이 있는지, 시작 시각이 무엇인지 **읽어서** 확인한다. 기억에 의존하지 마라.
128
+ 위 "READ-BACK 불변 규칙" 의 블록을 그대로 돌려 `START`·`ELAPSED` 를 얻는다. 여기서 읽은 `started_epoch` 는 4단계 검증의 기준값이다.
129
+
130
+ 파일이 없으면 최초 실행이다. 1단계로 간다. 있으면 **갱신**이다. 3단계로 간다.
131
+
132
+ ### 1. 자리 잡기 (docs/ + .gitignore, 최초 1회)
133
+ 산출물은 타깃 repo 의 `docs/intermission.md` 다. `docs/` 가 없으면 만든다.
134
+
135
+ 이건 **세션 로컬 작업물**이지 그 프로젝트의 문서가 아니다. 커밋되면 우리 세션의 진행 상황이 남의 repo 히스토리에 영구히 남는다. 그래서 `.gitignore` 에 기본 항목을 추가한다.
136
+
137
+ ```
138
+ # interval-report: session-local progress report (not project documentation)
139
+ docs/intermission.md
140
+ ```
141
+
142
+ - **`.gitignore` 는 그 프로젝트의 파일이지 우리가 만든 게 아니다.** 그러므로 **사용자에게 알리고 진행한다.** 무엇을 왜 추가하는지 한 줄로 말하면 된다. 승인을 기다리며 멈추라는 게 아니라, 사용자가 모르는 채로 남의 repo 가 바뀌는 일을 막으라는 것이다.
143
+ - **멱등하게.** 이미 그 항목이 있으면 추가하지 않는다. 이미 무시되고 있어도(상위 규칙, `docs/` 통째 무시 등) 추가하지 않는다. 확인은 이걸로 한다:
144
+ ```bash
145
+ git check-ignore -q docs/intermission.md && echo "이미 무시됨: 추가 불필요"
146
+ ```
147
+ - git repo 가 아니면(`git rev-parse --git-dir` 실패) 이 단계를 통째로 건너뛴다. 에러가 아니다.
148
+ - 사용자가 보고서를 커밋 대상으로 원한다고 명시하면 그건 사용자의 repo 다. 추가하지 말고 그대로 둔다.
149
+
150
+ ### 2. 보고서 렌더
151
+ 필수 항목 8개는 **전부** 있어야 한다: 시작 시간 / 현재 상태 / elapsed / 예상 잔여 / 예상 종료 / 진행·잔여 작업 표 / 발생 이벤트 / 인지 중인 constraints.
152
+
153
+ **작업 표는 하나다.** 완료분과 잔여분을 따로 두지 마라. 하나의 표에 순서대로 놓아야 trajectory 가 한눈에 들어온다. 상태 값은 `완료`·`진행중`·`대기` 3종뿐이다.
154
+
155
+ ```markdown
156
+ ---
157
+ started_epoch: 1784181572
158
+ started_human: "2026-07-16 14:59 KST"
159
+ updated_human: "2026-07-16 16:20 KST"
160
+ ---
161
+
162
+ # 중간 보고: DB 마이그레이션 롤아웃
163
+
164
+ | 항목 | 값 |
165
+ |---|---|
166
+ | 시작 | 2026-07-16 14:59 KST |
167
+ | 현재 상태 | 3번(스테이징 검증) 진행 중. 4번부터 대기. |
168
+ | elapsed | 1h 21m |
169
+ | 예상 잔여 | 40m ~ 2h (확신: 낮음) |
170
+ | 예상 종료 | 17:00 ~ 18:20 KST |
171
+
172
+ **ETA 가정:** 남은 3건이 끝난 2건과 비슷한 크기다(확인된 바 없음). 5번이 사용자 승인 대기라 상한이 열려 있다.
173
+
174
+ ## 작업
175
+
176
+ | # | 작업 | 상태 |
177
+ |---|---|---|
178
+ | 1 | 스키마 diff 추출 | 완료 |
179
+ | 2 | 마이그레이션 스크립트 작성 | 완료 |
180
+ | 3 | 스테이징 검증 | 진행중 |
181
+ | 4 | 롤백 절차 문서화 | 대기 |
182
+ | 5 | 운영 반영 | 대기 |
183
+
184
+ ## 발생 이벤트
185
+ - 15:34 스테이징 시드가 3개월 낡아 일부 검증을 건너뜀. 사용자 판단이 필요할 수 있음.
186
+ - 16:02 `npm i` 가 프록시 뒤에서 2회 실패. 재시도로 통과. 조치 불필요.
187
+
188
+ ## 인지 중인 constraints
189
+ - 운영 반영은 사용자 승인 후에만(전역 규칙).
190
+ - 스테이징 DB 는 18:00 에 야간 배치가 잡아간다. 3번을 그 전에 끝내야 한다.
191
+ - 롤백 절차는 아직 검증되지 않았다. 5번의 전제 조건이다.
192
+ ```
193
+
194
+ - **발생 이벤트**는 사용자가 알아야 할 것만 적는다. 로그가 아니다. 기준은 "이걸 몰랐다면 사용자가 다르게 판단했을까" 다. 아니면 빼라. 다만 **가정이 깨진 사건은 반드시 남긴다.**
195
+ - **constraints** 는 지금 인지하고 있는 제약이다. 환경·순서 의존·승인 게이트·외부 대기. 사용자가 풀어줄 수 있는 것이 여기 있으면 그게 이 절의 값어치다.
196
+ - 항목에 쓸 내용이 없으면 항목을 **지우지 말고** "없음" 이라고 적는다. 없는 항목과 빈 항목은 다르다.
197
+
198
+ ### 3. 갱신 리듬
199
+ 갱신은 **계획된 작업이 끝날 때까지** 이어진다. 시점은 사건이 정한다:
200
+
201
+ - 작업 표의 항목 상태가 바뀌었을 때(완료·진행중 전환).
202
+ - 사용자가 알아야 할 이벤트가 생겼을 때.
203
+ - constraints 가 바뀌었을 때(제약이 생기거나 풀렸을 때).
204
+ - ETA 의 가정이 깨졌을 때. **이때가 가장 중요하다.** 가정이 깨졌는데 ETA 를 그대로 두면 그 순간부터 보고서는 거짓이다.
205
+ - 사용자가 물었을 때.
206
+
207
+ 매 갱신마다 0단계 READ-BACK 을 먼저 한다. `updated_human` 을 새로 찍고 `started_epoch` 는 손대지 않는다.
208
+ 갱신할 게 없으면 갱신하지 않는다. 같은 내용으로 타임스탬프만 바꾸는 것은 신호가 아니라 잡음이다.
209
+
210
+ ### 4. 검증 (보고 의무)
211
+ **보고서를 썼다는 사실로 성공을 선언하지 마라.** 파일은 언제나 써진다. 붕괴한 시작 시각도, 자신감 있는 단일 숫자도 똑같이 잘 써진다.
212
+ 판정은 **갱신할 때마다** 아래를 실제로 돌려서 한다.
213
+
214
+ ```bash
215
+ F=docs/intermission.md
216
+ grep -m1 '^started_epoch:' "$F" # 0단계에서 읽은 값과 동일해야 한다
217
+ grep -m1 '^started_human:' "$F"
218
+ git check-ignore -q "$F" 2>/dev/null && echo "gitignore: ok" || echo "gitignore: 미적용 (git repo 가 아니면 정상, 아니면 1단계 확인)"
219
+ ```
220
+
221
+ 체크리스트. 하나라도 아니오면 고치고 다시 본다.
222
+ - `started_epoch` 가 0단계에서 읽은 값과 **같은가.** 다르면 재스탬프 사고이고 elapsed 는 거짓이다. 즉시 원래 값으로 되돌린다.
223
+ - elapsed 가 **직전 보고보다 커졌는가.** 줄었으면 같은 사고다.
224
+ - 필수 항목 8개가 **전부 있는가.** "없음" 도 값이다. 항목 자체를 지우지 않는다.
225
+ - 작업 표가 **하나인가.** 상태 값이 완료·진행중·대기 3종뿐인가.
226
+ - ETA 가 **범위 + 확신 라벨**인가. 단일 시각 하나면 규칙 위반이다.
227
+ - ETA 옆에 **가정이 적혀 있는가.** 가정이 지금도 유효한가.
228
+ - 표의 상태가 실제 진행과 맞는가. **갱신하지 않은 보고서는 오래된 보고서가 아니라 틀린 보고서다.**
229
+
230
+ 사용자에게는 파일 경로와 함께 **지금 값**을 한 줄로 준다. 파일을 열게 하지 마라.
231
+
232
+ ```
233
+ docs/intermission.md 갱신: elapsed 1h 21m, 2/5 완료, 예상 잔여 40m ~ 2h (확신: 낮음)
234
+ ```
235
+
236
+ ## 함정
237
+ - **now 로 재스탬프하는 것.** 이 스킬에서 데이터가 거짓이 되는 유일하고 실재하는 경로다. 파일에 시작 시각이 있으면 그게 정본이다.
238
+ - **세션 시작을 시작 시각으로 잡는 것.** 측정 대상은 마지막 사용자 지시로 시작된 수행분이다.
239
+ - **승인 한마디를 새 지시로 오인해 시작 시각을 옮기는 것.** 애매하면 옮기지 않는다.
240
+ - **자신감 있는 단일 숫자.** "정직한 ETA" 를 표방하면서 이걸 하면 자기모순이고, 침묵보다 나쁘다.
241
+ - **꼬리에서 선형 외삽을 믿는 것.** 9/10 이 남은 노력이 가장 큰 지점일 수 있다.
242
+ - **표를 둘로 쪼개는 것.** 완료 표와 잔여 표를 나누면 trajectory 가 끊긴다. 표는 하나다.
243
+ - **이벤트 절을 로그로 만드는 것.** 전부 적으면 아무것도 안 적은 것과 같다. 기준은 "사용자의 판단이 달라졌을까" 다.
244
+ - **파일만 쓰고 끝내는 것.** 파일은 언제나 써진다. 검증은 4단계 체크리스트다.
245
+ - **`.gitignore` 를 정리하려 드는 것.** 여기서 하는 일은 한 줄 멱등 추가지 정리가 아니다(정리는 `refresh-git-ignore` 소관이고 이 스킬이 부를 일이 아니다).
246
+ - **남의 repo 를 조용히 건드리는 것.** `.gitignore` 변경은 알리고 진행한다.
247
+ - **`date` 의 계산 옵션에 의존하는 것.** GNU 와 BSD 가 갈린다. epoch 뺄셈만 쓴다.
248
+ - **ultragoal 상태를 보고서에 복제하는 것.** 어긋나면 원장이 맞다.
249
+ - **자가 트리거를 설치하는 것.** `smart-compact` 영역이다.
250
+ - **보고서를 resume 프롬프트로 쓰는 것.** 그건 `ready-compact` 다. 독자가 다르다(사람 대 에이전트).
251
+
252
+ ## 원칙
253
+ - 시각은 `date` 로 실측한다. 추측·역산 금지.
254
+ - 시작 시각은 파일이 정본이다. READ-BACK 이 먼저, 계산은 그다음.
255
+ - 측정 단위는 세션이 아니라 마지막 사용자 지시로 시작된 수행분이다.
256
+ - ETA 는 실측량에서만 유도하고, 범위 + 확신 라벨로 내고, 가정을 곁에 적는다.
257
+ - 모르면 모른다고 적는다. 그게 허용된 답이고 자신 있는 오답보다 낫다.
258
+ - 표는 하나다. 전체 trajectory 가 한눈에 들어와야 한다.
259
+ - 이 스킬은 시간 레이어와 렌더러다. 원장도, 트리거도, resume 프롬프트도 아니다.
260
+ - 산출물은 세션 로컬이다. 남의 repo 에 남기지 않는다.
261
+ - 계획된 작업이 끝날 때까지 갱신한다. 끝나면 마지막 갱신으로 닫는다.
262
+
263
+ ARGUMENTS: [--path <파일>] [--close] [지시 요약] (없으면 `docs/intermission.md` 에 현재 수행분을 렌더하고 계획된 작업이 끝날 때까지 갱신)
264
+ - `--path <파일>`: 산출 경로를 바꾼다. `.gitignore` 게이트는 그 경로에 그대로 적용한다.
265
+ - `--close`: 마지막 갱신을 하고 보고서를 닫는다. 전 항목을 종료 상태로 맞추고, elapsed 를 확정하고, 예상 잔여·예상 종료를 실제 값으로 대체한다.
266
+ - 인자 없이 파일이 이미 있으면 그건 **갱신**이다. 새로 만들지 마라. 언제나 READ-BACK 이 먼저다.
@@ -0,0 +1,255 @@
1
+ ---
2
+ name: obsidizer
3
+ description: "(banker) AI 생성 마크다운 위키를 의미 보존한 채 Obsidian 지식그래프로 정규화·상호링크·백링크. '--enable'/'--disable' 로 이후 위키 생성 자동 정규화 켜기/끄기(Claude 한정). 'obsidizer'/'옵시디언화'/'obsidian 최적화'/'위키 그래프 정리' 시 사용."
4
+ ---
5
+
6
+ # obsidizer — AI 위키를 Obsidian 지식그래프로 정규화
7
+
8
+ AI가 생성한 마크다운 위키를 **의미 보존**한 채 그래프 관점(`tags`·`[[slug]]` 링크·`links[]`
9
+ 백링크·Related 푸터)에서 정규화한다 — "vault over file", 노트 하나가 아니라 그래프 속 자리를
10
+ 최적화한다. 의미 판단(어떤 언급이 진짜 링크인지, 어떤 섹션이 진짜 중복인지)은 LLM이 파일을 직접
11
+ 편집해 내리고, **결정적 캐노니컬라이저**(`obsidize.mjs`)가 마지막 기계적 패스로 바이트 단위 결과를
12
+ 고정한다. `wiki_*` 쓰기 의존 없음(검증에만 `wiki_lint` 읽기 전용 사용).
13
+
14
+ ## ⚠️ 정직한 실현 한계
15
+
16
+ `--enable`은 "이후 모든 위키 생성이 obsidizer 양식을 따른다"를 뜻하지 않는다. 실제 경계는 세 갈래다:
17
+
18
+ - **깊이 — 기계적 정규화만, 의미 보강은 절대 자동화하지 않는다.** `--enable`은 프론트매터
19
+ envelope·`tags`·줄바꿈/헤딩·`links[]` 정렬·Related 푸터 같은 **기계적 규칙**만 자동 적용한다.
20
+ 어떤 언급을 링크할지, 어떤 백링크가 진짜인지는 자동화 대상이 아니다.
21
+ ⚠️ hook은 실제로 `hookSpecificOutput.additionalContext`로 LLM에 지시를 주입**할 수 있다**(OMC
22
+ 자체 hook 10개가 이미 이 필드를 쓴다 — "hook은 LLM을 못 돌린다"는 말은 틀렸다). obsidizer는 이
23
+ 경로를 **품질 근거로 거부한다**(불가능이 아니라 선택): (1) soft·assistant-honored일 뿐 강제가
24
+ 아님, (2) 미리보기 없는 자동 변형 → dry-run-기본 원칙과 정면 충돌, (3) 자동 경로에 LLM 판단이
25
+ 들어가 결정성 축이 깨짐, (4) 세션을 가로챔(저장만 요청했는데 그래프 큐레이션이 끼어듦).
26
+ - **런타임 — Claude 전용.** Codex CLI엔 MCP 도구 hook 자체가 없다(PostToolUse=Bash·apply_patch
27
+ 만). `--enable`/`--disable`은 Codex에서 **정직한 no-op**: 명확한 메시지만 내고, 아무도 읽지
28
+ 않을 플래그를 쓰지 않으며, 성공을 보고하지도 않는다.
29
+ - **🔴 커버리지 — session-log 페이지는 절대 걸리지 않는다.** `session-hooks.ts:133`이 MCP 도구
30
+ 없이 `writePageUnsafe`를 직접 호출 → `PostToolUse` 이벤트 자체가 없다 → hook이 **영원히 안
31
+ 켜진다**. session-log는 이 vault에서 **가장 흔한 페이지 유형**(오늘 12개 중 3개, 세션마다 +1).
32
+ `--enable`의 약속은 **`wiki_ingest`/`wiki_add`로 쓰인 페이지에 한정**된다. session-log는 bare
33
+ `obsidizer` 실행으로만 정규화된다.
34
+
35
+ | | `--enable`로 자동 | bare `obsidizer` 필요 |
36
+ |---|---|---|
37
+ | 기계적 규칙 (`wiki_ingest`/`wiki_add` 경로) | ✅ | — |
38
+ | 기계적 규칙 (`session-log-*` 페이지) | ❌ 절대 안 걸림 | ✅ |
39
+ | 의미 보강 (링크·백링크·중복 판단) | ❌ (거부, 한계 아님) | ✅ |
40
+
41
+ ## 런타임 배선
42
+ - **엔진은 동일**: Claude·Codex 모두 LLM이 파일을 직접 편집(Read/Glob/Edit/Write)하고, 마지막에
43
+ `obsidize.mjs`(zero-dep 순수 Node 스크립트)가 기계적 패스로 마무리한다. `wiki_*` 쓰기 의존이
44
+ 없어 두 런타임에 동형으로 동작한다 — `target:both`가 정직한 이유.
45
+ - **기본 타깃이 다르다**: Claude → 감지된 `.omc/wiki/`. Codex/OMX → 사용자가 지정한 vault 경로
46
+ (자동 감지 대상이 없음).
47
+ - **제약 프로파일이 조직 축이다.** 타깃을 감지 즉시 **OMC-managed 위키**(`index.md`+`log.md`+
48
+ 9-필드 프론트매터 시그니처)와 **일반 Obsidian vault**로 분류하고, 이 분류가 이후 전부를
49
+ 게이팅한다: 프론트매터 envelope(9-key 한정 vs 자유) · 위키링크 형식(bare-only vs piped 허용) ·
50
+ reserved files · no-rename · `aliases`(OMC=거부, 일반=작성).
51
+ - **검증은 읽기 전용**: OMC-managed일 때만 `wiki_lint`를 ToolSearch로 로드해 before/after 베이스라인
52
+ 대조에 쓴다. **쓰기 의존은 없다** — `wiki_ingest`/`wiki_add`를 호출하지 않고 파일을 직접
53
+ 정규화한다.
54
+
55
+ ## 모드
56
+
57
+ **1) bare `obsidizer [path]` — 리뷰 & 수정 (기본 = dry-run)**
58
+ 1. **Phase 0 — 타깃 감지**: 경로 인자 우선, 없으면 `.omc/wiki/` 감지, 둘 다 없으면 중단. 프로파일
59
+ 분류(위 참조). OMC-managed면 `wiki_lint` 베이스라인 기록 + `<wikiDir>/.obsidizer-hook.log` 존재
60
+ 시 읽음.
61
+ 2. **Phase 1 — 전체 vault 스캔(읽기 전용)**: 모든 `.md` glob(reserved 파일은 개념맵용으로만 읽고
62
+ 변경 대상에서 제외). 프론트매터/제목/H1/헤딩/본문 링크/`tags`/`links[]`/Related 푸터 파싱 →
63
+ 슬러그 인덱스·H1 인덱스·링크 그래프·태그 어휘·orphan 집합으로 vault 모델 구축.
64
+ 3. **Phase 2 — 의미 분석(LLM, dry-run에선 쓰기 없음)**: 페이지별로 어떤 언급을 기존 슬러그에
65
+ 링크할지, 어떤 역방향 백링크가 진짜인지, 어떤 섹션이 진짜 중복인지 판단. Canvas-MOC·inline-`::`·
66
+ MOC-hub **생성**은 0.7.0 보류 — 이번 판에서 적용하지 않음.
67
+ 4. **Phase 3 — 미리보기(기본)**: 페이지별 변경 요약 + vault 카운트 제시. `--apply` 없이는 그대로
68
+ 종료.
69
+ 5. **Phase 4 — 안전 뮤테이션(`--apply` 시)**: 스냅샷(`cp -r`) → LLM이 의미 편집 적용 → **그 다음
70
+ `obsidize.mjs`가 최종 캐노니컬라이즈 1회** 실행. reserved 파일은 손대지 않음. rename 없음.
71
+ 6. **Phase 5 — 검증**: `wiki_lint` 재실행(신규 broken-ref 0, orphan ≤ 베이스라인), 슬러그 집합
72
+ 불변 확인, OMC 프로파일이면 bare-form 전용 grep + PM-1 프론트매터 grep, 캐노니컬라이저 멱등
73
+ 자가검사.
74
+
75
+ **2) `obsidizer --enable` / `--disable` — 지속 정규화 (Claude 전용, structural)**
76
+ banker의 **첫 플러그인 선언 hook**(`hooks/hooks.json`)이 OMC 위키 쓰기 도구(`wiki_ingest`/
77
+ `wiki_add`)에 `PostToolUse`로 걸려 항상 등록되어 있다. **`<wikiDir>/.obsidizer` 플래그 파일이
78
+ 스위치**다 — `--enable`은 이 파일을 쓰고 `--disable`은 지운다, 그게 전부다(`settings.json` 변경
79
+ 없음). 플래그가 없으면 hook은 즉시 exit 0. 방금 banker를 설치/갱신한 세션이면 완료 보고에
80
+ **`/reload-plugins`** 실행을 안내한다(hot-reload 경로). Codex에서는 위 한계 섹션대로 정직한 no-op.
81
+
82
+ ## 변환 규칙
83
+
84
+ | # | 변환 | v1 동작 | Durable | 레이어 |
85
+ |---|---|---|---|---|
86
+ | T1 | H1==title 단일화, 헤딩레벨 스킵 금지, 빈줄 정규화 | 적용 | 대체로(멱등) | canonicalizer |
87
+ | T2 | `tags` 정규화(선행 `#` 제거·dedup·결정적 순서, Unicode 과잉 제거 금지) | 적용 | YES | canonicalizer |
88
+ | T3 | 본문 `[[slug]]` 보강 — 기존 슬러그만, ghost node 금지. **OMC 프로파일: bare form만** | 적용 | YES | LLM(의미) |
89
+ | T4 | `links[]` 상호성 — 누락된 진짜 역방향 백링크 추가 → de-orphan. 본문의 해소 가능한 링크는 canonicalizer가 기계적으로 흡수(monotonic — 늘기만 하고 삭제 없음) | 적용 | YES | LLM 결정·canonicalizer 기록 |
90
+ | T5 | 페이지 내 섹션 dedup — 바이트 동일 중복 블록만 축약, 고유 사실 전량 보존 | 적용 | YES(재-ingest 전까지) | LLM(의미) |
91
+ | T6 | Related 푸터(OMC 프로파일 한정) — 본문의 해소 가능한 링크를 `links[]`에 흡수(T4)한 **뒤**, 그 해소 가능한 부분집합을 정렬·dedup·bare form으로 렌더. cap은 **추가분만** 제한(기존 푸터 링크는 캡 초과여도 유지 — 아래 안전 울타리) | 적용 | YES | canonicalizer |
92
+ | T7 | rename | **안 함** | n/a | — |
93
+ | T8 | `aliases` | 일반 vault만 적용(아래 별칭 정책) · OMC 트리 거부 | 일반 YES · OMC NO(destructive) | canonicalizer |
94
+ | T9 | inline `Key:: Value` 생성 | 0.7.0 보류(생성만); v1은 보존만 | YES(이미 durable) | — |
95
+ | T10 | Canvas-MOC 사이드카 생성 | 0.7.0 보류(생성만); v1은 보존만 | YES(이미 durable) | — |
96
+ | T11 | Excalidraw | **매 버전 보존만**, 절대 생성 안 함 | n/a(스키마 OPEN) | — |
97
+ | T12 | MOC 허브 페이지 생성 | 0.7.0 보류 | (ingest되면 durable) | — |
98
+
99
+ **T3 프로필 주석 (가독성 비용, 정직히 명시).** OMC 트리에선 mid-prose 슬러그 삽입보다 **Related
100
+ 푸터(T6)를 우선한다** — piped link가 금지라 문장 중간에 `claude-code-plugin-skill-...` 같은 원시
101
+ 슬러그가 끼면 가독성이 떨어진다. 그래프 엣지는 어디 있든 동일한 값이므로 푸터가 무비용으로 같은
102
+ 엣지를 전달한다. **일반 vault**에선 piped link가 허용되므로 mid-prose 링크가 가독성 있고 오히려
103
+ 선호된다.
104
+
105
+ ## 별칭(aliases) 정책
106
+
107
+ **일반 vault → v1 적용.** 캐노니컬라이저가 쓴다(기계적 ⇒ 결정적 ⇒ 바이트 테스트 가능). 유일한
108
+ 출처는 노트 자신의 **H1** — 절대 날조하지 않는다. 아래 6조건을 **모두** 만족해야
109
+ `aliases: ["<H1 텍스트>"]`를 쓴다:
110
+
111
+ | # | 조건 | 이유 |
112
+ |---|---|---|
113
+ | 1 | 프로파일 == 일반 vault | OMC 트리에선 항상 거부 |
114
+ | 2 | 프론트매터 블록 존재 + H1 정확히 1개 | 없는 블록을 새로 만들지 않음 |
115
+ | 3 | `norm(H1) !== norm(basename)` (의미 있게 다름) | 대소문자/구두점 차이는 무가치 |
116
+ | 4 | `norm(H1)`이 다른 노트의 basename과 겹치지 않음 | 별칭이 실제 파일을 가리는 것 방지 |
117
+ | 5 | H1 텍스트가 vault 전체에서 유일함 | N-way 충돌 방지 |
118
+ | 6 | 기존 `aliases` 키 없음 | 사용자 콘텐츠 보존 + 고정점 유지 |
119
+
120
+ `norm(x)` = 소문자화 → `[^a-z0-9]+`를 `-`로 축약 → 앞뒤 `-` 제거(64자 cap **없음** — OMC
121
+ `titleToSlug`의 cap을 흉내내면 긴 제목의 유효한 별칭을 부당하게 억제한다). 항상 **큰따옴표로
122
+ quote**(YAML 이스케이핑, `[[…]]` 오파싱 방지) + **FLOW 형식**(`aliases: ["X"]`) + 기존 키·순서를
123
+ 그대로 둔 채 **맨 끝에 추가**.
124
+
125
+ **OMC-managed 트리 → 쓰지 않음. 거부이지 보류가 아니다.** 이 키는 `parseFrontmatter`가 9-key를
126
+ 재구성하는 **READ 시점**에 조용히 사라지는 **silent loss**이고, 유일한 소비처(piped link)가
127
+ `links[]`를 오염시키는 **silent corruption**이다 — 파괴적 실패 모드는 어떤 노력으로도 정당화되지
128
+ 않는다. (측정상 OMC 트리에도 유용하고 충돌 없는 별칭 후보가 실제 존재한다 — 64자 cap에 잘린 H1
129
+ 1건이 오늘 살아있고, CJK 제목의 해시 폴백은 구조적으로 발생 가능. 그럼에도 거부하는 이유는 정확히
130
+ 이것 — 유용성이 있어도 실패가 파괴적이면 쓰지 않는다.)
131
+
132
+ **`cssclasses` → 어느 프로파일에서도 쓰지 않음.** OMC 트리에선 같은 destructive strip. 일반
133
+ vault에서도 문서 안에 출처가 없는 값이라 쓰면 **날조**가 된다 — 어떤 버전에서도 정당화되지 않는다.
134
+
135
+ ## 결정성·멱등
136
+
137
+ - **고정 변환 순서**: LLM 의미 편집(T3/T4-결정/T5)이 먼저 전부 끝나고, 그 다음 캐노니컬라이저가
138
+ T1→T2→T4-기록→T6→T8-일반 순으로 적용.
139
+ - **프론트매터 순서**: OMC 프로파일은 9-필드 고정 순서 보존(`serializePage`와 다투지 않음). 일반
140
+ 프로파일은 기존 키·순서를 그대로 두고 `aliases`만 맨 끝에 조건부 추가.
141
+ - **타임스탬프 무잡음**: `created`는 절대 안 건드림. `updated`는 직렬화된 내용이 실제로 바뀔 때만
142
+ (바이트 diff 게이트) 갱신 — no-op 페이지는 `updated`도 그대로.
143
+ - **하위 정렬도 결정적**: Related 슬러그·`tags`·`links[]` 모두 안정적 키로 정렬·dedup.
144
+ - **멱등 = 구조로 보장**: 캐노니컬라이저는 고정점(정규화된 입력 → 동일 바이트)이라 2회차 실행은
145
+ 마커 없이도 diff 0. `.obsidizer-state.json`(슬러그→정규화해시)은 속도 최적화일 뿐 정확성에
146
+ 의존하지 않는 선택적 캐시.
147
+ - **보존 전용 표면**: `## Update (<ts>)` 섹션·body-inline `Key:: Value`·sibling `.canvas`·
148
+ `.excalidraw.md`는 절대 재포맷/재작성하지 않는다.
149
+ - **bare-form-only(OMC 프로파일)**: 캐노니컬라이저와 LLM 모두 `[[slug]]`만 쓴다.
150
+ - **결정성 주장의 범위**: 바이트 멱등은 **캐노니컬라이저에만** 귀속(armed hook도 동일 코드라 포함).
151
+ **의미** 판단(링크·dedup 선택)은 설계상 실행마다 달라질 수 있어 **Unknown**으로 보고하며, 절대
152
+ 바이트로 단언하지 않는다.
153
+
154
+ ## 안전 울타리
155
+
156
+ - **기본 = dry-run.** `--apply` 전까지 아무것도 쓰지 않는다. 미리보기 없는 자동 변형은 금지(위
157
+ 한계 섹션에서 hook 경로를 거부하는 이유이기도 하다).
158
+ - **`--apply` 전 필수 스냅샷**: `cp -r <vault> <vault>.bak-<ts>` (compact-wiki와 동일 선례).
159
+ - **모든 쓰기는 원자적**: 유니크 temp(`randomUUID`) + `wx`(배타 생성) + `fsync` + `rename`. 절대
160
+ in-place bare write가 아니다 — OMC 자신의 `atomicWriteFileSync`와 같은 형태를, 빌려 쓰지 않고
161
+ 독립 구현한다. **read-back CAS는 hook이 아니라 캐노니컬라이저에 있고, 두 진입점(bare 실행·armed
162
+ hook)이 함께 상속**한다: temp를 먼저 다 쓰고 → 재읽어 바뀌었으면 skip → `rename`.
163
+ **`rename`은 torn read를 확실히 없앤다. CAS는 lost update의 창을 `rename` 한 번으로 줄일 뿐
164
+ 없애지 못한다** — 그 창에 낀 writer는 여전히 덮인다. 이 잔여 race는 **해결이 아니라 수용**이다:
165
+ 닫으려면 상대가 지켜주는 lock이 필요한데 OMC의 내부 `.wiki-lock`에 기대는 건 거부한다(남의
166
+ 플러그인 내부 파일은 이름이 바뀌는 날 조용히 깨진다). 없앴다고 읽지 말 것.
167
+ - **해소 가능한 bare-form 링크는 삭제하지 않는다**(두 예외가 아래 (1)·(2)이고, 제목이 그 둘을 이미
168
+ 배제하도록 좁혀 쓴 것이다): 푸터를 다시 그리기 **전에** 본문의 해소 가능한(on-disk)
169
+ 위키링크를 `links[]`에 **합집합으로 흡수**한다(`[[a|A]]`·`[[a#S]]`도 대상 `a`로 흡수. 코드펜스
170
+ 안은 제외 — Obsidian이 엣지로 치지 않으므로 흡수하면 없는 엣지를 만드는 셈이다). 푸터는 `links[]`
171
+ 만 보고 렌더링되므로, 이 흡수가 없으면 `links[]`에 없던 **수기 푸터 링크**가 조용히 사라진다
172
+ (OMC가 쓴 푸터는 `links[]` ⊇ 푸터가 항상 성립해 안 걸리고, 사람이 더한 링크만 걸린다).
173
+ `links[]` 항목은 어느 경로에서도 지우지 않으며(정렬·dedup만), 본문 중간 링크도 손대지 않는다.
174
+ 다시 그리는 건 푸터 줄 하나뿐이고(OMC 프로파일 한정 — 일반 vault에선 푸터를 렌더링조차 하지
175
+ 않으므로 있는 그대로 둔다), 거기서 링크가 빠지는 경우는 둘 — (1) 대상 파일이 없는 **broken-ref**,
176
+ (2) OMC 프로파일에서 **bare slug가 아닌 형태**(D8: `[[Weird Name]]`은 OMC `extractWikiLinks`가
177
+ `weird-name.md`로 슬러그화해 phantom을 만든다).
178
+ (1)은 "해소 가능한 링크 삭제"에 해당하지 않는다 — 가리키는 파일이 없으니 없앨 엣지 자체가 없고,
179
+ broken-ref 0은 검증 목표다. `links[]`에 같은 대상의 올바른 슬러그가 있으면 그게 대신 렌더돼
180
+ 결과적으로 **수리**가 된다(측정 확인: 깨진 `[[…-automation-ceiling]]` → 실재하는
181
+ `[[…-automation-ceil]]`). 없으면 그냥 빠진다. 캐노니컬라이저가 하는 일이라 **OMC 트리에서도**
182
+ 일어나며, bare 실행은 `--apply` 게이트 뒤에 있지만 **무장된 hook 경로엔 미리보기가 없다**.
183
+ (2)에서 살아남는 건 **OMC 그래프 하나뿐이다** — `links: ["Weird Name.md"]`는 따옴표 친 YAML
184
+ 문자열일 뿐 Obsidian이 엣지로 읽는 `[[…]]`가 아니다. 즉 **OMC 엣지(`links[]`)는 남고 Obsidian
185
+ 엣지는 사라진다**(본문 중간에 같은 링크가 또 있으면 거긴 손대지 않으므로 그쪽 Obsidian 엣지는
186
+ 유지된다). OMC가 만드는 파일명은 전부 bare slug라 (2)의 대상은 사람이 non-slug 파일명을 직접 넣은
187
+ 경우뿐이다.
188
+ **`FOOTER_CAP`(12)은 세 번째 경우가 아니다** — 캡은 obsidizer가 푸터에 **새로 더하는 양**만
189
+ 제한하고, 이미 푸터에 있는 해소 가능한 링크는 캡을 넘겨도 전부 유지한다(수기 푸터 15개 → 15개
190
+ 그대로). 그래서 `links[]`는 20인데 푸터엔 12만 보이는 상태가 정상이다: `links[]`는 OMC 그래프의
191
+ durable 전량이고 푸터는 그 위에 얹은 **표현**이며, 캡은 표현 예산이지 엣지 예산이 아니다.
192
+ - **본문은 재작성하지 않는다**: 추가·포맷만 하고 산문을 고쳐쓰지 않는다.
193
+ - **rename 없음**: 절대적. 예쁜 표시 이름이 필요해도 하지 않는다.
194
+ - **reserved 파일은 in-place 편집하지 않는다**: `index.md`/`log.md`/`environment.md`.
195
+ - **OMC 프로파일은 9-key envelope에 갇힌다**: 그 외 프론트매터 키는 절대 추가하지 않는다(body
196
+ `::`와 `.canvas`는 이 울타리 밖 — durable하므로 허용).
197
+ - **일반 프로파일은 사용자 콘텐츠를 덮지 않는다**: 기존 `aliases` 보존, 프론트매터 키 순서 보존,
198
+ 없는 프론트매터 블록을 새로 만들지 않음.
199
+ - **`%%obsidizer:ignore%%` 옵트아웃**: 파일 어디든 이 마커가 있으면 **프로파일 무관** 그 페이지를
200
+ 통째로 건너뛴다 — 읽기-수정-쓰기를 아예 하지 않고 리포트에 `ignored`로 집계한다. 건너뛴 페이지도
201
+ vault 모델에는 그대로 남는다(슬러그가 링크를 해소하고, H1이 다른 노트의 별칭 조건을 계속
202
+ 게이팅한다) — "고치지 마라"는 "없는 셈 쳐라"가 아니다. Obsidian은 `%%…%%`를 주석으로 렌더링하므로
203
+ 마커는 화면에 보이지 않는다.
204
+ - **사실을 날조하지 않는다**: 존재하지 않는 슬러그로 링크하지 않고, 별칭은 H1에서만 파생하며,
205
+ `cssclasses`·Excalidraw·Canvas·MOC를 절대 지어내지 않는다 — OPEN 항목은 보존-전용/미적용 +
206
+ Unknown 보고로 degrade.
207
+
208
+ ## 검증 (보고 의무)
209
+
210
+ 4-field 보고:
211
+ - **변경**: 적용된 변환 요약(어떤 페이지에 어떤 T#).
212
+ - **Evidence**: 카운트(스캔한 페이지 수·추가된 링크 수·해소된 orphan 수·병합된 중복 수·추가된
213
+ alias 수·불변 파일 수) + `wiki_lint` before→after.
214
+ - **검증**: 신규 broken-ref 0, rename 0, OMC 프로파일이면 bare-form grep 0 + PM-1 프론트매터
215
+ grep 0, 캐노니컬라이저 멱등 자가검사(2회 실행 diff 0).
216
+ - **Unknown**: 0.7.0 보류 기능·진짜 관계가 없어 남겨둔 orphan·OPEN 한계에 걸린 항목·바이트로
217
+ 단언하지 않는 의미 판단 편차.
218
+
219
+ **hook 건강 상태 보고(무장 시).** Phase 0에서 `<wikiDir>/.obsidizer-hook.log`를 읽는다: 로그
220
+ 없음/비어있음 + 플래그 존재 → Evidence("hook 무장, 에러 없음"). 로그에 항목 있음 →
221
+ Unknown("hook이 N회 에러 — 이 페이지들은 이번 bare 실행이 대신 정규화함, `--enable` 신뢰 전 조사
222
+ 필요"). 플래그 없음 → Evidence("hook 비무장"). hook은 모든 에러에서 exit 0이라 이 로그가 **유일한**
223
+ 발견 경로다.
224
+
225
+ ## 함정
226
+
227
+ - **비-bare 위키링크가 OMC 트리에서 `links[]`를 오염시킨다** — `[[slug|display]]`,
228
+ `[[slug#heading]]`은 Obsidian에선 완전히 정상이지만, OMC `extractWikiLinks`는 inner text
229
+ **전체**를 슬러그화한다(`titleToSlug("slug|display")` → `slug-display.md`). obsidizer 자신의
230
+ 검증은 통과하고(Edit/Write로 쓰고 OMC 파서를 재실행하지 않으므로) **나중에
231
+ `append_wiki`/`wiki_ingest`가 같은 페이지를 건드릴 때** broken-ref로 터진다. 규칙은 "inner text가
232
+ 정확히 bare slug인 형태만"이라는 whitelist다. **`![[slug]]`는 이 함정에 안 걸린다** — `!`가
233
+ 정규식 매치 밖이라 `titleToSlug("slug")` = 올바른 `slug.md`로 해석된다(측정 확인). OMC 트리에서
234
+ `![[…]]`를 피하는 이유는 오염이 아니라 **transclusion 렌더링**(대상 페이지 전문이 인라인됨)
235
+ 때문이다.
236
+ - **프론트매터 whitelist는 거짓 내구성을 준다** — `aliases`/`cssclasses`는 OMC
237
+ `parseFrontmatter`가 9-key를 재구성하는 **READ 시점**에 조용히 사라진다. 쓴 직후엔 보이지만
238
+ 다음 `wiki_*` 쓰기에서 증발한다. OMC 트리에는 절대 쓰지 않는다.
239
+ - **rename 절대 금지, 예쁜 표시 이름은 OMC 트리에서 어떤 수단으로도 불가능** — `wiki_rename`이
240
+ 없어 파일명을 바꾸면 모든 참조자의 `links[]`가 broken-ref가 된다. 예쁜 표시 이름은 rename도,
241
+ alias도, piped link도 전부 막혀 있어 OMC 트리에서 **어떤 방법으로도 전달 불가능**하다 — 이건
242
+ 실제 한계이지 숨길 일이 아니다.
243
+ - **MOC/de-orphan 링크 인플레 금지** — v1은 MOC 허브를 생성하지 않는다. 태그가 같다고 일괄 링크
244
+ 하지 않는다. 진짜 관계가 없는 orphan은 **orphan인 채로 두고 보고한다** — 억지로 연결하지 않는다.
245
+ - **Excalidraw는 절대 날조하지 않는다** — `.excalidraw.md` 스키마가 미검증(OPEN)이므로 모든
246
+ 버전에서 **보존만** 한다. 존재하는 파일을 그대로 두는 것 외엔 손대지 않는다.
247
+ - **body-`::`와 sibling `.canvas`는 durable — 지우지 마라** — `listPages`는 `.md`만 열거하므로
248
+ `.canvas`는 OMC에 안 보이고, `mergePage`/`serializePage`는 본문을 그대로 보존하므로 body-inline
249
+ `Key:: Value`도 안전하다. v1이 이들을 **생성**하지는 않지만(0.7.0 보류), 이미 있는 것은 절대
250
+ 재작성·삭제하지 않는다.
251
+ - **결정성은 캐노니컬라이저 한정이다** — 바이트 멱등을 주장할 수 있는 건 `obsidize.mjs` 뿐이다.
252
+ LLM이 내린 의미 판단(어떤 링크가 진짜인지 등)은 실행마다 달라질 수 있어 **Unknown**으로
253
+ 보고하며, 절대 바이트로 단언하지 않는다.
254
+
255
+ ARGUMENTS: [path] [--apply] | --enable | --disable