hypomnema 1.7.3 → 1.8.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 (65) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +1202 -0
  4. package/README.ko.md +7 -7
  5. package/README.md +7 -7
  6. package/commands/audit.md +1 -1
  7. package/commands/capture.md +2 -2
  8. package/commands/crystallize.md +4 -4
  9. package/commands/doctor.md +15 -1
  10. package/commands/feedback.md +1 -1
  11. package/commands/graph.md +1 -1
  12. package/commands/ingest.md +1 -1
  13. package/commands/init.md +2 -2
  14. package/commands/lint.md +1 -1
  15. package/commands/query.md +1 -1
  16. package/commands/rename.md +1 -1
  17. package/commands/resume.md +1 -1
  18. package/commands/stats.md +1 -1
  19. package/commands/uninstall.md +17 -5
  20. package/commands/upgrade.md +2 -2
  21. package/commands/verify.md +1 -1
  22. package/docs/ARCHITECTURE.md +9 -4
  23. package/docs/CONTRIBUTING.md +15 -5
  24. package/hooks/base-store.mjs +198 -0
  25. package/hooks/close-gate-store.mjs +436 -0
  26. package/hooks/hooks.json +2 -1
  27. package/hooks/hypo-auto-minimal-crystallize.mjs +10 -0
  28. package/hooks/hypo-close-guard.mjs +24 -4
  29. package/hooks/hypo-compact-guard.mjs +5 -3
  30. package/hooks/hypo-cwd-change.mjs +8 -0
  31. package/hooks/hypo-personal-check.mjs +128 -68
  32. package/hooks/hypo-session-end.mjs +21 -2
  33. package/hooks/hypo-session-start.mjs +91 -10
  34. package/hooks/hypo-shared.mjs +498 -180
  35. package/hooks/version-check.mjs +44 -6
  36. package/package.json +10 -3
  37. package/scripts/capture.mjs +64 -21
  38. package/scripts/crystallize.mjs +20 -2044
  39. package/scripts/doctor.mjs +334 -14
  40. package/scripts/init.mjs +132 -92
  41. package/scripts/lib/crystallize-args.mjs +50 -0
  42. package/scripts/lib/crystallize-close-apply.mjs +1830 -0
  43. package/scripts/lib/crystallize-close-check.mjs +238 -0
  44. package/scripts/lib/crystallize-close-gate.mjs +58 -0
  45. package/scripts/lib/crystallize-helpers.mjs +55 -0
  46. package/scripts/lib/design-history-stale.mjs +26 -7
  47. package/scripts/lib/extensions.mjs +235 -65
  48. package/scripts/lib/git-hooks-dir.mjs +214 -2
  49. package/scripts/lib/plugin-detect.mjs +176 -21
  50. package/scripts/lib/slug-resolver.mjs +181 -0
  51. package/scripts/lint.mjs +33 -23
  52. package/scripts/proposal.mjs +3 -3
  53. package/scripts/rename.mjs +38 -141
  54. package/scripts/uninstall.mjs +155 -58
  55. package/scripts/upgrade.mjs +243 -20
  56. package/skills/crystallize/SKILL.md +16 -14
  57. package/skills/graph/SKILL.md +3 -3
  58. package/skills/ingest/SKILL.md +2 -2
  59. package/skills/lint/SKILL.md +3 -3
  60. package/skills/query/SKILL.md +3 -3
  61. package/skills/verify/SKILL.md +3 -3
  62. package/templates/hypo-automation.md +59 -17
  63. package/templates/hypo-config.md +1 -1
  64. package/templates/hypo-guide.md +12 -9
  65. package/templates/hypo-help.md +1 -1
package/README.ko.md CHANGED
@@ -39,7 +39,7 @@ Andrej Karpathy의 "LLM 네이티브 위키" 스케치에서 출발했습니다.
39
39
 
40
40
  업그레이드 정책 하나만 미리 알아 두세요. `hypomnema upgrade --apply`는 사용자가 직접 편집한 `SCHEMA.md`를 덮어쓰지 않습니다. 스키마가 올라가면 위키 루트에 마이그레이션 보고서만 써 주고, 실제 반영은 사용자가 직접 합니다(코드에서 Option C라고 부르는 정책).
41
41
 
42
- 같은 보류 방식이 세션 마무리 파일을 쓸 때에도 적용됩니다. 세션 종료 쓰기의 대상 파일이 이 세션이 읽은 뒤에 바뀌었다면 Hypomnema는 덮어쓰지 않습니다. 그 내용을 `<위키>/.cache/proposals/` 아래에 보류하고 알려 줍니다. 검토와 적용은 직접 하세요. `hypomnema proposal list`로 목록을 보고 `hypomnema proposal apply <id>` 또는 `hypomnema proposal discard <id>`를 씁니다. 자동 적용은 없습니다.
42
+ 같은 보류 방식이 세션 마무리 파일을 쓸 때에도 적용됩니다. 세션 종료 쓰기의 대상 파일이 이 세션이 읽은 뒤에 바뀌었다면 Hypomnema는 덮어쓰지 않습니다. 그 내용을 `<위키>/.cache/proposals/` 아래에 보류하고 알려 줍니다. 검토와 적용은 직접 하세요. `hypomnema proposal list`로 목록을 보고 터미널에서 `hypomnema proposal apply <id>` 또는 `hypomnema proposal discard <id>`를 씁니다. 세션 안에서는 타이핑할 터미널이 없으므로 같은 승인을 대화에서 받습니다. `hypomnema proposal challenge`가 diff를 보여 주고, 거기 적힌 줄을 직접 입력하면, `hypomnema proposal resolve`가 본 그대로를 씁니다. 자동 적용은 없습니다.
43
43
 
44
44
  자동처럼 보이지만 실제로는 직접 실행해야 하는 기능이 두 가지 있습니다. extensions sync와 역방향 capture입니다. 위키 안 `~/hypomnema/extensions/{agents,commands,hooks,skills}/`에 둔 파일은 `~/.claude/`에 반영되는데(`--codex`를 붙이면 `hooks`·`commands`는 `~/.codex/`에도), 직접 `hypomnema init`, `hypomnema upgrade --apply`, 또는 dry-run이 아닌 `hypomnema capture`를 돌려야만 반영됩니다. 저절로 밀어 넣는 트리거는 없습니다. `hypomnema capture`(또는 `/hypo:capture`)는 반대 방향으로, 직접 만든 command·agent·hook·skill을 위키로 가져오는 경로인데 이것도 마찬가지로 명시적으로 불러야 동작합니다. 자세한 내용은 아래 명령어 표를 보세요.
45
45
 
@@ -74,9 +74,9 @@ hypomnema
74
74
 
75
75
  > 어느 경로든 첫 실행 뒤에는 Claude Code를 재시작(또는 새 세션 열기)해야 새 훅과 슬래시 커맨드가 반영됩니다.
76
76
 
77
- > `init`은 `~/.zshrc`(또는 `~/.bashrc`)에 `claude()` 셸 함수도 추가합니다. `# hypo-managed:shell-setup:start`와 `:end` 마커 사이에 들어가며, `cd`만 한 세션에도 프로젝트 컨텍스트가 주입되게 합니다. `--no-shell`로 건너뛰거나 `--shell-config=<path>`로 대상 파일을 바꿀 수 있습니다. `uninstall`은 이 블록을 지우지 않으니 마커 사이 줄을 직접 지우세요.
77
+ > `init`은 `~/.zshrc`(또는 `~/.bashrc`)에 `claude()` 셸 함수도 추가합니다. `# hypo-managed:shell-setup:start`와 `:end` 마커 사이에 들어가며, `cd`만 한 세션에도 프로젝트 컨텍스트가 주입되게 합니다. `--no-shell`로 건너뛰거나 `--shell-config=<path>`로 대상 파일을 바꿀 수 있습니다. `uninstall --apply`는 이 블록을 지웁니다(기본으로 `~/.zshrc`와 `~/.bashrc` 둘 다 확인하고, `--shell-config`로 지정한 파일이 있으면 그것만 봅니다). 다만 마커를 손으로 편집해서 중복되거나 순서가 뒤바뀐 상태라면, 무엇을 지워야 할지 안전하게 판단할 수 없으므로 블록을 그대로 둡니다. `--keep-shell`을 주면 이 셸 rc 정리만 건너뛰고 나머지 uninstall 은 그대로 돕니다.
78
78
 
