hypomnema 1.9.1 → 1.10.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.
Files changed (61) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +19 -0
  4. package/README.ko.md +31 -12
  5. package/README.md +31 -12
  6. package/commands/crystallize.md +72 -52
  7. package/commands/doctor.md +1 -0
  8. package/commands/init.md +2 -1
  9. package/commands/resume.md +7 -6
  10. package/commands/upgrade.md +30 -0
  11. package/docs/ARCHITECTURE.md +61 -25
  12. package/docs/CONTRIBUTING.md +13 -0
  13. package/hooks/atomic-write.mjs +54 -6
  14. package/hooks/base-store.mjs +6 -9
  15. package/hooks/close-gate-store.mjs +46 -22
  16. package/hooks/close-journal.mjs +26 -0
  17. package/hooks/close-receipt.mjs +32 -3
  18. package/hooks/hypo-auto-commit.mjs +98 -35
  19. package/hooks/hypo-auto-minimal-crystallize.mjs +17 -1
  20. package/hooks/hypo-auto-stage.mjs +68 -4
  21. package/hooks/hypo-close-guard.mjs +92 -208
  22. package/hooks/hypo-compact-guard.mjs +15 -3
  23. package/hooks/hypo-first-prompt.mjs +24 -14
  24. package/hooks/hypo-hot-rebuild.mjs +33 -159
  25. package/hooks/hypo-lookup.mjs +6 -1
  26. package/hooks/hypo-personal-check.mjs +9 -2
  27. package/hooks/hypo-session-start.mjs +414 -509
  28. package/hooks/hypo-shared.mjs +4045 -1169
  29. package/hooks/hypo-stop.mjs +10 -10
  30. package/hooks/session-entries.mjs +1313 -0
  31. package/hooks/session-views.mjs +1309 -0
  32. package/hooks/shared.json +3 -1
  33. package/package.json +2 -1
  34. package/scripts/crystallize.mjs +27 -21
  35. package/scripts/doctor.mjs +270 -1
  36. package/scripts/graph.mjs +6 -1
  37. package/scripts/init.mjs +41 -17
  38. package/scripts/lib/crystallize-args.mjs +3 -3
  39. package/scripts/lib/crystallize-close-apply.mjs +1910 -428
  40. package/scripts/lib/crystallize-close-check.mjs +64 -8
  41. package/scripts/lib/crystallize-close-gate.mjs +20 -7
  42. package/scripts/lib/hypo-ignore.mjs +13 -0
  43. package/scripts/lib/project-create.mjs +76 -65
  44. package/scripts/lib/template-schema-version.mjs +1 -0
  45. package/scripts/lint.mjs +114 -2
  46. package/scripts/query.mjs +6 -1
  47. package/scripts/rename.mjs +117 -2
  48. package/scripts/resume.mjs +84 -50
  49. package/scripts/session-entries.mjs +301 -0
  50. package/scripts/upgrade.mjs +223 -0
  51. package/scripts/verify.mjs +7 -1
  52. package/templates/SCHEMA.md +34 -22
  53. package/templates/gitattributes +6 -0
  54. package/templates/gitignore +14 -8
  55. package/templates/hypo-automation.md +5 -5
  56. package/templates/hypo-config.md +6 -3
  57. package/templates/hypo-guide.md +33 -14
  58. package/templates/hypo-help.md +2 -1
  59. package/templates/projects/_template/index.md +3 -2
  60. package/templates/projects/_template/hot.md +0 -28
  61. package/templates/projects/_template/session-state.md +0 -9
