hypomnema 1.8.2 → 1.9.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 (63) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +141 -51
  4. package/README.ko.md +18 -17
  5. package/README.md +16 -15
  6. package/commands/crystallize.md +89 -24
  7. package/commands/doctor.md +3 -1
  8. package/docs/ARCHITECTURE.md +45 -14
  9. package/docs/CONTRIBUTING.md +26 -8
  10. package/hooks/atomic-write.mjs +44 -0
  11. package/hooks/base-store.mjs +409 -90
  12. package/hooks/close-gate-store.mjs +131 -16
  13. package/hooks/close-journal.mjs +250 -0
  14. package/hooks/close-receipt.mjs +559 -0
  15. package/hooks/hooks.json +3 -38
  16. package/hooks/hypo-auto-commit.mjs +80 -10
  17. package/hooks/hypo-auto-minimal-crystallize.mjs +129 -25
  18. package/hooks/hypo-auto-stage.mjs +13 -1
  19. package/hooks/hypo-close-guard.mjs +2 -1
  20. package/hooks/hypo-cwd-change.mjs +45 -20
  21. package/hooks/hypo-file-watch.mjs +44 -6
  22. package/hooks/hypo-first-prompt.mjs +15 -9
  23. package/hooks/hypo-hot-rebuild.mjs +169 -112
  24. package/hooks/hypo-personal-check.mjs +9 -1
  25. package/hooks/hypo-session-record.mjs +1 -1
  26. package/hooks/hypo-session-start.mjs +566 -20
  27. package/hooks/hypo-shared.mjs +2250 -206
  28. package/hooks/hypo-stop.mjs +207 -0
  29. package/hooks/proposal-store.mjs +201 -35
  30. package/hooks/shared.json +15 -0
  31. package/hooks/version-check.mjs +9 -7
  32. package/package.json +3 -1
  33. package/scripts/crystallize.mjs +3 -4
  34. package/scripts/doctor.mjs +549 -123
  35. package/scripts/init.mjs +86 -109
  36. package/scripts/lib/core-hooks.mjs +48 -22
  37. package/scripts/lib/crystallize-close-apply.mjs +2397 -188
  38. package/scripts/lib/crystallize-close-check.mjs +43 -23
  39. package/scripts/lib/design-history-stale.mjs +36 -9
  40. package/scripts/lib/frontmatter.mjs +39 -0
  41. package/scripts/lib/git-hooks-dir.mjs +427 -52
  42. package/scripts/lib/hook-inventory.mjs +180 -0
  43. package/scripts/lib/page-usage.mjs +1 -1
  44. package/scripts/lib/pkg-provenance.mjs +18 -1
  45. package/scripts/lib/plugin-detect.mjs +43 -9
  46. package/scripts/lib/project-create.mjs +40 -44
  47. package/scripts/lib/schema-vocab.mjs +22 -11
  48. package/scripts/lib/template-schema-version.mjs +48 -0
  49. package/scripts/lib/time.mjs +46 -0
  50. package/scripts/lint.mjs +66 -18
  51. package/scripts/proposal.mjs +423 -18
  52. package/scripts/resume.mjs +3 -2
  53. package/scripts/session-audit.mjs +2 -1
  54. package/scripts/uninstall.mjs +144 -66
  55. package/scripts/upgrade.mjs +523 -234
  56. package/scripts/verify.mjs +2 -1
  57. package/scripts/weekly-report.mjs +2 -1
  58. package/templates/SCHEMA.md +27 -7
  59. package/templates/gitignore +11 -0
  60. package/templates/hypo-automation.md +10 -13
  61. package/templates/hypo-config.md +1 -1
  62. package/templates/hypo-guide.md +53 -25
  63. package/templates/hot.md +0 -24