79
- > `init`은 위키 저장소에 pre-commit 훅도 설치합니다(`<위키>/.git/hooks/pre-commit`, `# hypo-managed:pre-commit:*` 마커). `.hypoignore`에 걸리는 파일이 스테이지되면 커밋을 막습니다. 해당 파일을 unstage 하거나 `git commit --no-verify`로 우회하세요. 이 훅에는 설치된 패키지의 절대 경로가 박히므로, Hypomnema를 옮기거나 재설치했다면 `hypomnema init`을 다시 돌려 경로를 갱신해야 합니다. 그러지 않으면 위키의 모든 커밋이 실패합니다. `uninstall`은 이 파일을 지우지 않습니다.
79
+ > `init`은 위키 저장소에 pre-commit 훅도 설치합니다(`<위키>/.git/hooks/pre-commit`, `# hypo-managed:pre-commit:*` 마커). `.hypoignore`에 걸리는 파일이 스테이지되면 커밋을 막습니다. 해당 파일을 unstage 하거나 `git commit --no-verify`로 우회하세요. 이 훅에는 설치된 패키지의 절대 경로가 박히므로, Hypomnema를 옮기거나 재설치했다면 `hypomnema init`을 다시 돌려 경로를 갱신해야 합니다. 그러지 않으면 위키의 모든 커밋이 실패합니다. `uninstall --apply`는 이 훅을 지웁니다. 위키 위치는 `init`과 같은 방식으로 찾고, `--hypo-dir=<path>`로 직접 지정할 수도 있습니다. 다만 마커는 있지만 그 바깥에 다른 내용이 붙은 훅처럼 더 이상 온전히 Hypomnema 소유가 아닌 훅은 손대지 않고 그대로 둡니다. `--keep-wiki-hook`을 주면 이 위키 훅 정리만 건너뛰고 나머지 uninstall 은 그대로 돕니다.
80
80
 
81
81
  > 두 번째 기기에서는 그냥 `hypomnema`를 돌리지 마세요. 원격과 무관한 새 위키가 만들어집니다. 기존 위키를 클론하는 쪽을 쓰세요. `hypomnema --from-remote=<git-url>`이 위키 루트로 클론한 뒤 실제 Hypomnema 위키인지 확인하고, 새 git 히스토리를 만들지 않은 채 훅과 슬래시 커맨드만 설치합니다. 대상 디렉터리가 이미 있으면 거부합니다.
82
82
 
@@ -203,7 +203,7 @@ Hypomnema는 청크가 아니라 페이지를 지식 단위로 봅니다. 새
203
203
  |---|---|
204
204
  | `SessionStart` | "어디까지 했더라?" `hot.md` / `session-state.md` 다시 읽기 |
205
205
  | `UserPromptSubmit` | "이거 이미 정리해 뒀나?" BM25 룩업, top-3 주입 |
206
- | `PreCompact` | "세션 정리는 했나?" 체크리스트 가드 |
206
+ | `PreCompact` | "세션 정리는 했나?" 체크리스트 알림 (권고일 뿐, `/compact`를 막지 않음) |
207
207
  | `PostToolUse` (Write/Edit/MultiEdit) | `git add` |
208
208
  | `Stop` | `git commit && git pull --no-rebase && git push` |
209
209
 
@@ -261,7 +261,7 @@ Hypomnema는 청크가 아니라 페이지를 지식 단위로 봅니다. 새
261
261
  | `hypo-auto-stage.mjs` | `PostToolUse(Write/Edit/MultiEdit)` | 위키 파일 자동 stage |
262
262
  | `hypo-auto-commit.mjs` | `Stop` | 자동 commit + pull + push |
263
263
  | `hypo-hot-rebuild.mjs` | `Stop` | 루트 `hot.md` 포인터 테이블 재생성 (구조와 날짜) |
264
- | `hypo-personal-check.mjs` | `PreCompact` | session-close 미완, 위키 커밋/푸시 누락, hot.md 구조 위반, lint 블로커면 compact 차단 (우회: `HYPO_SKIP_GATE=1`) |
264
+ | `hypo-personal-check.mjs` | `PreCompact` | session-close 미완, 위키 커밋/푸시 누락, hot.md 구조 위반, lint 블로커를 `systemMessage`로 알림. `/compact`는 여기서 막지 않음. 세션 마무리 강제는 다른 자리에 남아 있음: Stop 훅(`hypo-auto-minimal-crystallize.mjs`)이 마무리 확인 없는 의미 있는 세션을 막고, `--mark-session-closed`는 여전히 red 게이트에서 마커를 거부함 |
265
265
  | `hypo-session-end.mjs` | `SessionEnd` | SessionEnd 마커 기록. 다음 SessionStart가 `source=clear` 복구를 감지하게 함 |
266
266
  | `hypo-session-record.mjs` | `Stop` | observability 점수 + auto-resume 신호용 세션 메타데이터 기록 |
267
267
  | `hypo-auto-minimal-crystallize.mjs` | `Stop` | 사용자가 마무리를 신호한 뒤, 의미 있는 작업을 했는데 세션 마무리가 확인되지 않으면 Stop을 막고 `crystallize.mjs --mark-session-closed` 명령을 돌려줌. 미커밋 변경이나 진행 중인 작업이 있으면 명령 대신 지금 닫을지 되물음 |
@@ -392,7 +392,7 @@ E. 멈춘 프로젝트 재개.
392
392
 
393
393
  세 번째 프라이버시 축은 경로가 아니라 페이지 단위로 걸립니다. 페이지 프런트매터에 `visibility_scope: machine:<device>`를 넣으면 그 기기에서만 노출됩니다. `<device>`의 기본값은 hostname이며, hostname이 고정적이지 않은 환경이라면 `HYPO_DEVICE`로 못박으세요. 그러지 않으면 페이지가 자기 기기와도 매치되지 않습니다. 필드를 생략하면 shared입니다.
394
394
 
395
- `HYPO_SKIP_GATE=1`은 세션 중간에 사용자를 막을 수 있는 게이트 전부가 존중합니다. `hypo-personal-check`(PreCompact), `hypo-compact-guard`, `hypo-close-guard`, `hypo-auto-minimal-crystallize`, 그리고 `hypo-web-fetch-ingest`의 ingest 안내까지입니다. 기록할 필요 없는 가벼운 세션에 씁니다.
395
+ `HYPO_SKIP_GATE=1`은 세션 중간에 사용자를 막을 수 있는 게이트가 존중합니다. `hypo-compact-guard`, `hypo-close-guard`, `hypo-auto-minimal-crystallize`, 그리고 `hypo-web-fetch-ingest`의 ingest 안내가 여기 해당합니다. `hypo-personal-check`(PreCompact)도 이 플래그를 읽지만, 이 훅은 이제 `/compact`를 막지 않으므로 플래그는 반복될 뻔한 미완성-마무리 `systemMessage`만 억제합니다. 기록할 필요 없는 가벼운 세션에 씁니다.
396
396
 
397
397
  > 모델 제공사에게 전송되는 범위: Hypomnema 훅은 위키 본문을 Claude Code의 추가 컨텍스트(`additionalContext`)에 실어 보내고, 이 내용은 프롬프트의 일부로 Claude 모델 제공사로 전송됩니다. `.hypoignore`에 등록된 경로는 모든 주입 훅(`hypo-file-watch`, `hypo-session-start`, `hypo-cwd-change`, `hypo-lookup`)과 `ingest`에서 제외되지만, 등록하지 않은 파일은 전송 대상입니다. (`hypo-auto-stage`/`hypo-auto-commit`은 git 스테이징용 훅이라 컨텍스트를 주입하지는 않지만, 스테이징 판단에도 `.hypoignore`를 참고합니다.) 비밀 정보는 위키에 두지 마시고, `HYPO_DIR` 아래에 민감한 내용을 저장하기 전에 `.hypoignore` 패턴을 먼저 점검하세요.