@@ -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.9.1",
14
+ "version": "1.10.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.9.1",
3
+ "version": "1.10.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/CHANGELOG.md CHANGED
@@ -5,6 +5,25 @@ All notable changes to Hypomnema are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.10.0] - 2026-10-15
9
+
10
+ > [!IMPORTANT]
11
+ > **Update every installation that uses a vault before you start sessions on 1.10.0 there. Hypomnema does not check the other installations' versions.** Before migration, a close on 1.10.0 writes a session entry and leaves the old `hot.md` and `session-state.md` unchanged, and older versions cannot read the new entries. A vault without a git remote attempts the migration by itself when a session starts, including a vault shared through a synced folder. For a vault with a git remote, run `/hypo:upgrade --migrate-sessions` once on one machine after every installation is updated: it commits the migration and tries to push it when an upstream branch is set. An installation still on an older version loses `hot.md` and `session-state.md` once it has the migration, and cannot complete a close (session-log text may still be written); update it and run `/hypo:doctor`. To go back to flat files, see the rollback command in the README.
12
+
13
+ > [!IMPORTANT]
14
+ > **볼트를 쓰는 모든 기기의 Hypomnema 를 업데이트한 뒤에 그 볼트에서 1.10.0 세션을 시작하세요. 다른 기기의 버전은 자동으로 확인하지 않습니다.** 이전 전에도 1.10.0 은 세션을 마감할 때 세션 기록을 쓰지만, 기존 `hot.md` 와 `session-state.md` 는 갱신하지 않습니다. 구버전은 새 세션 기록을 읽지 못합니다. git 원격 저장소가 없는 볼트는 세션을 시작할 때 자동으로 이전을 시도하며, 동기화 폴더로 공유하는 볼트도 마찬가지입니다. git 원격 저장소가 있는 볼트는 모든 기기를 업데이트한 뒤 한 기기에서 `/hypo:upgrade --migrate-sessions` 를 한 번 실행하세요. 이 명령은 이전 내용을 커밋하고, 업스트림 브랜치가 설정되어 있으면 바로 push 를 시도합니다. 구버전 기기는 이전 내용을 받는 순간 `hot.md` 와 `session-state.md` 가 사라지고, 세션 마감을 정상적으로 끝내지 못합니다(세션 로그에는 일부 기록이 남을 수 있습니다). 그 기기의 Hypomnema 를 업데이트한 뒤 `/hypo:doctor` 를 실행하세요. 평면 파일로 되돌리는 방법은 README 의 rollback 명령을 보세요.
15
+
16
+ ### New Features
17
+ #### English
18
+ - Session close writes one immutable entry per close under `projects/<name>/sessions/`, and `hot.md` and `session-state.md` are generated from the entries, so sessions that close at the same time no longer overwrite each other. An existing vault attempts the migration on its own without a remote and moves with `/hypo:upgrade --migrate-sessions` with one; a rollback command goes back to flat files. Project names may use any script, and a close refuses a vault stopped mid-merge or mid-rebase before writing. ([#316](https://github.com/sk-lim19f/Hypomnema/pull/316))
19
+ #### 한국어
20
+ - 세션을 마감할 때마다 `projects/<name>/sessions/` 아래에 수정하지 않는 기록 하나를 쓰고, `hot.md` 와 `session-state.md` 는 그 기록들로 만듭니다. 그래서 동시에 마감하는 세션이 더 이상 서로를 덮어쓰지 않습니다. 기존 볼트는 원격 저장소가 없으면 세션 시작 시 자동으로 이전을 시도하고, 있으면 `/hypo:upgrade --migrate-sessions` 로 이전하며, rollback 명령으로 평면 파일로 되돌릴 수 있습니다. 프로젝트 이름에는 한글 등 여러 문자 체계의 글자와 숫자를 쓸 수 있고, merge 나 rebase 도중인 볼트에서는 마감이 아무것도 쓰기 전에 거부됩니다. ([#316](https://github.com/sk-lim19f/Hypomnema/pull/316))
21
+
22
+ ### Changelog
23
+ - [#317](https://github.com/sk-lim19f/Hypomnema/pull/317) fix(session-entries): drop unreachable staged .gitignore handling and say how to stop an unresolved archive notice
24
+ - [#316](https://github.com/sk-lim19f/Hypomnema/pull/316) feat(session-close): write one entry per close and generate hot.md and session-state.md from the entries
25
+ Contributors: @sk-lim19f
26
+
8
27
  ## [1.9.1] - 2026-10-12
9
28
 
10
29
  ### Bug Fixes
package/README.ko.md CHANGED
@@ -162,8 +162,9 @@ Hypomnema ───► 합성 · 마크다운 · git · 훅 · 로컬
162
162
  | 단일 원천(source of truth, SoT) | 사용자가 편집하는 단 하나의 파일. projection은 거기서만 파생되고 역방향은 없습니다 |
163
163
  | 훅(hook) | Claude Code가 라이프사이클 이벤트(`SessionStart`, `Stop` 등)에 자동으로 실행하는 스크립트 |
164
164
  | 라이프사이클 이벤트 | Claude Code가 플러그인에 알리는 시점. 세션 시작·프롬프트 제출·도구 사용·compact 요청·세션 종료 등 |
165
- | `hot.md` | 프로젝트별 캐시. "방금 무엇을 했는지"(직전 세션의 핵심) |
166
- | `session-state.md` | 프로젝트별 캐시. "다음에 무엇을 할지"(다음 세션 시작 때 주입되는 이어받기 데이터) |
165
+ | 세션 원본(session entry) | 세션을 마무리할 때마다 `projects/<name>/sessions/`에 하나씩 쌓이는 파일. 요약 하나와 트랙별 갱신으로 이뤄집니다. `hot.md`와 `session-state.md`의 원본이고 지워지지 않습니다 |
166
+ | `hot.md` | 생성되는 현황 파일. "방금 무엇을 했는지"(최근 세션 요약). 세션 원본에서 만듭니다 |
167
+ | `session-state.md` | 생성되는 현황 파일. "다음에 무엇을 할지"(다음 세션 시작 때 주입되는 이어받기 데이터). 세션 원본에서 만듭니다 |
167
168
  | `.hypoignore` | 모든 콘텐츠 주입 훅과 `ingest`에서 제외할 경로(글롭 패턴). 프라이버시 경계 |
168
169
  | `.hyposcanignore` | 카탈로그 스캔(`lint`, `stats`, `query`, `verify`, `doctor`)에서만 제외할 경로. 프라이버시 경계가 아니며, 매치돼도 여전히 커밋되고 훅이 읽음 |
169
170
  | 관측성 점수(observability score) | 세션별 측정값(search·ingest·feedback 활동). 위키가 실제로 쓰였는지 보여줍니다 |
@@ -211,11 +212,13 @@ Hypomnema는 청크가 아니라 페이지를 지식 단위로 봅니다. 새
211
212
 
212
213
  ### 4. 왜 재개에 `hot.md` 캐시를 쓰는가
213
214
 
214
- 멈춘 프로젝트에서 가장 비싼 건 일을 다시 하는 게 아니라 컨텍스트를 다시 쌓는 일입니다. `session-log/`를 처음부터 다시 읽으면 분 단위 시간과 토큰이 들지만, 한 페이지짜리 `hot.md`를 읽는 건 둘 다 들지 않습니다. 그래서 최근 상태를 명시적으로 캐싱합니다. `hot.md`는 두 층입니다. 프로젝트별 `hot.md`는 세션 마무리(`crystallize`)가 쓰고, 볼트 루트의 `hot.md`는 그 파일들을 훑어 만드는 포인터 표입니다. 루트 쪽은 `SessionStart`와 `Stop`에서 훅이 다시 만드니 손으로 고치지 마세요. 둘 다 `SessionStart`에 주입됩니다. 재개는 O(1)입니다.
215
+ 멈춘 프로젝트에서 가장 비싼 건 일을 다시 하는 게 아니라 컨텍스트를 다시 쌓는 일입니다. `session-log/`를 처음부터 다시 읽으면 분 단위 시간과 토큰이 들지만, 한 페이지짜리 `hot.md`를 읽는 건 둘 다 들지 않습니다. 그래서 최근 상태를 명시적으로 캐싱합니다. 세션을 마무리할 때마다 `projects/<name>/sessions/`에 **세션 원본**이 하나씩 쌓이고, 세 파일이 그 원본에서 만들어집니다. 볼트 루트의 `hot.md`(프로젝트 포인터 표), 프로젝트별 `hot.md`, 프로젝트별 `session-state.md`입니다. 훅이 `SessionStart`와 `Stop`에서 다시 만들고, `SessionStart`에 주입됩니다. 재개는 세션 원본을 읽어 두 현황 파일을 그 자리에서 렌더링하므로, 비용이 파일 두 개를 읽는 수준에 머물지 않고 원본 수에 따라 늘어납니다. 세션마다 자기 파일을 따로 쓰므로 두 세션이 동시에 마무리돼도 서로의 기록을 덮어쓰지 않습니다.
215
216
 
216
- 루트 `hot.md`는 훅이 만들어 내는 결과물이지, 손으로 편집하는 자리가 아닙니다. 거기 직접 써 넣은 내용은 다음번 재생성(다음 `SessionStart`나 `Stop`) 때 사라집니다. 훅이 만든 내용인지 사람이 쓴 내용인지 구분해서 판단할 방법이 없기 때문입니다. 파일 rename에는 범용 compare and swap이 없고, 편집기도 훅이 잡은 advisory lock을 따르지 않습니다. 그래서 훅은 자기가 쓴 내용인지 먼저 확인하고, 아니라고 판단되면 그 내용을 버리는 대신 `hot.md.pre-projection-backup.md`(둘 이상 쌓이면 `-2.md`, `-3.md`, ...)로 같은 자리에 백업해 둡니다. 다만 이 백업은 보장이 아니라 최선을 다한 구조일 뿐입니다. 재작성 도중에 저장이 겹치거나, 편집기가 제자리 저장 방식을 쓰면 백업이 일부만 담기거나 아예 안 남을 수도 있습니다. 안전망이지 쓰는 자리가 아니라는 뜻입니다.
217
+ 이 세 파일은 손으로 고치는 자리가 아니고 커밋되지도 않습니다(볼트 `.gitignore`에 들어 있어서, 기계마다 pull로 받은 원본으로 각자 만듭니다). 직접 써 넣은 내용은 다음번 재생성(다음 `SessionStart`나 `Stop`) 때 사라집니다. 생성기가 남길 내용인지 방해되는 내용인지 구분할 방법이 없기 때문입니다. 파일 rename에는 범용 compare and swap이 없고, 편집기도 훅이 잡은 advisory lock을 따르지 않습니다. 그래서 생성기는 자기가 쓴 내용인지 먼저 확인하고, 아니라고 판단되면 그 내용을 버리는 대신 `.cache/backups/` 아래에 백업해 둡니다(예: `.cache/backups/hot.md.pre-projection-backup.md`, 둘 이상 쌓이면 `-2.md`, `-3.md`, ...). 다만 이 백업은 보장이 아니라 최선을 다한 구조일 뿐입니다. 재작성 도중에 저장이 겹치거나, 편집기가 제자리 저장 방식을 쓰면 백업이 일부만 담기거나 아예 안 남을 수도 있습니다. 안전망이지 쓰는 자리가 아니라는 뜻입니다.
217
218
 
218
- 백업을 다시 루트 `hot.md`로 되돌리지 마세요. 다음 훅 실행이 곧바로 또 덮어씁니다. 대신 백업 파일을 열어 읽어 보고, 남길 내용은 해당 프로젝트의 `projects/<slug>/hot.md`나 별도 페이지로 옮긴 뒤(어느 프로젝트 것인지는 훅이 판단 못 하니 사람이 정합니다) 백업을 지우세요. 볼트 전체에 얼마나 쌓였는지 보려면 볼트 루트에서 `ls hot.md.pre-projection-backup*.md`를 실행하면 됩니다.
219
+ 생성 파일의 내용을 바꾸고 싶으면 바꿀 내용으로 세션을 마무리하세요. 프로젝트의 모든 세션이 봐야 할 메모는 `projects/<name>/notes.md`에 적습니다. 이 파일은 생성되지 않는 내 파일이고, 프로젝트 현황과 함께 주입됩니다. 백업을 생성 파일로 되돌리지는 마세요. 다음 훅 실행이 곧바로 또 덮어씁니다. 대신 백업 파일을 열어 읽어 보고, 남길 내용은 `projects/<slug>/notes.md`나 별도 페이지로 옮긴 뒤(어느 프로젝트 것인지는 훅이 판단 못 하니 사람이 정합니다) 백업을 지우세요. 얼마나 쌓였는지 보려면 볼트 루트에서 `ls -R .cache/backups`를 실행하면 됩니다.
220
+
221
+ `sessions/`의 원본은 지워지지 않습니다. 생성된 `hot.md`에는 최근 요약 몇 개만 보이고 나머지는 링크로 이어집니다. 기존 볼트를 옮기는 방법은 [세션 원본과 기존 볼트 이행](#세션-원본과-기존-볼트-이행)을 보세요.
219
222
 
220
223
  ### 5. 왜 feedback → behavior 파이프라인인가
221
224
 
@@ -255,15 +258,15 @@ Hypomnema는 청크가 아니라 페이지를 지식 단위로 봅니다. 새
255
258
 
256
259
  | 훅 | 이벤트 | 역할 |
257
260
  |---|---|---|
258
- | `hypo-close-guard.mjs` | `PreToolUse` | Write/Edit/MultiEdit가 세션 마무리 쓰기로 보이는데 트랜스크립트에 사용자의 마무리 신호가 없으면, 파일이 바뀌기 전에 확인을 받음 |
259
- | `hypo-session-start.mjs` | `SessionStart` | `git pull --ff-only`, 루트 `hot.md` 투영 재생성, `hot.md` / `session-state.md` 주입 |
261
+ | `hypo-close-guard.mjs` | `PreToolUse` | 생성되는 세션 뷰(루트 `hot.md`, 프로젝트의 `hot.md`와 `session-state.md`)나 세션 항목(`projects/<p>/sessions/*.md`)에 대한 직접 Write/Edit/MultiEdit를 거부함 |
262
+ | `hypo-session-start.mjs` | `SessionStart` | `git pull --ff-only`, `projects/*/sessions/`에서 세션 현황 파일(루트 `hot.md`, 프로젝트별 `hot.md`와 `session-state.md`) 재생성, 그 파일들 주입 |
260
263
  | `hypo-first-prompt.mjs` | `UserPromptSubmit` | 마커 기반으로 첫 프롬프트에 재개 한 줄을 요구한다(10분 TTL). `hot.md`를 다시 읽지는 않는다 |
261
264
  | `hypo-lookup.mjs` | `UserPromptSubmit` | BM25 top-3 HIT 주입 / MISS면 가까운 슬러그 신호 |
262
265
  | `hypo-compact-guard.mjs` | `UserPromptSubmit` | 채팅에 입력된 `/compact`나 `/clear`를 감지해 마무리가 덜 됐으면 알린다. compact 자체는 막지 않는다 |
263
266
  | `hypo-cwd-change.mjs` | `CwdChanged` | cwd에 맞는 프로젝트 `hot.md`로 알림을 만든다(아래 전송 안내 참조) |
264
267
  | `hypo-file-watch.mjs` | `FileChanged` | 바뀐 위키 파일로 알림을 만든다(`.hypoignore` 준수). 이 패키지는 감시 경로를 등록하지 않아 기본 설치에서는 이벤트가 발생할 계기 자체가 없다(아래 전송 안내 참조) |
265
268
  | `hypo-auto-stage.mjs` | `PostToolUse(Write/Edit/MultiEdit)` | 위키 파일 자동 stage |
266
- | `hypo-stop.mjs` | `Stop` | 등록된 유일한 `Stop` 훅이다. 네 단계를 병렬이 아니라 순서대로 돌린다: 루트 `hot.md` 투영 재생성(`SessionStart`도 같은 생성기를 부른다), observability 점수와 auto-resume 신호용 세션 메타데이터 기록, commit + pull + push, 그리고 사용자가 마무리를 신호한 뒤 의미 있는 작업을 했는데 마무리가 확인되지 않으면 Stop을 막고 `crystallize.mjs --mark-session-closed` 명령을 돌려준다(미커밋 변경이나 진행 중인 작업이 있으면 명령 대신 지금 닫을지 되묻는다) |
269
+ | `hypo-stop.mjs` | `Stop` | 등록된 유일한 `Stop` 훅이다. 네 단계를 병렬이 아니라 순서대로 돌린다: 세션 현황 파일 재생성(`SessionStart`도 같은 생성기를 부른다), observability 점수와 auto-resume 신호용 세션 메타데이터 기록, commit + pull + push, 그리고 사용자가 마무리를 신호한 뒤 의미 있는 작업을 했는데 마무리가 확인되지 않으면 Stop을 막고 `crystallize.mjs --mark-session-closed` 명령을 돌려준다(미커밋 변경이나 진행 중인 작업이 있으면 명령 대신 지금 닫을지 되묻는다) |
267
270
  | `hypo-personal-check.mjs` | `PreCompact` | session-close 미완, 위키 커밋/푸시 누락, hot.md 구조 위반, lint 블로커를 `systemMessage`로 알린다. `/compact`는 여기서 막지 않는다. 세션 마무리 강제는 다른 자리에 남아 있다: `hypo-stop.mjs`의 crystallize 단계가 마무리 확인 없는 의미 있는 세션을 막고, `--mark-session-closed`는 여전히 red 게이트에서 마커를 거부한다 |
268
271
  | `hypo-session-end.mjs` | `SessionEnd` | SessionEnd 마커를 기록해 다음 SessionStart가 `source=clear` 복구를 감지하게 한다 |
269
272
  | `hypo-web-fetch-ingest.mjs` | `PostToolUse(WebFetch/WebSearch)` | WebFetch/WebSearch 뒤 `additionalContext`에 `/hypo:ingest` 권유를 주입한다(URL의 query/hash/userinfo는 제거) |
@@ -364,14 +367,30 @@ E. 멈춘 프로젝트 재개.
364
367
  ├── projects/ ← 프로젝트 아티팩트 및 세션 로그
365
368
  │ ├── _template/ ← 새 프로젝트를 만들 때 복사되는 스캐폴드
366
369
  │ └── <name>/
367
- │ ├── hot.md
368
- │ ├── session-state.md
370
+ │ ├── sessions/ ← 세션 마무리마다 하나씩 쌓이는 원본 (지워지지 않음)
371
+ │ ├── notes.md ← 모든 세션이 공유하는 메모 (직접 쓰는 파일, 선택)
372
+ │ ├── hot.md ← sessions/에서 생성, 커밋되지 않음
373
+ │ ├── session-state.md ← sessions/에서 생성, 커밋되지 않음
369
374
  │ └── session-log/
370
375
  ├── journal/ ← daily / weekly / monthly 기록
371
376
  ├── sources/ ← 원본 인제스트 소스 (수정 금지)
372
- └── .cache/ ← 런타임 상태(gitignore 대상). 세션 범위, 보류된 proposal, 동기화 상태
377
+ └── .cache/ ← 런타임 상태(gitignore 대상). 세션 범위, 보류된 proposal, 동기화 상태, 덮어쓴 생성 파일의 백업
373
378
  ```
374
379
 
380
+ ### 세션 원본과 기존 볼트 이행
381
+
382
+ 세션을 마무리하면 `projects/<name>/sessions/<date>-<close_id>.md`가 하나 생깁니다. 루트 `hot.md`와 프로젝트별 `hot.md`, `session-state.md`는 이 원본에서 만들어지는 파일이라 커밋되지 않습니다. 공통 메모는 `projects/<name>/notes.md`에 적습니다.
383
+
384
+ - **새 볼트**는 처음부터 세션 원본으로 시작합니다.
385
+ - **git 원격이 없는 기존 볼트**는 업그레이드한 뒤 첫 세션 시작 때 저절로 옮겨집니다.
386
+ - **git 원격이 있는 기존 볼트**는 저절로 옮겨지지 않습니다. 같은 볼트를 쓰는 모든 기계를 먼저 업그레이드하고, 한 기계에서 `/hypo:upgrade --migrate-sessions`를 한 번 실행하세요. 이 명령은 이행을 기록하고 바로 푸시하니, 모든 기계를 올린 뒤에만 실행해야 합니다. 그 전까지 세션 시작 때 하루에 한 번까지 안내가 나옵니다.
387
+
388
+ 이행 전에 새 버전에서 마무리하면 원본만 쌓입니다. 생성 파일은 옛 내용 그대로 멈춰 있고, 세션 시작과 `/hypo:resume`은 옛 파일을 출발점으로 보여 주면서 그 위에 새 원본을 얹습니다.
389
+
390
+ 업그레이드를 빠뜨린 기계는 이행 커밋을 pull하는 순간부터 영향을 받습니다. `hot.md`와 `session-state.md`가 사라지고, 세션 시작 때 주입할 내용이 없고, 그 기계의 마무리는 기록되지 않으며(마무리 영수증이 실패합니다), `sessions/`를 읽지 못하고, 그 파일을 고친 푸시 전 변경이 있으면 동기화가 멈춥니다. 그 기계를 업그레이드한 뒤 `/hypo:doctor`를 실행하세요. 구버전 기계의 최근 흔적을 알려 줍니다.
391
+
392
+ 평면 파일로 돌아가려면 함께 설치된 `scripts/session-entries.mjs rollback --at=<commit>`을 실행합니다. 플러그인 설치라면 Claude Code 안에서 `node "${CLAUDE_PLUGIN_ROOT}/scripts/session-entries.mjs" rollback --at=<commit>`, npm 설치라면 `node "$(npm root -g)/hypomnema/scripts/session-entries.mjs" rollback --at=<commit>`입니다. 그 커밋 시점의 원본으로 평면 `hot.md`와 `session-state.md`를 만들어 다시 추적하고, 표지 파일(`.hypo-session-entries-off`)을 더해 커밋 하나로 남깁니다. `sessions/` 아래는 아무것도 지우지 않습니다. 그 커밋 뒤에 쌓인 원본은 평면 파일에 없고, 그 뒤에 만든 프로젝트는 평면 파일 자체가 없으며, 구버전 기계는 여전히 `sessions/`를 읽지 못하며, 같은 내용이 원본과 평면 파일에 두 번 보일 수 있습니다. 항목이 모두 한 기계의 `machine:` 범위인 프로젝트는 그 `visibility_scope`를 단 평면 파일이 됩니다. 범위가 섞인 프로젝트는 `machine:` 항목이 평면 파일에서 빠지고, 명령이 빠진 프로젝트와 항목 수를 출력합니다. 표지가 있는 동안에는 어떤 훅도 볼트를 다시 옮기지 않습니다. 다음 동기화가 이 롤백을 원격에 올리므로, 이 볼트를 쓰는 모든 기계가 평면 파일로 돌아갑니다. 구버전 기계는 롤백을 받을 때 로컬의 `hot.md`와 `session-state.md`를 덮어쓰니, 그 기계에서 두 파일을 따로 복사해 두거나 먼저 업그레이드하세요. 표지에는 다시 켤 때 필요한 정보가 들어 있으니 직접 지우지 마세요. 세션 원본을 다시 켜려면 `/hypo:upgrade --migrate-sessions`를 실행하세요.
393
+
375
394
  ---
376
395
 
377
396
  ## 설정
@@ -434,7 +453,7 @@ E. 멈춘 프로젝트 재개.
434
453
 
435
454
  Claude(또는 다른 코딩 에이전트)로서 Hypomnema 위키를 운영한다면 아래가 기본 규칙입니다.
436
455
 
437
- 위키가 단일 원천입니다. 사용자가 기억해 달라고 한 것은 전부 위키 루트 아래 마크다운으로 있고, 기억에 의존해 답하기 전에 그것부터 읽습니다. 한 프로젝트의 배경은 `projects/<name>/hot.md`에, 다음에 할 일은 `projects/<name>/session-state.md`에 있으며 `/hypo:resume`이 둘 다 불러옵니다. 위키 루트의 `hot.md`는 다른 파일입니다. 어떤 프로젝트가 활성인지 가리키는 포인터 테이블입니다.
456
+ 위키가 단일 원천입니다. 사용자가 기억해 달라고 한 것은 전부 위키 루트 아래 마크다운으로 있고, 기억에 의존해 답하기 전에 그것부터 읽습니다. 한 프로젝트의 배경은 `projects/<name>/hot.md`에, 다음에 할 일은 `projects/<name>/session-state.md`에 있습니다. 둘 다 생성된 파일이므로 `session-state.md`를 직접 읽지 말고 `/hypo:resume`을 쓰세요. `/hypo:resume`은 세션 원본을 읽어 두 파일을 렌더링하고, 세션 원본으로 옮기기 전에는 디스크의 옛 파일이 `/hypo:resume`에 들어가는 새 마무리보다 뒤처질 수 있습니다. 위키 루트의 `hot.md`는 다른 파일입니다. 어떤 프로젝트가 활성인지 가리키는 포인터 테이블입니다.
438
457
 
439
458
  그 상태를 위키 밖에 또 두지 마세요. `MEMORY.md`와 `~/.claude/CLAUDE.md`의 `<learned_behaviors>` 블록은 스스로의 메모리처럼 보이지만, Hypomnema가 `pages/feedback/`에서 뽑아내는 단방향 projection입니다. 교정은 `/hypo:feedback`으로 적고 동기화가 다시 만들게 두면 됩니다. projection을 손으로 고치면 다음 동기화가 그것을 병합하지 않고 conflict로 멈춥니다. 어느 쪽이 진실인지 판단할 근거가 없기 때문이고, 그때부터 feedback 페이지로 되돌려 정리하는 일이 남습니다. 손으로 고친 내용이 위키가 쓸 내용과 이미 같다면 예외로, 다음 `--write`가 그대로 받아들입니다. 충돌한 블록을 위키 버전으로 되돌리려면 먼저 `hypomnema feedback-sync --import-target-change --from=<target>`으로 고친 내용을 초안으로 남겨 두고, 이어서 `hypomnema feedback-sync --accept-wiki=<slug>`를 실행하세요. 위키에 있어야 할 내용을 혼자만 보는 곳에 따로 챙겨 두면, 사용자의 다른 기기에는 끝내 닿지 않습니다.
440
459
 
package/README.md CHANGED
@@ -160,8 +160,9 @@ These are the recurring terms used in the rest of the README.
160
160
  | SoT ("source of truth") | The single file you edit; projections derive from it, never the other way around |
161
161
  | hook | A script Claude Code runs automatically on a lifecycle event (e.g. `SessionStart`, `Stop`) |
162
162
  | lifecycle event | A point Claude Code surfaces to plugins: session opens, prompt submitted, tool used, compact requested, session ends, etc. |
163
- | `hot.md` | Per-project cache: "what just happened" (most recent session highlights) |
164
- | `session-state.md` | Per-project cache: "what's next" (the resume payload for the next session) |
163
+ | session entry | One file per session close under `projects/<name>/sessions/`: a summary plus one update per track. The source of truth for `hot.md` and `session-state.md`, and never deleted |
164
+ | `hot.md` | Generated view: "what just happened" (the most recent session summaries), built from the session entries |
165
+ | `session-state.md` | Generated view: "what's next" (the resume payload for the next session), built from the session entries |
165
166
  | `.hypoignore` | Glob patterns that exclude paths from every content-injection hook and from `ingest`; a privacy boundary |
166
167
  | `.hyposcanignore` | Glob patterns that exclude paths from catalog scans only (`lint`, `stats`, `query`, `verify`, `doctor`); not a privacy boundary, a matched path still gets committed and read by hooks |
167
168
  | observability score | A per-session metric (search / ingest / feedback activity) that measures whether the wiki is actually being used |
@@ -209,11 +210,13 @@ Once installed, you stop _managing_ the wiki. It just accumulates.
209
210
 
210
211
  ### 4. Why a `hot.md` cache for resume
211
212
 
212
- The most expensive part of a paused project isn't redoing the work, it's rebuilding context. Reading `session-log/` from scratch costs minutes and tokens; reading a one-page `hot.md` costs neither. So we cache the most recent state explicitly: the root `hot.md` pointer table is a projection rebuilt from every project's own `hot.md` on both `SessionStart` and `Stop`, never hand-edited, while each project's own `hot.md` is written by crystallize; both get injected on `SessionStart`. Resume is O(1).
213
+ The most expensive part of a paused project isn't redoing the work, it's rebuilding context. Reading `session-log/` from scratch costs minutes and tokens; reading a one-page `hot.md` costs neither. So we cache the most recent state explicitly. Each session close writes one **session entry** to `projects/<name>/sessions/`, and three files are generated from the entries: the root `hot.md` pointer table, and each project's `hot.md` and `session-state.md`. The hooks rebuild them on both `SessionStart` and `Stop`, and they get injected on `SessionStart`. Resume reads the session entries and renders the views from them, so its cost grows with the number of entries rather than staying at two file reads. Because every session writes its own file, two sessions that close at the same time no longer overwrite each other.
213
214
 
214
- The root `hot.md` is a generated file, not a place to edit by hand. Whatever you type into it will be replaced the next time a hook rebuilds the projection (the next `SessionStart` or `Stop`), because the projection has no way to tell "content worth keeping" from "content in the way": there's no general-purpose compare-and-swap for a file rename, and your editor doesn't honor the advisory lock the hook takes. The first time the projection overwrites content it doesn't recognize as its own, it backs that content up next to the file as `hot.md.pre-projection-backup.md` (or `-2.md`, `-3.md`, ... if more than one has piled up), so nothing is silently discarded. But that backup is a best-effort save, not a safety guarantee: a save that lands mid-rewrite, or an editor that saves in place, can still leave a partial or missing copy. Treat it as a rescue net, not a place to write.
215
+ The three generated files are not a place to edit by hand, and they are not committed (the vault `.gitignore` lists them), so each machine builds its own from the entries it has pulled. Whatever you type into one will be replaced the next time a hook rebuilds it (the next `SessionStart` or `Stop`), because the generator has no way to tell "content worth keeping" from "content in the way": there's no general-purpose compare-and-swap for a file rename, and your editor doesn't honor the advisory lock the hook takes. The first time it overwrites content it doesn't recognize as its own, it backs that content up under `.cache/backups/` (for example `.cache/backups/hot.md.pre-projection-backup.md`, or `-2.md`, `-3.md`, ... if more than one has piled up), so nothing is silently discarded. But that backup is a best-effort save, not a safety guarantee: a save that lands mid-rewrite, or an editor that saves in place, can still leave a partial or missing copy. Treat it as a rescue net, not a place to write.
215
216
 
216
- Don't copy a backup back into the root `hot.md`. The next hook run will just replace it again. Instead: open the backup, read it, move whatever's worth keeping into the right project's `projects/<slug>/hot.md` or a standalone page (you decide which project it belongs to, the hook can't), then delete the backup. To see how many have piled up across the vault, list them from the vault root: `ls hot.md.pre-projection-backup*.md`.
217
+ To change what a generated file says, close a session with the new text. To keep a note that every session of a project should see, write it in `projects/<name>/notes.md`: that file is yours, it is not generated, and it is injected with the project's views. Don't copy a backup back into a generated file. Open the backup, read it, move whatever's worth keeping into `projects/<slug>/notes.md` or a standalone page (you decide which project it belongs to, the hook can't), then delete the backup. To see how many have piled up, list them from the vault root: `ls -R .cache/backups`.
218
+
219
+ Entries under `sessions/` are never deleted. The generated `hot.md` shows the latest few summaries and links the rest. [Session entries and the move to them](#session-entries-and-moving-an-existing-vault) covers existing vaults.
217
220
 
218
221
  ### 5. Why a feedback → behavior pipeline
219
222
 
@@ -251,15 +254,15 @@ Nine commands cover the full capture → retrieval → consolidation cycle.
251
254
 
252
255
  | Hook | Event | Role |
253
256
  |---|---|---|
254
- | `hypo-close-guard.mjs` | `PreToolUse` | When a Write/Edit/MultiEdit looks like a session-close write and the transcript carries no close signal from you, ask before it lands |
255
- | `hypo-session-start.mjs` | `SessionStart` | `git pull --ff-only`, rebuild the root `hot.md` projection, inject `hot.md` / `session-state.md` |
257
+ | `hypo-close-guard.mjs` | `PreToolUse` | Denies a direct Write/Edit/MultiEdit to a generated session view (root `hot.md`, a project's `hot.md` or `session-state.md`) or to a session entry (`projects/<p>/sessions/*.md`) |
258
+ | `hypo-session-start.mjs` | `SessionStart` | `git pull --ff-only`, rebuild the generated session views (root `hot.md`, and each project's `hot.md` and `session-state.md`) from `projects/*/sessions/`, inject them |
256
259
  | `hypo-first-prompt.mjs` | `UserPromptSubmit` | Marker-based one-shot request for a resume line on the first prompt (10-min TTL). It does not re-read `hot.md` |
257
260
  | `hypo-lookup.mjs` | `UserPromptSubmit` | BM25 top-3 HIT inject / MISS → closest-slug signal |
258
261
  | `hypo-compact-guard.mjs` | `UserPromptSubmit` | Detect a typed `/compact` or `/clear` and report an incomplete session close. It never blocks the compact |
259
262
  | `hypo-cwd-change.mjs` | `CwdChanged` | Build a notice from the matching project's `hot.md` (see the transmission note below) |
260
263
  | `hypo-file-watch.mjs` | `FileChanged` | Build a notice for a changed wiki file (honors `.hypoignore`); nothing in this package registers watch paths, so on a stock install the event has no trigger here at all (see the transmission note below) |
261
264
  | `hypo-auto-stage.mjs` | `PostToolUse(Write/Edit/MultiEdit)` | Auto-stage wiki-file edits |
262
- | `hypo-stop.mjs` | `Stop` | The only registered `Stop` hook. Runs four stages in order, not in parallel: rebuild the root `hot.md` projection (`SessionStart` calls the same generator), record session metadata for the observability score and auto-resume signaling, commit + pull + push, then, after the user signals wrap-up, block `Stop` on a substantial session with no verified close and hand back the `crystallize.mjs --mark-session-closed` command (with uncommitted changes or work still in flight it asks whether to close now instead) |
265
+ | `hypo-stop.mjs` | `Stop` | The only registered `Stop` hook. Runs four stages in order, not in parallel: rebuild the generated session views (`SessionStart` calls the same generator), record session metadata for the observability score and auto-resume signaling, commit + pull + push, then, after the user signals wrap-up, block `Stop` on a substantial session with no verified close and hand back the `crystallize.mjs --mark-session-closed` command (with uncommitted changes or work still in flight it asks whether to close now instead) |
263
266
  | `hypo-personal-check.mjs` | `PreCompact` | Surfaces an unfinished session-close, an uncommitted or unpushed wiki, malformed `hot.md`, or lint blockers as a `systemMessage`; it never blocks `/compact`. Session close is still enforced elsewhere: `hypo-stop.mjs`'s crystallize stage blocks on a substantial session with no verified close, and `--mark-session-closed` still refuses the marker on a red gate |
264
267
  | `hypo-session-end.mjs` | `SessionEnd` | Write a SessionEnd marker so SessionStart can detect `source=clear` recovery |
265
268
  | `hypo-web-fetch-ingest.mjs` | `PostToolUse(WebFetch/WebSearch)` | Inject a `/hypo:ingest` nudge into `additionalContext` after a WebFetch (URL redacted of query/hash/userinfo) or a WebSearch |
@@ -362,14 +365,30 @@ Do not store here:
362
365
  ├── projects/ ← project artifacts and session logs
363
366
  │ ├── _template/ ← scaffold copied when a new project is created
364
367
  │ └── <name>/
365
- │ ├── hot.md
366
- │ ├── session-state.md
368
+ │ ├── sessions/ ← one entry per session close (the source, never deleted)
369
+ │ ├── notes.md ← notes every session shares (yours, optional)
370
+ │ ├── hot.md ← generated from sessions/, not committed
371
+ │ ├── session-state.md ← generated from sessions/, not committed
367
372
  │ └── session-log/
368
373
  ├── journal/ ← daily / weekly / monthly entries
369
374
  ├── sources/ ← raw ingested sources (never edit)
370
- └── .cache/ ← runtime state (gitignored): session scope, parked proposals, sync status
375
+ └── .cache/ ← runtime state (gitignored): session scope, parked proposals, sync status, backups of overwritten generated files
371
376
  ```
372
377
 
378
+ ### Session entries and moving an existing vault
379
+
380
+ A session close writes `projects/<name>/sessions/<date>-<close_id>.md`. Root `hot.md` and each project's `hot.md` and `session-state.md` are generated from those entries and never committed. Shared notes go in `projects/<name>/notes.md`.
381
+
382
+ - **A new vault** starts on session entries.
383
+ - **An existing vault without a git remote** moves by itself at its first session start after the upgrade.
384
+ - **An existing vault with a git remote** does not move by itself. Upgrade every machine that uses the vault first, then run `/hypo:upgrade --migrate-sessions` once on one machine. It records the move and pushes it right away, so run it only when every machine is upgraded. Until then, session start tells you about it at most once a day.
385
+
386
+ Before the move, a close on the new version writes entries only. The generated files stay at their old content and don't change, and session start and `/hypo:resume` show the old files as the starting point with the new entries on top.
387
+
388
+ A machine that skipped the upgrade is affected after it pulls the move. Its `hot.md` and `session-state.md` disappear, its session start has nothing to inject, its closes are never recorded (the close receipt fails), it can't read `sessions/`, and an unpushed change there that edits those files stops its sync. Upgrade it and run `/hypo:doctor`, which reports recent traces of an older machine.
389
+
390
+ To go back to the flat files, run the bundled `scripts/session-entries.mjs rollback --at=<commit>`: `node "${CLAUDE_PLUGIN_ROOT}/scripts/session-entries.mjs" rollback --at=<commit>` from Claude Code on a plugin install, or `node "$(npm root -g)/hypomnema/scripts/session-entries.mjs" rollback --at=<commit>` on an npm install. It writes flat `hot.md` and `session-state.md` files rendered from the entries as they were at that commit, tracks them again, and adds a marker file (`.hypo-session-entries-off`) in one new commit. Nothing under `sessions/` is deleted. Entries added after that commit are not in the flat files, a project created after it gets no flat file at all, old-version machines still can't read `sessions/`, and the same text can appear both in an entry and in a flat file. A project whose entries all belong to one machine's `machine:` scope gets flat files tagged with that `visibility_scope`. A project that mixes scopes loses its `machine:` entries from the flat files, and the command prints which projects and how many entries. While the marker is there no hook moves the vault again. The next sync pushes the rollback, so every machine on this vault switches back to the flat files. A machine still on an older version overwrites its local `hot.md` and `session-state.md` when it pulls the rollback, so copy them aside on that machine first (or upgrade it first). The marker carries what re-enabling needs, so don't delete it by hand. To turn session entries back on, run `/hypo:upgrade --migrate-sessions`.
391
+
373
392
  ---
374
393
 
375
394
  ## Configuration
@@ -434,7 +453,7 @@ No external services. No API keys. No vector databases.
434
453
 
435
454
  If you are Claude (or another coding agent) operating a Hypomnema wiki, these are the ground rules.
436
455
 
437
- The wiki is the single source of truth. Everything the user asked you to remember about their work is markdown under the wiki root, and you read it before answering from recollection. `projects/<name>/hot.md` holds the background for one project, `projects/<name>/session-state.md` holds what to do next, and `/hypo:resume` loads both. The `hot.md` at the wiki root is a different file: a pointer table of which projects are active.
456
+ The wiki is the single source of truth. Everything the user asked you to remember about their work is markdown under the wiki root, and you read it before answering from recollection. `projects/<name>/hot.md` holds the background for one project and `projects/<name>/session-state.md` holds what to do next. Both are generated views, so use `/hypo:resume` rather than reading `session-state.md` directly: it reads the session entries and renders both views, and before the move to session entries the flat file on disk can lag the newer closes that `/hypo:resume` includes. The `hot.md` at the wiki root is a different file: a pointer table of which projects are active.
438
457
 
439
458
  Keep no second copy of that state. `MEMORY.md` and the `<learned_behaviors>` block inside `~/.claude/CLAUDE.md` look like your own memory, but they are one-way projections that Hypomnema derives from `pages/feedback/`. Record a correction with `/hypo:feedback` and let the sync regenerate them. Edit a projection by hand and the next sync stops on it as a conflict instead of merging it, because nothing can tell which side is now the truth; reconciling it back into the feedback page becomes your job. The one exception is a hand edit that already matches what the wiki would write, which the next `--write` takes over. To keep the wiki version of a conflicted block instead, run `hypomnema feedback-sync --import-target-change --from=<target>` first to save a copy of the edit as a draft, then `hypomnema feedback-sync --accept-wiki=<slug>`. And a wiki fact you stash somewhere only you can see never reaches the user's other machines at all.
440
459