@@ -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.8.2",
14
+ "version": "1.9.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.8.2",
3
+ "version": "1.9.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,96 @@ 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.9.0] - 2026-09-30
9
+
10
+ ### New Features
11
+ #### English
12
+ - Stop hooks now run as one ordered chain, so the Stop commit no longer misses files the same Stop just rebuilt, and a failed step is reported instead of passing as a clean Stop. upgrade removes the old Stop registrations on npm installs so no step runs twice. A close that dies before its commit is no longer read as finished, and a subagent hand-back no longer cancels a close the user approved. Plugin users are pointed at `/hypo:doctor` and `/hypo:crystallize` wherever an npm-only command was named. The root `hot.md` is now generated from the projects' own `hot.md` at every session start and stop instead of being edited by hand at session close; whenever it finds root `hot.md` content it did not write, it saves that content as `hot.md.pre-projection-backup.md` before overwriting. ([#301](https://github.com/sk-lim19f/Hypomnema/pull/301))
13
+ #### 한국어
14
+ - Stop 훅이 순서대로 한 줄로 돌아, 같은 Stop 이 방금 다시 만든 파일을 Stop 커밋이 빠뜨리지 않고 실패한 단계는 정상 종료처럼 넘어가지 않고 보고됩니다. npm 설치에서 upgrade 가 옛 Stop 등록을 지워 어느 단계도 두 번 돌지 않습니다. 커밋 전에 죽은 close 를 끝난 것으로 읽지 않고, 서브에이전트 결과 반환이 사용자가 승인한 close 를 취소하지 않습니다. npm 전용 명령을 안내하던 자리마다 plugin 사용자에게 `/hypo:doctor` 와 `/hypo:crystallize` 를 함께 알려 줍니다. 루트 `hot.md` 는 이제 세션 close 때 손으로 고치지 않고, 세션을 시작하고 멈출 때마다 각 프로젝트의 `hot.md` 로 다시 만들어집니다. 생성기가 직접 쓰지 않은 내용을 발견하면, 덮어쓰기 전에 그 내용을 `hot.md.pre-projection-backup.md` 로 저장합니다. ([#301](https://github.com/sk-lim19f/Hypomnema/pull/301))
15
+
16
+ ### Bug Fixes
17
+ #### English
18
+ - Session close no longer fails on another session's uncommitted root files: it issues a commit-backed close checkpoint receipt that certifies only the files this close wrote, blocks on this session's recorded writes and on uncommitted files in the project being closed, and reports the rest as notices; Stop, `--check-session-close` (new `close_state`), doctor and PreCompact now agree on when a close is finished. ([#305](https://github.com/sk-lim19f/Hypomnema/pull/305))
19
+ - lint and session close now reject dates that do not exist on the calendar (such as `2026-02-30`), and warn (W16) when a synthesis page's own `updated` is not a real date or when `sources_consulted` is written as a YAML block list ([#304](https://github.com/sk-lim19f/Hypomnema/pull/304))
20
+ - `doctor` now reports a plugin-plus-manual install on every run, and says the state is undetermined instead of advising an uninstall when the plugin's files cannot be located. `proposal list` shows each parked proposal's age and whether its target changed since it was parked. `hypo-cwd-change` and `hypo-file-watch` moved their notices onto a field the host does not ignore. ([#300](https://github.com/sk-lim19f/Hypomnema/pull/300))
21
+ - A proposal you approve now finishes the close that parked it, instead of leaving that close stuck re-running with nothing to show for it. `"restructure": true` no longer lets a payload approve its own destructive overwrite; a human approves the parked write. `doctor` names the hook files it reports missing, tells a real double registration apart from a healthy install, and distinguishes an approved-but-unfinished proposal from one awaiting review. New `hypomnema proposal reconcile` recovers an interrupted handoff without rewriting any page. ([#299](https://github.com/sk-lim19f/Hypomnema/pull/299))
22
+ #### 한국어
23
+ - 다른 세션의 미커밋 루트 파일 때문에 session close 가 실패하지 않습니다. close 는 이 close 가 쓴 파일만 증명하는 커밋 기반 close checkpoint receipt 를 발급하고, 이 세션이 기록한 쓰기와 close 하는 프로젝트 폴더의 미커밋 파일만 막으며 나머지는 알림으로 보고합니다. Stop, `--check-session-close`(새 `close_state`), doctor, PreCompact 가 close 완료 판정을 같이 합니다. ([#305](https://github.com/sk-lim19f/Hypomnema/pull/305))
24
+ - lint 와 세션 종료가 달력에 없는 날짜(`2026-02-30` 등)를 거절하고, synthesis 페이지 자신의 `updated` 가 실제 날짜가 아니거나 `sources_consulted` 를 YAML 블록 목록으로 쓴 경우 W16 경고를 냅니다 ([#304](https://github.com/sk-lim19f/Hypomnema/pull/304))
25
+ - `doctor`가 플러그인과 수동 설치의 공존을 매 실행 보고하고, 플러그인 파일을 못 찾으면 제거를 권하는 대신 판정 불가라고 말합니다. `proposal list`가 보류된 제안마다 나이와 대상이 파킹 이후 바뀌었는지를 보여 줍니다. `hypo-cwd-change`와 `hypo-file-watch`는 호스트가 무시하지 않는 필드로 알림을 옮겼습니다. ([#300](https://github.com/sk-lim19f/Hypomnema/pull/300))
26
+ - 승인한 proposal이 그것을 park한 close를 실제로 끝냅니다. 전에는 그 close가 아무 성과 없이 재실행만 반복했습니다. `"restructure": true`로 payload가 자기 파괴적 덮어쓰기를 승인할 수 없고, 파킹된 쓰기는 사람이 승인합니다. `doctor`가 누락된 훅 파일의 이름을 대고, 진짜 이중 등록을 정상 설치와 가르고, 승인됐지만 안 끝난 proposal을 검토 대기 중인 것과 구별합니다. 새 `hypomnema proposal reconcile`이 중단된 handoff를 페이지 수정 없이 복구합니다. ([#299](https://github.com/sk-lim19f/Hypomnema/pull/299))
27
+
28
+ ### Chores
29
+ #### English
30
+ - The README and docs now say that the `cd` and file-change notices are brief terminal notifications that do not reach Claude, that the resume cue fires only when Claude moves directories in-session, and when the auto-project offer actually reaches Claude. The no-match notice no longer claims to inject the global `hot.md`. ([#303](https://github.com/sk-lim19f/Hypomnema/pull/303))
31
+ #### 한국어
32
+ - README 와 문서가 `cd` 알림과 파일 변경 알림은 Claude 에게 닿지 않는 짧은 터미널 알림이라는 것, 재개 신호는 Claude 가 세션 안에서 디렉터리를 옮길 때만 걸린다는 것, 프로젝트 자동 생성 제안이 실제로 Claude 에게 닿는 조건을 사실대로 설명합니다. 매칭되는 프로젝트가 없을 때의 알림도 더 이상 전역 `hot.md` 를 주입한다고 말하지 않습니다. ([#303](https://github.com/sk-lim19f/Hypomnema/pull/303))
33
+
34
+ ### Changelog
35
+ - [#305](https://github.com/sk-lim19f/Hypomnema/pull/305) fix(close): certify only what a close wrote, so another session's uncommitted files no longer block it
36
+ - [#304](https://github.com/sk-lim19f/Hypomnema/pull/304) fix(lint): reject calendar-overflow dates, and warn when a synthesis's own updated is not a real date or sources_consulted is a block list
37
+ - [#303](https://github.com/sk-lim19f/Hypomnema/pull/303) docs(hooks): say what the cd and file-change notices actually reach, and when the auto-project offer shows
38
+ - [#302](https://github.com/sk-lim19f/Hypomnema/pull/302) refactor(scripts): share one day-length constant, and pin proposal age and failure branches with tests
39
+ - [#301](https://github.com/sk-lim19f/Hypomnema/pull/301) feat(hooks,close,upgrade): run Stop as one ordered chain, and keep close-intent until the commit
40
+ - [#300](https://github.com/sk-lim19f/Hypomnema/pull/300) fix(doctor,hooks): report a dual install without guessing, and stop parking bytes from being auto-deleted
41
+ - [#299](https://github.com/sk-lim19f/Hypomnema/pull/299) fix(close): carry approved proposals back to their close, and stop the writers that reported success while doing nothing
42
+ Contributors: @sk-lim19f
43
+
44
+ ## [1.8.4] - 2026-09-14
45
+
46
+ ### Bug Fixes
47
+
48
+ #### English
49
+
50
+ - A session close no longer blocks on another project's uncommitted files. It judges the scope the close actually owns, and a dirty file belonging to a different project comes back as a notice naming its path instead of a blocker. ([#296](https://github.com/sk-lim19f/Hypomnema/pull/296))
51
+ - A close that could not write its marker no longer spends the close signal, so the session stays closable. ([#296](https://github.com/sk-lim19f/Hypomnema/pull/296))
52
+ - A close whose commit fails used to deadlock: the retry skipped every field as already-current, dropped those paths from the commit scope, and the gate then blocked on them forever. A close journal records what this session wrote, and a later run reclaims a path only when those exact bytes are still on disk. ([#296](https://github.com/sk-lim19f/Hypomnema/pull/296))
53
+ - The hook file list moved out of `hooks/hooks.json` into its own `hooks/shared.json`, ending the harness warning that fired on every session. ([#297](https://github.com/sk-lim19f/Hypomnema/pull/297))
54
+ - Install, upgrade, doctor and uninstall read that list through one parser instead of five private ones, so a command target outside `hooks/`, a command carrying arguments, and an empty hook map now mean the same thing to all of them. Install refuses to write anything when a listed file is missing from the package, and uninstall falls back to its provenance record when the package list is unreadable rather than exiting with every hook still in place. ([#297](https://github.com/sk-lim19f/Hypomnema/pull/297))
55
+
56
+ #### 한국어
57
+
58
+ - 세션 close 가 다른 프로젝트의 미커밋 파일에 더는 막히지 않습니다. close 가 실제로 책임지는 범위로 판정하고, 다른 프로젝트의 dirty 파일은 blocker 가 아니라 경로가 적힌 notice 로 돌아옵니다. ([#296](https://github.com/sk-lim19f/Hypomnema/pull/296))
59
+ - 마커를 못 쓴 close 가 close 신호를 소진하지 않아, 세션을 계속 닫을 수 있습니다. ([#296](https://github.com/sk-lim19f/Hypomnema/pull/296))
60
+ - 커밋에 실패한 close 가 교착에 빠지던 것을 고쳤습니다. 재시도가 모든 필드를 already-current 로 건너뛰면서 그 경로를 커밋 범위에서 빠뜨렸고, 게이트가 영원히 막았습니다. close 저널이 이 세션이 쓴 것을 기록하고, 나중 실행은 그 bytes 가 디스크에 그대로 있을 때만 경로를 되찾습니다. ([#296](https://github.com/sk-lim19f/Hypomnema/pull/296))
61
+ - 훅 파일 목록이 `hooks/hooks.json` 밖으로 나와 `hooks/shared.json` 이 됐습니다. 세션마다 뜨던 하네스 경고가 사라집니다. ([#297](https://github.com/sk-lim19f/Hypomnema/pull/297))
62
+ - 설치와 업그레이드와 진단과 제거가 그 목록을 각자의 reader 다섯 대신 공용 parser 하나로 읽습니다. `hooks/` 밖을 가리키는 command, 인자가 붙은 command, 빈 훅 맵이 이제 넷 모두에게 같은 뜻입니다. 설치는 목록의 파일이 패키지에 없으면 아무것도 쓰지 않고, 제거는 패키지 목록을 못 읽으면 provenance 기록으로 되돌아갑니다. 훅을 전부 남긴 채 종료하지 않습니다. ([#297](https://github.com/sk-lim19f/Hypomnema/pull/297))
63
+
64
+ ### Changelog
65
+
66
+ - [#296](https://github.com/sk-lim19f/Hypomnema/pull/296) fix(close): scope the dirty gate and let a retry reclaim its own writes
67
+ - [#297](https://github.com/sk-lim19f/Hypomnema/pull/297) fix(hooks): move the shared file list out of hooks.json
68
+ - [#295](https://github.com/sk-lim19f/Hypomnema/pull/295) chore(release): v1.8.3
69
+
70
+ Contributors: @sk-lim19f
71
+
72
+ ## [1.8.3] - 2026-09-11
73
+
74
+ ### Bug Fixes
75
+
76
+ #### English
77
+
78
+ - The vault pre-commit hook finds the active install when it runs, instead of baking a path in at install time. It now survives a move, a reinstall, or a version bump. When it cannot find one it blocks the commit and prints how to fix it, rather than passing silently. ([#294](https://github.com/sk-lim19f/Hypomnema/pull/294))
79
+ - SessionStart repairs a drifted `hypo-pkg.json` pointer itself instead of only warning about it. It refuses a downgrade, refuses a value that is not semver, and serialises against other sessions with a file lock. ([#294](https://github.com/sk-lim19f/Hypomnema/pull/294))
80
+ - `upgrade` names what each SCHEMA version added, so a template-update notice says what changed rather than only that something did. ([#294](https://github.com/sk-lim19f/Hypomnema/pull/294))
81
+
82
+ #### 한국어
83
+
84
+ - 볼트 pre-commit 훅이 설치 경로를 설치 시점에 박아 두는 대신 실행할 때 찾습니다. 설치를 옮기거나 다시 깔거나 버전을 올려도 살아남습니다. 못 찾으면 조용히 통과시키지 않고 커밋을 막으면서 고치는 법을 알려 줍니다. ([#294](https://github.com/sk-lim19f/Hypomnema/pull/294))
85
+ - SessionStart 가 어긋난 `hypo-pkg.json` 포인터를 경고만 하지 않고 직접 고칩니다. 다운그레이드와 semver 가 아닌 값은 거부하고, 파일 잠금으로 다른 세션과 직렬화합니다. ([#294](https://github.com/sk-lim19f/Hypomnema/pull/294))
86
+ - `upgrade` 가 SCHEMA 버전마다 무엇이 더해졌는지 말해 줍니다. 템플릿 갱신 안내가 무언가 바뀌었다고만 하지 않고 무엇이 바뀌었는지 알려 줍니다. ([#294](https://github.com/sk-lim19f/Hypomnema/pull/294))
87
+
88
+ ### Chores
89
+
90
+ #### English
91
+
92
+ - Wiki decision pointers are gone from every public surface, this changelog included. The repo ships no directory for them, so they resolved to nothing for any reader outside the maintainer's private notes. ([#294](https://github.com/sk-lim19f/Hypomnema/pull/294))
93
+
94
+ #### 한국어
95
+
96
+ - 위키 결정 문서 포인터를 이 changelog 를 포함한 모든 공개 표면에서 없앴습니다. 레포가 그 문서를 담은 디렉터리를 출하하지 않아서, 관리자의 개인 기록 밖에 있는 독자에게는 아무것도 가리키지 않는 포인터였습니다. ([#294](https://github.com/sk-lim19f/Hypomnema/pull/294))
97
+
8
98
  ## [1.8.2] - 2026-09-10
9
99
 
10
100
  ### Bug Fixes
@@ -14,7 +104,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
14
104
  - Hook notices reach the model again. They were emitted with a top-level `additionalContext`, which Claude Code silently ignores, so the close gate, the resume contract, and lookup results never arrived. Measured after the fix: a canary planted in a project's `hot.md` came back verbatim from a session with every file tool blocked, and the same canary read as absent on the previous build. ([#287](https://github.com/sk-lim19f/Hypomnema/pull/287))
15
105
  - The session-close gate reads a background-task notification the same way on every delivery path, so a task finishing after you asked to close no longer cancels the close. A refusal also reports `gateReason`, naming which check refused. ([#289](https://github.com/sk-lim19f/Hypomnema/pull/289))
16
106
  - `lint`, `verify`, `graph`, `query` and `crystallize` exit 2 on an unrecognised flag or an empty `--hypo-dir=`, instead of silently dropping it and falling back to the default vault. Their command docs no longer print placeholder syntax as runnable arguments. ([#286](https://github.com/sk-lim19f/Hypomnema/pull/286))
17
- - The `/compact` and `/clear` notice no longer orders a session close, no longer fires on another project's uncommitted files, and no longer goes silent when the vault is broken enough to be worth reporting (`decisions/0101`). ([#285](https://github.com/sk-lim19f/Hypomnema/pull/285))
107
+ - The `/compact` and `/clear` notice no longer orders a session close, no longer fires on another project's uncommitted files, and no longer goes silent when the vault is broken enough to be worth reporting. ([#285](https://github.com/sk-lim19f/Hypomnema/pull/285))
18
108
  - `/hypo:doctor` no longer accepts a session-closed marker as proof for a project the close gate never evaluated. The marker records the set the gate actually checked, and the audit requires an artifact's project to be in that set as well as in the marker's attribution. ([#284](https://github.com/sk-lim19f/Hypomnema/pull/284))
19
109
 
20
110
  #### 한국어
@@ -22,7 +112,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
22
112
  - 훅 알림이 다시 모델에 닿습니다. 최상단 `additionalContext`로 나가고 있었는데 Claude Code가 그것을 조용히 무시해서 close 게이트, 세션 재개 계약, lookup 결과가 도착한 적이 없었습니다. 고친 뒤 실측했습니다. 프로젝트 `hot.md`에 심은 난수를 파일 도구를 전부 막은 세션이 그대로 답했고, 같은 난수를 이전 빌드는 못 읽었습니다. ([#287](https://github.com/sk-lim19f/Hypomnema/pull/287))
23
113
  - 세션 마무리 게이트가 background task 알림을 어느 전달 경로로 오든 같게 읽습니다. 마무리를 요청한 뒤 작업이 끝나도 그 마무리가 취소되지 않습니다. 거절할 때는 `gateReason`으로 어느 검사가 거절했는지도 함께 알립니다. ([#289](https://github.com/sk-lim19f/Hypomnema/pull/289))
24
114
  - `lint`·`verify`·`graph`·`query`·`crystallize`가 못 알아보는 플래그와 빈 `--hypo-dir=`를 조용히 버리고 기본 볼트로 가는 대신 exit 2를 냅니다. 명령 문서도 placeholder 문법을 실행 가능한 인자처럼 찍지 않습니다. ([#286](https://github.com/sk-lim19f/Hypomnema/pull/286))
25
- - `/compact`·`/clear` 알림이 세션 마무리를 지시하지 않고, 다른 프로젝트의 미커밋 파일로 뜨지 않으며, 보고할 값어치가 있을 만큼 볼트가 망가졌을 때 조용해지지 않습니다 (`decisions/0101`). ([#285](https://github.com/sk-lim19f/Hypomnema/pull/285))
115
+ - `/compact`·`/clear` 알림이 세션 마무리를 지시하지 않고, 다른 프로젝트의 미커밋 파일로 뜨지 않으며, 보고할 값어치가 있을 만큼 볼트가 망가졌을 때 조용해지지 않습니다. ([#285](https://github.com/sk-lim19f/Hypomnema/pull/285))
26
116
  - `/hypo:doctor`가 close 게이트가 한 번도 평가하지 않은 프로젝트에 대해 session-closed 마커를 증거로 받지 않습니다. 마커가 게이트의 실제 검사 집합을 기록하고, 감사가 산출물의 프로젝트를 귀속뿐 아니라 그 집합에서도 요구합니다. ([#284](https://github.com/sk-lim19f/Hypomnema/pull/284))
27
117
 
28
118
  ### Chores
@@ -41,13 +131,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
41
131
 
42
132
  #### English
43
133
 
44
- - Six slash names (`crystallize`, `graph`, `ingest`, `lint`, `query`, `verify`) shipped as both a `commands/*.md` file and a `skills/*/SKILL.md` directory, so each registered twice as a component and the command palette repeated them. Only the `commands/` half ever loaded, so the duplicates are removed with no behavior change, and `smoke:plugin` now fails on a name present in both directories (decisions/0100). ([#282](https://github.com/sk-lim19f/Hypomnema/pull/282))
134
+ - Six slash names (`crystallize`, `graph`, `ingest`, `lint`, `query`, `verify`) shipped as both a `commands/*.md` file and a `skills/*/SKILL.md` directory, so each registered twice as a component and the command palette repeated them. Only the `commands/` half ever loaded, so the duplicates are removed with no behavior change, and `smoke:plugin` now fails on a name present in both directories. ([#282](https://github.com/sk-lim19f/Hypomnema/pull/282))
45
135
  - A session close now checks that the root files it writes still carry a usable frontmatter block, and refuses a close that would leave invalid YAML in one of them. ([#281](https://github.com/sk-lim19f/Hypomnema/pull/281))
46
136
  - `/hypo:resume` and SessionStart now surface uncommitted vault work that belongs to another project, so a session does not silently adopt it. ([#280](https://github.com/sk-lim19f/Hypomnema/pull/280))
47
137
 
48
138
  #### 한국어
49
139
 
50
- - 여섯 슬래시 이름(`crystallize`, `graph`, `ingest`, `lint`, `query`, `verify`)이 `commands/*.md` 파일과 `skills/*/SKILL.md` 디렉터리로 둘 다 출하되어 component 가 두 번씩 등록되고 명령 팔레트가 그만큼 반복됐습니다. 실제로 로드된 것은 `commands/` 쪽뿐이라 중복을 동작 변화 없이 지웠고, 이제 한 이름이 두 디렉터리에 다 있으면 `smoke:plugin` 이 실패합니다 (decisions/0100). ([#282](https://github.com/sk-lim19f/Hypomnema/pull/282))
140
+ - 여섯 슬래시 이름(`crystallize`, `graph`, `ingest`, `lint`, `query`, `verify`)이 `commands/*.md` 파일과 `skills/*/SKILL.md` 디렉터리로 둘 다 출하되어 component 가 두 번씩 등록되고 명령 팔레트가 그만큼 반복됐습니다. 실제로 로드된 것은 `commands/` 쪽뿐이라 중복을 동작 변화 없이 지웠고, 이제 한 이름이 두 디렉터리에 다 있으면 `smoke:plugin` 이 실패합니다. ([#282](https://github.com/sk-lim19f/Hypomnema/pull/282))
51
141
  - 세션 마무리가 자기가 쓰는 루트 파일에 쓸 만한 프론트매터 블록이 남는지 검사하고, 그중 하나에 깨진 YAML 을 남길 마무리를 거절합니다. ([#281](https://github.com/sk-lim19f/Hypomnema/pull/281))
52
142
  - `/hypo:resume` 과 SessionStart 가 다른 프로젝트 소관의 미커밋 변경을 한 줄로 알려, 세션이 그것을 조용히 자기 일로 삼지 않게 합니다. ([#280](https://github.com/sk-lim19f/Hypomnema/pull/280))
53
143
 
@@ -74,32 +164,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
74
164
  - A session close no longer writes over a page that changed since it read it, in any case: every drifted whole-file overwrite parks for review. `CHANGELOG.md` ships in the npm package, so the README links to it resolve. ([#262](https://github.com/sk-lim19f/Hypomnema/pull/262))
75
165
  - Five skills told Claude to pass `--wiki-dir` to scripts that only ever parsed `--hypo-dir`, and none of those scripts rejects an unknown flag, so the value was dropped and the command silently used the default vault instead of the one you named. `crystallize` was among them, so a session close could land in the wrong vault and still report success. ([#266](https://github.com/sk-lim19f/Hypomnema/pull/266))
76
166
  - `hypomnema upgrade --apply` repoints the git pre-commit hook in your wiki repo at the install you are actually running, and `/hypo:doctor` reports when that hook names an older one. The hook holds an absolute path baked in at init time, so until now a vault could keep checking every commit against a release from months earlier while the version string read correctly. Installing a release does not repoint it: run the apply step once after updating. ([#267](https://github.com/sk-lim19f/Hypomnema/pull/267))
77
- - `/compact` is no longer blocked by an unfinished session close. The PreCompact hook reports what it finds and lets the compact through, and a close gate scoped to the project this session is actually closing stops charging one session for another session's unfinished close (decisions/0095). ([#273](https://github.com/sk-lim19f/Hypomnema/pull/273))
167
+ - `/compact` is no longer blocked by an unfinished session close. The PreCompact hook reports what it finds and lets the compact through, and a close gate scoped to the project this session is actually closing stops charging one session for another session's unfinished close. ([#273](https://github.com/sk-lim19f/Hypomnema/pull/273))
78
168
  - The session-close gate no longer demotes a file the session itself edited when the transcript is only partially readable, and each pending-work notice names its own type instead of being reported as a lint issue. ([#275](https://github.com/sk-lim19f/Hypomnema/pull/275))
79
169
  - A session that spans several days can close again: targets a later session start re-read are no longer parked as stale, while anything it was not shown in full still parks. ([#276](https://github.com/sk-lim19f/Hypomnema/pull/276))
80
- - When the plugin registry cannot be read, init and upgrade leave the package pointer and the vault's pre-commit hook alone instead of recording the npm copy the dual-install notice asks you to delete, and the drift banner and doctor point at repairing the registry (decisions/0097). ([#277](https://github.com/sk-lim19f/Hypomnema/pull/277))
170
+ - When the plugin registry cannot be read, init and upgrade leave the package pointer and the vault's pre-commit hook alone instead of recording the npm copy the dual-install notice asks you to delete, and the drift banner and doctor point at repairing the registry. ([#277](https://github.com/sk-lim19f/Hypomnema/pull/277))
81
171
 
82
172
  #### 한국어
83
173
 
84
174
  - 세션 마무리가 자기가 읽은 뒤 바뀐 페이지를 덮어쓰는 경우가 이제 없습니다. 드리프트된 전체 덮어쓰기는 전부 보류되고 검토를 기다립니다. `CHANGELOG.md` 가 npm 패키지에 실려 README 의 링크가 살아납니다. ([#262](https://github.com/sk-lim19f/Hypomnema/pull/262))
85
175
  - 다섯 스킬이 `--hypo-dir` 만 파싱하는 스크립트에 `--wiki-dir` 를 넘기라고 지시했고, 그 스크립트들이 미지정 플래그를 거부하지 않아 값이 버려진 채 지정한 볼트가 아니라 기본 볼트가 쓰였습니다. `crystallize` 가 그중 하나라 세션 마무리가 엉뚱한 볼트에 쓰이고도 성공을 보고할 수 있었습니다. ([#266](https://github.com/sk-lim19f/Hypomnema/pull/266))
86
176
  - `hypomnema upgrade --apply` 가 위키 레포의 git pre-commit 훅을 실제로 돌고 있는 설치로 다시 겨눕니다. 그리고 `/hypo:doctor` 가 그 훅이 더 오래된 설치를 가리키면 보고합니다. 이 훅은 init 시점의 절대경로를 들고 있어서, 지금까지는 버전 문자열이 맞게 읽히는 동안에도 볼트가 몇 달 전 릴리스로 모든 커밋을 계속 검사할 수 있었습니다. 릴리스를 설치하는 것만으로는 다시 겨눠지지 않으니 업데이트 후 apply 단계를 한 번 돌리세요. ([#267](https://github.com/sk-lim19f/Hypomnema/pull/267))
87
- - 세션 마무리가 미완결이어도 `/compact` 가 막히지 않습니다. PreCompact 훅은 찾은 것을 보고만 하고 compact 를 통과시키며, 이 세션이 실제로 닫는 프로젝트로 좁혀진 close 게이트가 남의 세션이 남긴 미완결 close 를 이 세션에 물리지 않습니다 (decisions/0095). ([#273](https://github.com/sk-lim19f/Hypomnema/pull/273))
177
+ - 세션 마무리가 미완결이어도 `/compact` 가 막히지 않습니다. PreCompact 훅은 찾은 것을 보고만 하고 compact 를 통과시키며, 이 세션이 실제로 닫는 프로젝트로 좁혀진 close 게이트가 남의 세션이 남긴 미완결 close 를 이 세션에 물리지 않습니다. ([#273](https://github.com/sk-lim19f/Hypomnema/pull/273))
88
178
  - transcript 가 일부만 읽히는 상황에서 세션이 직접 편집한 파일이 강등되지 않고, 남은 작업 notice 가 lint 로 뭉뚱그려지지 않고 각자 유형으로 표시됩니다. ([#275](https://github.com/sk-lim19f/Hypomnema/pull/275))
89
179
  - 여러 날 이어진 세션도 close 가 됩니다. 나중 세션 시작이 다시 읽어 보여준 대상은 묵었다고 파킹되지 않고, 전부를 보여주지 못한 것은 그대로 파킹됩니다. ([#276](https://github.com/sk-lim19f/Hypomnema/pull/276))
90
- - 플러그인 registry 를 못 읽을 때 init 과 upgrade 가 dual install 안내가 지우라고 하는 npm 사본을 기록하지 않고 포인터와 볼트 pre-commit 훅을 그대로 둡니다. drift 배너와 doctor 도 registry 수리를 지목합니다 (decisions/0097). ([#277](https://github.com/sk-lim19f/Hypomnema/pull/277))
180
+ - 플러그인 registry 를 못 읽을 때 init 과 upgrade 가 dual install 안내가 지우라고 하는 npm 사본을 기록하지 않고 포인터와 볼트 pre-commit 훅을 그대로 둡니다. drift 배너와 doctor 도 registry 수리를 지목합니다. ([#277](https://github.com/sk-lim19f/Hypomnema/pull/277))
91
181
 
92
182
  ### Chores
93
183
 
94
184
  #### English
95
185
 
96
186
  - The automation reference installed into a vault no longer claims that no hook makes a network request, lists all 15 registered hooks under their correct events, and corrects five further claims about auto-stage, the Stop chain, FileChanged, and `.hypoignore`. Existing vaults keep their old copy: the file has no update channel yet. ([#263](https://github.com/sk-lim19f/Hypomnema/pull/263))
97
- - `/hypo:doctor` tells an unreadable plugin registry apart from no plugin at all, so an install it cannot verify is reported instead of being treated as absent (decisions/0097). ([#272](https://github.com/sk-lim19f/Hypomnema/pull/272))
187
+ - `/hypo:doctor` tells an unreadable plugin registry apart from no plugin at all, so an install it cannot verify is reported instead of being treated as absent. ([#272](https://github.com/sk-lim19f/Hypomnema/pull/272))
98
188
 
99
189
  #### 한국어
100
190
 
101
191
  - 볼트에 설치되는 자동화 참조가 더 이상 "어떤 훅도 네트워크 요청을 하지 않는다" 고 말하지 않고, 등록된 훅 15 개를 각자의 올바른 이벤트 아래 나열하며, auto-stage·Stop 체인·FileChanged·`.hypoignore` 에 대한 다섯 가지 서술을 함께 고칩니다. 기존 볼트는 낡은 사본을 유지합니다. 이 파일에는 아직 갱신 채널이 없습니다. ([#263](https://github.com/sk-lim19f/Hypomnema/pull/263))
102
- - `/hypo:doctor` 가 읽을 수 없는 플러그인 registry 와 플러그인이 아예 없는 경우를 구별하여, 확인할 수 없는 설치를 부재로 취급하지 않고 보고합니다 (decisions/0097). ([#272](https://github.com/sk-lim19f/Hypomnema/pull/272))
192
+ - `/hypo:doctor` 가 읽을 수 없는 플러그인 registry 와 플러그인이 아예 없는 경우를 구별하여, 확인할 수 없는 설치를 부재로 취급하지 않고 보고합니다. ([#272](https://github.com/sk-lim19f/Hypomnema/pull/272))
103
193
 
104
194
  ## [1.7.4] - 2026-08-29
105
195
 
@@ -276,7 +366,7 @@ Contributors: @sk-lim19f
276
366
  #### English
277
367
 
278
368
  - A session close now verifies the user asked for it before writing anything, rather than after the files were already written and committed. ([#199](https://github.com/sk-lim19f/Hypomnema/pull/199))
279
- - The close gate tracks the latest user decision as a lease instead of scanning the whole transcript for any close signal, so a stale close, a queued change of mind, a `/clear`, or a model self-close no longer counts as a user close (decisions/0075). ([#213](https://github.com/sk-lim19f/Hypomnema/pull/213))
369
+ - The close gate tracks the latest user decision as a lease instead of scanning the whole transcript for any close signal, so a stale close, a queued change of mind, a `/clear`, or a model self-close no longer counts as a user close. ([#213](https://github.com/sk-lim19f/Hypomnema/pull/213))
280
370
  - The session-close marker is attributed to the project the session actually closed rather than the vault's most recent one, and close debt is charged to the session that made it: an incomplete close in a project this session never touched is now a non-blocking notice instead of blocking `/compact`. ([#193](https://github.com/sk-lim19f/Hypomnema/pull/193), [#212](https://github.com/sk-lim19f/Hypomnema/pull/212))
281
371
  - A session close can no longer apply one session's payload under another's marker: the payload temp path is session-scoped, and apply refuses a payload whose `sessionId` does not match. ([#204](https://github.com/sk-lim19f/Hypomnema/pull/204))
282
372
  - Two sessions withholding an overwrite for the same page no longer keep only the last one's bytes, and `proposal apply` reports any proposal still pending against the file it just wrote. ([#191](https://github.com/sk-lim19f/Hypomnema/pull/191))
@@ -294,7 +384,7 @@ Contributors: @sk-lim19f
294
384
  #### 한국어
295
385
 
296
386
  - 세션 close가 파일을 다 쓰고 커밋한 뒤가 아니라, 아무것도 쓰기 전에 사용자가 요청했는지 확인합니다. ([#199](https://github.com/sk-lim19f/Hypomnema/pull/199))
297
- - close 게이트가 트랜스크립트 전체에서 close 신호를 찾는 대신 가장 최근 사용자 결정을 lease로 추적해, 오래된 close나 큐에 넣은 마음 바꿈, `/clear`, 모델의 자기 종료가 더 이상 사용자 종료로 세어지지 않습니다(decisions/0075). ([#213](https://github.com/sk-lim19f/Hypomnema/pull/213))
387
+ - close 게이트가 트랜스크립트 전체에서 close 신호를 찾는 대신 가장 최근 사용자 결정을 lease로 추적해, 오래된 close나 큐에 넣은 마음 바꿈, `/clear`, 모델의 자기 종료가 더 이상 사용자 종료로 세어지지 않습니다. ([#213](https://github.com/sk-lim19f/Hypomnema/pull/213))
298
388
  - session-close 마커가 볼트에서 가장 최근이었던 프로젝트가 아니라 이 세션이 실제로 닫은 프로젝트에 귀속되고, 이 세션이 건드린 적 없는 프로젝트의 미완 close는 `/compact`를 막는 대신 비차단 notice가 됩니다. ([#193](https://github.com/sk-lim19f/Hypomnema/pull/193), [#212](https://github.com/sk-lim19f/Hypomnema/pull/212))
299
389
  - 세션 close가 한 세션의 payload를 다른 세션 마커로 적용할 수 없게 됐습니다: payload 임시 경로가 세션 범위로 바뀌었고, `sessionId`가 맞지 않는 payload는 apply가 거부합니다. ([#204](https://github.com/sk-lim19f/Hypomnema/pull/204))
300
390
  - 같은 페이지에 대해 두 세션이 overwrite를 보류할 때 마지막 세션의 바이트만 남던 문제를 고쳤고, `proposal apply`가 방금 쓴 파일에 남아 있는 proposal을 알려줍니다. ([#191](https://github.com/sk-lim19f/Hypomnema/pull/191))
@@ -692,13 +782,13 @@ Contributors: @sk-lim19f
692
782
 
693
783
  #### English
694
784
 
695
- - Template injection keeps your hand-authored page instead of dropping a duplicate orphan. `init` checked only the exact destination filename, so a stock `hypo-automation.md` landed next to a user's `wiki-automation.md` (the `hypo-`/`wiki-` namespace split), leaving a 0-reference duplicate orphan. `init` now carries an explicit equivalents map (the same idiom the hook-rename migration uses): when a legacy `wiki-*` page exists it keeps that page, skips the stock one, and reports the skip loudly so you can merge by hand. `hypo-guide.md` is exempt, because the runtime loads it by name and a mid-migration vault must still receive it. The check also fires in `--dry-run`. ([#133](https://github.com/sk-lim19f/Hypomnema/pull/133), ADR 0058)
696
- - Generated root artifacts no longer pollute the knowledge catalog. The upgrade report (`MIGRATION-v*.md`) sits at the vault root, and the catalog scans (lint link targets, rename, doctor) treated it as a knowledge page. (`GRAPH_REPORT.md` has no writer today; its name is reserved preventively so a future dump cannot pollute the catalog either.) They cannot go in `.hypoignore`, because that list also drives the pre-commit secret gate, so listing them there would block their own commit and freeze every auto-commit while the report sits at root. A root-anchored, catalog-only exclusion now hides them from the scans while the pre-commit gate and `ingest --check` keep treating them normally, so the report still commits and enters git history but stops cluttering the catalog. Same-named files nested under `pages/` or `projects/` are untouched. ([#133](https://github.com/sk-lim19f/Hypomnema/pull/133), ADR 0059)
785
+ - Template injection keeps your hand-authored page instead of dropping a duplicate orphan. `init` checked only the exact destination filename, so a stock `hypo-automation.md` landed next to a user's `wiki-automation.md` (the `hypo-`/`wiki-` namespace split), leaving a 0-reference duplicate orphan. `init` now carries an explicit equivalents map (the same idiom the hook-rename migration uses): when a legacy `wiki-*` page exists it keeps that page, skips the stock one, and reports the skip loudly so you can merge by hand. `hypo-guide.md` is exempt, because the runtime loads it by name and a mid-migration vault must still receive it. The check also fires in `--dry-run`. ([#133](https://github.com/sk-lim19f/Hypomnema/pull/133))
786
+ - Generated root artifacts no longer pollute the knowledge catalog. The upgrade report (`MIGRATION-v*.md`) sits at the vault root, and the catalog scans (lint link targets, rename, doctor) treated it as a knowledge page. (`GRAPH_REPORT.md` has no writer today; its name is reserved preventively so a future dump cannot pollute the catalog either.) They cannot go in `.hypoignore`, because that list also drives the pre-commit secret gate, so listing them there would block their own commit and freeze every auto-commit while the report sits at root. A root-anchored, catalog-only exclusion now hides them from the scans while the pre-commit gate and `ingest --check` keep treating them normally, so the report still commits and enters git history but stops cluttering the catalog. Same-named files nested under `pages/` or `projects/` are untouched. ([#133](https://github.com/sk-lim19f/Hypomnema/pull/133))
697
787
 
698
788
  #### 한국어
699
789
 
700
- - 템플릿 주입이 중복 고아를 떨구지 않고 손수 쓴 페이지를 유지합니다. `init`이 정확한 대상 파일명만 검사해서 stock `hypo-automation.md`가 사용자의 `wiki-automation.md`(hypo-/wiki- 네임스페이스 split) 옆에 떨어지며 참조 0곳 중복 고아를 남겼습니다. 이제 `init`은 명시적 등가본 맵(hook-rename 마이그레이션과 같은 관용구)을 갖춰, 레거시 `wiki-*` 페이지가 있으면 그 페이지를 유지하고 stock 페이지는 건너뛰며 그 사실을 눈에 띄게 보고해 사용자가 수동으로 머지하게 합니다. `hypo-guide.md`는 런타임이 이름으로 읽고 마이그레이션 중간 볼트도 받아야 하므로 예외입니다. `--dry-run`에서도 이 안내를 출력합니다. ([#133](https://github.com/sk-lim19f/Hypomnema/pull/133), ADR 0058)
701
- - 생성된 루트 산출물이 지식 카탈로그를 오염하지 않습니다. 업그레이드 리포트(`MIGRATION-v*.md`)가 볼트 루트에 있어 카탈로그 스캔(lint 링크 타깃·rename·doctor)이 지식 페이지로 취급했습니다. (`GRAPH_REPORT.md`는 현재 writer가 없고, 향후 덤프가 카탈로그를 오염하지 못하도록 이름만 예방적으로 예약했습니다.) 이들은 `.hypoignore`에 넣을 수 없는데, 그 목록이 pre-commit 시크릿 게이트도 구동하므로 거기 넣으면 자기 커밋이 차단되고 리포트가 루트에 있는 동안 모든 auto-commit이 동결되기 때문입니다. 이제 root-anchored 카탈로그 전용 제외 규칙이 스캔에서는 숨기되 pre-commit 게이트와 `ingest --check`은 정상 취급하므로, 리포트는 여전히 커밋되어 git 히스토리에 들어가면서 카탈로그를 어지럽히지 않습니다. `pages/`·`projects/` 아래 중첩 동명 파일은 영향받지 않습니다. ([#133](https://github.com/sk-lim19f/Hypomnema/pull/133), ADR 0059)
790
+ - 템플릿 주입이 중복 고아를 떨구지 않고 손수 쓴 페이지를 유지합니다. `init`이 정확한 대상 파일명만 검사해서 stock `hypo-automation.md`가 사용자의 `wiki-automation.md`(hypo-/wiki- 네임스페이스 split) 옆에 떨어지며 참조 0곳 중복 고아를 남겼습니다. 이제 `init`은 명시적 등가본 맵(hook-rename 마이그레이션과 같은 관용구)을 갖춰, 레거시 `wiki-*` 페이지가 있으면 그 페이지를 유지하고 stock 페이지는 건너뛰며 그 사실을 눈에 띄게 보고해 사용자가 수동으로 머지하게 합니다. `hypo-guide.md`는 런타임이 이름으로 읽고 마이그레이션 중간 볼트도 받아야 하므로 예외입니다. `--dry-run`에서도 이 안내를 출력합니다. ([#133](https://github.com/sk-lim19f/Hypomnema/pull/133))
791
+ - 생성된 루트 산출물이 지식 카탈로그를 오염하지 않습니다. 업그레이드 리포트(`MIGRATION-v*.md`)가 볼트 루트에 있어 카탈로그 스캔(lint 링크 타깃·rename·doctor)이 지식 페이지로 취급했습니다. (`GRAPH_REPORT.md`는 현재 writer가 없고, 향후 덤프가 카탈로그를 오염하지 못하도록 이름만 예방적으로 예약했습니다.) 이들은 `.hypoignore`에 넣을 수 없는데, 그 목록이 pre-commit 시크릿 게이트도 구동하므로 거기 넣으면 자기 커밋이 차단되고 리포트가 루트에 있는 동안 모든 auto-commit이 동결되기 때문입니다. 이제 root-anchored 카탈로그 전용 제외 규칙이 스캔에서는 숨기되 pre-commit 게이트와 `ingest --check`은 정상 취급하므로, 리포트는 여전히 커밋되어 git 히스토리에 들어가면서 카탈로그를 어지럽히지 않습니다. `pages/`·`projects/` 아래 중첩 동명 파일은 영향받지 않습니다. ([#133](https://github.com/sk-lim19f/Hypomnema/pull/133))
702
792
 
703
793
  ### Changelog
704
794
 
@@ -728,29 +818,29 @@ Contributors: @sk-lim19f
728
818
 
729
819
  #### English
730
820
 
731
- - `rename` handles a directory subtree, not just a single page. The rename helper shipped for pages in 1.3.2; renaming a folder still meant moving each page by hand. `scripts/rename.mjs` (and a `/hypo:rename` slash command) now relocates an entire subtree, rewrites the eligible inbound wikilinks across the vault the same way the page mode does, and when a destination path already exists it emits a merge/renumber collision report and refuses `--apply` (leaving the merge for manual handling) rather than clobbering. It stays a dry-run by default; pass `--apply` to write the move. ([#125](https://github.com/sk-lim19f/Hypomnema/pull/125), ADR 0053)
821
+ - `rename` handles a directory subtree, not just a single page. The rename helper shipped for pages in 1.3.2; renaming a folder still meant moving each page by hand. `scripts/rename.mjs` (and a `/hypo:rename` slash command) now relocates an entire subtree, rewrites the eligible inbound wikilinks across the vault the same way the page mode does, and when a destination path already exists it emits a merge/renumber collision report and refuses `--apply` (leaving the merge for manual handling) rather than clobbering. It stays a dry-run by default; pass `--apply` to write the move. ([#125](https://github.com/sk-lim19f/Hypomnema/pull/125))
732
822
 
733
823
  #### 한국어
734
824
 
735
- - `rename`이 단일 페이지뿐 아니라 디렉터리 서브트리를 처리합니다. rename 헬퍼는 1.3.2에서 페이지용으로 출시됐는데, 폴더 이름 변경은 여전히 페이지를 하나씩 손으로 옮겨야 했습니다. `scripts/rename.mjs`(및 `/hypo:rename` 슬래시 커맨드)가 이제 서브트리 전체를 옮기고, 페이지 모드와 동일하게 볼트 전체의 해당하는 인바운드 위키링크를 갱신하며, 대상 경로가 이미 있으면 clobber 대신 merge/renumber 충돌 리포트를 내고 `--apply`를 거부합니다(merge는 수동 처리에 맡깁니다). 기본은 dry-run이며 `--apply`로 이동을 기록합니다. ([#125](https://github.com/sk-lim19f/Hypomnema/pull/125), ADR 0053)
825
+ - `rename`이 단일 페이지뿐 아니라 디렉터리 서브트리를 처리합니다. rename 헬퍼는 1.3.2에서 페이지용으로 출시됐는데, 폴더 이름 변경은 여전히 페이지를 하나씩 손으로 옮겨야 했습니다. `scripts/rename.mjs`(및 `/hypo:rename` 슬래시 커맨드)가 이제 서브트리 전체를 옮기고, 페이지 모드와 동일하게 볼트 전체의 해당하는 인바운드 위키링크를 갱신하며, 대상 경로가 이미 있으면 clobber 대신 merge/renumber 충돌 리포트를 내고 `--apply`를 거부합니다(merge는 수동 처리에 맡깁니다). 기본은 dry-run이며 `--apply`로 이동을 기록합니다. ([#125](https://github.com/sk-lim19f/Hypomnema/pull/125))
736
826
 
737
827
  ### Bug Fixes
738
828
 
739
829
  #### English
740
830
 
741
- - A session is no longer marked closed without a real user close signal. Both session-closed marker writers gated only on "is the wiki compact-ready", never on whether you actually asked to close, so the model could self-close (write the marker and declare done) on its own judgment. A hard gate now requires a genuine user close signal in the session (a natural-language close phrase, `/compact`, or an AskUserQuestion close answer), resolved strictly from the session id, separate from the compact-readiness check. This blocks inadvertent over-close; it is not a claim of unforgeability (the model owns its own subprocess), and a direct marker write stays inert because the Stop hook only consults the marker once a close signal is already present. ([#129](https://github.com/sk-lim19f/Hypomnema/pull/129), ADR 0055)
742
- - Reading close-related text no longer false-blocks the turn. The Stop-gate close-intent check stringified `role:user` tool results and injected skill or command bodies, so a session that merely read close vocabulary (a prior transcript, the close docs, a skill body full of "wrap up" / "session close" examples) tripped the gate every turn. Close-intent now ignores tool results and injected meta/system content, and the Korean close pattern moved from a verb-suffix blacklist to a complete-terminal whitelist, so it matches the common real phrasings and rejects noun-modifier and negation forms. (Tool-result exclusion is [#126](https://github.com/sk-lim19f/Hypomnema/pull/126) / ADR 0054; the injected meta/system exclusion and the whitelist are [#129](https://github.com/sk-lim19f/Hypomnema/pull/129) / ADR 0055.)
831
+ - A session is no longer marked closed without a real user close signal. Both session-closed marker writers gated only on "is the wiki compact-ready", never on whether you actually asked to close, so the model could self-close (write the marker and declare done) on its own judgment. A hard gate now requires a genuine user close signal in the session (a natural-language close phrase, `/compact`, or an AskUserQuestion close answer), resolved strictly from the session id, separate from the compact-readiness check. This blocks inadvertent over-close; it is not a claim of unforgeability (the model owns its own subprocess), and a direct marker write stays inert because the Stop hook only consults the marker once a close signal is already present. ([#129](https://github.com/sk-lim19f/Hypomnema/pull/129))
832
+ - Reading close-related text no longer false-blocks the turn. The Stop-gate close-intent check stringified `role:user` tool results and injected skill or command bodies, so a session that merely read close vocabulary (a prior transcript, the close docs, a skill body full of "wrap up" / "session close" examples) tripped the gate every turn. Close-intent now ignores tool results and injected meta/system content, and the Korean close pattern moved from a verb-suffix blacklist to a complete-terminal whitelist, so it matches the common real phrasings and rejects noun-modifier and negation forms. (Tool-result exclusion is [#126](https://github.com/sk-lim19f/Hypomnema/pull/126); the injected meta/system exclusion and the whitelist are [#129](https://github.com/sk-lim19f/Hypomnema/pull/129).)
743
833
  - The proactive close offer can no longer close the session by itself. The "looks like you're wrapping up" path could proceed all the way to a self-close; it is now scoped to offering only, and the actual close still requires you to choose it. ([#128](https://github.com/sk-lim19f/Hypomnema/pull/128))
744
- - `--apply-session-close` writes the session-closed marker instead of silently skipping it. apply wrote its payload files (leaving the tree dirty) and then checked a gate whose git-clean blocker its own writes had just tripped, so the marker was skipped and you were nudged to run `--mark-session-closed` by hand, after the close had already reported success. apply now commits its own payload first, via the same `.hypoignore`-aware helper the auto-commit Stop hook uses, then writes and verifies the marker. Unpushed commits ("ahead") are demoted from a blocker to a notice across the shared gate (push is automatic and its failures are already non-fatal), so a committed-but-unpushed close still marks and still compacts. ([#130](https://github.com/sk-lim19f/Hypomnema/pull/130), ADR 0056)
745
- - Tracker bookkeeping no longer cross-blocks an unrelated project's `/compact`. The global close invariant treated a freshly-dated `session-state.md` as close activity, but routine tracker bookkeeping (mirroring a new item into "next tasks") bumps that date with no real session, so an unrelated project's `/compact` was held hostage demanding the bookkept project's full close. Close activity is now recognized only from the artifacts a real close writes (a today session-log heading or a `## [today] session | P` log entry); the soft state files (`session-state.md`, project `hot.md`, the root `hot.md` row) no longer count, which also stops `project-create` and hot-cache rebuilds from looking like sessions. ([#131](https://github.com/sk-lim19f/Hypomnema/pull/131), ADR 0057)
834
+ - `--apply-session-close` writes the session-closed marker instead of silently skipping it. apply wrote its payload files (leaving the tree dirty) and then checked a gate whose git-clean blocker its own writes had just tripped, so the marker was skipped and you were nudged to run `--mark-session-closed` by hand, after the close had already reported success. apply now commits its own payload first, via the same `.hypoignore`-aware helper the auto-commit Stop hook uses, then writes and verifies the marker. Unpushed commits ("ahead") are demoted from a blocker to a notice across the shared gate (push is automatic and its failures are already non-fatal), so a committed-but-unpushed close still marks and still compacts. ([#130](https://github.com/sk-lim19f/Hypomnema/pull/130))
835
+ - Tracker bookkeeping no longer cross-blocks an unrelated project's `/compact`. The global close invariant treated a freshly-dated `session-state.md` as close activity, but routine tracker bookkeeping (mirroring a new item into "next tasks") bumps that date with no real session, so an unrelated project's `/compact` was held hostage demanding the bookkept project's full close. Close activity is now recognized only from the artifacts a real close writes (a today session-log heading or a `## [today] session | P` log entry); the soft state files (`session-state.md`, project `hot.md`, the root `hot.md` row) no longer count, which also stops `project-create` and hot-cache rebuilds from looking like sessions. ([#131](https://github.com/sk-lim19f/Hypomnema/pull/131))
746
836
 
747
837
  #### 한국어
748
838
 
749
- - 실제 사용자 종료 신호 없이 세션이 닫히지 않습니다. 두 session-closed 마커 writer는 "위키가 compact-ready인가"만 검사하고 사용자가 실제로 종료를 요청했는지는 보지 않아, 모델이 자의로 self-close(마커를 쓰고 완료 선언)할 수 있었습니다. 이제 hard gate가 세션 안의 진짜 사용자 종료 신호(자연어 종료 표현·`/compact`·AskUserQuestion 종료 답변)를 요구하며, session id에서만 해석하고 compact-readiness 검사와는 별개입니다. 무심코 일어나는 over-close를 막는 장치이지 위조 불가를 주장하는 것은 아니며(모델은 자기 subprocess를 소유합니다), 직접 마커를 써도 Stop 훅은 종료 신호가 이미 있을 때만 마커를 참조하므로 무력합니다. ([#129](https://github.com/sk-lim19f/Hypomnema/pull/129), ADR 0055)
750
- - 종료 관련 텍스트를 읽는 것이 턴을 false-block하지 않습니다. Stop 게이트의 close-intent 검사가 `role:user` tool result와 주입된 스킬·커맨드 본문을 통째로 문자열화해서, 종료 어휘를 단지 읽은 세션(이전 transcript·종료 문서·"wrap up"·"session close" 예시가 가득한 스킬 본문)이 매 턴 게이트에 걸렸습니다. 이제 close-intent는 tool result와 주입된 meta/system 콘텐츠를 무시하고, 한국어 종료 패턴은 동사어미 blacklist에서 종결형 완전체 whitelist로 바뀌어 흔한 실제 표현은 잡고 명사수식·부정형은 거부합니다. (tool result 제외는 [#126](https://github.com/sk-lim19f/Hypomnema/pull/126) / ADR 0054, 주입된 meta/system 제외와 whitelist는 [#129](https://github.com/sk-lim19f/Hypomnema/pull/129) / ADR 0055입니다.)
839
+ - 실제 사용자 종료 신호 없이 세션이 닫히지 않습니다. 두 session-closed 마커 writer는 "위키가 compact-ready인가"만 검사하고 사용자가 실제로 종료를 요청했는지는 보지 않아, 모델이 자의로 self-close(마커를 쓰고 완료 선언)할 수 있었습니다. 이제 hard gate가 세션 안의 진짜 사용자 종료 신호(자연어 종료 표현·`/compact`·AskUserQuestion 종료 답변)를 요구하며, session id에서만 해석하고 compact-readiness 검사와는 별개입니다. 무심코 일어나는 over-close를 막는 장치이지 위조 불가를 주장하는 것은 아니며(모델은 자기 subprocess를 소유합니다), 직접 마커를 써도 Stop 훅은 종료 신호가 이미 있을 때만 마커를 참조하므로 무력합니다. ([#129](https://github.com/sk-lim19f/Hypomnema/pull/129))
840
+ - 종료 관련 텍스트를 읽는 것이 턴을 false-block하지 않습니다. Stop 게이트의 close-intent 검사가 `role:user` tool result와 주입된 스킬·커맨드 본문을 통째로 문자열화해서, 종료 어휘를 단지 읽은 세션(이전 transcript·종료 문서·"wrap up"·"session close" 예시가 가득한 스킬 본문)이 매 턴 게이트에 걸렸습니다. 이제 close-intent는 tool result와 주입된 meta/system 콘텐츠를 무시하고, 한국어 종료 패턴은 동사어미 blacklist에서 종결형 완전체 whitelist로 바뀌어 흔한 실제 표현은 잡고 명사수식·부정형은 거부합니다. (tool result 제외는 [#126](https://github.com/sk-lim19f/Hypomnema/pull/126), 주입된 meta/system 제외와 whitelist는 [#129](https://github.com/sk-lim19f/Hypomnema/pull/129)입니다.)
751
841
  - 선제적 종료 제안이 스스로 세션을 닫을 수 없습니다. "마무리하는 것 같다" 경로가 self-close까지 진행될 수 있었는데, 이제 제안만 하도록 스코프되고 실제 종료는 사용자가 선택해야 합니다. ([#128](https://github.com/sk-lim19f/Hypomnema/pull/128))
752
- - `--apply-session-close`가 session-closed 마커를 조용히 건너뛰지 않고 기록합니다. apply는 payload 파일을 쓰고(트리가 dirty해짐) 나서 자기 write가 방금 건드린 git-clean 차단을 검사해서, 마커가 생략되고 종료가 이미 성공으로 보고된 뒤에 사용자가 `--mark-session-closed`를 손수 돌리도록 내몰렸습니다. 이제 apply는 auto-commit Stop 훅과 동일한 `.hypoignore` 인지 헬퍼로 자기 payload를 먼저 커밋한 뒤 마커를 쓰고 검증합니다. unpushed 커밋("ahead")은 공유 게이트 전반에서 차단이 아니라 notice로 강등되어(push는 자동이고 그 실패는 이미 비치명적입니다), committed-but-unpushed 종료도 마커가 써지고 `/compact`도 통과합니다. ([#130](https://github.com/sk-lim19f/Hypomnema/pull/130), ADR 0056)
753
- - 트래커 bookkeeping이 무관한 프로젝트의 `/compact`를 cross-block하지 않습니다. 전역 close 불변식이 갓 갱신된 `session-state.md`를 close 활동으로 봤는데, 일상적 트래커 bookkeeping("다음 작업"에 새 항목 미러)은 실제 세션 없이 그 날짜를 bump하므로 무관한 프로젝트의 `/compact`가 bookkeeping된 프로젝트의 완전 close를 요구하며 인질이 됐습니다. 이제 close 활동은 실제 종료가 쓰는 아티팩트(오늘 session-log 헤딩 또는 `## [today] session | P` log 항목)에서만 인식하고, soft state 파일(`session-state.md`·프로젝트 `hot.md`·루트 `hot.md` row)은 치지 않으므로 `project-create`나 hot-cache 재빌드도 세션처럼 보이지 않습니다. ([#131](https://github.com/sk-lim19f/Hypomnema/pull/131), ADR 0057)
842
+ - `--apply-session-close`가 session-closed 마커를 조용히 건너뛰지 않고 기록합니다. apply는 payload 파일을 쓰고(트리가 dirty해짐) 나서 자기 write가 방금 건드린 git-clean 차단을 검사해서, 마커가 생략되고 종료가 이미 성공으로 보고된 뒤에 사용자가 `--mark-session-closed`를 손수 돌리도록 내몰렸습니다. 이제 apply는 auto-commit Stop 훅과 동일한 `.hypoignore` 인지 헬퍼로 자기 payload를 먼저 커밋한 뒤 마커를 쓰고 검증합니다. unpushed 커밋("ahead")은 공유 게이트 전반에서 차단이 아니라 notice로 강등되어(push는 자동이고 그 실패는 이미 비치명적입니다), committed-but-unpushed 종료도 마커가 써지고 `/compact`도 통과합니다. ([#130](https://github.com/sk-lim19f/Hypomnema/pull/130))
843
+ - 트래커 bookkeeping이 무관한 프로젝트의 `/compact`를 cross-block하지 않습니다. 전역 close 불변식이 갓 갱신된 `session-state.md`를 close 활동으로 봤는데, 일상적 트래커 bookkeeping("다음 작업"에 새 항목 미러)은 실제 세션 없이 그 날짜를 bump하므로 무관한 프로젝트의 `/compact`가 bookkeeping된 프로젝트의 완전 close를 요구하며 인질이 됐습니다. 이제 close 활동은 실제 종료가 쓰는 아티팩트(오늘 session-log 헤딩 또는 `## [today] session | P` log 항목)에서만 인식하고, soft state 파일(`session-state.md`·프로젝트 `hot.md`·루트 `hot.md` row)은 치지 않으므로 `project-create`나 hot-cache 재빌드도 세션처럼 보이지 않습니다. ([#131](https://github.com/sk-lim19f/Hypomnema/pull/131))
754
844
 
755
845
  ### Chores
756
846
 
@@ -886,7 +976,7 @@ Contributors: @sk-lim19f
886
976
 
887
977
  #### English
888
978
 
889
- - Update-notifier banners now actually reach the user. The SessionStart hook computed an "Update available" banner (ADR 0033) and a stale-sibling banner (ADR 0038 D3) but emitted them only to **stderr** (invisible in the normal TUI on a hook that exits 0) and to `additionalContext`, which is model-only. Per the Claude Code hooks contract the user-visible channel is the top-level **`systemMessage`** field, which the hook never set, so both notices were no-ops on screen (the version cache would even mark a version "notified" against a banner nobody saw). Both banners now route to `systemMessage` (and stay in `additionalContext`, so the model and the user see the same state). This applies equally to the **npm** and **Claude marketplace (plugin)** channels: the notifier fetches the latest version for both and shows the channel-appropriate upgrade command. Scope: only the update + stale-sibling notices; the sync/growth/clear/suggest lines remain intentionally transcript-only.
979
+ - Update-notifier banners now actually reach the user. The SessionStart hook computed an "Update available" banner and a stale-sibling banner but emitted them only to **stderr** (invisible in the normal TUI on a hook that exits 0) and to `additionalContext`, which is model-only. Per the Claude Code hooks contract the user-visible channel is the top-level **`systemMessage`** field, which the hook never set, so both notices were no-ops on screen (the version cache would even mark a version "notified" against a banner nobody saw). Both banners now route to `systemMessage` (and stay in `additionalContext`, so the model and the user see the same state). This applies equally to the **npm** and **Claude marketplace (plugin)** channels: the notifier fetches the latest version for both and shows the channel-appropriate upgrade command. Scope: only the update + stale-sibling notices; the sync/growth/clear/suggest lines remain intentionally transcript-only.
890
980
  - Plugin installs no longer double-register core hooks on `/hypo:upgrade --apply`. When Hypomnema is installed as a Claude Code **plugin**, the 15 core hooks and 14 slash commands are provided by the plugin loader (`hooks.json` + `commands/`), not copied into `~/.claude/`. `upgrade.mjs` assumed the manual/npm install model, reported ~47 items "missing", and recommended `--apply`, which copied the hooks into `~/.claude/hooks/` and registered 14 `settings.json` events, so Claude Code then ran **both** the plugin hooks and the user hooks and every hook fired **twice**. A plugin-mode guard (keyed on the running `upgrade.mjs` living under `~/.claude/plugins/`) now reports the core surface as "provided by the plugin loader", excludes it from drift, and skips copying/registering it on `--apply`. Vault extensions, the codex target (`--codex`), and `hypo-pkg.json` metadata stay managed: the metadata write is required so the runtime can resolve the package root for the PreCompact lint/feedback gates.
891
981
  - `/hypo:resume` respects the working directory on a same-date tie. With no `--project`, `resolveActiveProject` sorted the root `hot.md` "Active Projects" rows by date only; when two projects shared the latest date, the table's top row always won regardless of where you were working. A **tie-breaker-only** cwd match (cwd ↔ each project's `session-state.md` `working_dir`, longest-prefix) now breaks same-date ties without overriding a genuinely newer non-matching project. `resume.mjs` and the mirrored `hooks/hypo-shared.mjs` are kept in sync.
892
982
  - `resume.mjs` `--hypo-dir` header comment corrected. The comment described root resolution as a sequential `A / B / C` fallback chain, but `$HYPO_DIR` actually takes precedence and short-circuits the other steps. Pure comment fix, no runtime change.
@@ -895,7 +985,7 @@ Contributors: @sk-lim19f
895
985
 
896
986
  #### 한국어
897
987
 
898
- - 업데이트 notifier 배너가 이제 실제로 사용자에게 도달. SessionStart 훅이 "Update available" 배너(ADR 0033)와 stale-sibling 배너(ADR 0038 D3)를 계산하지만 **stderr**(exit 0 훅에선 일반 TUI에 비가시)와 모델 전용 `additionalContext`로만 출력했다. Claude Code 훅 계약상 사용자 가시 채널은 top-level **`systemMessage`**인데 훅이 이를 설정하지 않아 두 배너 모두 화면에선 무효였다(버전 캐시는 아무도 못 본 배너를 "notified"로 마킹까지 함). 두 배너를 이제 `systemMessage`로 라우팅한다(`additionalContext`에도 유지 → 모델·사용자 동기). **npm**·**Claude marketplace(plugin)** 두 채널 모두 동일 적용. notifier가 양 채널의 latest를 fetch해 채널별 업그레이드 명령을 보여준다. 범위: update + stale-sibling만, sync/growth/clear/suggest는 의도적으로 transcript 전용 유지.
988
+ - 업데이트 notifier 배너가 이제 실제로 사용자에게 도달. SessionStart 훅이 "Update available" 배너와 stale-sibling 배너를 계산하지만 **stderr**(exit 0 훅에선 일반 TUI에 비가시)와 모델 전용 `additionalContext`로만 출력했다. Claude Code 훅 계약상 사용자 가시 채널은 top-level **`systemMessage`**인데 훅이 이를 설정하지 않아 두 배너 모두 화면에선 무효였다(버전 캐시는 아무도 못 본 배너를 "notified"로 마킹까지 함). 두 배너를 이제 `systemMessage`로 라우팅한다(`additionalContext`에도 유지 → 모델·사용자 동기). **npm**·**Claude marketplace(plugin)** 두 채널 모두 동일 적용. notifier가 양 채널의 latest를 fetch해 채널별 업그레이드 명령을 보여준다. 범위: update + stale-sibling만, sync/growth/clear/suggest는 의도적으로 transcript 전용 유지.
899
989
  - 플러그인 설치에서 `/hypo:upgrade --apply`가 더는 core 훅을 중복 등록하지 않음. Hypomnema를 Claude Code **플러그인**으로 설치하면 core 훅 15개·슬래시 커맨드 14개를 플러그인 로더(`hooks.json` + `commands/`)가 제공하며 `~/.claude/`로 복사되지 않는다. `upgrade.mjs`가 수동/npm 설치 모델을 가정해 ~47개를 "missing"으로 보고하고 `--apply`를 권했고 → `--apply`가 훅을 `~/.claude/hooks/`로 복사 + `settings.json` 이벤트 14개 등록 → Claude Code가 플러그인 훅과 사용자 훅을 **둘 다** 실행해 모든 훅이 **2회씩** 발화했다. 플러그인 모드 가드(실행 중 `upgrade.mjs`가 `~/.claude/plugins/` 하위인지로 판정)가 이제 core 표면을 "provided by the plugin loader"로 보고하고 drift에서 제외하며 `--apply` 시 복사·등록을 skip한다. vault extensions·codex 타깃(`--codex`)·`hypo-pkg.json` 메타데이터는 계속 관리. 메타데이터 write는 런타임이 PreCompact lint/feedback 게이트용 패키지 루트를 해석하는 데 필요.
900
990
  - `/hypo:resume`가 동률 날짜에서 작업 디렉터리를 존중. `--project` 미지정 시 `resolveActiveProject`가 루트 `hot.md`의 "Active Projects" 행을 날짜로만 정렬해, 같은 날짜 프로젝트가 둘이면 작업 위치와 무관하게 테이블 최상단 행이 항상 선택됐다. **tie-breaker 전용** cwd 매칭(cwd ↔ 각 프로젝트 `session-state.md`의 `working_dir`, longest-prefix)이 더 최신인 비매칭 프로젝트를 덮어쓰지 않으면서 동률만 깬다. `resume.mjs`와 미러 `hooks/hypo-shared.mjs`를 동기화.
901
991
  - `resume.mjs` `--hypo-dir` 헤더 주석 정정. 주석이 root resolution을 순차 `A / B / C` 폴백 체인처럼 기술했으나, 실제로는 `$HYPO_DIR`이 최우선이며 나머지 단계를 단락(short-circuit)한다. 순수 주석 수정, 런타임 변경 없음.
@@ -935,37 +1025,37 @@ Contributors: @sk-lim19f
935
1025
 
936
1026
  #### English
937
1027
 
938
- - Stale-sibling install detection: downgrade guard + PATH-CLI notice + doctor scan (ADR 0038). When a second, older Hypomnema sits on `$PATH` (e.g. a leftover `npm i -g hypomnema`) while a newer copy owns the active hooks, running `hypomnema init` / `upgrade --apply` through the stale bin used to **silently downgrade** the newer registered hooks (dropping features like the update-notifier). Three coordinated defenses now exist: **(P, preventive)** `init` and `upgrade --apply` refuse to overwrite a newer active install. They compare the running package version against `~/.claude/hypo-pkg.json`'s `pkgVersion` using full semver and abort with **exit 2** unless `--allow-downgrade` is passed; a dev workspace re-running its own install is exempt via realpath-equal `pkgRoot` (so the post-commit sync hook and `npm link` setups are never mis-flagged). **(D3, detective, reaches the live victim)** the SessionStart notifier resolves the `hypomnema` bin on `$PATH` (fs-only; no `npm`/`which` spawn) and warns once per `(cliPath@version → activeVersion)` tuple when it is strictly older than the active install. This is the only surface that reaches a user already stuck on the old CLI, since `hypomnema doctor` invoked via the stale bin would run the *old* doctor. **(D, detective backstop)** `hypomnema doctor` adds a `PATH CLI vs active install` check (warn + `npm uninstall -g hypomnema` remediation). Note: the in-product notifier **cannot** retroactively warn installs older than v1.2.0 (the notifier did not exist yet): that bootstrap gap is unfixable in code; the guard protects forward, and the doctor/notifier surfaces flag the stale copy on any current install.
1028
+ - Stale-sibling install detection: downgrade guard + PATH-CLI notice + doctor scan. When a second, older Hypomnema sits on `$PATH` (e.g. a leftover `npm i -g hypomnema`) while a newer copy owns the active hooks, running `hypomnema init` / `upgrade --apply` through the stale bin used to **silently downgrade** the newer registered hooks (dropping features like the update-notifier). Three coordinated defenses now exist: **(P, preventive)** `init` and `upgrade --apply` refuse to overwrite a newer active install. They compare the running package version against `~/.claude/hypo-pkg.json`'s `pkgVersion` using full semver and abort with **exit 2** unless `--allow-downgrade` is passed; a dev workspace re-running its own install is exempt via realpath-equal `pkgRoot` (so the post-commit sync hook and `npm link` setups are never mis-flagged). **(D3, detective, reaches the live victim)** the SessionStart notifier resolves the `hypomnema` bin on `$PATH` (fs-only; no `npm`/`which` spawn) and warns once per `(cliPath@version → activeVersion)` tuple when it is strictly older than the active install. This is the only surface that reaches a user already stuck on the old CLI, since `hypomnema doctor` invoked via the stale bin would run the *old* doctor. **(D, detective backstop)** `hypomnema doctor` adds a `PATH CLI vs active install` check (warn + `npm uninstall -g hypomnema` remediation). Note: the in-product notifier **cannot** retroactively warn installs older than v1.2.0 (the notifier did not exist yet): that bootstrap gap is unfixable in code; the guard protects forward, and the doctor/notifier surfaces flag the stale copy on any current install.
939
1029
  - `hypomnema lint --strict` promotes selected warnings to errors. A new opt-in `--strict` flag promotes a frozen set of warning classes to errors so they exit 1: a general gate for release-checklists and opt-in pre-commit hooks. Stable warning IDs were introduced (`W1` no-frontmatter, `W2` unknown-type, `W3` missing-`updated`, `W4` broken-wikilink) alongside the pre-existing `W8` (design-history stale). `--strict` promotes `STRICT_PROMOTE_IDS = {W1, W2, W4}` (confirmed content defects) while leaving `W3` (auto-repaired by `--fix`) and `W8` (handled separately by the pre-compact hook) as warnings. Default `hypomnema lint` is **byte-identical**: only `W8` exposes an `id` in `--json` output, so existing consumers (`hooks/hypo-personal-check.mjs`) are unaffected. `npm run lint` and `prepublishOnly` keep using the default mode, and `--strict` is never auto-wired into CI.
940
- - Session-close now surfaces four advisory reflections (ADR 0029 Phase B). The `/hypo:crystallize` session-close flow (exposed both as the `crystallize.md` slash command and the `crystallize` skill) now prompts you, *advisory-only*, on four points before composing the session payload: flag a trivial session and recommend skipping close (without bypassing the mandatory checklist or marking the session closed); when a non-trivial decision lacks an ADR, record `ADR 없음: <reason>` in the session-log payload (it never auto-writes an ADR file); recommend refreshing a stale `design-history.md` (silently skips when none exists, never creates one); recommend `/hypo:ingest` for trustworthy external knowledge acquired in the session (user-confirmed). Every reflection is advisory: none performs an automatic action, none bypasses a gate, none writes a file on its own. A surface-drift guard test pins both surfaces to keep the four advisories and the identity-guard contract present.
1030
+ - Session-close now surfaces four advisory reflections. The `/hypo:crystallize` session-close flow (exposed both as the `crystallize.md` slash command and the `crystallize` skill) now prompts you, *advisory-only*, on four points before composing the session payload: flag a trivial session and recommend skipping close (without bypassing the mandatory checklist or marking the session closed); when a non-trivial decision lacks an ADR, record `ADR 없음: <reason>` in the session-log payload (it never auto-writes an ADR file); recommend refreshing a stale `design-history.md` (silently skips when none exists, never creates one); recommend `/hypo:ingest` for trustworthy external knowledge acquired in the session (user-confirmed). Every reflection is advisory: none performs an automatic action, none bypasses a gate, none writes a file on its own. A surface-drift guard test pins both surfaces to keep the four advisories and the identity-guard contract present.
941
1031
 
942
1032
  #### 한국어
943
1033
 
944
- - Stale-sibling 설치 감지: downgrade 가드 + PATH-CLI 노티 + doctor 스캔 (ADR 0038). 더 오래된 Hypomnema가 `$PATH`를 점유(예: 남아있는 `npm i -g hypomnema`)하고 더 새 사본이 active 훅을 소유한 상황에서, stale 바이너리로 `hypomnema init` / `upgrade --apply`를 돌리면 더 새 등록 훅이 **조용히 다운그레이드**(update-notifier 등 기능 제거)되던 footgun을 막는다. 세 방어를 함께 도입: **(P, 예방)** `init`·`upgrade --apply`가 실행 중 패키지 버전과 `~/.claude/hypo-pkg.json`의 `pkgVersion`을 full semver로 비교해, active가 더 새로우면 `--allow-downgrade` 없이는 **exit 2**로 거부한다. dev workspace가 자기 자신을 재실행하는 경우는 `pkgRoot` realpath 동일성으로 면제(post-commit sync 훅·`npm link` 오탐 없음). **(D3, 탐지, 현재 피해자에게 도달)** SessionStart notifier가 `$PATH`의 `hypomnema` 바이너리를 해석(fs-only, `npm`/`which` spawn 없음)해 active보다 엄격히 오래되면 `(cliPath@version → activeVersion)` 튜플당 1회 경고한다. stale 바이너리로 부른 `hypomnema doctor`는 *구버전* doctor를 돌리므로, 이미 옛 CLI에 갇힌 사용자에게 도달하는 유일한 경로다. **(D, 탐지 백스톱)** `hypomnema doctor`에 `PATH CLI vs active install` 체크 추가(warn + `npm uninstall -g hypomnema` 안내). 참고: in-product notifier는 v1.2.0 이전 설치(당시 notifier 미존재)에는 소급 경고할 수 **없다**. 이 bootstrap 갭은 코드로 수정 불가다. 가드는 앞으로를 보호하고, doctor/notifier 표면이 현재 설치에서 stale 사본을 적발한다.
1034
+ - Stale-sibling 설치 감지: downgrade 가드 + PATH-CLI 노티 + doctor 스캔. 더 오래된 Hypomnema가 `$PATH`를 점유(예: 남아있는 `npm i -g hypomnema`)하고 더 새 사본이 active 훅을 소유한 상황에서, stale 바이너리로 `hypomnema init` / `upgrade --apply`를 돌리면 더 새 등록 훅이 **조용히 다운그레이드**(update-notifier 등 기능 제거)되던 footgun을 막는다. 세 방어를 함께 도입: **(P, 예방)** `init`·`upgrade --apply`가 실행 중 패키지 버전과 `~/.claude/hypo-pkg.json`의 `pkgVersion`을 full semver로 비교해, active가 더 새로우면 `--allow-downgrade` 없이는 **exit 2**로 거부한다. dev workspace가 자기 자신을 재실행하는 경우는 `pkgRoot` realpath 동일성으로 면제(post-commit sync 훅·`npm link` 오탐 없음). **(D3, 탐지, 현재 피해자에게 도달)** SessionStart notifier가 `$PATH`의 `hypomnema` 바이너리를 해석(fs-only, `npm`/`which` spawn 없음)해 active보다 엄격히 오래되면 `(cliPath@version → activeVersion)` 튜플당 1회 경고한다. stale 바이너리로 부른 `hypomnema doctor`는 *구버전* doctor를 돌리므로, 이미 옛 CLI에 갇힌 사용자에게 도달하는 유일한 경로다. **(D, 탐지 백스톱)** `hypomnema doctor`에 `PATH CLI vs active install` 체크 추가(warn + `npm uninstall -g hypomnema` 안내). 참고: in-product notifier는 v1.2.0 이전 설치(당시 notifier 미존재)에는 소급 경고할 수 **없다**. 이 bootstrap 갭은 코드로 수정 불가다. 가드는 앞으로를 보호하고, doctor/notifier 표면이 현재 설치에서 stale 사본을 적발한다.
945
1035
  - `hypomnema lint --strict`가 선택 warning을 error로 승격한다. opt-in `--strict` 플래그가 동결된 warning 클래스 집합을 error로 승격해 exit 1로 만든다: release-checklist / opt-in pre-commit용 범용 게이트. 안정 warning ID(`W1` no-frontmatter, `W2` unknown-type, `W3` missing-`updated`, `W4` broken-wikilink)를 기존 `W8`(design-history stale)에 더해 부여했다. `--strict`는 `STRICT_PROMOTE_IDS = {W1, W2, W4}`(확정적 콘텐츠 결함)만 승격하고, `W3`(`--fix`로 자동복구)·`W8`(pre-compact 훅이 별도 처리)은 warning으로 유지한다. 기본 `hypomnema lint`는 **byte-identical**. `--json`에서 `W8`만 `id`를 노출하므로 기존 소비자(`hooks/hypo-personal-check.mjs`)는 무영향. `npm run lint`·`prepublishOnly`는 기본 모드를 그대로 사용하며 `--strict`는 CI에 자동 배선되지 않는다.
946
- - 세션-close가 네 가지 advisory 성찰을 띄운다 (ADR 0029 Phase B). `/hypo:crystallize` 세션-close 흐름(`crystallize.md` 슬래시 커맨드 + `crystallize` 스킬 양쪽)이 세션 payload 작성 전에 네 가지를 *advisory로만* 권고한다: trivial 세션이면 close 스킵 권고(필수 체크리스트 우회·세션 closed 표기는 하지 않음); 비자명 결정에 ADR이 없으면 session-log payload에 `ADR 없음: <이유>` 기록(ADR 파일을 auto-write 하지 않음); stale `design-history.md` 갱신 권고(없으면 silent skip, 생성하지 않음); 세션 중 습득한 신뢰할 만한 외부 지식에 `/hypo:ingest` 권고(user-confirm). 모든 성찰은 advisory다. 자동 동작·게이트 우회·파일 자동 작성을 하는 것은 하나도 없다. surface-drift 가드 테스트가 두 표면에 네 advisory와 identity-guard 계약 문구가 그대로 있는지 검증한다.
1036
+ - 세션-close가 네 가지 advisory 성찰을 띄운다. `/hypo:crystallize` 세션-close 흐름(`crystallize.md` 슬래시 커맨드 + `crystallize` 스킬 양쪽)이 세션 payload 작성 전에 네 가지를 *advisory로만* 권고한다: trivial 세션이면 close 스킵 권고(필수 체크리스트 우회·세션 closed 표기는 하지 않음); 비자명 결정에 ADR이 없으면 session-log payload에 `ADR 없음: <이유>` 기록(ADR 파일을 auto-write 하지 않음); stale `design-history.md` 갱신 권고(없으면 silent skip, 생성하지 않음); 세션 중 습득한 신뢰할 만한 외부 지식에 `/hypo:ingest` 권고(user-confirm). 모든 성찰은 advisory다. 자동 동작·게이트 우회·파일 자동 작성을 하는 것은 하나도 없다. surface-drift 가드 테스트가 두 표면에 네 advisory와 identity-guard 계약 문구가 그대로 있는지 검증한다.
947
1037
 
948
1038
  ### Bug Fixes
949
1039
 
950
1040
  #### English
951
1041
 
952
- - Session-close gate no longer blocks `/compact` on lint debt this session did not create (ADR 0037). The PreCompact gate and the crystallize apply gate both linted the *entire* vault, so unfinished session-close could be blocked by lint errors in other projects or shared pages you never touched. Each gate is now **scoped to the files this session actually touched** (the PreCompact gate to transcript-touched files ∪ the mandatory close-file targets, the apply gate to its payload files), and errors outside that scope downgrade to a non-blocking notice. A companion marker-coherence fix prevents a session from being marked closed without lint running on its own files: `--mark-session-closed --transcript-path` refuses the marker on scoped-lint failure (without `--transcript-path` it keeps the legacy freshness + clean-git recovery path), and the Stop hook surfaces the transcript path. Broken-wikilink stays `W4` warn-only (forward-references are legitimate; gating on them re-introduced friction).
1042
+ - Session-close gate no longer blocks `/compact` on lint debt this session did not create. The PreCompact gate and the crystallize apply gate both linted the *entire* vault, so unfinished session-close could be blocked by lint errors in other projects or shared pages you never touched. Each gate is now **scoped to the files this session actually touched** (the PreCompact gate to transcript-touched files ∪ the mandatory close-file targets, the apply gate to its payload files), and errors outside that scope downgrade to a non-blocking notice. A companion marker-coherence fix prevents a session from being marked closed without lint running on its own files: `--mark-session-closed --transcript-path` refuses the marker on scoped-lint failure (without `--transcript-path` it keeps the legacy freshness + clean-git recovery path), and the Stop hook surfaces the transcript path. Broken-wikilink stays `W4` warn-only (forward-references are legitimate; gating on them re-introduced friction).
953
1043
  - `/hypo:feedback` scope validator accepts cwd-derived project ids. The shared `scope:` validator rejected the project-id shape `deriveProjectId` emits (leading dash + mixed case, e.g. `project:-Users-you-Workspace-Project`), so writing a cwd-scoped feedback page failed lint and forced a manual `--project-id=<slug>` override. A single source-of-truth `FEEDBACK_SCOPE_RE` (`scripts/lib/feedback-scope.mjs`, imported by both `lint.mjs` and `feedback.mjs`) now accepts that form while still rejecting dot-only ids (`project:.` / `project:..`); the deriver, on-disk project dirs, and string-equality projection are unchanged.
954
1044
 
955
1045
  #### 한국어
956
1046
 
957
- - 세션-close 게이트가 이 세션이 만들지 않은 lint debt로 `/compact`를 더는 막지 않는다 (ADR 0037). PreCompact 게이트와 crystallize apply 게이트가 볼트 *전체*를 lint해서, 손대지도 않은 타 프로젝트·공유 페이지의 lint error로 세션-close가 막히던 버그를 수정. 이제 각 게이트는 **이 세션이 실제로 touch한 파일로 스코프**된다(PreCompact는 transcript-touched ∪ 필수 close-파일 타깃, apply는 자신의 payload 파일로). 그리고 스코프 밖 error는 non-blocking notice로 강등된다. marker-coherence 보강으로 자기 파일에 lint가 돌지 않은 채 세션이 closed로 표기되는 것을 방지: `--mark-session-closed --transcript-path`가 스코프-lint 실패 시 marker를 거부하고(`--transcript-path` 없으면 legacy freshness + clean-git 복구 경로 유지), Stop 훅이 transcript path를 노출한다. broken-wikilink는 `W4` warn-only 유지(forward-reference는 정상이며, 게이트 시 마찰 재발).
1047
+ - 세션-close 게이트가 이 세션이 만들지 않은 lint debt로 `/compact`를 더는 막지 않는다. PreCompact 게이트와 crystallize apply 게이트가 볼트 *전체*를 lint해서, 손대지도 않은 타 프로젝트·공유 페이지의 lint error로 세션-close가 막히던 버그를 수정. 이제 각 게이트는 **이 세션이 실제로 touch한 파일로 스코프**된다(PreCompact는 transcript-touched ∪ 필수 close-파일 타깃, apply는 자신의 payload 파일로). 그리고 스코프 밖 error는 non-blocking notice로 강등된다. marker-coherence 보강으로 자기 파일에 lint가 돌지 않은 채 세션이 closed로 표기되는 것을 방지: `--mark-session-closed --transcript-path`가 스코프-lint 실패 시 marker를 거부하고(`--transcript-path` 없으면 legacy freshness + clean-git 복구 경로 유지), Stop 훅이 transcript path를 노출한다. broken-wikilink는 `W4` warn-only 유지(forward-reference는 정상이며, 게이트 시 마찰 재발).
958
1048
  - `/hypo:feedback` scope 검증기가 cwd 유래 project id를 수용한다. 공유 `scope:` 검증기가 `deriveProjectId`가 내보내는 project-id 형태(leading dash + 대소문자 혼합, 예 `project:-Users-you-Workspace-Project`)를 거부해, cwd-스코프 feedback 페이지 작성이 lint를 통과 못 하고 수동 `--project-id=<slug>` override를 강요당했다. 단일 SoT `FEEDBACK_SCOPE_RE`(`scripts/lib/feedback-scope.mjs`, `lint.mjs`·`feedback.mjs` 양쪽 import)가 이제 그 형태를 수용하되 dot-only id(`project:.` / `project:..`)는 여전히 거부한다. deriver·on-disk project 디렉터리·string-equality projection은 변경 없음.
959
1049
 
960
1050
  ### Chores
961
1051
 
962
1052
  #### English
963
1053
 
964
- - Maintainer tooling and repo hygiene (no user-facing surface change). `fix:verify` test-linkage CLI plus its `STUB_SPEC` vacuous-gate rejection and the fix-manifest evidence-only SoT + ADR-line grep gate (ADR 0036/0039); a pre-commit auto-format hook for staged files; publish-time bilingual CHANGELOG + annotated-tag enforcement (`check-bilingual.mjs`); a `feedback-sync` per-mode source-loader refactor (byte-identical golden tests); inline-comment hygiene cleanup; `actions/checkout` + `actions/setup-node` bumped to v5; and untracking of personal dev-workflow commands (`.claude/` is now fully gitignored: the repo ships only the published plugin surface). These touch dev/CI/maintainer workflows only; the installed product surface is unchanged.
1054
+ - Maintainer tooling and repo hygiene (no user-facing surface change). `fix:verify` test-linkage CLI plus its `STUB_SPEC` vacuous-gate rejection and the fix-manifest evidence-only SoT + ADR-line grep gate; a pre-commit auto-format hook for staged files; publish-time bilingual CHANGELOG + annotated-tag enforcement (`check-bilingual.mjs`); a `feedback-sync` per-mode source-loader refactor (byte-identical golden tests); inline-comment hygiene cleanup; `actions/checkout` + `actions/setup-node` bumped to v5; and untracking of personal dev-workflow commands (`.claude/` is now fully gitignored: the repo ships only the published plugin surface). These touch dev/CI/maintainer workflows only; the installed product surface is unchanged.
965
1055
 
966
1056
  #### 한국어
967
1057
 
968
- - 메인테이너 도구·repo 위생 (사용자 대상 표면 변경 없음). `fix:verify` test-linkage CLI와 그 `STUB_SPEC` vacuous-gate 거부, fix-manifest evidence-only SoT + ADR-line grep 게이트(ADR 0036/0039), staged 파일용 pre-commit auto-format 훅, publish 시점 bilingual CHANGELOG + annotated-tag 강제(`check-bilingual.mjs`), `feedback-sync`의 per-mode source-loader 리팩터(byte-identical golden 테스트), inline-comment 위생 정리, `actions/checkout`·`actions/setup-node` v5 bump, 개인 dev-workflow 커맨드 untrack(`.claude/`를 완전히 gitignore: repo는 published plugin 표면만 ship). 모두 dev/CI/maintainer 워크플로만 건드리며 설치되는 제품 표면은 동일하다.
1058
+ - 메인테이너 도구·repo 위생 (사용자 대상 표면 변경 없음). `fix:verify` test-linkage CLI와 그 `STUB_SPEC` vacuous-gate 거부, fix-manifest evidence-only SoT + ADR-line grep 게이트, staged 파일용 pre-commit auto-format 훅, publish 시점 bilingual CHANGELOG + annotated-tag 강제(`check-bilingual.mjs`), `feedback-sync`의 per-mode source-loader 리팩터(byte-identical golden 테스트), inline-comment 위생 정리, `actions/checkout`·`actions/setup-node` v5 bump, 개인 dev-workflow 커맨드 untrack(`.claude/`를 완전히 gitignore: repo는 published plugin 표면만 ship). 모두 dev/CI/maintainer 워크플로만 건드리며 설치되는 제품 표면은 동일하다.
969
1059
 
970
1060
  ### Changelog
971
1061
 
@@ -982,7 +1072,7 @@ Contributors: @sk-lim19f
982
1072
  - [#80](https://github.com/sk-lim19f/Hypomnema/pull/80) scope session-close lint to touched files + coherent marker gate
983
1073
  - [#81](https://github.com/sk-lim19f/Hypomnema/pull/81) stale-sibling install detection (downgrade guard + PATH-CLI notice + doctor scan)
984
1074
  - [#82](https://github.com/sk-lim19f/Hypomnema/pull/82) fix-manifest SoT + ADR-line grep + bare-anchor gate
985
- - [#83](https://github.com/sk-lim19f/Hypomnema/pull/83) session-close advisory reflections (ADR 0029 Phase B)
1075
+ - [#83](https://github.com/sk-lim19f/Hypomnema/pull/83) session-close advisory reflections
986
1076
  - [#84](https://github.com/sk-lim19f/Hypomnema/pull/84) document claude-worker HOME-isolation limits
987
1077
  - [#85](https://github.com/sk-lim19f/Hypomnema/pull/85) untrack personal dev-workflow commands; fully gitignore .claude/
988
1078
  - [#86](https://github.com/sk-lim19f/Hypomnema/pull/86) prepare v1.3.0 (bump version + reconcile CHANGELOG)
@@ -1022,7 +1112,7 @@ Contributors: @sk-lim19f
1022
1112
  ## [1.2.0] - 2026-05-24
1023
1113
 
1024
1114
  > [!IMPORTANT]
1025
- > **`SCHEMA.md` version 2.0: `feedback` page type now requires 9 hard fields (ADR 0031 / ADR 0034, PR [#60](https://github.com/sk-lim19f/Hypomnema/pull/60)).** Pages of `type: feedback` must declare `status`, `scope`, `tier`, `targets`, `sensitivity`, `priority`, `memory_summary`, `reason`, `source`. When `targets` includes `claude-learned`, the page must additionally be `scope: global` + `tier: L1` and declare `global_summary` + `promote_to_global: true`. `hypomnema upgrade --apply` now writes `MIGRATION-v2.0.md` into the wiki root with a manual-backfill checklist; the upgrade deliberately does NOT auto-stub the fields because wrong defaults for `scope` / `tier` / `targets` / `sensitivity` / `reason` / `source` would silently project wrong behavior. `SCHEMA.md` itself remains user-owned and byte-equal across upgrade (Option C, preserved by PR [#57](https://github.com/sk-lim19f/Hypomnema/pull/57)'s invariants). The migration report also carries the `project-id` ↔ slug regex caveat from PR [#59](https://github.com/sk-lim19f/Hypomnema/pull/59): to use `scope: project:*` in v1.2.0 you must `--project-id=<slug>` override.
1115
+ > **`SCHEMA.md` version 2.0: `feedback` page type now requires 9 hard fields (PR [#60](https://github.com/sk-lim19f/Hypomnema/pull/60)).** Pages of `type: feedback` must declare `status`, `scope`, `tier`, `targets`, `sensitivity`, `priority`, `memory_summary`, `reason`, `source`. When `targets` includes `claude-learned`, the page must additionally be `scope: global` + `tier: L1` and declare `global_summary` + `promote_to_global: true`. `hypomnema upgrade --apply` now writes `MIGRATION-v2.0.md` into the wiki root with a manual-backfill checklist; the upgrade deliberately does NOT auto-stub the fields because wrong defaults for `scope` / `tier` / `targets` / `sensitivity` / `reason` / `source` would silently project wrong behavior. `SCHEMA.md` itself remains user-owned and byte-equal across upgrade (Option C, preserved by PR [#57](https://github.com/sk-lim19f/Hypomnema/pull/57)'s invariants). The migration report also carries the `project-id` ↔ slug regex caveat from PR [#59](https://github.com/sk-lim19f/Hypomnema/pull/59): to use `scope: project:*` in v1.2.0 you must `--project-id=<slug>` override.
1026
1116
 
1027
1117
  > [!IMPORTANT]
1028
1118
  > **SCHEMA 2.0: `feedback` page 9 hard 필드 + claude-learned conditional 2 필드 강제.** `hypomnema upgrade --apply` 시 `MIGRATION-v2.0.md`가 자동 작성되어 backfill checklist 제공. `SCHEMA.md`는 사용자 소유 (Option C 보존, byte-equal). 자동 stub은 거부. `scope` / `tier` / `targets` / `sensitivity` / `reason` / `source`는 의미 결정이라 wrong default가 wrong behavior로 이어짐.
@@ -1033,14 +1123,14 @@ Contributors: @sk-lim19f
1033
1123
 
1034
1124
  - `lint` emits `W8` design-history-stale warning. The PreCompact hook (`hypo-personal-check.mjs`) has filtered `lint --json` warns for `id === 'W8'` since the initial OSS hook drop, but `scripts/lint.mjs` never emitted that id: so `design-history.md` aging next to a fresher `session-log.md` (or `session-log/YYYY-MM.md` directory layout) was silently invisible to the gate. Lint now runs `findDesignHistoryStale()` once per project (outside the page loop), and emits a `W8`-tagged warn per stale project with a POSIX-separated `file` literal (`projects/<name>/design-history.md`) so the consumer's `file.split('/')` contract stays portable. The JSON `warn` shape gains an optional `id` field, omitted for legacy id-less warns.
1035
1125
  - Update notifier. The SessionStart hook now shows an "Update available!" banner when a newer Hypomnema version has been published, detecting both distribution channels (npm package and Claude Code plugin) and printing the channel-appropriate update command (`npm install -g hypomnema`, or `/plugin marketplace update hypomnema` + `/reload-plugins`). The check never blocks session start: the hook reads a 24-hour cache only, and a detached worker refreshes it out-of-band, so a newer version surfaces from the next session. Per-channel notification state prevents the same banner from repeating, and `current >= latest` (local dev) is silently skipped. Opt out with `HYPO_NO_UPDATE_CHECK`, `NO_UPDATE_NOTIFIER`, or `CI`.
1036
- - `feedback`-as-source-of-truth + one-way projections to MEMORY / `<learned_behaviors>` (ADR 0031, PR [#36](https://github.com/sk-lim19f/Hypomnema/pull/36)). A new `pages/feedback/<slug>.md` page type replaces ad-hoc human-side sync of behavior corrections across three storage surfaces. `hypomnema feedback-sync` derives `~/.claude/projects/<project-id>/memory/MEMORY.md` (200-line cap) and `~/.claude/CLAUDE.md` `<learned_behaviors>` (max 10 entries, strict gate: `scope:global` + `tier:L1` + `targets:claude-learned` + `promote_to_global:true` + `sensitivity ∈ {public, sanitized}`) from the wiki. Managed blocks are marker- and hash-fenced; hand-edits are flagged as `CONFLICT_MANUAL_EDIT`. PreCompact integration runs inside `hypo-personal-check` (single-blocking-gate invariant). `sensitivity: private` is forbidden: the wiki is git-pushed; private data must stay outside the wiki entirely. `/hypo:feedback` slash command writes pages directly; `hypomnema feedback-sync --bootstrap` scaffolds drafts from existing MEMORY/CLAUDE state under `pages/feedback/_drafts/` for human review.
1037
- - Extensions companion sync (ADR 0024, PRs [#42](https://github.com/sk-lim19f/Hypomnema/pull/42)~[#47](https://github.com/sk-lim19f/Hypomnema/pull/47)). A new `extensions/` taxonomy in the wiki (`agents/`, `commands/`, `hooks/`, `skills/`) lets users ship Claude Code / Codex companion files alongside their wiki. `hypomnema init` scaffolds the directory; `hypomnema upgrade` mirrors the inventory into `~/.claude/` and (with `--codex`) **only the `hooks` and `commands` subset** into `~/.codex/` (agents/skills are Claude-only and skipped on the Codex target by design, see `scripts/lib/extensions.mjs` `CODEX_TYPES`). Conflict detection (`--force-extensions` to overwrite), and `hypomnema doctor extensions` audits integrity (orphan duplicates, matcher drift, non-registrable orphans). `hypomnema uninstall` cleans up the companion files. PR [#49](https://github.com/sk-lim19f/Hypomnema/pull/49) added settings.json mixed-group surgical write so settings.json edits stay minimal and merge-friendly.
1126
+ - `feedback`-as-source-of-truth + one-way projections to MEMORY / `<learned_behaviors>` (PR [#36](https://github.com/sk-lim19f/Hypomnema/pull/36)). A new `pages/feedback/<slug>.md` page type replaces ad-hoc human-side sync of behavior corrections across three storage surfaces. `hypomnema feedback-sync` derives `~/.claude/projects/<project-id>/memory/MEMORY.md` (200-line cap) and `~/.claude/CLAUDE.md` `<learned_behaviors>` (max 10 entries, strict gate: `scope:global` + `tier:L1` + `targets:claude-learned` + `promote_to_global:true` + `sensitivity ∈ {public, sanitized}`) from the wiki. Managed blocks are marker- and hash-fenced; hand-edits are flagged as `CONFLICT_MANUAL_EDIT`. PreCompact integration runs inside `hypo-personal-check` (single-blocking-gate invariant). `sensitivity: private` is forbidden: the wiki is git-pushed; private data must stay outside the wiki entirely. `/hypo:feedback` slash command writes pages directly; `hypomnema feedback-sync --bootstrap` scaffolds drafts from existing MEMORY/CLAUDE state under `pages/feedback/_drafts/` for human review.
1127
+ - Extensions companion sync (PRs [#42](https://github.com/sk-lim19f/Hypomnema/pull/42)~[#47](https://github.com/sk-lim19f/Hypomnema/pull/47)). A new `extensions/` taxonomy in the wiki (`agents/`, `commands/`, `hooks/`, `skills/`) lets users ship Claude Code / Codex companion files alongside their wiki. `hypomnema init` scaffolds the directory; `hypomnema upgrade` mirrors the inventory into `~/.claude/` and (with `--codex`) **only the `hooks` and `commands` subset** into `~/.codex/` (agents/skills are Claude-only and skipped on the Codex target by design, see `scripts/lib/extensions.mjs` `CODEX_TYPES`). Conflict detection (`--force-extensions` to overwrite), and `hypomnema doctor extensions` audits integrity (orphan duplicates, matcher drift, non-registrable orphans). `hypomnema uninstall` cleans up the companion files. PR [#49](https://github.com/sk-lim19f/Hypomnema/pull/49) added settings.json mixed-group surgical write so settings.json edits stay minimal and merge-friendly.
1038
1128
  - `hypomnema upgrade --codex` mirrors core hooks (PR [#50](https://github.com/sk-lim19f/Hypomnema/pull/50)). `init --codex` has always installed Hypomnema's core hooks into `~/.codex/hooks/` and registered them in `~/.codex/settings.json`, but `upgrade` only mirrored user extensions: so a v1.1.x → v1.2.0 codex user's core hooks stayed stale until a fresh install. The flag now drives drift detection, hook-file apply, settings.json registration, and the `wiki-*.mjs → hypo-*.mjs` rename migration on both targets in one pass. The human-readable report labels the two blocks ("Hook files (codex)", "settings.json (codex)") and JSON output gains `hooksCodex` / `settingsCodex` / `oldHookRefsCodex` plus matching `applied.*Codex` keys. Without `--codex` nothing under `~/.codex/` is inspected (parity with the existing extensions behaviour).
1039
- - `hypomnema upgrade` v1→v2 migration report (ADR 0034, PR [#60](https://github.com/sk-lim19f/Hypomnema/pull/60)). Major SCHEMA bump now writes `MIGRATION-v2.0.md` into the wiki root with v1→v2-specific guidance: ADR 0031 / ADR 0034 references, all 9 unconditional `feedback` fields, the conditional `claude-learned` set, the explicit no-auto-stub policy, the "fix existing pages before `/hypo:feedback` append" warning, the PR [#59](https://github.com/sk-lim19f/Hypomnema/pull/59) `project-id` ↔ slug regex caveat, and a closing re-run-lint checklist. Other major jumps keep the original generic body. PR [#57](https://github.com/sk-lim19f/Hypomnema/pull/57) invariants preserved: `SCHEMA.md` is byte-equal after `--apply` (Option C), report tag stays `[schema]` (the only token historically valid across all shipped Meta vocabularies).
1129
+ - `hypomnema upgrade` v1→v2 migration report (PR [#60](https://github.com/sk-lim19f/Hypomnema/pull/60)). Major SCHEMA bump now writes `MIGRATION-v2.0.md` into the wiki root with v1→v2-specific guidance: all 9 unconditional `feedback` fields, the conditional `claude-learned` set, the explicit no-auto-stub policy, the "fix existing pages before `/hypo:feedback` append" warning, the PR [#59](https://github.com/sk-lim19f/Hypomnema/pull/59) `project-id` ↔ slug regex caveat, and a closing re-run-lint checklist. Other major jumps keep the original generic body. PR [#57](https://github.com/sk-lim19f/Hypomnema/pull/57) invariants preserved: `SCHEMA.md` is byte-equal after `--apply` (Option C), report tag stays `[schema]` (the only token historically valid across all shipped Meta vocabularies).
1040
1130
  - PostToolUse WebFetch / WebSearch auto-ingest signal (PR [#48](https://github.com/sk-lim19f/Hypomnema/pull/48)). When Claude resolves a URL via WebFetch or runs WebSearch, the PostToolUse hook injects a nudge in `hookSpecificOutput.additionalContext` so Claude considers running `/hypo:ingest`. URL query/hash tokens and userinfo (`user:pass@host`) are stripped before injection. Non-HTTP schemes (`file://`, `ftp://`, `data:`) and missing URLs are silent skips. Opt out with `HYPO_SKIP_GATE=1`. Fail-open on invalid JSON stdin; stderr carries the unified `[hypo-web-fetch-ingest] error:` tag.
1041
- - Stop-chain auto-minimal-crystallize (ADR 0022 Layer 3, PR [#34](https://github.com/sk-lim19f/Hypomnema/pull/34)). A session that crossed a "non-trivial" threshold now offers (and on `Y` runs) `/hypo:crystallize --apply-session-close --minimal` automatically from the Stop hook chain. Combined with PR [#31](https://github.com/sk-lim19f/Hypomnema/pull/31)~[#33](https://github.com/sk-lim19f/Hypomnema/pull/33) `/clear` detection and SessionEnd marker / SessionStart `source=clear` recovery, the personal-check gate now catches forgotten session closes and reopens cleanly when the user runs `/clear`.
1131
+ - Stop-chain auto-minimal-crystallize (PR [#34](https://github.com/sk-lim19f/Hypomnema/pull/34)). A session that crossed a "non-trivial" threshold now offers (and on `Y` runs) `/hypo:crystallize --apply-session-close --minimal` automatically from the Stop hook chain. Combined with PR [#31](https://github.com/sk-lim19f/Hypomnema/pull/31)~[#33](https://github.com/sk-lim19f/Hypomnema/pull/33) `/clear` detection and SessionEnd marker / SessionStart `source=clear` recovery, the personal-check gate now catches forgotten session closes and reopens cleanly when the user runs `/clear`.
1042
1132
  - `crystallize --apply-session-close` programmatic entrypoint (PRs [#21](https://github.com/sk-lim19f/Hypomnema/pull/21), [#23](https://github.com/sk-lim19f/Hypomnema/pull/23)~[#26](https://github.com/sk-lim19f/Hypomnema/pull/26)). Strict 11-step session-close validation (PreCompact hard gate + crystallize). `--payload <json>` and `--apply-session-close` make the path machine-callable from the Stop hook chain; `--probe` early-exit (option D) keeps no-op closes fast. Lint preflight + post-apply gate ensures the wiki ends up clean.
1043
- - Auto-project creation on cwd match (ADR 0023, PR [#41](https://github.com/sk-lim19f/Hypomnema/pull/41)). When you start a session (or change directory) inside a git repository that carries a project marker (`package.json`, `Cargo.toml`, `go.mod`, `pyproject.toml`, `pom.xml`, `build.gradle`, `composer.json`, `Gemfile`) but matches no existing wiki project's `working_dir`, the SessionStart/CwdChanged hook now offers to create one. The offer is a nudge only; on "Y" Claude runs the new internal scaffold helper (`scripts/lib/project-create.mjs`) which materializes the project from `templates/projects/_template/` with token substitution, adds the root `hot.md` pointer row, and logs the creation. On "N" the cwd is recorded under `skips[]` in `.cache/project-suggestions.json` and never offered again (a 5-minute per-cwd cooldown also suppresses repeats within a session). Temp and marker-less directories never trigger the offer. `hypomnema doctor` validates the skip-persistence file's schema. The deprecated `hypomnema project new` subcommand is not introduced (ADR 0023). PR [#41](https://github.com/sk-lim19f/Hypomnema/pull/41) also strengthens the templated Session Start guidance: the first response must lead with a resume summary.
1133
+ - Auto-project creation on cwd match (PR [#41](https://github.com/sk-lim19f/Hypomnema/pull/41)). When you start a session (or change directory) inside a git repository that carries a project marker (`package.json`, `Cargo.toml`, `go.mod`, `pyproject.toml`, `pom.xml`, `build.gradle`, `composer.json`, `Gemfile`) but matches no existing wiki project's `working_dir`, the SessionStart/CwdChanged hook now offers to create one. The offer is a nudge only; on "Y" Claude runs the new internal scaffold helper (`scripts/lib/project-create.mjs`) which materializes the project from `templates/projects/_template/` with token substitution, adds the root `hot.md` pointer row, and logs the creation. On "N" the cwd is recorded under `skips[]` in `.cache/project-suggestions.json` and never offered again (a 5-minute per-cwd cooldown also suppresses repeats within a session). Temp and marker-less directories never trigger the offer. `hypomnema doctor` validates the skip-persistence file's schema. The deprecated `hypomnema project new` subcommand is not introduced. PR [#41](https://github.com/sk-lim19f/Hypomnema/pull/41) also strengthens the templated Session Start guidance: the first response must lead with a resume summary.
1044
1134
  - First-prompt resume summary + cwd-change re-trigger (PR [#39](https://github.com/sk-lim19f/Hypomnema/pull/39)). SessionStart's resume nudge now forces the resume summary on the first response, and a cwd change inside the session re-triggers the project match check (so opening a new repo without restarting Claude still picks up the right project).
1045
1135
  - `weekly-report` migrates output to `journal/weekly/<YYYY-Www>.md` (PR [#29](https://github.com/sk-lim19f/Hypomnema/pull/29)). Single source of truth per spec §6.4. Old report locations are no longer written.
1046
1136
  - Lint type-conditional fields + tag vocabulary lock (PRs [#28](https://github.com/sk-lim19f/Hypomnema/pull/28), [#38](https://github.com/sk-lim19f/Hypomnema/pull/38)). Lint now enforces per-type required fields and rejects unknown tags (vocabulary outside SCHEMA `Tag Vocabulary`). PR [#38](https://github.com/sk-lim19f/Hypomnema/pull/38) adds `B6` warn for `pages/` subdirs absent from SCHEMA taxonomy.
@@ -1049,10 +1139,10 @@ Contributors: @sk-lim19f
1049
1139
 
1050
1140
  #### 한국어
1051
1141
 
1052
- - `feedback`-as-SoT + 단방향 projection (ADR 0031): `pages/feedback/<slug>.md`가 행동 교정의 단일 source-of-truth. `hypomnema feedback-sync`로 MEMORY.md (cwd-scoped, 200줄 cap) + CLAUDE.md `<learned_behaviors>` (max 10, 엄격 게이트) 자동 동기.
1053
- - Extensions companion sync (ADR 0024): wiki에 `extensions/{agents,commands,hooks,skills}` 동봉. init/upgrade가 `~/.claude/` (+`--codex`로 `~/.codex/`) 미러링, conflict 감지, doctor 무결성 검사.
1054
- - Auto-project creation on cwd match (ADR 0023): git project marker 있는 cwd에 wiki project 없으면 SessionStart에서 생성 권유.
1055
- - Stop-chain auto-minimal-crystallize + `/clear` 감지 + SessionEnd marker 복구 (ADR 0022): session 종료 누락 시 자동 minimal crystallize 권유, `/clear` 후 재시작 시 깔끔 복구.
1142
+ - `feedback`-as-SoT + 단방향 projection: `pages/feedback/<slug>.md`가 행동 교정의 단일 source-of-truth. `hypomnema feedback-sync`로 MEMORY.md (cwd-scoped, 200줄 cap) + CLAUDE.md `<learned_behaviors>` (max 10, 엄격 게이트) 자동 동기.
1143
+ - Extensions companion sync: wiki에 `extensions/{agents,commands,hooks,skills}` 동봉. init/upgrade가 `~/.claude/` (+`--codex`로 `~/.codex/`) 미러링, conflict 감지, doctor 무결성 검사.
1144
+ - Auto-project creation on cwd match: git project marker 있는 cwd에 wiki project 없으면 SessionStart에서 생성 권유.
1145
+ - Stop-chain auto-minimal-crystallize + `/clear` 감지 + SessionEnd marker 복구: session 종료 누락 시 자동 minimal crystallize 권유, `/clear` 후 재시작 시 깔끔 복구.
1056
1146
  - Update notifier: SessionStart에서 신규 버전 알림 (npm 패키지 / Claude Code plugin 두 채널), opt out: `HYPO_NO_UPDATE_CHECK` / `NO_UPDATE_NOTIFIER` / `CI`.
1057
1147
  - PostToolUse WebFetch / WebSearch auto-ingest 신호: URL fetch 시 `/hypo:ingest` 권유 nudge 자동 주입 (privacy redaction 포함).
1058
1148
 
@@ -1061,9 +1151,9 @@ Contributors: @sk-lim19f
1061
1151
  #### English
1062
1152
 
1063
1153
  - `doctor` orphan duplicate scan + matcher drift surfacing (PRs [#53](https://github.com/sk-lim19f/Hypomnema/pull/53)~[#56](https://github.com/sk-lim19f/Hypomnema/pull/56), PR [#54](https://github.com/sk-lim19f/Hypomnema/pull/54) follow-ups). `doctor extensions` now surfaces non-registrable orphans, gated `matcher:""` specific message on `hookExact`, and reports orphan duplicate counts. `parseManifest` handles empty matcher; the canonical-pick mirror keeps the doctor view aligned with the actual registered hook.
1064
- - `extensions` settings.json mixed-group surgical write (PR [#49](https://github.com/sk-lim19f/Hypomnema/pull/49), ADR 0024 amendment). Edits to `settings.json` for extensions registration are now surgical inside mixed groups, leaving siblings + matcher in the source group exactly as found.
1154
+ - `extensions` settings.json mixed-group surgical write (PR [#49](https://github.com/sk-lim19f/Hypomnema/pull/49)). Edits to `settings.json` for extensions registration are now surgical inside mixed groups, leaving siblings + matcher in the source group exactly as found.
1065
1155
  - `crystallize --apply-session-close` lint preflight + post-apply gate (PR [#25](https://github.com/sk-lim19f/Hypomnema/pull/25)). Lint runs before AND after the apply to fail loudly on dirty input or post-write drift.
1066
- - PreCompact `/clear` detection + SessionEnd marker recovery (PRs [#31](https://github.com/sk-lim19f/Hypomnema/pull/31)~[#33](https://github.com/sk-lim19f/Hypomnema/pull/33) + amendments, ADR 0022). `compact-guard` detects `/clear` so it does not block; `personal-check` capacity bypass removed ([#32](https://github.com/sk-lim19f/Hypomnema/pull/32)); SessionEnd marker + SessionStart `source=clear` recovery makes /clear-then-restart cleanup work end-to-end.
1156
+ - PreCompact `/clear` detection + SessionEnd marker recovery (PRs [#31](https://github.com/sk-lim19f/Hypomnema/pull/31)~[#33](https://github.com/sk-lim19f/Hypomnema/pull/33) + amendments). `compact-guard` detects `/clear` so it does not block; `personal-check` capacity bypass removed ([#32](https://github.com/sk-lim19f/Hypomnema/pull/32)); SessionEnd marker + SessionStart `source=clear` recovery makes /clear-then-restart cleanup work end-to-end.
1067
1157
  - Test hermeticity: child HOME isolation in `tests/runner.mjs` (PR [#30](https://github.com/sk-lim19f/Hypomnema/pull/30)). Tests no longer rely on the dev's real `$HOME`; child processes get an isolated home so external writes can't pollute or break the suite.
1068
1158
  - `withWiki()` fixture date local-time alignment (PR [#52](https://github.com/sk-lim19f/Hypomnema/pull/52)). UTC vs local boundary flake removed.
1069
1159
 
@@ -1078,7 +1168,7 @@ Contributors: @sk-lim19f
1078
1168
 
1079
1169
  #### English
1080
1170
 
1081
- - `feedback-sync` MEMORY projection is now strictly cwd-scoped (ADR 0031 §4 amendment, PR [#59](https://github.com/sk-lim19f/Hypomnema/pull/59)). `memoryTarget.filter` previously accepted any `scope: project:*` page regardless of the resolved project-id, so a `scope: project:other` page was silently projected into `~/.claude/projects/<this-project>/memory/`. The filter is now `scope === 'global' || scope === \`project:${projectId}\`` (exact match). `templates/SCHEMA.md` §3.1 and `commands/feedback.md` `--scope` flag clarify that `<project-id>` must exact-match the resolved project-id (default: `cwd → '/'.'.' → '-'`; or `--project-id=<id>` override). Mismatch = silent MEMORY skip (not a lint error). The lint regex `^project:[a-z0-9][a-z0-9-]*$` and the default cwd-derived id are incompatible: to use a `project:*` scope you must `--project-id=<slug>` override. Full resolved-id ↔ wiki-slug reconciliation is deferred to v1.3.0.
1171
+ - `feedback-sync` MEMORY projection is now strictly cwd-scoped (PR [#59](https://github.com/sk-lim19f/Hypomnema/pull/59)). `memoryTarget.filter` previously accepted any `scope: project:*` page regardless of the resolved project-id, so a `scope: project:other` page was silently projected into `~/.claude/projects/<this-project>/memory/`. The filter is now `scope === 'global' || scope === \`project:${projectId}\`` (exact match). `templates/SCHEMA.md` §3.1 and `commands/feedback.md` `--scope` flag clarify that `<project-id>` must exact-match the resolved project-id (default: `cwd → '/'.'.' → '-'`; or `--project-id=<id>` override). Mismatch = silent MEMORY skip (not a lint error). The lint regex `^project:[a-z0-9][a-z0-9-]*$` and the default cwd-derived id are incompatible: to use a `project:*` scope you must `--project-id=<slug>` override. Full resolved-id ↔ wiki-slug reconciliation is deferred to v1.3.0.
1082
1172
  - `hypomnema upgrade` migration report tag historical regression fix (PR [#57](https://github.com/sk-lim19f/Hypomnema/pull/57)). `writeMigrationReport()` previously emitted `tags: [hypomnema, migration, schema]`, but the v1.0 / v1.1 historical Meta vocab is `wiki, index, operations, guide, schema`: neither `hypomnema` nor `migration` are present. Because Option C deliberately does NOT touch the user's `SCHEMA.md`, a v1.0 / v1.1 user upgrading would have a lint-failing page created at the wiki root. Tag tightened to `[schema]` (the only token historically valid). Added two regression tests: `--apply leaves user SCHEMA.md byte-equal` (Option C contract) and `--apply migration report tags are all in installed SCHEMA vocab` (vocab-level assertion, with the installed Meta vocab back-dated to the oldest shipped set). Also clarified `upgrade.mjs` dry-run wording and removed the self-referential "Run /hypo:upgrade --apply" action item from the report body.
1083
1173
  - Unified `[hypo-<name>] error:` stderr log tag across all lifecycle hooks (PR [#40](https://github.com/sk-lim19f/Hypomnema/pull/40)). Every hook (`hypo-cwd-change`, `hypo-first-prompt`, `hypo-compact-guard`, `hypo-file-watch`, `hypo-lookup`, `hypo-personal-check`, `hypo-auto-minimal-crystallize`, `hypo-auto-stage`, `hypo-web-fetch-ingest`) emits its forced-catch path with the same `[hypo-<name>] error: ...` prefix so dogfood log triage is grep-friendly.
1084
1174
  - Prettier setup + format pass (chore commits `dbc228f`, `4dac33c`, `4696abf`). Repository-wide Prettier config + `npm run format` / `format:check` scripts. `.git-blame-ignore-revs` for the reformat commit so `git blame` stays clean.
@@ -1086,7 +1176,7 @@ Contributors: @sk-lim19f
1086
1176
 
1087
1177
  #### 한국어
1088
1178
 
1089
- - `feedback-sync` MEMORY cross-project pollution fix (PR [#59](https://github.com/sk-lim19f/Hypomnema/pull/59) / ADR 0031 §4 amendment): `scope: project:*` exact-match 강제.
1179
+ - `feedback-sync` MEMORY cross-project pollution fix (PR [#59](https://github.com/sk-lim19f/Hypomnema/pull/59)): `scope: project:*` exact-match 강제.
1090
1180
  - `hypomnema upgrade` migration report tag historical regression fix (PR [#57](https://github.com/sk-lim19f/Hypomnema/pull/57)): tag `[schema]`로 좁힘. v1.0/v1.1 historical vocab에 있는 유일 안전 토큰.
1091
1181
  - Code comment rot cleanup Phase 1. 13 files comment-only diff. `fix #NN` / `PR #NN follow-up` 등 시간에 따라 stale 되는 참조 제거, ADR / contract / spec anchor 보존.
1092
1182