398
398
 
@@ -404,7 +404,7 @@ E. 멈춘 프로젝트 재개.
404
404
 
405
405
  | 설치 경로 | 슬래시 커맨드 위치 |
406
406
  |---|---|
407
- | 플러그인 (Path A) | Claude Code 플러그인 캐시. `/plugin marketplace update hypomnema` 후 `/reload-plugins`로 갱신 |
407
+ | 플러그인 (Path A) | Claude Code 플러그인 캐시. `/plugin marketplace update hypomnema` 후 `/reload-plugins`로 갱신. 캐시 디렉터리 이름이 매니페스트 버전이라, 그 버전이 바뀌어야 갱신이 반영된다. 버전을 그대로 둔 채 머지한 커밋은 이미 설치된 곳에 닿지 않는다. |
408
408
  | npm CLI (Path B) | `~/.claude/commands/hypo/`. `hypomnema upgrade --apply`로 갱신, 파일별 SHA 추적. 사용자 수정본까지 덮어쓰려면 `--force-commands`(원본은 `.bak`으로 보존) |
409
409
 
410
410
  ---
package/README.md CHANGED
@@ -72,7 +72,7 @@ hypomnema
72
72
 
73
73
  `hypomnema` (or `hypomnema --help` for flags) scaffolds the wiki and installs hooks. It also copies the slash command files to `~/.claude/commands/hypo/`, so `/hypo:*` works inside Claude Code afterwards. Later `hypomnema upgrade` runs use per-file SHA tracking, so anything you hand-edited stays put.
74
74
 
75
- `init` also appends a `claude()` shell function to `~/.zshrc` or `~/.bashrc`, marked between `# hypo-managed:shell-setup:start` and `:end`, so a cd-only session still gets project context. Pass `--no-shell` to skip it, or `--shell-config=<path>` to target a different file. `uninstall` does not remove this block; delete the marked lines by hand.
75
+ `init` also appends a `claude()` shell function to `~/.zshrc` or `~/.bashrc`, marked between `# hypo-managed:shell-setup:start` and `:end`, so a cd-only session still gets project context. Pass `--no-shell` to skip it, or `--shell-config=<path>` to target a different file. `uninstall --apply` removes this block (checking both `~/.zshrc` and `~/.bashrc` by default, or the file named by `--shell-config`); it leaves the block untouched if a hand-edit has since duplicated or reordered the markers, since it has no safe way to guess what you meant. Pass `--keep-shell` to skip this shell rc cleanup (the rest of the uninstall still runs).
76
76
 
77
77
  > Either path: restart Claude Code (or open a new session) after the first run so the new hooks and slash commands are picked up.
78
78
 
@@ -201,7 +201,7 @@ Friction kills personal knowledge systems. If saving a thought takes three click
201
201
  |---|---|
202
202
  | `SessionStart` | "Where did I leave off?" reading `hot.md` / `session-state.md` |
203
203
  | `UserPromptSubmit` | "Do I already know this?" a BM25 lookup, top-3 inject |
204
- | `PreCompact` | "Did I close the session?" the checklist guard |
204
+ | `PreCompact` | "Did I close the session?" a checklist notice (advisory only; it does not block `/compact`) |
205
205
  | `PostToolUse` (Write/Edit/MultiEdit) | `git add` |
206
206
  | `Stop` | `git commit && git pull --no-rebase && git push` |
207
207
 
@@ -257,7 +257,7 @@ Nine commands cover the full capture → retrieval → consolidation cycle.
257
257
  | `hypo-auto-stage.mjs` | `PostToolUse(Write/Edit/MultiEdit)` | Auto-stage wiki-file edits |
258
258
  | `hypo-auto-commit.mjs` | `Stop` | Auto commit + pull + push |
259
259
  | `hypo-hot-rebuild.mjs` | `Stop` | Rebuild the root `hot.md` pointer table (structure + dates) |
260
- | `hypo-personal-check.mjs` | `PreCompact` | Block compact on an unfinished session-close, an uncommitted or unpushed wiki, malformed `hot.md`, or lint blockers (bypass: `HYPO_SKIP_GATE=1`) |
260
+ | `hypo-personal-check.mjs` | `PreCompact` | Surfaces an unfinished session-close, an uncommitted or unpushed wiki, malformed `hot.md`, or lint blockers as a `systemMessage`; it never blocks `/compact`. Session close is still enforced elsewhere: the Stop hook (`hypo-auto-minimal-crystallize.mjs`) blocks on a substantial session with no verified close, and `--mark-session-closed` still refuses the marker on a red gate |
261
261
  | `hypo-session-end.mjs` | `SessionEnd` | Write a SessionEnd marker so SessionStart can detect `source=clear` recovery |
262
262
  | `hypo-session-record.mjs` | `Stop` | Record session metadata for the observability score and auto-resume signaling |
263
263
  | `hypo-auto-minimal-crystallize.mjs` | `Stop` | After the user signals wrap-up, blocks Stop on a substantial session with no verified close and hands back the `crystallize.mjs --mark-session-closed` command. With uncommitted changes or work still in flight it asks whether to close now instead |
@@ -384,15 +384,15 @@ Place a `hypo-config.md` at the wiki root to make it portable across machines wi
384
384
 
385
385
  `.hypoignore` controls which paths the hooks ignore (default: `*.pdf`, `*.zip`, `*.pem`, `.env*`, `*credentials*`, `*secret*`, `*token*`, `*password*`, `*passwd*`, …). Edit it directly; there is no privacy mode flag. One file, one source of truth.
386
386
 
387
- > `init` also installs a pre-commit hook into the wiki repo at `<wiki>/.git/hooks/pre-commit`, marked `# hypo-managed:pre-commit:*`. It refuses any commit that stages a path matching `.hypoignore`; unstage the file, or override with `git commit --no-verify`. The hook embeds the absolute path of the installed package, so if you move or reinstall Hypomnema, re-run `hypomnema init` to repoint it or every wiki commit will fail. `uninstall` leaves this file in place.
387
+ > `init` also installs a pre-commit hook into the wiki repo at `<wiki>/.git/hooks/pre-commit`, marked `# hypo-managed:pre-commit:*`. It refuses any commit that stages a path matching `.hypoignore`; unstage the file, or override with `git commit --no-verify`. The hook embeds the absolute path of the installed package, so if you move or reinstall Hypomnema, this path goes stale and every wiki commit fails. `hypomnema upgrade --apply` (or `/hypo:upgrade`, confirming the apply step, on a plugin install) repoints it in most cases, since a plugin-channel version bump is exactly the case it is built to detect; re-run `hypomnema init` when `upgrade --apply` cannot, which happens for a manual/npm install running alongside an enabled plugin whose active root it cannot positively resolve, or when the vault itself (not just the package) has moved, since `upgrade` only ever corrects the embedded root, never the embedded `--hypo-dir`. `uninstall --apply` removes this hook, resolving the vault the same way `init` does (`--hypo-dir=<path>` to override); it only removes a hook that still carries the marker and no content outside it, so a hook you have since edited by hand is left in place. Pass `--keep-wiki-hook` to skip this wiki hook cleanup (the rest of the uninstall still runs).
388
388
 
389
389
  > The credential-style patterns above are substring globs, so an ordinary page such as `pages/oauth-token-refresh.md` is matched too: it is never injected by any hook, and the wiki pre-commit hook refuses to commit it. If you write about these topics, narrow the patterns or rename the page.
390
390
 
391
391
  `.hyposcanignore` is a different file with a narrower job. `init` writes one at the root of every new vault, and it only excludes paths from catalog scans (`lint`, `stats`, `query`, `verify`, `doctor`). It is not a privacy boundary: a path matched by `.hyposcanignore` is still committed and still readable by hooks. To actually keep something out of injection and commits, use `.hypoignore`.
392
392
 
