hypomnema 1.10.0 → 1.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +19 -0
- package/README.ko.md +2 -2
- package/README.md +2 -2
- package/commands/crystallize.md +3 -3
- package/commands/upgrade.md +10 -1
- package/docs/ARCHITECTURE.md +126 -62
- package/docs/CONTRIBUTING.md +7 -11
- package/hooks/hypo-shared.mjs +301 -65
- package/hooks/session-views.mjs +9 -3
- package/package.json +2 -1
- package/scripts/doctor.mjs +12 -1
- package/scripts/init.mjs +60 -22
- package/scripts/lib/crystallize-close-apply.mjs +82 -0
- package/scripts/lib/extensions.mjs +2 -2
- package/scripts/lib/git-hooks-dir.mjs +228 -31
- package/scripts/lib/plugin-detect.mjs +57 -4
- package/scripts/lib/settings-write.mjs +254 -0
- package/scripts/uninstall.mjs +3 -2
- package/scripts/upgrade.mjs +82 -21
- package/templates/Home.md +1 -1
- package/templates/hypo-config.md +1 -1
- package/templates/hypo-guide.md +7 -5
- package/templates/session-state.md +0 -12
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,25 @@ All notable changes to Hypomnema are documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.10.1] - 2026-10-27
|
|
9
|
+
|
|
10
|
+
### Bug Fixes
|
|
11
|
+
#### English
|
|
12
|
+
- The 1.10.0 migration no longer loses a `.gitignore` or staged change made during its window, keeps an earlier run's archive record on undo, and reports git's own error when it stops. The vault pre-commit hook now runs the enabled plugin's worker over a stale sidecar (`hypomnema upgrade --apply` rewrites older hooks). `settings.json` stays a symlink, keeps its mode and survives a failed write. init no longer pushes without the hook guard and no longer copies a root `session-state.md`. ([#320](https://github.com/sk-lim19f/Hypomnema/pull/320))
|
|
13
|
+
#### 한국어
|
|
14
|
+
- 1.10.0 이행이 그 사이에 생긴 `.gitignore` 변경과 stage 변경을 잃지 않고, 되돌릴 때 앞 실행의 보관 기록을 남기며, 멈추면 git 의 오류를 그대로 알립니다. 볼트 pre-commit 훅이 오래된 sidecar 대신 켜진 플러그인의 worker 를 돌립니다(옛 훅은 `hypomnema upgrade --apply` 가 새로 씁니다). `settings.json` 이 symlink 로 남고 mode 를 지키며 쓰기가 실패해도 온전합니다. init 이 훅 가드 없이 push 하지 않고 루트 `session-state.md` 를 복사하지 않습니다. ([#320](https://github.com/sk-lim19f/Hypomnema/pull/320))
|
|
15
|
+
|
|
16
|
+
### Chores
|
|
17
|
+
#### English
|
|
18
|
+
- The vault guide template is now version 4, and `hypomnema upgrade` reports an older guide until you merge it by hand and raise its `version:` stamp (`--apply` never overwrites it). The architecture and contributor guides now describe both install channels. ([#319](https://github.com/sk-lim19f/Hypomnema/pull/319))
|
|
19
|
+
#### 한국어
|
|
20
|
+
- 볼트 안내 템플릿이 버전 4 가 됐습니다. 예전 안내는 손으로 병합하고 `version:` 스탬프를 올릴 때까지 `hypomnema upgrade` 가 경고합니다(`--apply` 는 덮어쓰지 않습니다). 아키텍처 문서와 기여자 문서가 두 설치 채널을 설명합니다. ([#319](https://github.com/sk-lim19f/Hypomnema/pull/319))
|
|
21
|
+
|
|
22
|
+
### Changelog
|
|
23
|
+
- [#320](https://github.com/sk-lim19f/Hypomnema/pull/320) fix: keep local changes through the 1.10.0 migration, run the enabled plugin's hook worker, write settings.json through links
|
|
24
|
+
- [#319](https://github.com/sk-lim19f/Hypomnema/pull/319) docs: bring the architecture, contributor and vault guides in line with 1.10.0
|
|
25
|
+
Contributors: @sk-lim19f
|
|
26
|
+
|
|
8
27
|
## [1.10.0] - 2026-10-15
|
|
9
28
|
|
|
10
29
|
> [!IMPORTANT]
|
package/README.ko.md
CHANGED
|
@@ -76,7 +76,7 @@ hypomnema
|
|
|
76
76
|
|
|
77
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`로 우회하세요. 이 훅은 설치 경로를 박아 두지 않습니다. 커밋 시점에 `~/.claude/
|
|
79
|
+
> `init`은 위키 저장소에 pre-commit 훅도 설치합니다(`<위키>/.git/hooks/pre-commit`, `# hypo-managed:pre-commit:*` 마커). `.hypoignore`에 걸리는 파일이 스테이지되면 커밋을 막습니다. 해당 파일을 unstage 하거나 `git commit --no-verify`로 우회하세요. 이 훅은 설치 경로를 박아 두지 않습니다. 커밋 시점에 후보 둘을 봅니다. 하나는 `~/.claude/settings.json`에서 켜 둔 플러그인(그 플러그인의 `~/.claude/plugins/installed_plugins.json` 항목, user scope 우선)이고, 다른 하나는 `~/.claude/hypo-pkg.json`의 `pkgRoot`입니다. 둘 다 찾히면 버전이 더 높은 쪽을 쓰고, 같으면 플러그인을 씁니다. 둘 다 안 되면 레지스트리의 `hypo@hypomnema` 항목이나 옛 `hypomnema@hypomnema` 항목을 user scope 우선으로 봅니다. 후보로 인정하는 조건은 절대 경로이고, 그 디렉터리의 `package.json` 이름이 `hypomnema`이며 버전이 있고, 실제로 부를 스크립트가 거기 있는 것입니다. 이보다 느슨하면 당신이 쓰지 않은 `pkgRoot`가 위키에서 실행될 수 있습니다. 아무것도 못 찾으면 `.hypoignore` 가드를 조용히 건너뛰는 대신, 무엇을 확인했고 어떻게 고치는지 출력하며 커밋을 거부합니다. 이렇게 해석하는 값으로 단계마다 Node 기동이 한 번 더 들어가 약 80ms가 붙고, `--lint-strict` 위키는 커밋당 대략 165ms를 냅니다. 예전 릴리스가 설치한 훅은 여전히 버전 고정 형태(`init` 시점에 절대 설치 경로가 박히고, 옮기거나 재설치할 때마다 낡는 형태)일 수 있는데, `hypomnema upgrade --apply`(또는 플러그인 설치라면 `/hypo:upgrade`에서 apply 단계 확인)가 스스로 경로를 찾는 새 형태로 옮겨 줍니다. 박힌 경로가 디스크에 이미 없어도 마찬가지로 옮겨 줍니다. 1.8.4부터 1.10.0까지가 설치한 훅은 예전 순서(`pkgRoot` sidecar를 먼저 보는 순서라, 낡은 sidecar가 켜 둔 플러그인을 이깁니다)로 해석합니다. `doctor`가 이를 경고하고, 같은 `upgrade --apply`가 지금 순서로 다시 써 줍니다. 본문 자체를 알아볼 수 없는 훅(손으로 고쳤거나 손상된 경우)은 다릅니다. `doctor`와 `upgrade` 모두 손대지 않고 경고만 하며, 이때 고치는 명령은 `upgrade --apply`가 아니라 `hypomnema init --force-commands`(또는 플러그인의 `/hypo:init --force-commands`)로 가드를 다시 설치하는 것입니다. 패키지가 아니라 위키 자체를 옮겼다면 여전히 `hypomnema init`을 새로 돌려야 합니다. `upgrade`는 설치 루트 해석만 고치고, `--lint-strict` 훅에 이미 박힌 `--hypo-dir`은 건드리지 않기 때문입니다. `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
|
|
|
@@ -254,7 +254,7 @@ Hypomnema는 청크가 아니라 페이지를 지식 단위로 봅니다. 새
|
|
|
254
254
|
| `/hypo:graph` | 위키링크 의존성 그래프 생성 | 구조가 어떻게 자랐는지 보고 싶을 때 |
|
|
255
255
|
| `/hypo:rename` | 페이지·디렉터리 이름 변경 + 인바운드 `[[위키링크]]` 갱신 | 페이지나 프로젝트 폴더 이름을 바꿀 때 |
|
|
256
256
|
|
|
257
|
-
### 라이프사이클 훅 (
|
|
257
|
+
### 라이프사이클 훅 (12개)
|
|
258
258
|
|
|
259
259
|
| 훅 | 이벤트 | 역할 |
|
|
260
260
|
|---|---|---|
|
package/README.md
CHANGED
|
@@ -250,7 +250,7 @@ Nine commands cover the full capture → retrieval → consolidation cycle.
|
|
|
250
250
|
| `/hypo:graph` | Generates a wikilink dependency graph | When you want to see how the structure grew |
|
|
251
251
|
| `/hypo:rename` | Renames a page or directory and rewrites inbound `[[wikilinks]]` | When a page or project folder needs a new name |
|
|
252
252
|
|
|
253
|
-
### Lifecycle hooks (
|
|
253
|
+
### Lifecycle hooks (12)
|
|
254
254
|
|
|
255
255
|
| Hook | Event | Role |
|
|
256
256
|
|---|---|---|
|
|
@@ -406,7 +406,7 @@ Place a `hypo-config.md` at the wiki root to make it portable across machines wi
|
|
|
406
406
|
|
|
407
407
|
`.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.
|
|
408
408
|
|
|
409
|
-
> `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 does not bake in an install path: it resolves the active Hypomnema install itself, at commit time
|
|
409
|
+
> `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 does not bake in an install path: it resolves the active Hypomnema install itself, at commit time. It looks at two candidates: the plugin enabled in `~/.claude/settings.json` (that plugin's row in `~/.claude/plugins/installed_plugins.json`, user-scope first) and `~/.claude/hypo-pkg.json`'s `pkgRoot`. When both resolve, the newer version wins, and the plugin wins a tie. When neither does, it falls back to any `hypo@hypomnema` or legacy `hypomnema@hypomnema` registry entry, user-scope first. A candidate only counts when it is an absolute path whose `package.json` names `hypomnema` and carries a version, and whose script is actually there — anything looser and a `pkgRoot` you did not write could run in your vault. When nothing resolves, it refuses the commit and prints what it checked and how to fix it, rather than silently skip the `.hypoignore` guard. Resolving this way costs a second Node start per step, about 80ms, so a `--lint-strict` vault pays roughly 165ms per commit. A hook an older release wrote may still carry the version-pinned form (an absolute install path baked in at `init` time, which goes stale on every move or reinstall); `hypomnema upgrade --apply` (or `/hypo:upgrade`, confirming the apply step, on a plugin install) migrates it onto the self-resolving form, even when the baked-in path no longer exists on disk. A hook written by 1.8.4 through 1.10.0 resolves in the older order (the `pkgRoot` sidecar first, so a stale sidecar wins over the enabled plugin); `doctor` warns about it and the same `upgrade --apply` rewrites it onto the current order. A hook whose body cannot be recognized at all (hand-edited, corrupted) is different: `doctor` and `upgrade` both warn about it instead of touching it, and the fix is `hypomnema init --force-commands` (or the plugin's `/hypo:init --force-commands`) to reinstall the guard, not `upgrade --apply`. Moving the vault itself, not just the package, still needs a fresh `hypomnema init`: `upgrade` only ever fixes the install-root resolution, never a `--hypo-dir` a `--lint-strict` hook already baked in. `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).
|
|
410
410
|
|
|
411
411
|
> 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.
|
|
412
412
|
|
package/commands/crystallize.md
CHANGED
|
@@ -63,8 +63,8 @@ Optional fields, which stay out of the JSON template above:
|
|
|
63
63
|
|
|
64
64
|
- `date`: defaults to today (local). If supplied, it must be a real calendar date in `YYYY-MM-DD` form; a day that does not exist, such as `2026-09-31`, fails the apply before any write.
|
|
65
65
|
- `new: true` on a track element starts a track the project has not registered yet. An unregistered id without it is refused (`track-unknown`, and the error lists the registered ids). `new: true` on a registered id is refused as well (`track-exists`).
|
|
66
|
-
- `done: true` on a track element ends the track. It may be sent without a body: `{ "id": "<track-id>", "done": true }`. It ends only the heads this session knew about (the ones it was shown in full or as a pointer line). A head another session wrote to the same track at the same time stays active. To end that one too, read that entry first, then send `done` again in a later close.
|
|
67
|
-
- `supersedes` on a track element names the earlier heads of that track this update replaces. Leave it out and the update replaces every head of the track this session was told about. When you name them, use a full close id as shown in the injection or the view, or a prefix that is unique within that track. A close id that does not exist, or an ambiguous prefix, is refused (`supersedes-unknown`).
|
|
66
|
+
- `done: true` on a track element ends the track. It may be sent without a body: `{ "id": "<track-id>", "done": true }`. It ends only the heads this session knew about (the ones it was shown in full or as a pointer line). The baseline heads made from the old `session-state.md` that this session was shown before the vault moved to session entries count too. A head another session wrote to the same track at the same time stays active. To end that one too, read that entry first, then send `done` again in a later close.
|
|
67
|
+
- `supersedes` on a track element names the earlier heads of that track this update replaces. Leave it out and the update replaces every head of the track this session was told about (baseline heads made from the old `session-state.md` it was shown before the vault moved included). When you name them, use a full close id as shown in the injection or the view, or a prefix that is unique within that track. A close id that does not exist, or an ambiguous prefix, is refused (`supersedes-unknown`).
|
|
68
68
|
- Never put the same track id in `tracks` twice (`track-duplicate`). A normal update plus a separate `done` for one track is the usual way to hit it: send one element for that track instead.
|
|
69
69
|
- `log.entry`: a custom line for `<hypo-root>/log.md`. Omit it by default: apply derives the canonical `## [date] session | <project>` line from your `sessionLog` heading. Supply it only for a custom line, which must still be a canonical `session | <project>` heading, or the apply fails at `stage='pre-apply-verification'`.
|
|
70
70
|
- `openQuestions.content`: the full body of `pages/open-questions.md`. Include it only when this session actually changed that file. It is the one overwrite left in a close, so it goes through the base protection and the approval path described below and in Step 4 (`proposal-pending`).
|
|
@@ -214,7 +214,7 @@ The result JSON of `--apply-session-close` includes a `stage` field when `ok: fa
|
|
|
214
214
|
| `track-duplicate` | The same track id appears in `tracks` more than once (typically a normal update plus a separate `done` for one track). Caught **before** any write. | Merge the elements into one per track id, then re-run. No payload bytes were written. |
|
|
215
215
|
| `track-unknown` | A `tracks` element names an id the project has no track for and does not carry `new: true`. The `error` lists the registered ids. Caught **before** any write. | If it is a typo, use the registered id. If it is a new track, add `"new": true`. Re-run. Nothing was written. |
|
|
216
216
|
| `track-exists` | A `tracks` element carries `new: true` for an id the project already has. Caught **before** any write. | Drop `"new": true` to update the track (or pick another id for a new one), then re-run. Nothing was written. |
|
|
217
|
-
| `supersedes-unknown` | A `supersedes` value is not a close id of an update of that track, or is a prefix that matches more than one. The `error` lists the track's updates. Caught **before** any write. | Use a full close id or a prefix unique within that track, or drop `supersedes` to take the default (every head this session was told about), then re-run. Nothing was written. |
|
|
217
|
+
| `supersedes-unknown` | A `supersedes` value is not a close id of an update of that track, or is a prefix that matches more than one. The `error` lists the track's updates. Caught **before** any write. | Use a full close id or a prefix unique within that track, or drop `supersedes` to take the default (every head this session was told about, baseline heads made from the old `session-state.md` it was shown before the vault moved included), then re-run. Nothing was written. |
|
|
218
218
|
| `payload-reserved-marker` | The summary or a track body holds a line that is exactly an entry marker of this close (the whole line, carrying this close's id). Caught **before** any write. | Change or remove that line, then re-run. Other HTML comments and similar text do not trip it. Nothing was written. |
|
|
219
219
|
| `invalid-entry` | The entry could not be formatted for a reason other than a marker or a duplicate track: a value that does not fit the entry format (for example a malformed session id or project name). The `error` names it. Caught **before** any write. | Fix the value the `error` names, then re-run. Nothing was written. |
|
|
220
220
|
| `entry-project-mismatch` | This close request already published its session entry in another project (or recorded the intent to), and this payload names a different `project`, typically a retry after a failed commit with the project changed. One close request writes one entry in one project. The `error` names the existing entry and its project. Caught **before** any write; `committed: null`. | Re-run with `project` set to the project the `error` names, which finishes that close. Close the other project afterwards, with a new close request. |
|
package/commands/upgrade.md
CHANGED
|
@@ -32,7 +32,7 @@ node ${CLAUDE_PLUGIN_ROOT}/scripts/upgrade.mjs [--hypo-dir="<path>"]
|
|
|
32
32
|
|
|
33
33
|
Show the output verbatim.
|
|
34
34
|
|
|
35
|
-
> **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**:
|
|
35
|
+
> **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**: hooks written by 1.8.4 through 1.10.0 read the sidecar before the enabled plugin, and older ones bake an install path, so reloading the plugin does not move them. `--apply` rewrites both onto the current resolver. `--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).
|
|
36
36
|
|
|
37
37
|
> **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.
|
|
38
38
|
|
|
@@ -44,6 +44,15 @@ Show the output verbatim.
|
|
|
44
44
|
- `⚠` — minor update available (stale hook or missing settings entry)
|
|
45
45
|
- `✗` — major version bump or missing hook files (action required)
|
|
46
46
|
|
|
47
|
+
If a `⚠ hypo-guide.md` line shows a version change (`vN → vM`) or says the installed copy has no version stamp, `--apply` will not fix it: it never overwrites the vault's guide. Tell the user to finish it by hand, and offer to do the merge. A line that says the installed copy is ahead of the package, or that the versions cannot be compared, is not drift: leave the stamp alone and only report it.
|
|
48
|
+
|
|
49
|
+
1. Run the `diff` command printed on that line to compare the package template with the vault's `hypo-guide.md`.
|
|
50
|
+
2. Merge the changed parts into the vault copy and keep the user's own edits.
|
|
51
|
+
3. Set `version:` in the vault copy's frontmatter to the package version shown on that line.
|
|
52
|
+
4. Run the check again. The warning clears.
|
|
53
|
+
|
|
54
|
+
The stamp only records that the template was reviewed. It does not prove the contents match, so do not raise it before the merge is done.
|
|
55
|
+
|
|
47
56
|
For a **major SCHEMA bump**: warn the user that `--apply` will additionally write a `MIGRATION-vX.Y.md` informational file in their Hypomnema root and that they must manually merge the SCHEMA diff after applying.
|
|
48
57
|
|
|
49
58
|
---
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Hypomnema — Architecture
|
|
2
2
|
|
|
3
|
-
> Internal reference for
|
|
3
|
+
> Internal reference for the current release. For the user-facing pitch, see [README.md](../README.md).
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -18,10 +18,12 @@ Hypomnema is a Claude Code extension with three runtime layers stacked on a plai
|
|
|
18
18
|
│ ↓ writes / reads │
|
|
19
19
|
│ Wiki vault (markdown + git) │
|
|
20
20
|
│ ↑ reads / stages / commits │
|
|
21
|
-
│ Lifecycle hooks
|
|
21
|
+
│ Lifecycle hooks (background automation) │
|
|
22
22
|
│ ├──► SessionStart, UserPromptSubmit, PreCompact, │
|
|
23
|
-
│ │ PostToolUse, Stop,
|
|
24
|
-
│
|
|
23
|
+
│ │ PreToolUse, PostToolUse, Stop, SessionEnd, │
|
|
24
|
+
│ │ CwdChanged, FileChanged │
|
|
25
|
+
│ └──► hypo-shared.mjs + other (copied beside the hooks, │
|
|
26
|
+
│ shared modules no imports outside the dir) │
|
|
25
27
|
└──────────────────────────────────────────────────────────────┘
|
|
26
28
|
```
|
|
27
29
|
|
|
@@ -34,36 +36,38 @@ The three layers are deliberately **decoupled by file artifacts**. Slash command
|
|
|
34
36
|
```
|
|
35
37
|
hypomnema/
|
|
36
38
|
├── commands/ ← slash command definitions (.md prompts)
|
|
37
|
-
├── hooks/ ← lifecycle hooks
|
|
38
|
-
│ ├── hypo-*.mjs ←
|
|
39
|
-
│ ├── hypo-shared.mjs ← shared utilities,
|
|
40
|
-
│
|
|
39
|
+
├── hooks/ ← lifecycle hooks (copied to ~/.claude/hooks/ on the npm channel)
|
|
40
|
+
│ ├── hypo-*.mjs ← hook entry points and modules (see Hook registry below)
|
|
41
|
+
│ ├── hypo-shared.mjs ← shared utilities, copied beside the hooks
|
|
42
|
+
│ ├── hooks.json ← source of truth for the event to hook map
|
|
43
|
+
│ └── shared.json ← source of truth for the shared module list
|
|
41
44
|
├── scripts/ ← Node.js implementations
|
|
42
45
|
│ ├── init.mjs / upgrade.mjs / uninstall.mjs / doctor.mjs
|
|
43
46
|
│ ├── ingest.mjs / query.mjs / lint.mjs / verify.mjs / graph.mjs / stats.mjs
|
|
44
|
-
│ ├── crystallize.mjs / resume.mjs / feedback.mjs
|
|
45
|
-
│ ├──
|
|
46
|
-
│
|
|
47
|
-
│
|
|
48
|
-
│ ├── hypo-root.mjs
|
|
49
|
-
│ └── hypo-ignore.mjs
|
|
47
|
+
│ ├── crystallize.mjs / resume.mjs / feedback.mjs / feedback-sync.mjs
|
|
48
|
+
│ ├── capture.mjs / rename.mjs / proposal.mjs / session-entries.mjs
|
|
49
|
+
│ ├── session-audit.mjs / weekly-report.mjs
|
|
50
|
+
│ └── lib/ ← helpers that scripts import (shipped set: `files` in package.json; one hook, hypo-pre-commit.mjs, also imports from here)
|
|
50
51
|
├── skills/ ← directory-form Agent Skills (skills/<name>/SKILL.md)
|
|
51
52
|
├── templates/ ← baseline files copied on init
|
|
52
|
-
│ ├── hypo-config.md, index.md,
|
|
53
|
+
│ ├── hypo-config.md, index.md, log.md, SCHEMA.md, hypo-guide.md
|
|
53
54
|
│ ├── Home.md, Overview.md, hypo-automation.md, hypo-help.md
|
|
54
|
-
│ ├──
|
|
55
|
+
│ ├── .hypoignore, .hyposcanignore, gitignore, gitattributes
|
|
56
|
+
│ ├── pages/_index.md, pages/observability/_index.md
|
|
55
57
|
│ └── projects/_template/
|
|
56
58
|
├── tests/
|
|
57
59
|
│ ├── harness.mjs ← test()/testAsync()/suite(), selection, reporting
|
|
58
60
|
│ ├── helpers.mjs ← fixtures shared by more than one area file
|
|
59
61
|
│ ├── runner.mjs ← entry: imports every *.test.mjs, owns no tests
|
|
60
62
|
│ ├── parallel.mjs ← runs the entry as N processes (`npm test`)
|
|
61
|
-
│ └── <area>.test.mjs ← one file per production area
|
|
63
|
+
│ └── <area>.test.mjs ← one file per production area
|
|
62
64
|
├── docs/ ← ARCHITECTURE.md, CONTRIBUTING.md
|
|
63
65
|
├── .claude-plugin/plugin.json← plugin manifest
|
|
64
66
|
└── package.json ← npm metadata, no runtime deps
|
|
65
67
|
```
|
|
66
68
|
|
|
69
|
+
On the npm channel, which `scripts/` files ship is decided by the `files` list in `package.json`, which names each one. Anything under `scripts/` that is not on that list is maintainer tooling (release checks, the git-hook installer, and similar) and is absent from the npm package. A product script left off `files` is missing from the npm package only. A hook helper left off `hooks/shared.json` breaks in every install that copies the hooks and runs the copy (the npm channel, a checkout deployed with init or upgrade, and `--codex` copies in `~/.codex/hooks`). It still works where hooks run in place: the plugin channel and a checkout run directly. The plugin channel ships the whole tracked tree instead (see the "What ships" row under Install channels). The entry points above are the ones a user runs.
|
|
70
|
+
|
|
67
71
|
The package has **zero npm runtime dependencies**. Everything is Node.js built-ins (`fs`, `path`, `os`, `child_process`, `crypto`).
|
|
68
72
|
|
|
69
73
|
---
|
|
@@ -77,7 +81,7 @@ Each user-facing operation is a pair: an LLM-facing prompt + a Node.js script.
|
|
|
77
81
|
| `commands/<name>.md` | Prompt definition — instructions Claude follows when the command is invoked |
|
|
78
82
|
| `scripts/<name>.mjs` | Script implementation — Node.js logic the prompt may call via Bash |
|
|
79
83
|
|
|
80
|
-
**Commands
|
|
84
|
+
**Commands** (one file each under `commands/`): `init`, `doctor`, `upgrade`, `uninstall`, `ingest`, `query`, `crystallize`, `resume`, `feedback`, `verify`, `lint`, `stats`, `graph`, `audit`, `capture`, `rename`.
|
|
81
85
|
|
|
82
86
|
Every one of those files is itself an Agent Skill; see below.
|
|
83
87
|
|
|
@@ -107,18 +111,22 @@ won every one of them. `scripts/smoke-plugin.mjs` now fails the build on a colli
|
|
|
107
111
|
|
|
108
112
|
## Lifecycle hooks
|
|
109
113
|
|
|
110
|
-
Hooks run automatically at Claude Code lifecycle events.
|
|
114
|
+
Hooks run automatically at Claude Code lifecycle events. On the plugin channel the harness runs them straight from the plugin directory. On the npm channel `init` / `upgrade` copy them to `~/.claude/hooks/` and register them in `~/.claude/settings.json` (see Install channels below).
|
|
111
115
|
|
|
112
116
|
### Hook registry (`hooks/hooks.json`)
|
|
113
117
|
|
|
114
|
-
`hooks.json` is the **single source of truth** for hook ↔ event mapping. `init`, `upgrade`, and `doctor` all read from it; the previous v1.0 design that maintained three separate `HOOK_MAP` constants was deleted in v1.1. A `git post-commit` hook re-applies
|
|
118
|
+
`hooks.json` is the **single source of truth** for hook ↔ event mapping. `init`, `upgrade`, and `doctor` all read from it; the previous v1.0 design that maintained three separate `HOOK_MAP` constants was deleted in v1.1. A `git post-commit` hook in a checkout re-applies the upgrade, but only when the last commit changed something under `hooks/`.
|
|
119
|
+
|
|
120
|
+
Counts are left out on purpose, because they go stale: `hooks/hooks.json` lists the events and registered handlers, and `hooks/shared.json` lists the modules copied beside them. At the time of writing that is nine events and twelve handlers.
|
|
115
121
|
|
|
116
122
|
| Event | Hooks (in order) |
|
|
117
123
|
|---|---|
|
|
124
|
+
| `PreToolUse` | `hypo-close-guard.mjs` |
|
|
118
125
|
| `SessionStart` | `hypo-session-start.mjs` |
|
|
126
|
+
| `SessionEnd` | `hypo-session-end.mjs` |
|
|
119
127
|
| `UserPromptSubmit` | `hypo-first-prompt.mjs` → `hypo-lookup.mjs` → `hypo-compact-guard.mjs` |
|
|
120
128
|
| `PreCompact` | `hypo-personal-check.mjs` |
|
|
121
|
-
| `PostToolUse` (
|
|
129
|
+
| `PostToolUse` (no matcher in `hooks.json`; each hook filters on `tool_name` itself) | `hypo-auto-stage.mjs` (Write/Edit/MultiEdit) → `hypo-web-fetch-ingest.mjs` (WebFetch/WebSearch) |
|
|
122
130
|
| `Stop` | `hypo-stop.mjs` (runs `hypo-hot-rebuild.mjs` → `hypo-session-record.mjs` → `hypo-auto-commit.mjs` → `hypo-auto-minimal-crystallize.mjs` as ordered stages, not separate registrations) |
|
|
123
131
|
| `CwdChanged` | `hypo-cwd-change.mjs` |
|
|
124
132
|
| `FileChanged` | `hypo-file-watch.mjs` |
|
|
@@ -128,11 +136,14 @@ Hooks run automatically at Claude Code lifecycle events. They are deployed to `~
|
|
|
128
136
|
| Hook | Responsibility |
|
|
129
137
|
|---|---|
|
|
130
138
|
| `hypo-session-start` | Run `git pull --ff-only` (silent fail on missing remote), rebuild the generated session views from `projects/*/sessions/` (not on a vault with a remote that has not moved yet, which gets a notice instead), inject `index.md`, root `hot.md`, project `hot.md`/`session-state.md` |
|
|
139
|
+
| `hypo-close-guard` | `PreToolUse`: denies a direct Write/Edit/MultiEdit to a generated session view (root `hot.md`, a project's `hot.md` or `session-state.md`) or to a session entry under `projects/<p>/sessions/`. Writes made through Bash are out of its scope, and the regular close apply runs through Bash |
|
|
140
|
+
| `hypo-session-end` | `SessionEnd`: on `reason=clear` only, writes `.cache/clear-marker.json` so the next `SessionStart` (`source=clear`) can issue a recovery nudge. It also records the close-gate resolution in `.cache/close-gate/<session-id>.json` (the close gate is the check that a substantial session has been closed; `/clear` ends the session, so it counts as resolving the gate). That record is best effort: it is written only when the session's transcript resolves, and a failure never blocks `/clear`. Other exit reasons write nothing |
|
|
141
|
+
| `hypo-web-fetch-ingest` | `PostToolUse`: after a WebFetch or WebSearch, injects a nudge to run `/hypo:ingest` (URL reduced to origin and path). It is a signal only and never ingests anything itself |
|
|
131
142
|
| `hypo-first-prompt` | Reads the marker left by `hypo-session-start` or `hypo-cwd-change` and forces a one-line resume on the first user prompt (10-min TTL). It does not re-read or re-inject `hot.md` |
|
|
132
143
|
| `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` |
|
|
133
144
|
| `hypo-compact-guard` | Detect `/compact` or `/clear` typed in chat and, if session close is incomplete, tell Claude so. It never blocks `/compact` |
|
|
134
145
|
| `hypo-personal-check` | PreCompact detection: lint blockers, uncommitted changes, missing session-log entries surface as a `systemMessage`; `/compact` is never blocked here |
|
|
135
|
-
| `hypo-auto-stage` | After Write/Edit on a wiki path, run `git add` (skips paths matching `.hypoignore`, and every path while a merge is open) |
|
|
146
|
+
| `hypo-auto-stage` | After Write/Edit/MultiEdit on a wiki path, run `git add` (skips paths matching `.hypoignore`, and every path while a merge is open) |
|
|
136
147
|
| `hypo-hot-rebuild` | At session stop, regenerate the generated session views (root `hot.md`, project `hot.md` and `session-state.md`; the same generator `hypo-session-start` also calls); emit growth metrics + cache for next SessionStart. A scan failure (not just an empty `projects/`) skips the write and leaves a health notice for the next `SessionStart` to surface |
|
|
137
148
|
| `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) |
|
|
138
149
|
| `hypo-auto-commit` | At session stop, filter changed paths through `.hypoignore`, finish a merge Hypomnema left open, commit non-ignored changes, `git pull --no-rebase` + `git push` (silent fail on missing remote; the push is skipped after a pull that did not land, and when the tree tracks generated views under a migrated upstream) |
|
|
@@ -141,12 +152,12 @@ Hooks run automatically at Claude Code lifecycle events. They are deployed to `~
|
|
|
141
152
|
|
|
142
153
|
### Deployment constraint
|
|
143
154
|
|
|
144
|
-
|
|
155
|
+
On the npm channel hooks run from `~/.claude/hooks/`, an isolated directory. They **cannot import from anything outside their own directory**; sibling modules in that directory are fine. Therefore:
|
|
145
156
|
|
|
146
|
-
- Shared hook logic lives in the modules `hooks/shared.json` declares, not only in `hypo-shared.mjs`. That file (a plain JSON array of `.mjs` basenames) is the source of truth for the list
|
|
147
|
-
- Hook utilities use **only Node.js built-ins**
|
|
157
|
+
- Shared hook logic lives in the modules `hooks/shared.json` declares, not only in `hypo-shared.mjs`. That file (a plain JSON array of `.mjs` basenames) is the source of truth for the list. Each listed file is copied next to the hooks at deploy time, not inlined into them. It used to be a `shared` field inside `hooks.json` itself; it moved to this sibling file because the harness started warning on that unknown top-level key once it began validating `hooks.json` against its own hook-registration schema.
|
|
158
|
+
- Hook utilities use **only Node.js built-ins** and sibling modules: no imports outside the hooks directory, no npm dependencies.
|
|
148
159
|
|
|
149
|
-
Scripts in `scripts/` are not
|
|
160
|
+
Scripts in `scripts/` are not copied anywhere. They run from the package install path, so they can import from `scripts/lib/`, and they may import from `hooks/`. The reverse is not allowed for any file that gets copied to `~/.claude/hooks` (anything in `hooks.json` or `shared.json`): a copied hook that imported from `scripts/` would resolve in a checkout and on the plugin channel, where hooks run in place, and fail with `ERR_MODULE_NOT_FOUND` wherever the hooks are copied (the npm channel, a checkout deployed with init or upgrade, and `--codex` copies in `~/.codex/hooks`). The one exception is `hooks/hypo-pre-commit.mjs`. It is a git hook worker that runs from the install root and is never copied, so it imports `../scripts/lib/hypo-ignore.mjs`.
|
|
150
161
|
|
|
151
162
|
---
|
|
152
163
|
|
|
@@ -156,26 +167,36 @@ Scripts in `scripts/` are not deployed — they run from the package install pat
|
|
|
156
167
|
|---|---|
|
|
157
168
|
| `HYPO_DIR` | Resolved wiki root path |
|
|
158
169
|
| `PKG_ROOT` | Package install path, read from `~/.claude/hypo-pkg.json` |
|
|
159
|
-
| `resolveHypoRoot()` | `HYPO_DIR` env → `hypo-config.md`
|
|
170
|
+
| `resolveHypoRoot()` | `HYPO_DIR` env → `hypo-config.md` in a fixed list of candidate directories (see Configuration resolution) → `~/hypomnema` fallback |
|
|
160
171
|
| `loadHypoIgnore()` | Parse `.hypoignore` into a pattern list |
|
|
161
172
|
| `isIgnored(path)` | Test a path against `.hypoignore` patterns |
|
|
162
173
|
| `hypoIsClean()` | Check git status + unpushed commits |
|
|
163
174
|
| `hotMdIsClean()` | Validate `hot.md` structure |
|
|
164
175
|
| `isCompactCommand(prompt)` | Detect `/compact` invocations |
|
|
165
176
|
| `buildOutput(hookEventName, context, extra)` | Build hook output that nests `additionalContext` under `hookSpecificOutput`, keeping control fields as top-level siblings. The event name is required: Claude Code drops a payload whose `hookEventName` does not match the firing event |
|
|
166
|
-
| `SESSION_STATE_NEXT_HEADINGS` | Allowed headings for "next tasks"
|
|
177
|
+
| `SESSION_STATE_NEXT_HEADINGS` | Allowed headings for "next tasks": `## 다음 이어받기`, `## 다음 작업`, `## Next Up`, `## Next`. Lint reuses this constant (DRY) |
|
|
178
|
+
|
|
179
|
+
`hypo-shared.mjs` is one of the shared modules. The full list is `hooks/shared.json`; besides the session-close stores and the update-check modules it includes `session-entries.mjs`, which holds the session entry format and the renderers, and `session-views.mjs`, which loads the entries and writes the generated views.
|
|
167
180
|
|
|
168
181
|
---
|
|
169
182
|
|
|
170
183
|
## Scripts lib (`scripts/lib/`)
|
|
171
184
|
|
|
172
|
-
Helpers used by command scripts only.
|
|
185
|
+
Helpers used by command scripts. They are not copied beside the hooks. The only hook that imports from here is `hypo-pre-commit.mjs`, a git hook worker that runs from the install root (see Deployment constraint). On the npm channel the shipped set is the `scripts/lib/` entries in `package.json`'s `files`. The main ones:
|
|
173
186
|
|
|
174
187
|
| File | Purpose |
|
|
175
188
|
|---|---|
|
|
176
189
|
| `frontmatter.mjs` | Parse and serialize YAML frontmatter |
|
|
177
190
|
| `hypo-root.mjs` | Wiki root resolution (mirrors hook logic, allows imports) |
|
|
178
191
|
| `hypo-ignore.mjs` | `.hypoignore` parsing (mirrors hook logic, allows imports) |
|
|
192
|
+
| `project-create.mjs` | Creates a project: `index.md`, `prd.md`, `decisions/`, `session-log/` |
|
|
193
|
+
| `crystallize-close-*.mjs` | The session-close check, gate and apply behind `/hypo:crystallize` |
|
|
194
|
+
| `extensions.mjs` | Syncs the vault's `extensions/` into `~/.claude` |
|
|
195
|
+
| `plugin-detect.mjs` | Detects whether the plugin is also enabled, so the npm path does not register every hook twice |
|
|
196
|
+
| `wikilink.mjs`, `slug-resolver.mjs` | Wikilink parsing and slug resolution |
|
|
197
|
+
| `rename-marker.mjs` | The crash-recovery marker `rename.mjs --apply` writes and `doctor.mjs` reads |
|
|
198
|
+
|
|
199
|
+
The session entry format and the generated views are not here. `hooks/session-entries.mjs` and `hooks/session-views.mjs` own them, because hooks need them too and scripts may import from `hooks/` but not the other way round. `scripts/session-entries.mjs` is the command-line entry for them (`rollback` and `render`; the move to entries is `/hypo:upgrade --migrate-sessions`).
|
|
179
200
|
|
|
180
201
|
---
|
|
181
202
|
|
|
@@ -190,10 +211,20 @@ Helpers used by command scripts only. Not deployed to hooks.
|
|
|
190
211
|
├── SCHEMA.md ← type system reference
|
|
191
212
|
├── hypo-guide.md ← operations guide
|
|
192
213
|
├── .hypoignore ← glob patterns the hooks must ignore
|
|
214
|
+
├── .hyposcanignore ← glob patterns excluded from catalog scans only (not a privacy boundary)
|
|
215
|
+
├── .gitignore ← written by init (generated views, .cache/)
|
|
216
|
+
├── .gitattributes ← written by init
|
|
217
|
+
├── extensions/ ← your own agents/commands/hooks/skills, mirrored to ~/.claude
|
|
218
|
+
│ └── {agents,commands,hooks,skills}/
|
|
193
219
|
├── pages/ ← permanent knowledge pages
|
|
194
|
-
│
|
|
220
|
+
│ ├── feedback/ ← AI behavior corrections
|
|
221
|
+
│ └── observability/ ← _index.md (the autonomy score definition)
|
|
195
222
|
├── projects/ ← per-project artifacts
|
|
223
|
+
│ ├── _template/ ← scaffold: index.md, prd.md, decisions/, session-log/
|
|
196
224
|
│ └── <name>/
|
|
225
|
+
│ ├── index.md ← overview (working_dir: field)
|
|
226
|
+
│ ├── prd.md ← purpose / success criteria
|
|
227
|
+
│ ├── decisions/ ← ADRs (created with the project)
|
|
197
228
|
│ ├── sessions/ ← one entry per session close (source of truth, never deleted)
|
|
198
229
|
│ ├── notes.md ← notes every session shares (hand-written, optional)
|
|
199
230
|
│ ├── hot.md ← recent session summaries (generated, not committed)
|
|
@@ -204,10 +235,11 @@ Helpers used by command scripts only. Not deployed to hooks.
|
|
|
204
235
|
│ ├── daily/
|
|
205
236
|
│ ├── weekly/
|
|
206
237
|
│ └── monthly/
|
|
207
|
-
|
|
238
|
+
├── sources/ ← raw ingested sources (append-only, never edit)
|
|
239
|
+
└── .cache/ ← runtime state (gitignored): session index, markers, backups
|
|
208
240
|
```
|
|
209
241
|
|
|
210
|
-
`hypo-config.md` presence is the wiki root marker. `resolveHypoRoot()`
|
|
242
|
+
`hypo-config.md` presence is the wiki root marker. `resolveHypoRoot()` checks a fixed list of candidate directories under `$HOME` for it (it does not walk up from the cwd).
|
|
211
243
|
|
|
212
244
|
---
|
|
213
245
|
|
|
@@ -216,36 +248,57 @@ Helpers used by command scripts only. Not deployed to hooks.
|
|
|
216
248
|
The wiki path is resolved in this order:
|
|
217
249
|
|
|
218
250
|
1. **`HYPO_DIR` environment variable** — explicit override
|
|
219
|
-
2. **`hypo-config.md` scan
|
|
220
|
-
3. **`~/hypomnema
|
|
251
|
+
2. **`hypo-config.md` scan**: the first of these directories that holds a `hypo-config.md` wins: `~/hypomnema`, `~/wiki`, `~/notes`, `~/knowledge`, `~/Documents/hypomnema`, `~/Documents/wiki`, and, in scripts only, `~/Documents/notes`
|
|
252
|
+
3. **`~/hypomnema`**: default fallback
|
|
221
253
|
|
|
222
|
-
Hooks
|
|
254
|
+
Hooks carry this logic in `hypo-shared.mjs`. Scripts use `scripts/lib/hypo-root.mjs`, which is importable but not identical: its candidate list has the extra `~/Documents/notes` entry that the hook copy lacks.
|
|
223
255
|
|
|
224
256
|
`PKG_ROOT` is stored in `~/.claude/hypo-pkg.json` by `init`. `hypo-personal-check.mjs` uses it to locate the lint script (`PKG_ROOT ?? HYPO_DIR`); `upgrade --apply` detects drift and refreshes it.
|
|
225
257
|
|
|
226
258
|
---
|
|
227
259
|
|
|
260
|
+
## Install channels
|
|
261
|
+
|
|
262
|
+
Hypomnema reaches a machine through two channels, and they deploy hooks differently.
|
|
263
|
+
|
|
264
|
+
| | Plugin channel | npm channel |
|
|
265
|
+
|---|---|---|
|
|
266
|
+
| Who runs the hooks | The harness, straight from `${CLAUDE_PLUGIN_ROOT}/hooks` as `hooks/hooks.json` says | `~/.claude/hooks/`, registered in `~/.claude/settings.json` |
|
|
267
|
+
| What ships | The whole git-tracked tree of that commit. Observed 2026-10-15 in the plugin cache (`~/.claude/plugins/cache/hypomnema/hypo/1.10.0/`): it holds `scripts/bump-version.mjs`, `scripts/lib/check-tracker-ids.mjs` and `tests/` | The `package.json` `files` allowlist, which names each shipped `scripts/` file one by one. `npm run check:pack-surface` compares the tarball against a committed snapshot |
|
|
268
|
+
| Deploy step | None for the Claude hooks. `--codex` still copies hooks to `~/.codex/hooks` | `init` and `upgrade` copy the hooks and shared modules to `~/.claude/hooks`, then merge `settings.json` |
|
|
269
|
+
| How an update arrives | The installer caches each version in a directory named after `plugin.json`'s `version` string. If the version has not changed, the directory already exists and new commits never reach the installed copy | `npm install -g hypomnema` fetches the new package, then `upgrade --apply` copies again |
|
|
270
|
+
|
|
271
|
+
The consequence for releases: on the plugin channel a fix reaches nobody until the version string moves. `init` and `upgrade` detect an enabled plugin and skip the copy and the `settings.json` merge, because doing both would fire every hook twice. In the steps below, each step is marked `[both]`, `[npm only]`, or `[plugin: skipped]`.
|
|
272
|
+
|
|
228
273
|
## Init and upgrade flow
|
|
229
274
|
|
|
230
275
|
### `/hypo:init`
|
|
231
276
|
|
|
232
|
-
1. Wizard collects: wiki path, hook scope (`~/.claude` and/or `~/.codex`), git remote URL.
|
|
233
|
-
2. Creates the vault directory structure: `pages/`, `projects/`, `sources/`, `journal/{daily,weekly,monthly}
|
|
234
|
-
3. Copies `templates/` files:
|
|
235
|
-
4.
|
|
236
|
-
5. Writes `~/.claude/hypo-pkg.json` with `pkgRoot` for upgrade tracking (skipped when the
|
|
277
|
+
1. `[both]` Wizard collects: wiki path, hook scope (`~/.claude` and/or `~/.codex`), git remote URL.
|
|
278
|
+
2. `[both]` Creates the vault directory structure: `pages/`, `pages/observability/`, `projects/`, `sources/`, `journal/{daily,weekly,monthly}/`, and `extensions/{hooks,commands,skills,agents}/` (each with a `.gitkeep`).
|
|
279
|
+
3. `[both]` Copies `templates/` files: `index.md`, a generated root `hot.md`, `log.md`, `SCHEMA.md`, `hypo-guide.md`, the helpers `Home.md`, `Overview.md`, `hypo-help.md`, `hypo-automation.md`, `pages/_index.md`, `pages/observability/_index.md`, and `projects/_template/` (`index.md`, `prd.md`, `decisions/`, `session-log/`). It then writes `hypo-config.md`, `.hypoignore`, `.hyposcanignore`, `.gitignore` and `.gitattributes`.
|
|
280
|
+
4. `[npm only]` Copies the hooks and shared modules to `~/.claude/hooks/`, installs the commands under `~/.claude/commands/hypo/`, and merges entries into `~/.claude/settings.json` (idempotent; preserves non-hypo hooks). `[plugin: skipped]` when the plugin is enabled.
|
|
281
|
+
5. `[both]` Writes `~/.claude/hypo-pkg.json` with `pkgRoot` for upgrade tracking (skipped when the
|
|
237
282
|
plugin channel judgment fails; see `init.mjs`'s `resolveDurableRoot`).
|
|
238
|
-
6. `
|
|
283
|
+
6. `[both]` Syncs the vault's `extensions/` into `~/.claude` (with hooks enabled), and into `~/.codex` for `--codex`. The codex mirror covers hooks and commands only; skills and agents are skipped.
|
|
284
|
+
7. `[both]` Installs the `claude()` shell function into the shell rc file (unless shell setup is turned off).
|
|
285
|
+
8. `[both]` With `--codex`, copies the hooks to `~/.codex/hooks` and merges `~/.codex/settings.json`. The plugin does not provide this surface.
|
|
286
|
+
9. `[both]` `git init` for the vault.
|
|
287
|
+
10. `[both]` Installs the vault's pre-commit hook, which guards `.hypoignore`. It is skipped when the channel judgment fails.
|
|
288
|
+
11. `[both]` Installs a post-commit hook in the package checkout that re-syncs the hooks after a commit there that changed files under `hooks/`. It is skipped when the package is not a git checkout, which is the case for npm and plugin installs.
|
|
289
|
+
12. `[both]` First commit `chore: init hypomnema (<date>)`. Pushes to remote when one is provided.
|
|
239
290
|
|
|
240
291
|
`--dry-run` previews; `--no-hooks` and `--no-git-init` are also supported. Re-running `init` is idempotent — existing files are skipped, never overwritten.
|
|
241
292
|
|
|
242
293
|
### `/hypo:upgrade`
|
|
243
294
|
|
|
244
|
-
1. Reads `pkgRoot` from `~/.claude/hypo-pkg.json`.
|
|
245
|
-
2. Re-deploys hooks from the current package version.
|
|
246
|
-
3. Re-merges `settings.json` entries.
|
|
247
|
-
4. Detects drift (e.g., `pkgRoot` no longer exists) and refreshes the pointer.
|
|
248
|
-
5.
|
|
295
|
+
1. `[both]` Reads `pkgRoot` from `~/.claude/hypo-pkg.json`.
|
|
296
|
+
2. `[npm only]` Re-deploys hooks from the current package version.
|
|
297
|
+
3. `[npm only]` Re-merges `settings.json` entries.
|
|
298
|
+
4. `[both]` Detects drift (e.g., `pkgRoot` no longer exists) and refreshes the pointer.
|
|
299
|
+
5. `[both]` Re-copies hooks for `--codex` and syncs vault extensions. Only the Claude core surface is skipped on the plugin channel.
|
|
300
|
+
6. `[both]` The check reports `hypo-guide.md` drift (a vault guide whose stamp is older than the package template's, or that has no stamp) as a warning and a non-zero exit, and `--apply` never overwrites the vault's guide. A stamp ahead of the package, or one that cannot be compared, is a warning without the non-zero exit. To clear drift, run the `diff` command the check prints against the package template, merge the changed parts by hand while keeping your own edits, raise `version:` in the vault guide's frontmatter to the package value, and run the check again. The stamp records that you reviewed the template. It does not prove the contents match.
|
|
301
|
+
7. `--json` output emits `{ schema, hooks, settings, applied }`. `--dry-run` previews; `--apply` commits changes.
|
|
249
302
|
|
|
250
303
|
The SessionStart hook (`hooks/hypo-session-start.mjs`) is a third writer of this file, outside init/upgrade entirely. `hooks/hypo-shared.mjs`'s `pkgRootDriftStatus` detects drift by comparing the canonicalized `pkgRoot` alone, not `pkgVersion`, so a file whose `pkgRoot` already matches self-location reads as `match` even when its `pkgVersion` is stale. Only a `pkgRoot` mismatch triggers the rewrite (temp file, then rename), and `pkgVersion` is carried along in that same write rather than checked or fixed on its own. The rewrite is guarded by a lockfile, so two sessions self-locating to different install roots at once cannot race each other's write, and it writes nothing on a downgrade (self-location resolves to an older version than the file already records), an incoming version that fails to parse as semver, a lock it could not acquire, or an opted-out session.
|
|
251
304
|
|
|
@@ -304,8 +357,12 @@ SessionStart
|
|
|
304
357
|
│ ├─► hypo-lookup.mjs (BM25 inject)
|
|
305
358
|
│ └─► hypo-compact-guard.mjs (report incomplete checklist; never blocks /compact)
|
|
306
359
|
│
|
|
307
|
-
├─►
|
|
308
|
-
│ └─► hypo-
|
|
360
|
+
├─► PreToolUse
|
|
361
|
+
│ └─► hypo-close-guard.mjs (denies a direct write to a generated view or a session entry)
|
|
362
|
+
│
|
|
363
|
+
├─► PostToolUse (no matcher; each hook filters on tool_name)
|
|
364
|
+
│ ├─► hypo-auto-stage.mjs (Write/Edit/MultiEdit: git add)
|
|
365
|
+
│ └─► hypo-web-fetch-ingest.mjs (WebFetch/WebSearch: nudge toward /hypo:ingest, never ingests)
|
|
309
366
|
│
|
|
310
367
|
├─► PreCompact
|
|
311
368
|
│ └─► hypo-personal-check.mjs (lint + session-close gate)
|
|
@@ -313,12 +370,18 @@ SessionStart
|
|
|
313
370
|
├─► CwdChanged
|
|
314
371
|
│ └─► hypo-cwd-change.mjs (builds a hot.md notice on systemMessage; the reference documents the terminal notification, tracing the installed binary as of Claude Code 2.1.276, checked 2026-09-18 and reconfirmed on 2.1.283, shows it does not reach the model, and no live-session observation exists either way; see the hook table above)
|
|
315
372
|
│
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
373
|
+
├─► FileChanged
|
|
374
|
+
│ └─► hypo-file-watch.mjs (builds a notice for a changed wiki file; no trigger on a stock install, treated as not reaching the model; see the hook table above)
|
|
375
|
+
│
|
|
376
|
+
├─► Stop
|
|
377
|
+
│ └─► hypo-stop.mjs, running four stages in order:
|
|
378
|
+
│ ├─► hypo-hot-rebuild.mjs (regenerate the session views + growth cache; SessionStart also calls this generator)
|
|
379
|
+
│ ├─► hypo-session-record.mjs (append .cache/sessions/index.jsonl)
|
|
380
|
+
│ ├─► hypo-auto-commit.mjs (.hypoignore-filtered stage + commit + pull + push)
|
|
381
|
+
│ └─► hypo-auto-minimal-crystallize.mjs (blocks Stop if session close never ran)
|
|
382
|
+
│
|
|
383
|
+
└─► SessionEnd
|
|
384
|
+
└─► hypo-session-end.mjs (on /clear only: writes .cache/clear-marker.json for the next SessionStart, and the close-gate resolution record .cache/close-gate/<session-id>.json)
|
|
322
385
|
```
|
|
323
386
|
|
|
324
387
|
Root hot.md's frontmatter `updated:` is the max of every project row date, and a row's date is the
|
|
@@ -481,7 +544,7 @@ tests/
|
|
|
481
544
|
├── helpers.mjs ← fixtures used by more than one area file
|
|
482
545
|
├── runner.mjs ← the entry: imports every *.test.mjs, owns no tests itself
|
|
483
546
|
├── parallel.mjs ← runs the entry as N concurrent processes (`npm test`)
|
|
484
|
-
└── <area>.test.mjs ← one file per production area
|
|
547
|
+
└── <area>.test.mjs ← one file per production area
|
|
485
548
|
```
|
|
486
549
|
|
|
487
550
|
Tests live next to the thing they test: `extensions.test.mjs`, `lint.test.mjs`,
|
|
@@ -493,9 +556,10 @@ Everything uses Node built-ins only. Fixtures are built in code with scoped help
|
|
|
493
556
|
|
|
494
557
|
**Selection is by suite, never by test.** `suite(label)` opens a selection unit, and
|
|
495
558
|
`--shard=i/n` keeps a whole suite in one process, in order. So tests inside one suite may build
|
|
496
|
-
on each other, and suites may not build on each other. `node tests/parallel.mjs --shards
|
|
497
|
-
runs every suite alone in a fresh process and
|
|
498
|
-
you add or split
|
|
559
|
+
on each other, and suites may not build on each other. `node tests/parallel.mjs --shards=<n>`,
|
|
560
|
+
with `n` at or above the current `suite()` count, runs every suite alone in a fresh process and
|
|
561
|
+
is the proof of that invariant. A smaller `n` stops proving it. Run it whenever you add or split
|
|
562
|
+
a suite.
|
|
499
563
|
|
|
500
564
|
| Command | What it does |
|
|
501
565
|
|---|---|
|
|
@@ -504,11 +568,11 @@ you add or split a suite.
|
|
|
504
568
|
| `node tests/runner.mjs --grep=<regex>` | just the matching tests, in seconds |
|
|
505
569
|
| `node tests/runner.mjs --file=lint` | one area file |
|
|
506
570
|
| `node tests/parallel.mjs --by-file` | one process per area file |
|
|
507
|
-
| `node tests/parallel.mjs --shards
|
|
571
|
+
| `node tests/parallel.mjs --shards=<n>` | every suite alone, with `n` at or above the `suite()` count: the order-independence proof |
|
|
508
572
|
|
|
509
573
|
Round-robin is the default rather than one-process-per-file, and the reason is measured, not
|
|
510
574
|
assumed: on a 2026 laptop the whole suite runs in about 70s round-robined and about 100s
|
|
511
|
-
`--by-file`. The areas differ by an order of magnitude in size, and `--by-file` starts
|
|
575
|
+
`--by-file`. The areas differ by an order of magnitude in size, and `--by-file` starts every area
|
|
512
576
|
at once, which oversubscribes the cores. The big files (`extensions`, `proposal-base`,
|
|
513
577
|
`close-global`) then finish last under contention while the small ones exit in seconds and
|
|
514
578
|
leave the tail of the run half idle. Reach for `--by-file` to read one area's output, not to go
|
|
@@ -572,7 +636,7 @@ Requires `NPM_TOKEN` secret.
|
|
|
572
636
|
{
|
|
573
637
|
"type": "command",
|
|
574
638
|
"command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/hypo-<name>.mjs",
|
|
575
|
-
"timeout": <
|
|
639
|
+
"timeout": <seconds>
|
|
576
640
|
}
|
|
577
641
|
]
|
|
578
642
|
}
|
|
@@ -587,7 +651,7 @@ The shared-file list (helpers a registered hook imports, such as `hypo-shared.mj
|
|
|
587
651
|
["hypo-shared.mjs", "version-check.mjs", "..."]
|
|
588
652
|
```
|
|
589
653
|
|
|
590
|
-
`hooks/hooks.json` is still the source of truth for the event-to-hook map; `hooks/shared.json` is the source of truth for the shared-file list. A hook registered in the former, or a helper listed in the latter, is what init/upgrade
|
|
654
|
+
`hooks/hooks.json` is still the source of truth for the event-to-hook map; `hooks/shared.json` is the source of truth for the shared-file list. A hook registered in the former, or a helper listed in the latter, is what init/upgrade copy on the npm channel. A hook left off the former never fires on the plugin channel either, because the harness reads that file. A helper left off the latter still resolves on the plugin channel, where the whole tree is present, and fails with `ERR_MODULE_NOT_FOUND` only in installs that copy the hooks (npm, a checkout deployed with init or upgrade, `--codex` copies) (see the "What ships" row under Install channels).
|
|
591
655
|
|
|
592
656
|
`claude plugin validate .` is run as part of CI's plugin-snapshot checks.
|
|
593
657
|
|