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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.ko.md +36 -18
- package/README.md +26 -8
- package/commands/audit.md +1 -1
- package/commands/capture.md +40 -0
- package/commands/crystallize.md +15 -7
- package/docs/ARCHITECTURE.md +2 -2
- package/docs/CONTRIBUTING.md +18 -0
- package/hooks/hypo-lookup.mjs +37 -2
- package/hooks/hypo-session-start.mjs +28 -2
- package/hooks/hypo-shared.mjs +159 -14
- package/package.json +1 -1
- package/scripts/capture.mjs +695 -0
- package/scripts/crystallize.mjs +292 -121
- package/scripts/doctor.mjs +71 -4
- package/scripts/init.mjs +16 -5
- package/scripts/lib/core-hooks.mjs +156 -0
- package/scripts/lib/extensions.mjs +317 -9
- package/scripts/lib/page-usage.mjs +141 -0
- package/scripts/uninstall.mjs +84 -42
- package/scripts/upgrade.mjs +68 -0
- package/skills/crystallize/SKILL.md +10 -4
- package/skills/graph/SKILL.md +1 -1
- package/skills/ingest/SKILL.md +1 -1
- package/skills/lint/SKILL.md +1 -1
- package/skills/query/SKILL.md +1 -1
- package/skills/verify/SKILL.md +1 -1
- package/templates/hypo-config.md +1 -1
- package/templates/hypo-guide.md +1 -1
package/README.ko.md
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
Claude Code를 위한 LLM 네이티브 개인 위키. 쓸수록 지식이 쌓입니다.
|
|
17
17
|
|
|
18
|
-
|
|
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.
|
|
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)에 있습니다.
|
|
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·
|
|
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는
|
|
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년치 도구 생태계가 받쳐 줍니다. 스택은 일부러
|
|
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 도구는 교정을
|
|
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 결정은 결국
|
|
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
|
|
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`에서 제외되지만, 등록하지
|
|
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/`로). 이 관리 대상
|
|
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
|
-
|
|
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.
|
|
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).
|
|
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 (
|
|
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
|
|
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,
|
|
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.
|
package/commands/crystallize.md
CHANGED
|
@@ -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` +
|
|
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
|
-
-
|
|
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**
|
|
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
|
|
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
|
-
| `
|
|
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
|
|
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
|
|
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
|
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -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
|
|
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]]
|
|
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
|
|
package/docs/CONTRIBUTING.md
CHANGED
|
@@ -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:
|
package/hooks/hypo-lookup.mjs
CHANGED
|
@@ -11,7 +11,15 @@
|
|
|
11
11
|
|
|
12
12
|
import { readFileSync, existsSync, readdirSync, statSync } from 'fs';
|
|
13
13
|
import { join, basename } from 'path';
|
|
14
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
376
|
-
|
|
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);
|