393
- > Gate bypass: `HYPO_SKIP_GATE=1` is honored by every gate that can stop you mid-session, not just one: `hypo-personal-check` (PreCompact), `hypo-compact-guard`, `hypo-close-guard`, `hypo-auto-minimal-crystallize`, and the ingest nudge in `hypo-web-fetch-ingest`. Set it for a trivial session you do not want recorded.
393
+ > Gate bypass: `HYPO_SKIP_GATE=1` is honored by every gate that can stop you mid-session: `hypo-compact-guard`, `hypo-close-guard`, `hypo-auto-minimal-crystallize`, and the ingest nudge in `hypo-web-fetch-ingest`. `hypo-personal-check` (PreCompact) also reads the flag, but since that hook never blocks `/compact` anymore, the flag only suppresses the incomplete-close `systemMessage` it would otherwise repeat. Set it for a trivial session you do not want recorded.
394
394
 
395
- > If a session-close write finds its target changed since this session read it, Hypomnema does not overwrite it. It parks the bytes under `<wiki>/.cache/proposals/` and tells you. Review and apply them yourself: `hypomnema proposal list`, then `hypomnema proposal apply <id>` or `hypomnema proposal discard <id>`. There is no auto-apply.
395
+ > If a session-close write finds its target changed since this session read it, Hypomnema does not overwrite it. It parks the bytes under `<wiki>/.cache/proposals/` and tells you. Review and apply them yourself: `hypomnema proposal list`, then `hypomnema proposal apply <id>` (at a terminal) or `hypomnema proposal discard <id>`. Inside a session, where there is no terminal to type at, the same approval is collected in the conversation: `hypomnema proposal challenge` prints the diffs, you type the line it gives you, and `hypomnema proposal resolve` writes exactly what you saw. There is no auto-apply.
396
396
 
397
397
  > Provider transmission disclaimer: Hypomnema hooks emit wiki content into Claude Code's `additionalContext`, which is transmitted to the Claude model provider as part of the prompt. `.hypoignore` is enforced at every content-injection hook (`hypo-file-watch`, `hypo-session-start`, `hypo-cwd-change`, `hypo-lookup`) and at `ingest`, but any file _not_ matched by `.hypoignore` is fair game for transmission. (`hypo-auto-stage` and `hypo-auto-commit` are git-staging hooks, not injection points, and also honor `.hypoignore` for their staging decisions.) Keep secrets out of the wiki, and review `.hypoignore` patterns before storing anything sensitive under `HYPO_DIR`.
398
398
 
@@ -404,7 +404,7 @@ Place a `hypo-config.md` at the wiki root to make it portable across machines wi
404
404
 
405
405
  | Install path | Slash commands served from |
406
406
  |---|---|
407
- | Plugin (Path A) | Claude Code's plugin cache; updated via `/plugin marketplace update hypomnema` then `/reload-plugins` |
407
+ | Plugin (Path A) | Claude Code's plugin cache; updated via `/plugin marketplace update hypomnema` then `/reload-plugins`. The cache directory is named after the manifest version, so an update lands only when that version has changed. A commit merged under an unchanged version does not reach an existing install. |
408
408
  | npm CLI (Path B) | `~/.claude/commands/hypo/`; updated via `hypomnema upgrade --apply` with per-file SHA tracking. Pass `--force-commands` to overwrite hand-edits (creates `.bak`). |
409
409
 
410
410
  ---
package/commands/audit.md CHANGED
@@ -16,7 +16,7 @@ Definition: [[pages/observability/_index]].
16
16
 
17
17
  ## Step 1 — Run script
18
18
 
19
- Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema upgrade --apply` or reinstall instead of guessing the cache layout.
19
+ Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema upgrade --apply` (or `/hypo:upgrade` on a plugin install) or reinstall instead of guessing the cache layout.
20
20
 
21
21
  If the user specified a Hypomnema directory, pass it as `--hypo-dir="<path>"`. Otherwise omit the flag.
22
22
 
@@ -6,7 +6,7 @@ You are running `/hypo:capture`. Bring a command, agent, or skill that the user
6
6
 
7
7
  Scope: commands, agents, skills, and hooks. Commands and agents are enumerated from `~/.claude/{commands,agents}/`; a skill is a whole directory (`~/.claude/skills/<name>/SKILL.md` plus its subtree); hooks are read from the `~/.claude/settings.json` registration.
8
8
 
9
- Hooks and skills are captured only when they round-trip losslessly, because what lands in the wiki is exactly what the far machine installs. A hook qualifies when its command is the canonical `node $HOME/.claude/hooks/<name>.mjs` form and its event, matcher, and timeout are preserved. A skill is refused whole (never captured as a partial subset) when its subtree holds anything that cannot survive the trip: a symlink, a hardlink, an empty directory (git cannot carry one), a VCS control directory, or more than 500 files / 5 MiB. That ceiling is what keeps a vendored skill with its own `node_modules` out of the vault. Content is reproduced byte for byte; the executable bit is not carried by sync yet, so a captured skill holding executable scripts prints a warning.
9
+ Hooks and skills are captured only when they round-trip losslessly, because what lands in the wiki is exactly what the far machine installs. A hook qualifies when its command is the canonical `node $HOME/.claude/hooks/<name>.mjs` form and its event, matcher, and timeout are preserved. A skill is refused whole (never captured as a partial subset) when its subtree holds anything that cannot survive the trip: a symlink, a hardlink, an empty directory (git cannot carry one), a VCS control directory, or more than 500 files / 5 MiB. That ceiling is what keeps a vendored skill with its own `node_modules` out of the vault. Content is reproduced byte for byte, and a newly captured executable script keeps its executable bit through the wiki and onto every install. A skill captured before this behavior shipped has its wiki copy recorded non-executable, and there is no automatic fix once that has happened: it is already managed by the wiki, so it never shows up as a capture candidate again, and forward-sync's own heal only adds a missing exec bit while the install's content still matches the wiki record byte for byte. If the wiki content changes later for an unrelated reason, the install picks up whatever mode the new wiki bytes carry, exec bit included, so the mode can move in either direction at that point, not just up. The only working fix for an already-committed non-executable script is `chmod +x` directly on the wiki file; since that is a mode-only change with the content untouched, forward-sync's heal then carries the bit to every install from there.
10
10
 
11
11
  ## Step 1: Resolve the package root
12
12
 
