hypomnema 1.5.0 → 1.6.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.
@@ -11,7 +11,7 @@
11
11
  "name": "hypo",
12
12
  "source": "./",
13
13
  "description": "LLM-native personal wiki — session-aware knowledge base for Claude Code",
14
- "version": "1.5.0",
14
+ "version": "1.6.0",
15
15
  "homepage": "https://github.com/sk-lim19f/Hypomnema"
16
16
  }
17
17
  ]
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hypo",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "LLM-native personal wiki system — session-aware knowledge base for Claude Code",
5
5
  "author": {
6
6
  "name": "sk-lim19f",
package/README.ko.md CHANGED
@@ -15,7 +15,7 @@
15
15
 
16
16
  Claude Code를 위한 LLM 네이티브 개인 위키. 쓸수록 지식이 쌓입니다.
17
17
 
18
- _Claude에게 기록을 맡기고, 그 기록이 실제로 쌓이는지 측정하세요._
18
+ Claude에게 기록을 맡기고, 그 기록이 실제로 쌓이는지 측정하세요.
19
19
 
20
20
  [빠른 시작](#빠른-시작) • [다른 시스템과의 비교](#다른-시스템과의-비교) • [설계 결정](#설계-결정) • [기능](#기능) • [아키텍처](docs/ARCHITECTURE.md) • [기여](docs/CONTRIBUTING.md)
21
21
 
@@ -25,14 +25,21 @@ Andrej Karpathy의 "LLM 네이티브 위키" 스케치에서 출발했습니다.
25
25
 
26
26
  ### 지금 어디까지 자동인가
27
27
 
28
- 현재 릴리스는 v1.5.0이고, 머신 안정성과 일상의 마찰로 초점을 옮겼습니다: git 동기화로 여러 머신이 공유하는 볼트에서 `cwd`-first resume올바른 프로젝트를 찾고, 코드 레포에서 작업하는 세션이 매번 위키를 다시 찾는 대신 위키 위치를 안내받으며, 세션 마무리가 무관한 lint 부채를 다시 나열하거나 수백 경고를 컨텍스트에 쏟지 않습니다. 그 앞 v1.4.x는 세션 마무리 경로를 견고하게 다듬었습니다. 위키 작업(자료 정리·검색·세션 마무리)은 아직 `/hypo:*` 명령어를 직접 입력해 시작합니다. v2의 목표는 Claude가 시키지 않아도 위키를 읽고 쓰고 합성하는 완전 자율 동작이고, 지금은 그쪽으로 가는 중입니다. v1.1.0이 위키가 세션에서 얼마나 쓰였는지 재는 관측성 점수를 넣었습니다. v1.2.0은 자동으로 동작하는 영역 가지를 추가했습니다.
28
+ 현재 릴리스는 v1.6.0입니다. 역방향 확장 capture를 완성했습니다. `hypomnema capture`(그리고 `/hypo:capture` 슬래시 커맨드)`~/.claude/` 아래에 보통 방식으로 만든 command·agent·hook을 위키로 끌어와, forward-sync가 다른 머신에 원래 이름 그대로 전파합니다. 손으로 만든 확장의 "한 머신에서 등록, 다른 머신에서 동기화" 갭을 데몬 없이 메웁니다. 그 앞 v1.5.1은 위키가 자기 신선도를 알리는 방식을 다듬었습니다. `verify_by_date`가 지난 페이지는 주입되는 순간 `[STALE ...]`로 표시되고, lookup 사용을 로컬에서 추적해 링크됐지만 번도 주입되지 않은 페이지를 crystallize가 cold 후보로 표면화하며, 표준 `session | project: title` 콜론 로그 포맷을 stale로 오진하던 세션 마무리도 견고해졌습니다. 다시 그 앞 v1.5.0은 머신 안정성에 집중했습니다. git 동기화 볼트의 `cwd`-first resume와 코드 레포 세션에 위키 위치를 안내하는 것이었습니다.
29
+
30
+ 위키 작업(자료 정리·검색·세션 마무리)은 아직 `/hypo:*` 명령이나 자연어로 시작합니다. v2의 목표는 Claude가 시키지 않아도 위키를 읽고 쓰고 합성하는 완전 자율 동작이고, 지금 그쪽으로 가는 중입니다.
31
+
32
+ 이미 자동으로 도는 영역은 다음과 같습니다. v1.1.0은 위키가 한 세션에서 얼마나 쓰였는지 재는 관측성 점수를 넣었고, v1.2.0은 네 영역을 자동화했습니다.
29
33
 
30
34
  - 피드백 한 곳만 고치면 나머지는 따라옵니다. `pages/feedback/`에 한 번 적으면 위키가 `MEMORY.md`와 `~/.claude/CLAUDE.md`의 `<learned_behaviors>` 블록을 자동으로 갱신합니다(단방향 projection).
31
35
  - 확장 파일도 함께 동기화. 위키 안 `~/hypomnema/extensions/{agents,commands,hooks,skills}/`에 둔 파일을 `~/.claude/`에 자동 반영합니다. `--codex`를 붙이면 `hooks`·`commands`는 `~/.codex/`에도 반영하지만, `agents`와 `skills`는 Claude 전용이라 건너뜁니다.
36
+ - 역방향 capture는 반대로 갑니다. `hypomnema capture`(또는 `/hypo:capture`)는 `~/.claude/{commands,agents}/`에 보통 방식으로 만든 command·agent나 `~/.claude/settings.json`에 등록한 정규형 hook을 위키로 끌어와, 이후 다른 머신에 원래 이름 그대로 동기화되게 합니다. hook은 정규형으로 무손실 라운드트립되는 것만 capture하고, 나머지는 이유와 함께 건너뜁니다. capture는 명시적이며 내용이 다른 위키 파일은 덮어쓰지 않습니다. skill은 아직 capture 대상이 아닙니다.
32
37
  - 프로젝트 자동 생성. 프로젝트 표식(`package.json`·`Cargo.toml` 등)이 있는 git 저장소로 `cd` 했는데 대응하는 위키 프로젝트가 없으면 만들지 물어봅니다.
33
38
  - 세션 종료 자동 정리와 `/clear` 복구. 의미 있는 세션이 끝나면 "마무리 메모를 짧게 남길까요?"가 자동으로 뜹니다. 마무리하지 않은 채 `/clear`를 입력해도 다음 세션 시작 때 이어서 정리할 수 있습니다.
34
39
 
35
- 버전별로 무엇이 바뀌었는지는 [CHANGELOG](CHANGELOG.md)에 있습니다. 한 가지만 알아 두면, `hypomnema upgrade --apply`는 사용자가 직접 편집한 `SCHEMA.md`를 덮어쓰지 않습니다. 스키마가 올라가면 위키 루트에 마이그레이션 보고서를 써 주고, 실제 반영은 사용자가 직접 결정합니다(코드에서 _Option C_로 부르는 정책).
40
+ 버전별로 무엇이 바뀌었는지는 [CHANGELOG](CHANGELOG.md)에 있습니다.
41
+
42
+ 업그레이드 정책 하나만 미리 알아 두세요. `hypomnema upgrade --apply`는 사용자가 직접 편집한 `SCHEMA.md`를 덮어쓰지 않습니다. 스키마가 올라가면 위키 루트에 마이그레이션 보고서만 써 주고, 실제 반영은 사용자가 직접 결정합니다(코드에서 Option C라고 부르는 정책).
36
43
 
37
44
  ---
38
45
 
@@ -75,6 +82,8 @@ hypomnema
75
82
 
76
83
  나머지는 훅이 합니다. 자동 staging, 자동 commit/push, 세션 상태 주입, 관련 노트 룩업까지 알아서 처리합니다.
77
84
 
85
+ > 명령어를 외울 필요는 없습니다. 하려는 일을 평범한 말로 적으면 대화 내용에 맞는 스킬이 슬래시 명령 없이 자동으로 뜹니다. "이 글 위키에 저장해줘"는 `ingest`, "내가 X에 대해 뭘 알고 있지?"는 `query`, "이번 세션 마무리하자"는 `crystallize`로 이어집니다. 동작 원리는 [Claude Agent Skills](#claude-agent-skills) 참고.
86
+
78
87
  > 여러 기기 동기화: 위키는 처음부터 git 저장소입니다. remote만 한 번 연결해 두면 이후 매 세션 종료 때 `Stop` 훅이 동기화를 맞춰 줍니다.
79
88
 
80
89
  ---
@@ -138,7 +147,7 @@ Hypomnema ───► 합성 · 마크다운 · git · 훅 · 로컬
138
147
  |---|---|
139
148
  | frontmatter(프런트매터) | 마크다운 페이지 맨 위의 YAML 블록. `title`, `type`, `tags` 같은 항목이 들어갑니다 |
140
149
  | wikilink(위키링크) | `[[페이지-슬러그]]` 형태의 페이지 간 교차 참조. `lint`가 유효성을 확인합니다 |
141
- | ADR | "Architecture Decision Record". 자명하지 않은 설계 결정을 __ 했는지 짧게 적는 마크다운 페이지 |
150
+ | ADR | "Architecture Decision Record". 자명하지 않은 설계 결정을 왜 했는지 짧게 적는 마크다운 페이지 |
142
151
  | schema(스키마) | `SCHEMA.md`에 정의된 타입 분류와 필수 항목 규칙. 페이지가 유효한지 판단하는 기준 |
143
152
  | lint | 읽기 전용 검증기(`hypomnema lint`). 프런트매터·위키링크·스키마를 한꺼번에 점검 |
144
153
  | projection(투영) | 한 방향 자동 파생. `pages/feedback/*.md` → `MEMORY.md`와 CLAUDE.md `<learned_behaviors>` |
@@ -148,7 +157,7 @@ Hypomnema ───► 합성 · 마크다운 · git · 훅 · 로컬
148
157
  | `hot.md` | 프로젝트별 캐시. "방금 무엇을 했는지"(직전 세션의 핵심) |
149
158
  | `session-state.md` | 프로젝트별 캐시. "다음에 무엇을 할지"(다음 세션 시작 때 주입되는 이어받기 데이터) |
150
159
  | `.hypoignore` | 모든 콘텐츠 주입 훅과 `ingest`에서 제외할 경로(글롭 패턴) |
151
- | 관측성 점수(observability score) | 세션별 측정값(ingest·query·session-close·citation 비율). 위키가 실제로 쓰였는지 보여줍니다 |
160
+ | 관측성 점수(observability score) | 세션별 측정값(search·ingest·feedback 활동). 위키가 실제로 쓰였는지 보여줍니다 |
152
161
  | manifest | 설치 스크립트가 쓰는 작은 JSON. 어떤 파일을 어떤 SHA로 설치했는지 기록 |
153
162
  | `additionalContext` | Claude Code 훅이 프롬프트에 컨텍스트를 끼워 넣는 필드. 콘텐츠 주입 훅의 출력 위치 |
154
163
  | 바이트 동일(byte-equal) | `--apply` 전후가 비트 단위로 같은 파일. "건드리지 않았다"의 가장 강한 보장 |
@@ -165,7 +174,7 @@ Hypomnema ───► 합성 · 마크다운 · git · 훅 · 로컬
165
174
 
166
175
  ### 1. 왜 청크 기반 RAG가 아니라 합성인가
167
176
 
168
- RAG는 _낯선_ 코퍼스에 강합니다. 100만 페이지짜리 법률 아카이브를 주면 관련 단편을 잘 찾아냅니다. 그런데 _개인_ 지식의 실패는 정반대 지점에서 옵니다.
177
+ RAG는 낯선 코퍼스에 강합니다. 100만 페이지짜리 법률 아카이브를 주면 관련 단편을 잘 찾아냅니다. 그런데 내가 아는 개인 지식의 실패는 정반대 지점에서 옵니다.
169
178
 
170
179
  - 코퍼스가 작은 대신 중복이 많습니다(같은 주제의 글 3편).
171
180
  - 사용자는 단편이 아니라 관점을 원합니다.
@@ -175,7 +184,7 @@ Hypomnema는 청크가 아니라 페이지를 지식 단위로 봅니다. 새
175
184
 
176
185
  ### 2. 왜 독자 포맷이 아니라 마크다운 + git인가
177
186
 
178
- 개인 지식 베이스는 특정 도구 하나보다 오래 살아남아야 합니다. 마크다운은 살아남습니다. git도 살아남습니다. 둘 다 어떤 LLM이든 읽고 오프라인에서 동작합니다. 30년치 도구 생태계가 받쳐 줍니다. 스택은 일부러 _지루하게_ 골랐습니다. 흥미로운 부분은 _그 위에서 Claude가 무엇을 하느냐_니까요.
187
+ 개인 지식 베이스는 특정 도구 하나보다 오래 살아남아야 합니다. 마크다운은 살아남습니다. git도 살아남습니다. 둘 다 어떤 LLM이든 읽고 오프라인에서 동작합니다. 30년치 도구 생태계가 받쳐 줍니다. 스택은 일부러 지루하게 골랐습니다. 흥미로운 부분은 그 위에서 Claude가 무엇을 하느냐에 있으니까요.
179
188
 
180
189
  ### 3. 왜 수동 명령이 아니라 라이프사이클 훅인가
181
190
 
@@ -189,7 +198,7 @@ Hypomnema는 청크가 아니라 페이지를 지식 단위로 봅니다. 새
189
198
  | `PostToolUse` (Write/Edit) | `git add` |
190
199
  | `Stop` | `git pull --rebase && git commit && git push` |
191
200
 
192
- 설치하고 나면 위키를 _관리_하는 일을 그만두게 됩니다. 그냥 쌓입니다.
201
+ 설치하고 나면 위키를 관리하는 일을 그만두게 됩니다. 그냥 쌓입니다.
193
202
 
194
203
  ### 4. 왜 재개에 `hot.md` 캐시를 쓰는가
195
204
 
@@ -197,7 +206,7 @@ Hypomnema는 청크가 아니라 페이지를 지식 단위로 봅니다. 새
197
206
 
198
207
  ### 5. 왜 feedback → behavior 파이프라인인가
199
208
 
200
- 대부분의 AI 도구는 교정을 _지금 대화에 한해서만_ 받아들입니다. 다음으로 이어지지 않습니다. Hypomnema는 모든 `/hypo:feedback`을 `pages/feedback/`으로 흘려보냅니다. 오래 갈 규칙은 `CLAUDE.md`의 `<learned_behaviors>` 블록으로 승격합니다. 이후 모든 세션, 위키를 pull하는 모든 기기에서 살아 있습니다.
209
+ 대부분의 AI 도구는 교정을 지금 대화에 한해서만 받아들입니다. 다음으로 이어지지 않습니다. Hypomnema는 모든 `/hypo:feedback`을 `pages/feedback/`으로 흘려보냅니다. 오래 갈 규칙은 `CLAUDE.md`의 `<learned_behaviors>` 블록으로 승격합니다. 이후 모든 세션, 위키를 pull하는 모든 기기에서 살아 있습니다.
201
210
 
202
211
  ### 6. 왜 API 키도, 벡터 DB도, 외부 서비스도 없는가
203
212
 
@@ -205,7 +214,7 @@ Hypomnema는 청크가 아니라 페이지를 지식 단위로 봅니다. 새
205
214
 
206
215
  ### 7. 왜 privacy mode 플래그가 아니라 `.hypoignore`인가
207
216
 
208
- v1.0에서는 `personal / shared / public` 3-mode를 만들었습니다. 현실과 부딪히자 바로 무너졌습니다. 모든 privacy 결정은 결국 _경로 단위_ 질문이었고, 그건 단일 파일(`.hypoignore`) 하나로 처리됩니다. v1.1에서 mode 개념을 통째로 지웠습니다. 파일 하나가 모든 결정을 담는 단일 원천 구조입니다.
217
+ v1.0에서는 `personal / shared / public` 3-mode를 만들었습니다. 현실과 부딪히자 바로 무너졌습니다. 모든 privacy 결정은 결국 경로 단위 질문이었고, 그건 단일 파일(`.hypoignore`) 하나로 처리됩니다. v1.1에서 mode 개념을 통째로 지웠습니다. 파일 하나가 모든 결정을 담는 단일 원천 구조입니다.
209
218
 
210
219
  ---
211
220
 
@@ -217,9 +226,9 @@ v1.0에서는 `personal / shared / public` 3-mode를 만들었습니다. 현실
217
226
 
218
227
  | 명령어 | 하는 일 | 언제 쓰나 |
219
228
  |---|---|---|
220
- | `/hypo:ingest` | 원본을 `sources/`에 보관하고 Claude가 `pages/`에 구조화된 페이지를 합성. 셸 헬퍼(`scripts/ingest.mjs`)는 읽기 전용이라 아직 인제스트 안 된 소스를 _목록만_ 출력 | 보관할 가치가 있는 글을 읽었을 때 |
229
+ | `/hypo:ingest` | 원본을 `sources/`에 보관하고 Claude가 `pages/`에 구조화된 페이지를 합성. 셸 헬퍼(`scripts/ingest.mjs`)는 읽기 전용이라 아직 인제스트 안 된 소스를 목록만 출력 | 보관할 가치가 있는 글을 읽었을 때 |
221
230
  | `/hypo:query` | BM25 검색 + LLM 합성 + `[[wikilink]]` 인용 | 자기 노트에 근거한 답이 필요할 때 |
222
- | `/hypo:crystallize` | 세션 마무리 체크리스트(1~6단계) 실행. 요청하면 초안 합성(7~11단계)까지 | 단순하지 않은 세션을 마칠 때 |
231
+ | `/hypo:crystallize` | 세션 마무리 체크리스트(1-6단계) 실행. 요청하면 초안 합성(7-11단계)까지 | 단순하지 않은 세션을 마칠 때 |
223
232
  | `/hypo:resume` | 활성 프로젝트의 가장 최근 세션 상태 불러오기 | 잠시 미뤄둔 프로젝트로 돌아올 때 |
224
233
  | `/hypo:feedback` | AI 행동 교정을 기록. 영구 규칙 승격 후보가 됨 | Claude가 잘못했을 때, 또는 반대로 잘했을 때 |
225
234
  | `/hypo:verify` | `verify_by` 프런트매터가 붙은 페이지를 점검 | 시간이 지나 옛 정보가 됐을 수 있을 때 |
@@ -248,8 +257,6 @@ v1.0에서는 `personal / shared / public` 3-mode를 만들었습니다. 현실
248
257
 
249
258
  모든 훅은 위키 루트를 `HYPO_DIR` 환경변수 → `hypo-config.md` 스캔 → `~/hypomnema` 기본값 순으로 찾고, `hypo-shared.mjs`(`hooks.json`의 `shared` 필드로 선언)를 공유합니다.
250
259
 
251
- 이와 별도로 `SessionStart` 훅은 npm 레지스트리와 Claude Code 플러그인 마켓플레이스를 백그라운드에서 확인합니다(세션 시작을 막지 않습니다). 새 버전이 올라와 있으면 다음 세션 시작 때 "Update available!" 안내가 한 줄 뜹니다. `HYPO_NO_UPDATE_CHECK=1`, `NO_UPDATE_NOTIFIER=1`을 주거나 `CI=true` 환경이면 건너뜁니다.
252
-
253
260
  ### 셋업 & 유지보수
254
261
 
255
262
  | 명령어 | 목적 |
@@ -261,9 +268,20 @@ v1.0에서는 `personal / shared / public` 3-mode를 만들었습니다. 현실
261
268
  | `/hypo:stats` | 위키 통계 |
262
269
  | `/hypo:audit` | observability 감사 (세션별 메트릭, 주간 보고서) |
263
270
 
271
+ > 업데이트 알림: `SessionStart` 훅이 npm 레지스트리와 Claude Code 플러그인 마켓플레이스를 백그라운드에서 확인합니다(세션 시작을 막지 않습니다). 새 버전이 올라와 있으면 다음 세션 시작 때 "Update available!" 안내가 한 줄 뜹니다. `HYPO_NO_UPDATE_CHECK=1`, `NO_UPDATE_NOTIFIER=1`을 주거나 `CI=true` 환경이면 건너뜁니다.
272
+
264
273
  ### Claude Agent Skills
265
274
 
266
- 합성이 핵심인 명령어(`ingest`, `query`, `crystallize`, `lint`, `verify`, `graph`)는 `skills/<name>/SKILL.md`로도 등록돼 있습니다. 대화 내용이 해당 스킬의 `description`과 맞으면 Claude Agent Skills 메커니즘이 슬래시 명령 없이도 자동으로 호출합니다.
275
+ 합성이 핵심인 명령어(`ingest`, `query`, `crystallize`, `lint`, `verify`, `graph`)는 `skills/<name>/SKILL.md`로도 등록돼 있습니다. 대화 내용이 해당 스킬의 `description`과 맞으면 Claude Agent Skills 메커니즘이 슬래시 명령 없이도 자동으로 호출합니다. 즉 명령어를 정확히 몰라도 하려는 일을 말로 적으면 됩니다.
276
+
277
+ | 이렇게 말하면 | 트리거되는 스킬 |
278
+ |---|---|
279
+ | "이 링크(글) 위키에 저장해줘" | `ingest` |
280
+ | "내가 X에 대해 뭘 알고 있지?", "예전에 내린 결정 찾아줘" | `query` |
281
+ | "이번 세션 마무리하자", "오늘 작업 정리해줘" | `crystallize` |
282
+ | "이 페이지들 링크 안 깨졌는지 확인해줘" | `lint` |
283
+
284
+ 슬래시 명령은 트리거가 확실할 때, 자연어는 흐름을 끊고 싶지 않을 때 쓰면 됩니다. 둘은 같은 스킬로 이어집니다.
267
285
 
268
286
  ---
269
287
 
@@ -302,7 +320,7 @@ E. 멈춘 프로젝트 재개.
302
320
  - 자격증명·토큰·비밀. `.hypoignore`로 민감 경로를 제외하세요
303
321
  - 이번 세션의 일시적 작업 목록. 대화 안의 작업 목록을 쓰세요
304
322
  - 레포에서 도출되는 코드 패턴. `git log`, `grep`이 원본 기준입니다
305
- - 관리 주체가 따로 있는 정보 (Jira, Confluence, API 문서). 미러가 아니라 _합성본_만 인제스트하세요
323
+ - 관리 주체가 따로 있는 정보 (Jira, Confluence, API 문서). 미러가 아니라 합성한 결과만 인제스트하세요
306
324
 
307
325
  ---
308
326
 
@@ -345,9 +363,9 @@ E. 멈춘 프로젝트 재개.
345
363
 
346
364
  `.hypoignore`는 훅이 무시할 경로를 정합니다 (기본: `*.pdf`, `*.zip`, `*.pem`, `*.env` 등). 직접 편집하면 됩니다. privacy mode 플래그는 없습니다. 파일 하나가 모든 결정을 담는 단일 원천 구조입니다.
347
365
 
348
- > 모델 사업자에게 전송되는 범위: Hypomnema 훅은 위키 본문을 Claude Code의 추가 컨텍스트(`additionalContext`)에 실어 보내고, 이 내용은 프롬프트의 일부로 Claude 모델 사업자에게 전송됩니다. `.hypoignore`에 등록된 경로는 모든 주입 훅(`hypo-file-watch`, `hypo-session-start`, `hypo-cwd-change`, `hypo-lookup`)과 `ingest`에서 제외되지만, 등록하지 _않은_ 파일은 전송 대상입니다. (`hypo-auto-stage`/`hypo-auto-commit`은 git 스테이징용 훅이라 컨텍스트를 주입하지는 않지만, 스테이징 판단에도 `.hypoignore`를 참고합니다.) 비밀 정보는 위키에 두지 마시고, `HYPO_DIR` 아래에 민감한 내용을 저장하기 전에 `.hypoignore` 패턴을 먼저 점검하세요.
366
+ > 모델 사업자에게 전송되는 범위: Hypomnema 훅은 위키 본문을 Claude Code의 추가 컨텍스트(`additionalContext`)에 실어 보내고, 이 내용은 프롬프트의 일부로 Claude 모델 사업자에게 전송됩니다. `.hypoignore`에 등록된 경로는 모든 주입 훅(`hypo-file-watch`, `hypo-session-start`, `hypo-cwd-change`, `hypo-lookup`)과 `ingest`에서 제외되지만, 등록하지 않은 파일은 전송 대상입니다. (`hypo-auto-stage`/`hypo-auto-commit`은 git 스테이징용 훅이라 컨텍스트를 주입하지는 않지만, 스테이징 판단에도 `.hypoignore`를 참고합니다.) 비밀 정보는 위키에 두지 마시고, `HYPO_DIR` 아래에 민감한 내용을 저장하기 전에 `.hypoignore` 패턴을 먼저 점검하세요.
349
367
 
350
- > git sync 범위: Hypomnema는 `~/hypomnema/` 위키 자체만 git sync합니다. 단 `init` / `upgrade`는 `~/.claude/` 안의 관리 대상 영역(Hypomnema 자체 hook `~/.claude/hooks/`, 슬래시 커맨드 `~/.claude/commands/hypo/`, `settings.json` 등록)을 설치·SHA 추적하고, v1.2.0 extensions companion sync로 위키의 `~/hypomnema/extensions/`에 둔 `agents/`·`commands/`·`hooks/`·`skills/`도 자동 미러링합니다(`--codex`면 `hooks`·`commands` 부분집합만 `~/.codex/`로). 이 관리 대상 _바깥_의 `~/.claude/` 콘텐츠는 일부러 관리하지 않습니다. 위키를 거치지 않는 기타 agent/skill, 머신 고유 `settings.local.json` 같은 일반 Claude Code 설정의 기기 간 동기화는 [chezmoi](https://www.chezmoi.io/) 같은 별도 dotfiles 매니저를 권합니다.
368
+ > git sync 범위: Hypomnema는 `~/hypomnema/` 위키 자체만 git sync합니다. 단 `init` / `upgrade`는 `~/.claude/` 안의 관리 대상 영역(Hypomnema 자체 hook `~/.claude/hooks/`, 슬래시 커맨드 `~/.claude/commands/hypo/`, `settings.json` 등록)을 설치·SHA 추적하고, v1.2.0 extensions companion sync로 위키의 `~/hypomnema/extensions/`에 둔 `agents/`·`commands/`·`hooks/`·`skills/`도 자동 미러링합니다(`--codex`면 `hooks`·`commands` 부분집합만 `~/.codex/`로). 이 관리 대상 바깥에 있는 `~/.claude/` 콘텐츠는 일부러 관리하지 않습니다. 위키를 거치지 않는 기타 agent/skill, 머신 고유 `settings.local.json` 같은 일반 Claude Code 설정의 기기 간 동기화는 [chezmoi](https://www.chezmoi.io/) 같은 별도 dotfiles 매니저를 권합니다.
351
369
 
352
370
  ### `/hypo:*` 커맨드는 어디서 오는가
353
371
 
package/README.md CHANGED
@@ -15,7 +15,7 @@ English | [한국어](README.ko.md)
15
15
 
16
16
  LLM-native personal wiki for Claude Code. Knowledge that compounds.
17
17
 
18
- _Make Claude take notes, and measure whether it actually does._
18
+ Make Claude take notes, and measure whether it actually does.
19
19
 
20
20
  [Quick Start](#quick-start) • [How It Compares](#how-it-compares) • [Design Decisions](#design-decisions) • [Features](#features) • [Architecture](docs/ARCHITECTURE.md) • [Contributing](docs/CONTRIBUTING.md)
21
21
 
@@ -25,14 +25,21 @@ New to the terms below? Keep the [Term decoder](#term-decoder) open in another t
25
25
 
26
26
  ### Where automation stands today
27
27
 
28
- The current release is v1.5.0, which turns to cross-machine reliability and everyday friction: `cwd`-first resume now finds the right project on a git-synced vault shared across machines, a session working in a code repo is told where the wiki lives instead of re-discovering it each time, and session close stops re-listing unrelated lint debt or dumping hundreds of warnings into context. The v1.4.x line hardened the session-close path before it. Wiki work (ingest, query, session-close) still runs on explicit `/hypo:*` commands. The v2 goal is full autonomy: Claude reading, writing, and synthesizing the wiki without being asked, which is the direction this is heading. v1.1.0 shipped the observability score that measures how often the wiki is actually used per session. v1.2.0 added four autonomous lanes on top:
28
+ The current release is v1.6.0. It completes reverse extension capture: `hypomnema capture` (and the `/hypo:capture` slash command) pulls a command, agent, or hook you created the normal way under `~/.claude/` into the wiki, so forward-sync propagates it to your other machines under its original name. That closes the "register on one machine, sync on another" gap for extensions you author by hand, without a daemon. The v1.5.1 release before it sharpened how the wiki signals its own freshness: a page whose `verify_by_date` has passed is flagged `[STALE ...]` the moment it is injected, lookup usage is tracked locally so crystallize can surface a linked-but-never-injected page as a cold candidate, and session close was made robust to the standard `session | project: title` colon log format that had been misread as stale. The v1.5.0 line before that turned to cross-machine reliability: `cwd`-first resume on a git-synced vault and a code-repo session told where the wiki lives.
29
+
30
+ Wiki work (ingest, query, session-close) still starts from an explicit `/hypo:*` command or plain language. The v2 goal is full autonomy: Claude reading, writing, and synthesizing the wiki without being asked, which is the direction this is heading.
31
+
32
+ The lanes that already run on their own: v1.1.0 shipped the observability score that measures how often the wiki is used per session, and v1.2.0 added four automated areas on top.
29
33
 
30
34
  - Edit feedback in one place and the rest follows. `pages/feedback/` is the single source of truth for behavior corrections, and Hypomnema derives `MEMORY.md` and the `<learned_behaviors>` block inside `~/.claude/CLAUDE.md` from it automatically.
31
35
  - Extensions sync alongside. Anything under `~/hypomnema/extensions/{agents,commands,hooks,skills}/` is mirrored into `~/.claude/` automatically. The `--codex` flag also mirrors `hooks` and `commands` into `~/.codex/`; `agents` and `skills` are Claude-only and skipped on purpose.
36
+ - Reverse capture goes the other way. `hypomnema capture` (or `/hypo:capture`) pulls a command or agent you made the normal way under `~/.claude/{commands,agents}/`, or a canonical hook you registered in `~/.claude/settings.json`, into the wiki, so it then syncs to your other machines under its original name. Hooks are captured only when they round-trip losslessly to the canonical form; others are skipped with a reason. Capture is explicit and never overwrites a differing wiki file. Skills are not captured yet.
32
37
  - Auto-project creation. When you `cd` into a git repo with a project marker (`package.json`, `Cargo.toml`, etc.) and no matching wiki project exists, Hypomnema offers to scaffold one.
33
38
  - Session-close cleanup and `/clear` recovery. After a non-trivial session, a "save a minimal session-close note?" prompt appears automatically; a `/clear` after a forgotten close is detected and recovered at the next session start.
34
39
 
35
- What changed per version lives in the [CHANGELOG](CHANGELOG.md). One policy worth knowing up front: `hypomnema upgrade --apply` never overwrites a `SCHEMA.md` you have edited. When the schema bumps, upgrade writes a migration report into the wiki root and leaves the actual changes for you to apply (the policy the code calls _Option C_).
40
+ What changed per version lives in the [CHANGELOG](CHANGELOG.md).
41
+
42
+ One upgrade policy worth knowing up front: `hypomnema upgrade --apply` never overwrites a `SCHEMA.md` you have edited. When the schema bumps, upgrade writes a migration report into the wiki root and leaves the actual changes for you to apply (the policy the code calls Option C).
36
43
 
37
44
  ---
38
45
 
@@ -75,6 +82,8 @@ hypomnema
75
82
 
76
83
  Hooks handle the rest: auto-staging, auto-commit/push, session-state injection, and lookup signals.
77
84
 
85
+ > You don't have to memorize the commands. Describe what you want in plain language and the matching skill fires with no slash command: "save this article to the wiki" triggers `ingest`, "what do I know about X?" triggers `query`, "let's wrap up this session" triggers `crystallize`. See [Claude Agent Skills](#claude-agent-skills) for how it works.
86
+
78
87
  > Sync across machines: the wiki is already a git repo. Add a remote, push once, and the `Stop` hook keeps every machine in sync afterwards.
79
88
 
80
89
  ---
@@ -148,7 +157,7 @@ These are the recurring terms used in the rest of the README. Keep this table op
148
157
  | `hot.md` | Per-project cache: "what just happened" (most recent session highlights) |
149
158
  | `session-state.md` | Per-project cache: "what's next" (the resume payload for the next session) |
150
159
  | `.hypoignore` | Glob patterns that exclude paths from every content-injection hook and from `ingest` |
151
- | observability score | A per-session metric (ingest / query / session-close / citation rates) that measures whether the wiki is actually being used |
160
+ | observability score | A per-session metric (search / ingest / feedback activity) that measures whether the wiki is actually being used |
152
161
  | manifest | A small JSON the install scripts write to track exactly which files were installed and at what SHA |
153
162
  | `additionalContext` | The Claude Code hook field that injects extra context into the prompt: where content-injection hooks emit |
154
163
  | byte-equal | A file that comes out of `--apply` bit-for-bit identical to before: the strongest "we did not touch this" guarantee |
@@ -219,7 +228,7 @@ Nine commands cover the full capture → retrieval → consolidation cycle.
219
228
  |---|---|---|
220
229
  | `/hypo:ingest` | Saves the raw source under `sources/`; Claude synthesizes a structured page under `pages/`. The shell helper (`scripts/ingest.mjs`) is read-only and only _lists_ pending sources | Anytime you read something worth keeping |
221
230
  | `/hypo:query` | BM25 retrieval + LLM synthesis with `[[wikilink]]` citations | When you need an answer grounded in your own notes |
222
- | `/hypo:crystallize` | Runs the session-close checklist (steps 1~6) and, on request, synthesizes drafts (steps 7~11) | End of a non-trivial session |
231
+ | `/hypo:crystallize` | Runs the session-close checklist (steps 1-6) and, on request, synthesizes drafts (steps 7-11) | End of a non-trivial session |
223
232
  | `/hypo:resume` | Loads the most recent session state for an active project | Coming back to a paused project |
224
233
  | `/hypo:feedback` | Records an AI behavior correction; eligible for promotion to permanent rules | When Claude gets something wrong, or gets it exactly right |
225
234
  | `/hypo:verify` | Audits pages with `verify_by` frontmatter | When time-bound knowledge might have aged out |
@@ -248,8 +257,6 @@ Nine commands cover the full capture → retrieval → consolidation cycle.
248
257
 
249
258
  All hooks resolve the wiki root via `HYPO_DIR` env → `hypo-config.md` scan → `~/hypomnema` default, and share `hypo-shared.mjs` (declared via `hooks.json`'s `shared` field).
250
259
 
251
- The `SessionStart` hook also runs a non-blocking background check against npm and the Claude Code plugin marketplace, and prints an "Update available!" banner the next time a newer version has been published. Opt out with `HYPO_NO_UPDATE_CHECK=1`, `NO_UPDATE_NOTIFIER=1`, or by running under `CI=true`.
252
-
253
260
  ### Setup & maintenance
254
261
 
255
262
  | Command | Purpose |
@@ -261,9 +268,20 @@ The `SessionStart` hook also runs a non-blocking background check against npm an
261
268
  | `/hypo:stats` | Wiki statistics |
262
269
  | `/hypo:audit` | Observability audit (per-session metrics, weekly report) |
263
270
 
271
+ > Update notice: the `SessionStart` hook runs a non-blocking background check against npm and the Claude Code plugin marketplace, and prints an "Update available!" banner the next time a newer version has been published. Opt out with `HYPO_NO_UPDATE_CHECK=1`, `NO_UPDATE_NOTIFIER=1`, or by running under `CI=true`.
272
+
264
273
  ### Claude Agent Skills
265
274
 
266
- The synthesis-heavy commands (`ingest`, `query`, `crystallize`, `lint`, `verify`, `graph`) are also exposed as Claude Agent Skills in `skills/<name>/SKILL.md`, so they auto-trigger when the conversation matches their description, with no slash command required.
275
+ The synthesis-heavy commands (`ingest`, `query`, `crystallize`, `lint`, `verify`, `graph`) are also exposed as Claude Agent Skills in `skills/<name>/SKILL.md`, so they auto-trigger when the conversation matches their description, with no slash command required. You don't need to know the exact command; just say what you want.
276
+
277
+ | Say this | Skill it triggers |
278
+ |---|---|
279
+ | "save this link/article to the wiki" | `ingest` |
280
+ | "what do I know about X?", "find that decision I made" | `query` |
281
+ | "let's wrap up this session", "save what we did today" | `crystallize` |
282
+ | "check these pages for broken links" | `lint` |
283
+
284
+ Use the slash command when you want to be explicit, plain language when you don't want to break your flow. Both reach the same skill.
267
285
 
268
286
  ---
269
287
 
package/commands/audit.md CHANGED
@@ -2,7 +2,7 @@
2
2
  description: Audit recent sessions for observability gaps and optionally write the weekly report. Use when the user asks to review session quality, find tracking gaps, or generate the weekly summary.
3
3
  ---
4
4
 
5
- You are running `/hypo:audit`. Inspect recent Claude Code sessions to see how much of the wiki's value motion is actually happening (search, ingest, citation, feedback), then either show the result inline or write a weekly observability page.
5
+ You are running `/hypo:audit`. Inspect recent Claude Code sessions to see how much of the wiki's value motion is actually happening (search, ingest, feedback), then either show the result inline or write a weekly observability page.
6
6
 
7
7
  ## What this shows
8
8
 
@@ -0,0 +1,40 @@
1
+ ---
2
+ description: Pull extensions you made the normal way under ~/.claude/{commands,agents}, or a canonical hook registered in ~/.claude/settings.json, into the wiki so they sync to your other machines. Use when the user wants to capture, adopt, or back up a locally-authored slash command, agent, or hook into Hypomnema.
3
+ ---
4
+
5
+ You are running `/hypo:capture`. Bring a command or agent that the user created the normal way (directly under `~/.claude/commands/` or `~/.claude/agents/`), or a hook they registered in `~/.claude/settings.json`, into the wiki `extensions/` tree so the existing forward-sync propagates it to their other machines under its original name.
6
+
7
+ Scope: commands, agents, and hooks. Commands and agents are enumerated from `~/.claude/{commands,agents}/`; hooks are read from the `~/.claude/settings.json` registration. A hook is captured only when its command is the canonical `node $HOME/.claude/hooks/<name>.mjs` form and it round-trips losslessly (original event, matcher, and timeout preserved); non-canonical or lossy registrations are skipped with a reason instead of being rewritten. Skills are not captured yet.
8
+
9
+ ## Step 1: Resolve the package root
10
+
11
+ Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema capture` or reinstall instead of guessing the cache layout.
12
+
13
+ If the user specified a Hypomnema directory, pass it as `--hypo-dir="<path>"`. Otherwise omit the flag.
14
+
15
+ ## Step 2: List what can be captured
16
+
17
+ ```bash
18
+ node ${CLAUDE_PLUGIN_ROOT}/scripts/capture.mjs [--hypo-dir="<path>"]
19
+ ```
20
+
21
+ With no names and no `--all`, the script lists capturable candidates and stops. Candidates come from two sources: commands and agents are the unowned regular `.md` files under `~/.claude/{commands,agents}/`, and hooks are the canonical `node $HOME/.claude/hooks/<name>.mjs` entries registered in `~/.claude/settings.json`. It excludes anything already managed by the wiki, the reserved `hypo-*` namespace, symlinks and other non-regular files, core hooks, and any hook whose registration would not round-trip losslessly. Skipped hook registrations are printed with the reason; unowned commands and agents that fail these filters are simply omitted from the list, without a per-item reason.
22
+
23
+ Show the list. Note that these are unowned candidates, not a provenance check: explicit selection is the trust boundary. A third-party tool's command, agent, or hook could appear here, so let the user pick deliberately.
24
+
25
+ ## Step 3: Capture the user's selection
26
+
27
+ Ask which ones to capture, then run with the chosen names (space-separated), or `--all` if the user wants everything listed:
28
+
29
+ ```bash
30
+ node ${CLAUDE_PLUGIN_ROOT}/scripts/capture.mjs [--hypo-dir="<path>"] <name> [<name> ...]
31
+ node ${CLAUDE_PLUGIN_ROOT}/scripts/capture.mjs [--hypo-dir="<path>"] --all
32
+ ```
33
+
34
+ A name is the filename (`mycmd.md`, or `myhook.mjs` for a hook), its stem (`mycmd`), or the `type/file` form (`commands/mycmd.md`, `hooks/myhook.mjs`). Add `--dry-run` to preview without writing.
35
+
36
+ Each capture stores the file in the wiki as `extensions/<type>/hypo-ext-<name>.<ext>` (`.md` for commands and agents, `.mjs` for hooks) with a sidecar `hypo-ext-<name>.manifest.json` that records the original install name (and, for a hook, its event, matcher, and timeout), then adopts it so the currently-installed copy is left in place and tracked. A wiki file that already exists with different content (or a mismatched manifest) is refused, not overwritten.
37
+
38
+ ## Step 4: Commit the wiki, then sync elsewhere
39
+
40
+ Show the script output verbatim. When something was captured, remind the user to commit and push the wiki, then run `hypomnema upgrade --apply` on their other machine to install the captured extension under its original name.
@@ -32,7 +32,7 @@ These are judgment calls; when uncertain, surface the question rather than skip
32
32
 
33
33
  The session-close path is **payload-driven**. Instead of writing the 5 mandatory files one-by-one, you compose a single JSON payload that describes the full session-close state, then hand it to the `--apply-session-close` apply path (the Step 3 command), which performs idempotent atomic writes and gates the result with lint.
34
34
 
35
- Payload shape (`project` + 5 content fields required, 1 conditional, per Spec §5.2.7 / §8.3):
35
+ Payload shape (`project` + 4 content fields required; `log` optional/derived; `openQuestions` conditional, per Spec §5.2.7 / §8.3):
36
36
 
37
37
  ```json
38
38
  {
@@ -54,7 +54,8 @@ Field rules:
54
54
  - `project`: **required**. Slug of the project being closed (matches a `projects/<slug>/` directory). Must be a single path segment, charset `A-Za-z0-9._-`, with at least one alphanumeric and not a dot-only name (`.`, `..`, `...`). Apply never infers the target from recency; a same-date pointer-table tie could otherwise write the close into the wrong project (B-3). A missing, malformed, or non-existent value fails the apply before any write.
55
55
  - `date` — optional. Defaults to today (local). Must be `YYYY-MM-DD` if supplied.
56
56
  - `openQuestions` — optional. Include only when `pages/open-questions.md` exists and changed this session.
57
- - All other top-level fields are required.
57
+ - `log`: optional. Omit it by default (apply derives the canonical `## [date] session | <project>` line from your `sessionLog` heading). Supply it only for a custom log.md line, which must still be a canonical `session | <project>` heading, or the apply fails at `stage='pre-apply-verification'`.
58
+ - The remaining fields (`sessionState`, `projectHot`, `rootHot`, `sessionLog`) are required.
58
59
 
59
60
  Notes:
60
61
 
@@ -68,7 +69,7 @@ Content guidance for each slot:
68
69
  2. **projectHot** — session snapshot under 500 words: what changed and decisions made. Do **not** put next-step tasks here; those belong in `sessionState`.
69
70
  3. **rootHot** — active-projects pointer table with this project's `Last Session` date set to today.
70
71
  4. **sessionLog** — one session entry to append to `projects/<name>/session-log/YYYY-MM-DD.md` (daily shard).
71
- 5. **log** one `session` entry to append to `<hypo-root>/log.md`.
72
+ 5. **log** (optional): a custom `session` entry for `<hypo-root>/log.md`. Omit it to let apply derive the canonical line from `sessionLog`.
72
73
  6. **openQuestions** (conditional) — only if `pages/open-questions.md` exists and questions were raised or resolved this session.
73
74
 
74
75
  ---
@@ -96,7 +97,9 @@ so it stops re-prompting. Omit it only when running crystallize purely for
96
97
  synthesis (no session-close intent) — the marker is then simply not written.
97
98
 
98
99
  > **Source rule for `--session-id`:** use only the main conversation's session id
99
- > (the id shown in the `[WIKI_AUTOCLOSE]` block reason, or `$CLAUDE_SESSION_ID`).
100
+ > (the id shown in the `[WIKI_AUTOCLOSE]` block reason, or the injected
101
+ > `$CLAUDE_CODE_SESSION_ID`; accept the legacy spelling via
102
+ > `${CLAUDE_CODE_SESSION_ID:-$CLAUDE_SESSION_ID}`).
100
103
  > Do NOT extract it from a background task output path or Agent thread (e.g.,
101
104
  > `/tmp/.../<uuid>/tasks/...`). A UUID from such a path is a background task id,
102
105
  > not the main conversation id. Passing it causes `markerSkipReason:
@@ -129,7 +132,8 @@ The result JSON includes a `stage` field when `ok: false`. Branch on it:
129
132
 
130
133
  | `stage` | What broke | How to recover |
131
134
  |---|---|---|
132
- | `preflight-lint` | A payload file (append target session-log / log.md) has a pre-existing blocking lint error. | Fix the lint error in that file, then re-run. No payload bytes were written. (Debt outside the payload files is a non-blocking notice, not this stage.) |
135
+ | `pre-apply-verification` | A payload heading does not match the freshness contract the close gate enforces: `sessionLog.entry` has no dated `## [YYYY-MM-DD] …` heading, or an explicit `log.entry` is not the canonical `## [date] session | <project>` line. Caught **before** any write. | Fix the heading in the payload (the session-log entry needs a bracketed dated heading; the log line needs `session \| <project>` after the date, colon or space delimiter), then re-run. No payload bytes were written. |
136
+ | `preflight-lint` | A payload file (append target: session-log / log.md) has a pre-existing blocking lint error. | Fix the lint error in that file, then re-run. No payload bytes were written. (Debt outside the payload files is a non-blocking notice, not this stage.) |
133
137
  | `post-apply-verification` | A mandatory file's `updated:` frontmatter is stale (≠ today) after apply. | Edit the payload's stale `content` (or supply correct `date`), then re-run. Writes are idempotent — re-applying a corrected payload is safe. |
134
138
  | `post-apply-lint` | The payload introduced an error-level lint blocker in a payload file (malformed body / bad frontmatter), or lint crashed. | Fix the offending content in the payload, then re-run. (Broken wikilinks are W4 warnings — not gated.) |
135
139
  | `post-apply-verification+lint` | Both above. | Fix both; re-run. |
@@ -142,11 +146,15 @@ Once `ok: true`, report:
142
146
  - ✓ open-questions applied (or skipped if unchanged)
143
147
  - ✓ log.md entry appended
144
148
  - ✓ post-apply lint clean
145
- - **marker written?** (required check): if `markerWritten: true`, report "session-close marker written"; if `markerWritten: false`, report "session-close marker NOT written (reason: `<markerSkipReason>`)" and do NOT declare the session "closed" or "complete". A missing marker means the Stop-chain is still open. Instruct the user to re-run with the correct main-conversation `--session-id`.
149
+ - **marker written?** (required check): if `markerWritten: true`, report "session-close marker written"; if `markerWritten: false`, report "session-close marker NOT written (reason: `<markerSkipReason>`)" and do NOT declare the session "closed" or "complete". A missing marker means the Stop-chain is still open; recover per the `markerSkipReason` branch below.
146
150
 
147
151
  If `markerWritten: true` (or `--session-id` was not passed): ask: "Session closed. Would you like to also run knowledge synthesis now, or stop here?"
148
152
 
149
- If `markerWritten: false`: do NOT say "session closed." Say instead: "Files applied and verified (ok: true), but the session-close marker was not written (reason: `<markerSkipReason>`). The Stop-chain is still active. To fully close: re-run with the correct main-conversation `--session-id`."
153
+ If `markerWritten: false`, do NOT say "session closed." Branch on `markerSkipReason`:
154
+
155
+ - `no-user-close-signal`: the files applied cleanly and the `--session-id` resolved a transcript, but that transcript carries no close phrase the gate recognizes (the user asked to close in wording that fell outside the close-signal set, e.g. "세션 마무리까지 진행해줘" or "세션 마무리 진행"). Re-running with the same id will not help, since the transcript is unchanged. Instead, confirm intent once with `AskUserQuestion`, header "세션", a single option labelled **세션 마무리** (설명: "이 세션을 마무리하고 close 마커를 기록"). If the user picks 세션 마무리, that answer becomes a recognized close signal in the transcript, so re-run the exact same `--apply-session-close … --session-id` command (the writes are idempotent no-ops; the marker now lands). If the user declines, leave the session unmarked: the wiki record stands, but the session is not closed. Do NOT touch the close-signal matcher itself.
156
+ - `transcript-unresolved` (or a background / agent id was passed): say "Files applied and verified (ok: true), but the session-close marker was not written (reason: `<markerSkipReason>`). The Stop-chain is still active. To fully close: re-run with the correct main-conversation `--session-id`."
157
+ - any other reason (`compact-gate-not-ok`, `commit-failed: …`, `marker-did-not-land`): surface the reason verbatim and address it (resolve the compact blocker, fix the git / disk issue) before re-running.
150
158
 
151
159
  If the user says stop, end here. Otherwise continue to Step 5.
152
160
 
@@ -110,7 +110,7 @@ Hooks run automatically at Claude Code lifecycle events. They are deployed to `~
110
110
  |---|---|
111
111
  | `hypo-session-start` | Inject `index.md`, root `hot.md`, project `hot.md`/`session-state.md`. Run `git pull --ff-only` (silent fail on missing remote) |
112
112
  | `hypo-first-prompt` | Marker-based one-shot `hot.md` injection on first user prompt (10-min TTL) — for sessions that bypass `SessionStart` |
113
- | `hypo-lookup` | BM25 search over the wiki on every prompt. **HIT** → inject top-3 page snippets (≤2000 chars each, with verify-by-date warnings). **MISS** → emit closest-slug signal that prompts Claude to research + `/hypo:ingest` |
113
+ | `hypo-lookup` | BM25 search over the wiki on every prompt. **HIT** → inject top-3 page snippets (≤2000 chars each; a page whose `verify_by_date` is overdue gets a `[STALE verify_by_date=…]` marker prepended). **MISS** → emit closest-slug signal that prompts Claude to research + `/hypo:ingest` |
114
114
  | `hypo-compact-guard` | Detect `/compact` invocations → enforce session-close checklist before allowing compact |
115
115
  | `hypo-personal-check` | PreCompact validation: lint blockers, uncommitted changes, missing session-log entries → block compact |
116
116
  | `hypo-auto-stage` | After Write/Edit on a wiki path, run `git add` (skips paths matching `.hypoignore`) |
@@ -379,7 +379,7 @@ If `git status` shows no `.md` changes, the diff step is skipped — Stop hook f
379
379
 
380
380
  ### Citation convention
381
381
 
382
- The six writer-side skills (`crystallize`, `query`, `ingest`, `verify`, `graph`, `lint`) carry an identical footer instructing Claude to cite wiki pages inline as `[[page-slug]]`. The audit script counts these citations as a "wiki was actually consulted" signal in future iterations.
382
+ The six writer-side skills (`crystallize`, `query`, `ingest`, `verify`, `graph`, `lint`) carry an identical footer instructing Claude to cite wiki pages inline as `[[page-slug]]`, which keeps them connected in the graph. The observability audit does not scan for these inline citations: it scores sessions on tool and command usage recorded by `hypo-session-record` (search / ingest / feedback counts). Counting citations as a signal is a possible future iteration, not current behavior.
383
383
 
384
384
  ---
385
385
 
@@ -331,6 +331,17 @@ from being dropped.
331
331
  Prerequisite: the changelog collector in step 2 reads merged-PR data through the GitHub CLI, so have `gh` installed and authenticated (`gh auth status`) before you start. It is a maintainer-only release script and is not shipped to npm.
332
332
 
333
333
  ```bash
334
+ # 0. (minor+ releases only) Ensure the wiki vault carries this release's version
335
+ # spec. Convention (version-plan lifecycle: born-at-stable-path, flip-in-place,
336
+ # recorded in the vault's decisions log): a minor-or-higher release has
337
+ # projects/hypomnema/specs/spec-v<version>.md with status: active and
338
+ # version: <version>. If it is missing, create it from the vault's minimal spec
339
+ # shape (omit patch-only fields for a minor/major). If you created or changed
340
+ # it, run the wiki lint and commit + push the vault before bumping, so the
341
+ # active pre-ship state is recorded remotely. Patch releases are exempt: the
342
+ # CHANGELOG section and PR body cover them, no spec doc required.
343
+ $EDITOR ~/hypomnema/projects/hypomnema/specs/spec-v<version>.md
344
+
334
345
  # 1. Bump the version across package.json, plugin.json, marketplace.json,
335
346
  # and templates/hypo-config.md. Takes a concrete semver (not patch/minor/major).
336
347
  node scripts/bump-version.mjs <new-semver> # e.g. 1.2.2 or 1.3.0-rc.1
@@ -397,6 +408,13 @@ npm publish --dry-run --access public # packs + prepublishOnly, NO registry PU
397
408
  # 8. Push the SPECIFIC tag alone — NOT --tags (that would push stale local tags
398
409
  # and could trigger releases for versions you did not intend).
399
410
  git push origin v<version>
411
+
412
+ # 9. (minor+ releases only) After the release workflow succeeds, close the spec
413
+ # lifecycle IN PLACE (no path move): set status: archived and
414
+ # archived_date: <release date> in the same spec, run the wiki lint, then
415
+ # commit and push the vault. Do this only after the release is green, never at
416
+ # tag time: a failed release must not leave an archived spec behind.
417
+ $EDITOR ~/hypomnema/projects/hypomnema/specs/spec-v<version>.md
400
418
  ```
401
419
 
402
420
  The `release.yml` workflow then:
@@ -11,7 +11,15 @@
11
11
 
12
12
  import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
13
13
  import { join, basename } from 'path';
14
- import { HYPO_DIR, buildOutput, loadHypoIgnore, isIgnored } from './hypo-shared.mjs';
14
+ import {
15
+ HYPO_DIR,
16
+ buildOutput,
17
+ loadHypoIgnore,
18
+ isIgnored,
19
+ staleMarkerFor,
20
+ pageUsageLoggingAllowed,
21
+ recordLookupUsage,
22
+ } from './hypo-shared.mjs';
15
23
 
16
24
  const INDEX_PATH = join(HYPO_DIR, 'index.md');
17
25
  const MAX_HITS = 3;
@@ -178,6 +186,8 @@ process.stdin.on('end', () => {
178
186
  try {
179
187
  const data = JSON.parse(input);
180
188
  const prompt = (data.prompt || '').trim();
189
+ // Runtime sends a session UUID; older payloads may omit it (parse defensively).
190
+ const sessionId = data.session_id || null;
181
191
 
182
192
  if (!prompt || !existsSync(INDEX_PATH)) {
183
193
  console.log(JSON.stringify({ continue: true, suppressOutput: true }));
@@ -235,14 +245,32 @@ process.stdin.on('end', () => {
235
245
  ),
236
246
  };
237
247
 
248
+ // UTC to match doctor.mjs' overdue set (D1/D2). STALE is advisory, so a
249
+ // one-day UTC/local skew never misleads.
250
+ const TODAY = new Date().toISOString().slice(0, 10);
251
+
238
252
  const injected = [];
253
+ const injectedSlugs = [];
239
254
  for (const { slug } of matched.slice(0, MAX_HITS)) {
240
255
  const path =
241
256
  pageMap[slug] ??
242
257
  pageMap[slug.replace(/^(pages|projects)\//, '')] ??
243
258
  pageMap[basename(slug)];
244
259
  if (path && existsSync(path)) {
245
- injected.push(`=== [[${slug}]] ===\n${readFileSync(path, 'utf-8').slice(0, MAX_CHARS)}`);
260
+ const raw = readFileSync(path, 'utf-8');
261
+ // Compute the marker on raw (pre-slice) so a long body can't truncate
262
+ // frontmatter out of reach. fail-open: a marker failure drops the marker,
263
+ // never the page.
264
+ let marker = '';
265
+ try {
266
+ marker = staleMarkerFor(raw, TODAY);
267
+ } catch {
268
+ marker = '';
269
+ }
270
+ injected.push(
271
+ `=== [[${slug}]] ===\n${marker ? marker + '\n' : ''}${raw.slice(0, MAX_CHARS)}`,
272
+ );
273
+ injectedSlugs.push(slug);
246
274
  }
247
275
  }
248
276
 
@@ -262,6 +290,13 @@ process.stdin.on('end', () => {
262
290
  return;
263
291
  }
264
292
 
293
+ // Page-usage logging (B): observability only, gated fail-closed on commit
294
+ // coverage, and fail-open so it never disturbs the injection above. Only the
295
+ // real HIT branch reaches here (miss / index-hit-but-missing returned early).
296
+ if (pageUsageLoggingAllowed(HYPO_DIR, sessionId)) {
297
+ recordLookupUsage(HYPO_DIR, { sessionId, slugs: injectedSlugs });
298
+ }
299
+
265
300
  const overflow =
266
301
  matched.length > MAX_HITS
267
302
  ? `\n(+${matched.length - MAX_HITS} more matches — search wiki index for more)`
@@ -31,6 +31,7 @@ import {
31
31
  pickProjectByCwd,
32
32
  collectProjectWorkingDirs,
33
33
  buildVaultOrientation,
34
+ staleMarkerFor,
34
35
  } from './hypo-shared.mjs';
35
36
  import {
36
37
  defaultCachePath,
@@ -56,6 +57,21 @@ function readIfNotIgnored(path, maxChars, patterns) {
56
57
  return readFileSync(path, 'utf-8').slice(0, maxChars);
57
58
  }
58
59
 
60
+ // Compute the STALE marker for a hot/state file from its RAW content (readIfNotIgnored
61
+ // already slices, which could truncate frontmatter). Honors the same .hypoignore
62
+ // privacy guard, and returns '' for any miss (no path, ignored, absent, no
63
+ // verify_by_date, or error) so derived summaries pass through unchanged.
64
+ function staleMarkerForPath(path, patterns, today) {
65
+ try {
66
+ if (!path) return '';
67
+ if (patterns.length > 0 && isIgnored(path, HYPO_DIR, patterns)) return '';
68
+ if (!existsSync(path)) return '';
69
+ return staleMarkerFor(readFileSync(path, 'utf-8'), today);
70
+ } catch {
71
+ return '';
72
+ }
73
+ }
74
+
59
75
  // Directory of the running hook, and the install root one level up
60
76
  // (<root>/hooks/...). The root is derived from the RUNNING hook path rather
61
77
  // than ~/.claude/hypo-pkg.json so a dual install (npm + plugin) or a stale
@@ -372,8 +388,18 @@ process.stdin.on('end', () => {
372
388
  const hitPrefix = vaultOrientation ? `${vaultOrientation}\n\n` : '';
373
389
 
374
390
  if (hit) {
375
- const hotContent = readIfNotIgnored(hit.hotPath, HOT_CHARS, ignorePatterns);
376
- const stateContent = readIfNotIgnored(hit.statePath, STATE_CHARS, ignorePatterns);
391
+ // project hot/state only. root/global hot (below) is a derived pointer
392
+ // table with no per-page frontmatter, so it is never a STALE target and
393
+ // gets no marker logic. TODAY is UTC to match doctor.mjs (D1/D2). The
394
+ // marker is computed on raw content (staleMarkerForPath), then prepended
395
+ // onto the sliced display content; a no-op when there is no verify_by_date.
396
+ const TODAY = new Date().toISOString().slice(0, 10);
397
+ let hotContent = readIfNotIgnored(hit.hotPath, HOT_CHARS, ignorePatterns);
398
+ let stateContent = readIfNotIgnored(hit.statePath, STATE_CHARS, ignorePatterns);
399
+ const hotMarker = staleMarkerForPath(hit.hotPath, ignorePatterns, TODAY);
400
+ const stateMarker = staleMarkerForPath(hit.statePath, ignorePatterns, TODAY);
401
+ if (hotContent && hotMarker) hotContent = `${hotMarker}\n${hotContent}`;
402
+ if (stateContent && stateMarker) stateContent = `${stateMarker}\n${stateContent}`;
377
403
 
378
404
  if (hotContent || stateContent) {
379
405
  printTerminalSummary(hit.proj, hotContent, stateContent);