@@ -39,4 +39,4 @@ A flat capture stores the file in the wiki as `extensions/<type>/hypo-ext-<name>
39
39
 
40
40
  ## Step 4: Commit the wiki, then sync elsewhere
41
41
 
42
- Show the script output verbatim. When something was captured, remind the user to commit and push the wiki, then run `hypomnema upgrade --apply` on their other machine to install the captured extension under its original name.
42
+ Show the script output verbatim. When something was captured, remind the user to commit and push the wiki, then run `hypomnema upgrade --apply` (or `/hypo:upgrade` on a plugin install) on their other machine to install the captured extension under its original name.
@@ -20,8 +20,8 @@ If `/hypo:crystallize` was invoked to close a session (via an explicit close sig
20
20
  Before composing the payload (Step 2), run these four reflections and surface each to the user. Every one is **advisory** (identity guard): the user confirms or declines, and none performs an automatic action, writes a file on its own, or bypasses the mandatory gate.
21
21
 
22
22
  1. **Trivial-session check (#44)** — Was this session trivial (a single bug fix, a single-file edit, or Q&A with no durable artifact)? If so, recommend skipping session-close: *"이 세션은 trivial해 보입니다 — session-close를 건너뛸까요?"* and proceed only if the user wants a close. A trivial skip is a recommendation, **not** a bypass: it must not mark the session closed, must not run `--mark-session-closed`, and must not claim `/compact` can pass. Any real close still requires all 5 mandatory files.
23
- 2. **ADR-candidate check (#41)** — Did this session make an architectural or design decision (a new pattern, a tradeoff chosen, a convention established)? If yes, ask whether it warrants an ADR and, if so, capture that intent in the `sessionLog` entry you compose in Step 2. If nothing rose to ADR level, you may record `ADR 없음 — <one-line reason>` in that same `sessionLog` entry — but gate it on #42's bar: the marker is machine-read and W8 excludes an entry carrying `ADR 없음` (with no ADR reference) from the design-history staleness check. Write it only when the session had **no design change at all**; a sub-ADR design shift takes #42a (append) instead, since the marker would suppress the W8 nudge it needs. **Never auto-write an ADR file** — recording the decision (or its absence) in the session-log payload is the only action here.
24
- 3. **design-history staleness check (#42)** — Two branches, so a stale W8 never blocks a clean close: (a) if this session changed design decisions `projects/<name>/design-history.md` does not yet reflect — including sub-ADR background / tradeoff / differentiation shifts — recommend appending now (the W8 lint warning flags this mechanically; an active-project W8 hard-blocks at PreCompact — append before you commit). (b) only if the session made **no** design change does the `ADR 없음` marker (#41) exempt the entry from W8; do not touch design-history. `ADR 없음` means "no design change," a stricter bar than "no ADR-level decision." If the file does not exist, skip silently — do **not** create it just for this check. Never auto-update it.
23
+ 2. **ADR-candidate check (#41).** Did this session make an architectural or design decision (a new pattern, a tradeoff chosen, a convention established)? If yes, ask whether it warrants an ADR and, if so, capture that intent in the `sessionLog` entry you compose in Step 2. If nothing rose to ADR level, you may record `ADR 없음: <one-line reason>` in that same `sessionLog` entry, but gate it on #42's bar: the marker is machine-read and W8 excludes an entry carrying `ADR 없음` (with no ADR reference) from the design-history staleness check. Write it only when the session had **no design change at all**; a sub-ADR design shift takes #42a (append) instead, since the marker would suppress the W8 nudge it needs. **Never auto-write an ADR file.** Recording the decision (or its absence) in the session-log payload is the only action here. This check carries no `decisions/` directory precondition: run it whether or not that directory exists.
24
+ 3. **design-history staleness check (#42).** Two branches, so a stale W8 never blocks a clean close: (a) if this session changed design decisions `projects/<name>/design-history.md` does not yet reflect (including sub-ADR background, tradeoff, or differentiation shifts), recommend appending now: the W8 lint warning flags this mechanically, and an active-project W8 still counts as a `--check-session-close` blocker even though the PreCompact hook no longer stops `/compact` on it, so append before you commit. **If the file does not exist yet and this session had a design change, recommend creating it now** with that change as the first entry; lint separately flags a missing-but-needed file as W14, a warning that never blocks. (b) only if the session made **no** design change does the `ADR 없음` marker (#41) exempt the entry from W8; do not touch design-history, and do not create the file just to satisfy this branch's check. `ADR 없음` means "no design change," a stricter bar than "no ADR-level decision." Never auto-write the file yourself in either branch: recommend it, and let the user decide.
25
25
  4. **Ingest check (#43)** — Did this session consume trustworthy external knowledge (a fetched URL, official docs, or code you verified directly)? If so, recommend running `/hypo:ingest` to capture it under `sources/`. Proceed only on the user's confirmation.
26
26
 
27
27
  These are judgment calls; when uncertain, surface the question rather than skip it. None of the four blocks the close or writes on its own.
@@ -78,7 +78,7 @@ Content guidance for each slot:
78
78
 
79
79
  ## Step 3 — Apply the payload
80
80
 
81
- Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema upgrade --apply` or reinstall instead of guessing the cache layout.
81
+ Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema upgrade --apply` (or `/hypo:upgrade` on a plugin install) or reinstall instead of guessing the cache layout.
82
82
 
83
83
  ```bash
84
84
  node ${CLAUDE_PLUGIN_ROOT}/scripts/crystallize.mjs \
@@ -179,7 +179,7 @@ If the user says stop, end here. Otherwise continue to Step 5.
179
179
 
180
180
  ## Step 5 — Surface synthesis candidates
181
181
 
182
- Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema upgrade --apply` or reinstall instead of guessing the cache layout.
182
+ Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema upgrade --apply` (or `/hypo:upgrade` on a plugin install) or reinstall instead of guessing the cache layout.
183
183
 
184
184
  ```bash
185
185
  node ${CLAUDE_PLUGIN_ROOT}/scripts/crystallize.mjs [--hypo-dir="<path>"] [--min-group=2]
@@ -18,7 +18,7 @@ You are running `/hypo:doctor`. Verify the health of the current Hypomnema wiki
18
18
 
19
19
  ## Step 1 — Resolve Hypomnema directory
20
20
 
21
- Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema upgrade --apply` or reinstall instead of guessing the cache layout.
21
+ Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema upgrade --apply` (or `/hypo:upgrade` on a plugin install) or reinstall instead of guessing the cache layout.
22
22
 
23
23
  If the user specified a Hypomnema directory, pass it as `--hypo-dir="<path>"`. Otherwise omit the flag and the script resolves the Hypomnema root automatically: `HYPO_DIR` env → `hypo-config.md` scan → `~/hypomnema` default.
24
24
 
@@ -52,6 +52,15 @@ For any `✗` failures:
52
52
  - Missing Hypomnema root or required directories/files → run `/hypo:init`.
53
53
  - 0 hook files installed → run `/hypo:init`.
54
54
  - 0 hook registrations in settings.json → run `/hypo:init`.
55
+ - `Hook execution smoke test` failed → show the stderr excerpt in the detail
56
+ verbatim; it names the cause. The repair depends on the channel, and
57
+ `/hypo:init` is only right on one of them. If `Hook files installed` says
58
+ "provided by the plugin loader", the install is plugin-managed and
59
+ `/hypo:init` deliberately leaves core hooks alone: tell the user to
60
+ re-install the plugin. Otherwise (npm or manual install) `/hypo:init`
61
+ repairs an incomplete copy. When the excerpt is not a missing module, have
62
+ the user run the hook directly in their shell
63
+ (`echo '{}' | node <the path in the detail>`) and read the error there.
55
64
 
56
65
  For `⚠` warnings:
57
66
  - Missing `hypo-config.md` → run `/hypo:init` — it creates the config marker.
@@ -59,6 +68,11 @@ For `⚠` warnings:
59
68
  - Partial hook files (some missing) → run `/hypo:init` to install missing hooks.
60
69
  - Partial settings.json registrations → run `/hypo:init` to merge missing entries.
61
70
  - Missing git remote → `git -C <hypo-dir> remote add origin <url>`.
71
+ - `Installed cache vs marketplace HEAD` says the install is behind → the plugin
72
+ version string did not move, so the update left the old cache in place. Wait
73
+ for the next version bump, or swap the cache directory by hand knowing the
74
+ next real update overwrites it. If it says *ahead* instead, that is a local
75
+ checkout running in front of the marketplace and there is nothing to do.
62
76
  - Broken `[[links]]` → list the affected files and ask if the user wants to fix them now.
63
77
  - Overdue `verify_by_date` → offer to open the affected pages for review.
64
78
  - Missing `verify_by` question → suggest adding a `verify_by` field to the listed pages.
@@ -36,7 +36,7 @@ If **claude-learned** is among the targets, the page must be `scope: global` + `
36
36
 
37
37
  ## Step 2 — List existing feedback (optional)
38
38
 
39
- Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema upgrade --apply` or reinstall instead of guessing the cache layout.
39
+ Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema upgrade --apply` (or `/hypo:upgrade` on a plugin install) or reinstall instead of guessing the cache layout.
40
40
 
41
41
  To check for an existing topic, run:
42
42
 
package/commands/graph.md CHANGED
@@ -14,7 +14,7 @@ You are running `/hypo:graph`. Generate a link dependency graph from wiki pages.
14
14
 
15
15
  ## Step 1 — Run script
16
16
 
17
- Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema upgrade --apply` or reinstall instead of guessing the cache layout.
17
+ Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema upgrade --apply` (or `/hypo:upgrade` on a plugin install) or reinstall instead of guessing the cache layout.
18
18
 
19
19
  If the user specified a Hypomnema directory, pass it as `--hypo-dir="<path>"`. Otherwise omit the flag.
20
20
 
@@ -27,7 +27,7 @@ Do **not** fetch the URL or read the file yet — the privacy guard in Step 2 mu
27
27
 
28
28
  ## Step 2 — Privacy guard (`.hypoignore`)
29
29
 
30
- Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema upgrade --apply` or reinstall instead of guessing the cache layout.
30
+ Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema upgrade --apply` (or `/hypo:upgrade` on a plugin install) or reinstall instead of guessing the cache layout.
31
31
 
32
32
  Refuse to ingest secrets (`.env`, SSH keys, credentials) before they ever reach `sources/`. Run the guard for **both** the input path and the destination path:
33
33
 
package/commands/init.md CHANGED
@@ -43,7 +43,7 @@ Ask the following questions **one at a time**. Use the default if the user press
43
43
 
44
44
  ## Step 2a — From Remote (skip wizard) {#step-2a}
45
45
 
46
- Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema upgrade --apply` or reinstall instead of guessing the cache layout.
46
+ Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema upgrade --apply` (or `/hypo:upgrade` on a plugin install) or reinstall instead of guessing the cache layout.
47
47
 
48
48
  If the user provided `--from-remote <url>`, run:
49
49
 
@@ -64,7 +64,7 @@ node ${CLAUDE_PLUGIN_ROOT}/scripts/init.mjs \
64
64
 
65
65
  ## Step 2 — Run the init script (new wiki)
66
66
 
67
- Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema upgrade --apply` or reinstall instead of guessing the cache layout.
67
+ Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema upgrade --apply` (or `/hypo:upgrade` on a plugin install) or reinstall instead of guessing the cache layout.
68
68
  Then run:
69
69
 
70
70
  ```bash
package/commands/lint.md CHANGED
@@ -16,7 +16,7 @@ You are running `/hypo:lint`. Validate all wiki pages for frontmatter correctnes
16
16
 
17
17
  ## Step 1 — Run script
18
18
 
19
- Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema upgrade --apply` or reinstall instead of guessing the cache layout.
19
+ Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema upgrade --apply` (or `/hypo:upgrade` on a plugin install) or reinstall instead of guessing the cache layout.
20
20
 
21
21
  If the user specified a Hypomnema directory, pass it as `--hypo-dir="<path>"`. Otherwise omit the flag and the script resolves the Hypomnema root automatically via `HYPO_DIR` → `hypo-config.md` scan → `~/hypomnema`.
22
22
 
package/commands/query.md CHANGED
@@ -20,7 +20,7 @@ Ask the user what they want to know if it was not provided in the command invoca
20
20
 
21
21
  ## Step 2 — Search
22
22
 
23
- Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema upgrade --apply` or reinstall instead of guessing the cache layout.
23
+ Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema upgrade --apply` (or `/hypo:upgrade` on a plugin install) or reinstall instead of guessing the cache layout.
24
24
 
25
25
  Run full-text search:
26
26
 
@@ -16,7 +16,7 @@ You are running `/hypo:rename`. Move a page or directory and content-aware rewri
16
16
 
17
17
  ## Step 1 — Run script
18
18
 
19
- Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema upgrade --apply` or reinstall instead of guessing the cache layout.
19
+ Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema upgrade --apply` (or `/hypo:upgrade` on a plugin install) or reinstall instead of guessing the cache layout.
20
20
 
21
21
  If the user specified a Hypomnema directory, pass it as `--hypo-dir="<path>"`. Otherwise omit the flag.
22
22
 
@@ -20,7 +20,7 @@ If the user named a project in the command invocation, use that. Otherwise, run:
20
20
  node ${CLAUDE_PLUGIN_ROOT}/scripts/resume.mjs [--hypo-dir="<path>"] [--project=<name>]
21
21
  ```
22
22
 
23
- Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema upgrade --apply` or reinstall instead of guessing the cache layout.
23
+ Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema upgrade --apply` (or `/hypo:upgrade` on a plugin install) or reinstall instead of guessing the cache layout.
24
24
 
25
25
  When `--project` is omitted, the script prefers the project whose `working_dir` contains the current directory (cwd-first); if nothing under the current directory matches, it falls back to the most recently active project from `hot.md`.
26
26
 
package/commands/stats.md CHANGED
@@ -15,7 +15,7 @@ You are running `/hypo:stats`. Display a summary of wiki health and activity.
15
15
 
16
16
  ## Step 1 — Run script
17
17
 
18
- Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema upgrade --apply` or reinstall instead of guessing the cache layout.
18
+ Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema upgrade --apply` (or `/hypo:upgrade` on a plugin install) or reinstall instead of guessing the cache layout.
19
19
 
20
20
  If the user specified a Hypomnema directory, pass it as `--hypo-dir="<path>"`. Otherwise omit the flag.
21
21
 
@@ -8,19 +8,24 @@ You are running `/hypo:uninstall`. Remove Hypomnema from this machine.
8
8
 
9
9
  - Removes Hypomnema hook files from `~/.claude/hooks/` (and optionally `~/.codex/hooks/`)
10
10
  - Strips Hypomnema entries from `~/.claude/settings.json`, leaving all other hooks untouched
11
- - **Dry-run by default** — shows what would be removed without making any changes
11
+ - Removes the `claude()` shell function block from `~/.zshrc` and/or `~/.bashrc` (whichever carries the marker `init` wrote; use `--shell-config=<path>` to target a different file)
12
+ - Removes the marked pre-commit hook from the wiki repo (`<wiki>/.git/hooks/pre-commit`); resolve a non-default wiki with `--hypo-dir=<path>`
13
+ - Removes tracked slash commands, extension hard-copies, and `~/.claude/hypo-pkg.json` when nothing user-modified is left behind
14
+ - Every removal above is marker- or SHA-gated: a block or file you have since hand-edited is reported and left in place, never guessed at
15
+ - **The wiki content itself (pages, journal, sources under the vault root) is never deleted, only the git hook and shell block Hypomnema installed**
16
+ - **Dry-run by default**: shows what would be removed without making any changes
12
17
 
13
18
  ---
14
19
 
15
20
  ## Step 1 — Confirm intent
16
21
 
17
22
  Say:
18
- > "This will remove Hypomnema hooks from your system. Your wiki files are NOT deleted.
23
+ > "This will remove Hypomnema's hooks, slash commands, the claude() shell function block, and the wiki's pre-commit hook. Your wiki content (pages, journal, sources) is NOT deleted.
19
24
  > Run in dry-run mode first to preview changes? [yes]"
20
25
 
21
26
  Default: yes (dry-run first)
22
27
 
23
- Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema upgrade --apply` or reinstall instead of guessing the cache layout.
28
+ Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema upgrade --apply` (or `/hypo:upgrade` on a plugin install) or reinstall instead of guessing the cache layout.
24
29
 
25
30
  ---
26
31
 
@@ -44,11 +49,18 @@ If no → abort and confirm nothing was changed.
44
49
  node ${CLAUDE_PLUGIN_ROOT}/scripts/uninstall.mjs --apply
45
50
  ```
46
51
 
47
- If the user also wants Codex hooks removed, append `--codex`.
52
+ If the user also wants Codex hooks removed, append `--codex`. Other flags worth knowing about:
53
+ - `--hypo-dir=<path>`: the wiki whose pre-commit hook gets removed, if it is not the default-resolved one
54
+ - `--shell-config=<path>`: the single rc file to strip the shell block from, instead of checking both `~/.zshrc` and `~/.bashrc`
55
+ - `--force-commands` / `--force-extensions`: remove a slash command or extension file even if its content no longer matches what Hypomnema installed
56
+ - `--hooks-dir=<path>`: only redirects the `~/.claude/hooks/*.mjs` cleanup, so a run scoped to a sandbox hooks directory still touches the real shell rc files and wiki vault unless `--keep-shell` and/or `--keep-wiki-hook` are also passed. The script warns about this before it does anything if it detects the combination
57
+ - `--keep-shell`: skip the shell rc `claude()` block removal entirely
58
+ - `--keep-wiki-hook`: skip the wiki pre-commit hook removal entirely, including the step that resolves which vault it would have looked at
48
59
 
49
60
  ---
50
61
 
51
62
  ## Notes
52
63
 
53
- - Wiki content (`~/hypomnema/`) is never touched — only hook files and settings.json entries
64
+ - Wiki content under `~/hypomnema/` (pages, journal, sources, etc.) is never touched
65
+ - What IS removed by `--apply`: hook files, settings.json entries, the shell rc `claude()` block, and the wiki's own pre-commit hook
54
66
  - To reinstall, run `/hypo:init`
@@ -14,7 +14,7 @@ You are running `/hypo:upgrade`. Check if the installed Hypomnema wiki is out of
14
14
 
15
15
  ## Step 1 — Run script
16
16
 
17
- Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema upgrade --apply` or reinstall instead of guessing the cache layout.
17
+ Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema upgrade --apply` (or `/hypo:upgrade` on a plugin install) or reinstall instead of guessing the cache layout.
18
18
 
19
19
  If the user specified a Hypomnema directory, pass it as `--hypo-dir="<path>"`. Otherwise omit the flag.
20
20
 
@@ -28,7 +28,7 @@ node ${CLAUDE_PLUGIN_ROOT}/scripts/upgrade.mjs [--hypo-dir="<path>"]
28
28
 
29
29
  Show the output verbatim.
30
30
 
31
- > **Plugin installs**: if the output begins with `ℹ Plugin install detected`, the core hooks, slash commands, and `settings.json` wiring are managed by the Claude Code plugin loader — **not** by `/hypo:upgrade`. Do **not** run `--apply` expecting it to update them (it intentionally skips those to avoid double-registering every hook). To upgrade the plugin itself: `/plugin marketplace update hypomnema` then `/reload-plugins`. `--apply` in plugin mode applies vault-side migrations (SCHEMA, `.hypoignore`), refreshes package metadata, and still syncs any vault extensions — but does **not** install the core hooks/commands/settings (the plugin provides those).
31
+ > **Plugin installs**: if the output begins with `ℹ Plugin install detected`, the core hooks, slash commands, and `settings.json` wiring are managed by the Claude Code plugin loader — **not** by `/hypo:upgrade`. Do **not** run `--apply` expecting it to update them (it intentionally skips those to avoid double-registering every hook). To upgrade the plugin itself: `/plugin marketplace update hypomnema` then `/reload-plugins`. **Then run `--apply` anyway**: the vault's git pre-commit hook holds an absolute path baked in at init time, and reloading the plugin does not move it, so without that step every wiki commit keeps running the install you had when you first ran init. `--apply` in plugin mode applies vault-side migrations (SCHEMA, `.hypoignore`), refreshes package metadata, and still syncs any vault extensions — but does **not** install the core hooks/commands/settings (the plugin provides those).
32
32
 
33
33
  > **Note**: A major SCHEMA bump is only **detected** in this step. The informational `MIGRATION-vX.Y.md` file is written later by `--apply` (Step 4) and only on a major bump. `SCHEMA.md` is never auto-overwritten.
34
34
 
@@ -13,7 +13,7 @@ You are running `/hypo:verify`. Audit wiki pages for overdue or missing `verify_
13
13
 
14
14
  ## Step 1 — Run
15
15
 
16
- Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema upgrade --apply` or reinstall instead of guessing the cache layout.
16
+ Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema upgrade --apply` (or `/hypo:upgrade` on a plugin install) or reinstall instead of guessing the cache layout.
17
17
 
18
18
  ```bash
19
19
  node ${CLAUDE_PLUGIN_ROOT}/scripts/verify.mjs [--hypo-dir="<path>"] [--file=<path>]
@@ -117,7 +117,7 @@ Hooks run automatically at Claude Code lifecycle events. They are deployed to `~
117
117
  | `hypo-first-prompt` | Marker-based one-shot `hot.md` injection on first user prompt (10-min TTL) — for sessions that bypass `SessionStart` |
118
118
  | `hypo-lookup` | BM25 search over the wiki on every prompt. **HIT** → inject top-3 page snippets (≤2000 chars each; a page whose `verify_by_date` is overdue gets a `[STALE verify_by_date=…]` marker prepended). **MISS** → emit closest-slug signal that prompts Claude to research + `/hypo:ingest` |
119
119
  | `hypo-compact-guard` | Detect `/compact` invocations → enforce session-close checklist before allowing compact |
120
- | `hypo-personal-check` | PreCompact validation: lint blockers, uncommitted changes, missing session-log entries → block compact |
120
+ | `hypo-personal-check` | PreCompact detection: lint blockers, uncommitted changes, missing session-log entries surface as a `systemMessage`; `/compact` is never blocked here |
121
121
  | `hypo-auto-stage` | After Write/Edit on a wiki path, run `git add` (skips paths matching `.hypoignore`) |
122
122
  | `hypo-hot-rebuild` | At session stop, regenerate root `hot.md` from recent activity; emit growth metrics + cache for next SessionStart |
123
123
  | `hypo-session-record` | At session stop, append `{session_id, transcript_path, recorded_at, cwd, device}` to `.cache/sessions/index.jsonl` (primary source for the observability audit) |
@@ -218,7 +218,8 @@ Hooks inline this logic in `hypo-shared.mjs`. Scripts use `scripts/lib/hypo-root
218
218
  2. Creates the vault directory structure: `pages/`, `projects/`, `sources/`, `journal/{daily,weekly,monthly}/`.
219
219
  3. Copies `templates/` files: 6 root files (`hypo-config.md`, `index.md`, `hot.md`, `log.md`, `SCHEMA.md`, `hypo-guide.md`) + 4 helpers (`Home.md`, `Overview.md`, `hypo-automation.md`, `hypo-help.md`) + `pages/_index.md` + `projects/_template/`.
220
220
  4. Deploys 10 hooks to `~/.claude/hooks/` and merges entries into `~/.claude/settings.json` (idempotent; preserves non-hypo hooks).
221
- 5. Writes `~/.claude/hypo-pkg.json` with `pkgRoot` for upgrade tracking.
221
+ 5. Writes `~/.claude/hypo-pkg.json` with `pkgRoot` for upgrade tracking (skipped when the
222
+ plugin channel judgment fails; see `init.mjs`'s `resolveDurableRoot`).
222
223
  6. `git init` + first commit `init: hypomnema wiki`. Pushes to remote when one is provided.
223
224
 
224
225
  `--dry-run` previews; `--no-hooks` and `--no-git-init` are also supported. Re-running `init` is idempotent — existing files are skipped, never overwritten.
@@ -233,7 +234,7 @@ Hooks inline this logic in `hypo-shared.mjs`. Scripts use `scripts/lib/hypo-root
233
234
 
234
235
  ### `/hypo:uninstall`
235
236
 
236
- Removes hypo-prefixed hooks from `~/.claude/hooks/` and matching entries from `~/.claude/settings.json`. **Non-hypo hooks are preserved**. The wiki vault itself is never touched.
237
+ Removes hypo-prefixed hooks from `~/.claude/hooks/` and matching entries from `~/.claude/settings.json`. **Non-hypo hooks are preserved**. `--apply` also reaches past `~/.claude/`: it strips the marked `claude()` block from the shell rc file(s) init wrote to, and removes the marked pre-commit hook from the wiki's own git repo. Both removals are marker-gated the same way the hooks-dir cleanup is, so a hand-edited block or hook is left in place rather than guessed at.
237
238
 
238
239
  ---
239
240
 
@@ -448,7 +449,11 @@ does not matter.
448
449
 
449
450
  ## CI / Release
450
451
 
451
- ### `ci.yml` — 7 independent jobs
452
+ ### `ci.yml` — independent jobs
453
+
454
+ The table below is not exhaustive and the count is deliberately left out: it went
455
+ stale once already. `.github/workflows/ci.yml` is the source of truth for which
456
+ jobs exist.
452
457
 
453
458
  | Job | Purpose |
454
459
  |---|---|
@@ -120,7 +120,8 @@ If you need to share new logic, prefer extending an existing helper over adding
120
120
 
121
121
  ```bash
122
122
  npm test # tests/*.test.mjs, sharded across processes — unit + smoke + contract
123
- npm run lint # scripts/lint.mjs — frontmatter + wikilink validation + W8 (design-history stale vs session-log)
123
+ npm run lint # scripts/lint.mjs — frontmatter + wikilink validation + W8 (design-history stale
124
+ # vs session-log) + W14 (design-history missing but session-log implies one)
124
125
  npm run fix:verify # Phase 1 of learned_behavior #6 — verifies fix #N status claims in
125
126
  # a wiki spec against `// @fix #N: <test-name>` anchors, read as a
126
127
  # union across every tests/*.mjs. Maintainer dogfood; needs a wiki at
@@ -172,17 +173,19 @@ Some hook behavior is only observable inside a Claude Code session. Document the
172
173
 
173
174
  `npm install` in this checkout installs a git `pre-commit` hook that runs `prettier --write` on staged files only. The hook is **non-blocking**: formatter failures print a notice but the commit still proceeds. The only block is when `git add` itself fails during restage (true index corruption).
174
175
 
175
- **Requirements**: Git ≥ 2.13 (uses `--absolute-git-dir`; `--git-common-dir` is 2.5+).
176
+ **Requirements**: Git ≥ 2.13. The installed shell shim itself only calls `--git-common-dir` (2.5+), but the installer and `pre-commit-format.mjs`'s own identity guard still call `--absolute-git-dir`, which is what actually sets the floor.
176
177
 
177
178
  **Path-locked to your checkout.** The shim embeds the absolute paths of your `HYPOMNEMA_ROOT` and `.git/` directory at install time. If you `mv` the checkout, re-run `npm install` to regenerate the shim — until then it safely no-ops.
178
179
 
179
- **Main worktree only.** `git worktree add` checkouts silently skip — the shared `.git/hooks/pre-commit` can only point at one embedded root at a time. Commit from the main worktree to get auto-format, or accept the no-op in linked worktrees.
180
+ **Linked worktrees work too.** Both the shell shim and `pre-commit-format.mjs`'s own identity guard compare on `--git-common-dir`, not `--show-toplevel` or `--absolute-git-dir`. A linked worktree's toplevel and git dir differ from the main checkout's, but its common dir is still the same shared `.git`, so a commit made from a linked worktree gets the same auto-format and tracker-id gate as one made from the main checkout.
180
181
 
181
182
  **CI is skipped.** `npm ci` runs `prepare`, but the installer detects `CI=true` and exits 0 without touching `.git/hooks/`. CI runs never mutate hooks.
182
183
 
183
184
  **Symlink-safe.** If `.git/hooks/` is a symlink, or an existing `pre-commit` is a symlink, the installer refuses to write through it.
184
185
 
185
- **Shared `core.hooksPath` safe.** The shim verifies both `--show-toplevel` and `--absolute-git-dir` against the embedded values before executing. Foreign repos that share your global `core.hooksPath` will silently no-op.
186
+ **Shared `core.hooksPath` safe.** The shim verifies the live `--git-common-dir` against the embedded value before executing; `pre-commit-format.mjs` re-checks the same axis plus containment on `--absolute-git-dir` as a second, independent layer. Foreign repos that share your global `core.hooksPath` have their own, different common dir, so they still silently no-op.
187
+
188
+ **Reversed `GIT_DIR`/`GIT_WORK_TREE` mixes are also closed.** The common-dir check above proves the git dir Git reports is ours; it never looks at `--show-toplevel`. That leaves a reversed mix open: `GIT_DIR` pointed at one of your own linked-worktree admin dirs (genuinely ours) while `GIT_WORK_TREE` points at an unrelated repo. `pre-commit-format.mjs` closes it with a worktree-binding check: it reads the toplevel's own `.git` entry (a directory for a main checkout, a `gitdir:` pointer file for a linked worktree) and requires it to resolve back to the git dir Git reported. A foreign toplevel's `.git` always points at its own git dir, never at yours, so it fails there regardless of what `GIT_DIR`/`GIT_WORK_TREE` claim. The shell shim's own `--git-common-dir` check is a fast pre-filter only; this second axis is enforced in the Node script, not the shell.
186
189
 
187
190
  **Env-override defense.** The Node side strips every `GIT_*` env from `git rev-parse --local-env-vars` (plus `GIT_NAMESPACE`, `GIT_CEILING_DIRECTORIES`, `GIT_CONFIG_*`) before its own git spawns. Inherited `GIT_INDEX_FILE` is preserved **only** when invoked from the installed shell shim (signalled via a sentinel env var). Direct `node scripts/pre-commit-format.mjs` invocation drops `GIT_INDEX_FILE` and falls back to the default `.git/index`, closing the class of attacks that try to drive the formatter against a crafted alternate index.
188
191
 
@@ -244,7 +247,7 @@ After both blocks, language-neutral:
244
247
 
245
248
  - **PR title**: Conventional Commits plus a scope, e.g. `feat(feedback): add failure_type enum`. The type drives the CHANGELOG section (see the classification table below).
246
249
  - **Merge commit**: the squash-merge subject carries the PR number (`#123`). That is where `#N` comes from, not the PR title. The two conventions stay separate.
247
- - Internal tracker ids (`FEAT-`, `IMPR-`, `ISSUE-`, `PRAC-`, `fix #N`) may appear in your local notes and in `tests/` / `qa-runs/` (where they aid test-to-issue traceability and never reach an installed user), but not in shipped code or workflow comments, and never on the published changelog and release surface: not in the CHANGELOG body, not in the PR `## Changelog` block, not in a tag annotation, not in a GitHub Release. The only tracker identifier that ships in those is the PR number `#N`; the lone exception is the ADR carve-out noted below. `check-tracker-ids` gates the file, message, and tag surfaces (`--all`/`--staged` for files, `--commit-msg` for messages, `--tag` for the tag body), and `check-pr-surface` gates the PR title and body; the migration keeps the CHANGELOG body clean. `ADR NNNN` / `decisions/NNNN` are exempt on the changelog surfaces (the CHANGELOG body, the tag body, and the PR `## Changelog` block), where a release line legitimately cites the decision behind it.
250
+ - Internal tracker ids (`FEAT-`, `IMPR-`, `ISSUE-`, `PRAC-`, `fix #N`) may appear in your local notes and in `tests/` (where they aid test-to-issue traceability and never reach an installed user), but not in shipped code or workflow comments, and never on the published changelog and release surface: not in the CHANGELOG body, not in the PR `## Changelog` block, not in a tag annotation, not in a GitHub Release. The only tracker identifier that ships in those is the PR number `#N`; the lone exception is the ADR carve-out noted below. `check-tracker-ids` gates the file, message, and tag surfaces (`--all`/`--staged` for files, `--commit-msg` for messages, `--tag` for the tag body), and `check-pr-surface` gates the PR title and body; the migration keeps the CHANGELOG body clean. `ADR NNNN` / `decisions/NNNN` are exempt on the changelog surfaces (the CHANGELOG body, the tag body, and the PR `## Changelog` block), where a release line legitimately cites the decision behind it.
248
251
 
249
252
  ### The `## Changelog` block
250
253
 
@@ -326,6 +329,12 @@ in **both** the CHANGELOG section AND the git tag annotation. The release
326
329
  workflow enforces this with `scripts/check-bilingual.mjs`; a lightweight tag, or
327
330
  a gated CHANGELOG section missing its `#### 한국어` sub-block, will block `npm publish`.
328
331
 
332
+ A release is what makes a fix reachable, on both channels. The plugin installer names its
333
+ cache directory after the manifest version, so it skips the copy when that version has not
334
+ moved: commits merged to `main` under an unchanged version never reach an existing install.
335
+ "It is on main, so people have it" is false. Bump the version and cut the release, or the
336
+ work sits where nobody can run it.
337
+
329
338
  The READMEs are not part of a release. They describe what Hypomnema does now, not
330
339
  what each version added, so cutting a release never edits them. Version history
331
340
  lives in `CHANGELOG.md` alone. (A gate used to require the release version to
@@ -375,6 +384,7 @@ npm run check:bilingual # each gated CHANGELOG section has #### English + ###
375
384
  npm run smoke:plugin # plugin manifest + hooks/commands/skills load-valid
376
385
  npm run smoke-pack # packed tarball installs and resolves
377
386
  npm run check:tracker-ids # no private-tracker pointers leaked into shipped files
387
+ npm run format:check # every checked file matches Prettier's output
378
388
 
379
389
  # 4. Commit every file the bump touched, plus the lockfile.
380
390
  git add package.json package-lock.json .claude-plugin/ templates/hypo-config.md \