hypomnema 1.4.1 → 1.5.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 (69) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.ko.md +1 -1
  4. package/README.md +1 -1
  5. package/commands/audit.md +1 -1
  6. package/commands/crystallize.md +11 -9
  7. package/commands/doctor.md +1 -1
  8. package/commands/feedback.md +2 -2
  9. package/commands/graph.md +1 -1
  10. package/commands/ingest.md +1 -1
  11. package/commands/init.md +2 -2
  12. package/commands/lint.md +1 -1
  13. package/commands/query.md +1 -1
  14. package/commands/rename.md +1 -1
  15. package/commands/resume.md +1 -1
  16. package/commands/stats.md +1 -1
  17. package/commands/uninstall.md +4 -2
  18. package/commands/upgrade.md +1 -1
  19. package/commands/verify.md +1 -1
  20. package/docs/ARCHITECTURE.md +2 -2
  21. package/docs/CONTRIBUTING.md +20 -12
  22. package/hooks/hypo-auto-commit.mjs +2 -2
  23. package/hooks/hypo-auto-minimal-crystallize.mjs +30 -18
  24. package/hooks/hypo-compact-guard.mjs +2 -2
  25. package/hooks/hypo-cwd-change.mjs +27 -37
  26. package/hooks/hypo-personal-check.mjs +37 -22
  27. package/hooks/hypo-session-end.mjs +1 -1
  28. package/hooks/hypo-session-record.mjs +2 -2
  29. package/hooks/hypo-session-start.mjs +34 -40
  30. package/hooks/hypo-shared.mjs +364 -82
  31. package/hooks/version-check.mjs +1 -1
  32. package/package.json +5 -1
  33. package/scripts/check-tracker-ids.mjs +69 -31
  34. package/scripts/crystallize.mjs +322 -70
  35. package/scripts/doctor.mjs +7 -7
  36. package/scripts/feedback-sync.mjs +5 -5
  37. package/scripts/feedback.mjs +9 -10
  38. package/scripts/init.mjs +7 -7
  39. package/scripts/lib/check-tracker-ids.mjs +90 -32
  40. package/scripts/lib/design-history-stale.mjs +1 -1
  41. package/scripts/lib/extensions.mjs +7 -7
  42. package/scripts/lib/failure-type.mjs +1 -1
  43. package/scripts/lib/feedback-scope.mjs +1 -1
  44. package/scripts/lib/project-create.mjs +25 -7
  45. package/scripts/lib/schema-vocab.mjs +105 -1
  46. package/scripts/lib/template-schema-version.mjs +1 -1
  47. package/scripts/lib/wd-match.mjs +181 -0
  48. package/scripts/lib/wikilink.mjs +1 -1
  49. package/scripts/lint.mjs +14 -6
  50. package/scripts/rename.mjs +1 -1
  51. package/scripts/resume.mjs +20 -22
  52. package/scripts/session-audit.mjs +1 -1
  53. package/scripts/stats.mjs +3 -3
  54. package/scripts/uninstall.mjs +3 -3
  55. package/scripts/upgrade.mjs +17 -18
  56. package/skills/crystallize/SKILL.md +16 -8
  57. package/skills/graph/SKILL.md +2 -2
  58. package/skills/ingest/SKILL.md +4 -4
  59. package/skills/lint/SKILL.md +2 -2
  60. package/skills/query/SKILL.md +2 -2
  61. package/skills/verify/SKILL.md +2 -2
  62. package/templates/SCHEMA.md +12 -4
  63. package/templates/hypo-config.md +1 -1
  64. package/templates/hypo-guide.md +24 -15
  65. package/templates/projects/_template/hot.md +1 -1
  66. package/scripts/fix-status-verify.mjs +0 -256
  67. package/scripts/lib/adr-corpus.mjs +0 -79
  68. package/scripts/lib/fix-manifest.mjs +0 -109
  69. package/scripts/lib/fix-status-verify.mjs +0 -439
@@ -11,7 +11,7 @@
11
11
  "name": "hypo",
12
12
  "source": "./",
13
13
  "description": "LLM-native personal wiki — session-aware knowledge base for Claude Code",
14
- "version": "1.4.1",
14
+ "version": "1.5.0",
15
15
  "homepage": "https://github.com/sk-lim19f/Hypomnema"
16
16
  }
17
17
  ]
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hypo",
3
- "version": "1.4.1",
3
+ "version": "1.5.0",
4
4
  "description": "LLM-native personal wiki system — session-aware knowledge base for Claude Code",
5
5
  "author": {
6
6
  "name": "sk-lim19f",
package/README.ko.md CHANGED
@@ -25,7 +25,7 @@ Andrej Karpathy의 "LLM 네이티브 위키" 스케치에서 출발했습니다.
25
25
 
26
26
  ### 지금 어디까지 자동인가
27
27
 
28
- 현재 릴리스는 v1.4.1입니다. 위키 작업(자료 정리·검색·세션 마무리)은 아직 `/hypo:*` 명령어를 직접 입력해 시작합니다. v2의 목표는 Claude가 시키지 않아도 위키를 읽고 쓰고 합성하는 완전 자율 동작이고, 지금은 그쪽으로 가는 중입니다. v1.1.0이 위키가 한 세션에서 얼마나 쓰였는지 재는 관측성 점수를 넣었습니다. v1.2.0은 자동으로 동작하는 영역 네 가지를 추가했습니다.
28
+ 현재 릴리스는 v1.5.0이고, 머신 간 안정성과 일상의 마찰로 초점을 옮겼습니다: git 동기화로 여러 머신이 공유하는 볼트에서 `cwd`-first resume가 올바른 프로젝트를 찾고, 코드 레포에서 작업하는 세션이 매번 위키를 다시 찾는 대신 위키 위치를 안내받으며, 세션 마무리가 무관한 lint 부채를 다시 나열하거나 수백 개 경고를 컨텍스트에 쏟지 않습니다. 그 앞 v1.4.x는 세션 마무리 경로를 견고하게 다듬었습니다. 위키 작업(자료 정리·검색·세션 마무리)은 아직 `/hypo:*` 명령어를 직접 입력해 시작합니다. v2의 목표는 Claude가 시키지 않아도 위키를 읽고 쓰고 합성하는 완전 자율 동작이고, 지금은 그쪽으로 가는 중입니다. v1.1.0이 위키가 한 세션에서 얼마나 쓰였는지 재는 관측성 점수를 넣었습니다. v1.2.0은 자동으로 동작하는 영역 네 가지를 추가했습니다.
29
29
 
30
30
  - 피드백 한 곳만 고치면 나머지는 따라옵니다. `pages/feedback/`에 한 번 적으면 위키가 `MEMORY.md`와 `~/.claude/CLAUDE.md`의 `<learned_behaviors>` 블록을 자동으로 갱신합니다(단방향 projection).
31
31
  - 확장 파일도 함께 동기화. 위키 안 `~/hypomnema/extensions/{agents,commands,hooks,skills}/`에 둔 파일을 `~/.claude/`에 자동 반영합니다. `--codex`를 붙이면 `hooks`·`commands`는 `~/.codex/`에도 반영하지만, `agents`와 `skills`는 Claude 전용이라 건너뜁니다.
package/README.md CHANGED
@@ -25,7 +25,7 @@ New to the terms below? Keep the [Term decoder](#term-decoder) open in another t
25
25
 
26
26
  ### Where automation stands today
27
27
 
28
- The current release is v1.4.1. Wiki work (ingest, query, session-close) still runs on explicit `/hypo:*` commands. The v2 goal is full autonomy: Claude reading, writing, and synthesizing the wiki without being asked, which is the direction this is heading. v1.1.0 shipped the observability score that measures how often the wiki is actually used per session. v1.2.0 added four autonomous lanes on top:
28
+ The current release is v1.5.0, which turns to cross-machine reliability and everyday friction: `cwd`-first resume now finds the right project on a git-synced vault shared across machines, a session working in a code repo is told where the wiki lives instead of re-discovering it each time, and session close stops re-listing unrelated lint debt or dumping hundreds of warnings into context. The v1.4.x line hardened the session-close path before it. Wiki work (ingest, query, session-close) still runs on explicit `/hypo:*` commands. The v2 goal is full autonomy: Claude reading, writing, and synthesizing the wiki without being asked, which is the direction this is heading. v1.1.0 shipped the observability score that measures how often the wiki is actually used per session. v1.2.0 added four autonomous lanes on top:
29
29
 
30
30
  - Edit feedback in one place and the rest follows. `pages/feedback/` is the single source of truth for behavior corrections, and Hypomnema derives `MEMORY.md` and the `<learned_behaviors>` block inside `~/.claude/CLAUDE.md` from it automatically.
31
31
  - Extensions sync alongside. Anything under `~/hypomnema/extensions/{agents,commands,hooks,skills}/` is mirrored into `~/.claude/` automatically. The `--codex` flag also mirrors `hooks` and `commands` into `~/.codex/`; `agents` and `skills` are Claude-only and skipped on purpose.
package/commands/audit.md CHANGED
@@ -16,7 +16,7 @@ Definition: [[pages/observability/_index]].
16
16
 
17
17
  ## Step 1 — Run script
18
18
 
19
- The script path below resolves via `${CLAUDE_PLUGIN_ROOT}`, which the plugin harness expands to this package's absolute path before you see it, so run it as written. If it appears unexpanded (a literal `${CLAUDE_PLUGIN_ROOT}`), read the package root from the `hypo@hypomnema` installPath in `~/.claude/plugins/installed_plugins.json` rather than guessing from 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 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
 
@@ -17,7 +17,7 @@ If `/hypo:crystallize` was invoked to close a session (via an explicit close sig
17
17
 
18
18
  ## Step 1a — Session-close advisory reflections
19
19
 
20
- Before composing the payload (Step 2), run these four reflections and surface each to the user. Every one is **advisory** (ADR 0029 identity guard) the user confirms or declines, and none performs an automatic action, writes a file on its own, or bypasses the mandatory gate.
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
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.
@@ -30,9 +30,9 @@ These are judgment calls; when uncertain, surface the question rather than skip
30
30
 
31
31
  ## Step 2 — Compose the session-close payload
32
32
 
33
- The session-close path is **payload-driven**. Instead of writing the 5 mandatory files one-by-one, you compose a single JSON payload that describes the full session-close state, then hand it to `crystallize.mjs --apply-session-close`, which performs idempotent atomic writes and gates the result with lint.
33
+ The session-close path is **payload-driven**. Instead of writing the 5 mandatory files one-by-one, you compose a single JSON payload that describes the full session-close state, then hand it to the `--apply-session-close` apply path (the Step 3 command), which performs idempotent atomic writes and gates the result with lint.
34
34
 
35
- Payload shape (5 required + 1 conditional, per Spec §5.2.7 / §8.3 + ADR 0029):
35
+ Payload shape (`project` + 5 content fields required, 1 conditional, per Spec §5.2.7 / §8.3):
36
36
 
37
37
  ```json
38
38
  {
@@ -51,7 +51,7 @@ Payload shape (5 required + 1 conditional, per Spec §5.2.7 / §8.3 + ADR 0029):
51
51
 
52
52
  Field rules:
53
53
 
54
- - `project` optional. Falls back to the active project from root `hot.md` pointer table.
54
+ - `project`: **required**. Slug of the project being closed (matches a `projects/<slug>/` directory). Must be a single path segment, charset `A-Za-z0-9._-`, with at least one alphanumeric and not a dot-only name (`.`, `..`, `...`). Apply never infers the target from recency; a same-date pointer-table tie could otherwise write the close into the wrong project (B-3). A missing, malformed, or non-existent value fails the apply before any write.
55
55
  - `date` — optional. Defaults to today (local). Must be `YYYY-MM-DD` if supplied.
56
56
  - `openQuestions` — optional. Include only when `pages/open-questions.md` exists and changed this session.
57
57
  - All other top-level fields are required.
@@ -75,7 +75,7 @@ Content guidance for each slot:
75
75
 
76
76
  ## Step 3 — Apply the payload
77
77
 
78
- The script paths in this command resolve via `${CLAUDE_PLUGIN_ROOT}`, which the plugin harness expands to this package's absolute path before you see it, so run them as written. If one appears unexpanded (a literal `${CLAUDE_PLUGIN_ROOT}`), read the package root from the `hypo@hypomnema` installPath in `~/.claude/plugins/installed_plugins.json` rather than guessing from the cache layout.
78
+ 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.
79
79
 
80
80
  ```bash
81
81
  node ${CLAUDE_PLUGIN_ROOT}/scripts/crystallize.mjs \
@@ -114,12 +114,12 @@ synthesis (no session-close intent) — the marker is then simply not written.
114
114
 
115
115
  **Two lint gates run automatically, scoped to the files this close writes:**
116
116
 
117
- Both gates judge only the **payload files** (the 5 mandatory close files + `open-questions.md`). Lint debt in other projects or shared `pages/` this close did not author is reported as a non-blocking `notices[]` entry, never gated so an unrelated broken page elsewhere cannot block your close.
117
+ Both gates judge only the **payload files** (the 5 mandatory close files + `open-questions.md`). Lint debt this close did not author is never gated so an unrelated broken page elsewhere cannot block your close. It is reported as a non-blocking notice, scoped to the close-target project: debt under `projects/<project>/` is listed by file in `notices[]`; debt elsewhere (other projects, shared `pages/`, root files) folds into the `otherDebtCount` integer so the same untouched-file debt does not re-list its filenames on every close (run `/hypo:lint` for the full list).
118
118
 
119
- 1. **Preflight** — `lint.mjs --json` runs **before** any payload bytes are written. Errors in overwrite targets (sessionState / projectHot / rootHot / openQuestions) are filtered (about to be replaced). Errors in an **append target** (session-log / log.md) still block (appending can't repair existing corruption) → exit 1 with `stage='preflight-lint'`. Errors outside the payload files → `notices[]`, apply proceeds.
119
+ 1. **Preflight** — an internal `lint --json` preflight runs **before** any payload bytes are written. Errors in overwrite targets (sessionState / projectHot / rootHot / openQuestions) are filtered (about to be replaced). Errors in an **append target** (session-log / log.md) still block (appending can't repair existing corruption) → exit 1 with `stage='preflight-lint'`. Errors outside the payload files → `notices[]`, apply proceeds.
120
120
  2. **Post-apply** — lint re-runs after the writes. Blocks only on **errors** in payload files (a payload-introduced malformed body / bad frontmatter); pre-existing errors elsewhere → `notices[]`. A lint crash (unparseable output) always blocks. Broken wikilinks are lint **warnings** (W4 — forward references to planned pages are normal) and are not gated here. Surfaces as `stage='post-apply-lint'` (or `'post-apply-verification+lint'` if freshness also fails).
121
121
 
122
- > **Manual close (direct Write tool calls)** clears the Stop-chain block via `--mark-session-closed --session-id=<id>`. Both marker writers apply a **user-close hard gate** (ADR 0055): the marker is written only when the session's transcript carries a genuine user close signal — an NL close phrase, a `/compact`, or an accepted AskUserQuestion [세션 마무리] answer. The transcript is resolved **strictly from `--session-id`** (a globally-unique id, globbed under `~/.claude/projects/`), never from a CLI arg, so a model that runs the writer on its own — without the user ever signalling close — is refused, and a forged path cannot point the gate at someone else's close-intent. The lint scope is widened from that same resolved transcript. `--transcript-path` is **not** consulted by the marker gate; it survives only to scope `--check-session-close`'s lint (which writes no marker).
122
+ > **Manual close (direct Write tool calls)** clears the Stop-chain block via `--mark-session-closed --session-id=<id>`. Both marker writers apply a **user-close hard gate**: the marker is written only when the session's transcript carries a genuine user close signal — an NL close phrase, a `/compact`, or an accepted AskUserQuestion [세션 마무리] answer. The transcript is resolved **strictly from `--session-id`** (a globally-unique id, globbed under `~/.claude/projects/`), never from a CLI arg, so a model that runs the writer on its own — without the user ever signalling close — is refused, and a forged path cannot point the gate at someone else's close-intent. The lint scope is widened from that same resolved transcript. `--transcript-path` is **not** consulted by the marker gate; it survives only to scope `--check-session-close`'s lint (which writes no marker).
123
123
 
124
124
  ---
125
125
 
@@ -154,7 +154,7 @@ If the user says stop, end here. Otherwise continue to Step 5.
154
154
 
155
155
  ## Step 5 — Surface synthesis candidates
156
156
 
157
- The script path below resolves via `${CLAUDE_PLUGIN_ROOT}`, which the plugin harness expands to this package's absolute path before you see it, so run it as written. If it appears unexpanded (a literal `${CLAUDE_PLUGIN_ROOT}`), read the package root from the `hypo@hypomnema` installPath in `~/.claude/plugins/installed_plugins.json` rather than guessing from the cache layout.
157
+ 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.
158
158
 
159
159
  ```bash
160
160
  node ${CLAUDE_PLUGIN_ROOT}/scripts/crystallize.mjs [--hypo-dir="<path>"] [--min-group=2]
@@ -234,3 +234,5 @@ node ${CLAUDE_PLUGIN_ROOT}/scripts/crystallize.mjs --check-session-close [--hypo
234
234
  ```
235
235
 
236
236
  It reports any file as `missing` or `stale`. For an actual close, prefer `--apply-session-close --payload=<path>` (Step 3) — it bundles freshness + lint into one gate and is the documented dogfood path. (`parseArgs` only accepts the `--payload=<path>` spelling; a space-separated `--payload <path>` is silently ignored and triggers "payload is required".)
237
+
238
+ Add `--project=<slug>` to scope the check to one project (close status + lint scope) when recency picks the wrong one. This is a project-scoped diagnostic only: a green scoped result (JSON `scope: "project"`) attests that slug is close-complete, **not** that `/compact` is unblocked globally. The marker writer (`--mark-session-closed --project=<slug>`) keeps a **global** gate; there `--project` only sets the marker's attribution slug.
@@ -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
- The script path below resolves via `${CLAUDE_PLUGIN_ROOT}`, which the plugin harness expands to this package's absolute path before you see it, so run it as written. If it appears unexpanded (a literal `${CLAUDE_PLUGIN_ROOT}`), read the package root from the `hypo@hypomnema` installPath in `~/.claude/plugins/installed_plugins.json` rather than guessing from 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 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
 
@@ -2,7 +2,7 @@
2
2
  description: Record an AI behavior correction or preference into the wiki. Use when the user corrects how you work or states a lasting preference to remember.
3
3
  ---
4
4
 
5
- You are running `/hypo:feedback`. Capture a behavior correction or preference into `pages/feedback/` — the **single source of truth** for learned behaviors (ADR 0031).
5
+ You are running `/hypo:feedback`. Capture a behavior correction or preference into `pages/feedback/` — the **single source of truth** for learned behaviors.
6
6
 
7
7
  ## What this does
8
8
 
@@ -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
- The script path below resolves via `${CLAUDE_PLUGIN_ROOT}`, which the plugin harness expands to this package's absolute path before you see it, so run it as written. If it appears unexpanded (a literal `${CLAUDE_PLUGIN_ROOT}`), read the package root from the `hypo@hypomnema` installPath in `~/.claude/plugins/installed_plugins.json` rather than guessing from 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 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
- The script path below resolves via `${CLAUDE_PLUGIN_ROOT}`, which the plugin harness expands to this package's absolute path before you see it, so run it as written. If it appears unexpanded (a literal `${CLAUDE_PLUGIN_ROOT}`), read the package root from the `hypo@hypomnema` installPath in `~/.claude/plugins/installed_plugins.json` rather than guessing from 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 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
- The script paths below resolve via `${CLAUDE_PLUGIN_ROOT}`, which the plugin harness expands to this package's absolute path before you see it, so run them as written. If one appears unexpanded (a literal `${CLAUDE_PLUGIN_ROOT}`), read the package root from the `hypo@hypomnema` installPath in `~/.claude/plugins/installed_plugins.json` rather than guessing from 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 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
- The script path below resolves via `${CLAUDE_PLUGIN_ROOT}`, which the plugin harness expands to this package's absolute path before you see it, so run it as written. If it appears unexpanded (a literal `${CLAUDE_PLUGIN_ROOT}`), read the package root from the `hypo@hypomnema` installPath in `~/.claude/plugins/installed_plugins.json` rather than guessing from 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 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
- The script path below resolves via `${CLAUDE_PLUGIN_ROOT}`, which the plugin harness expands to this package's absolute path before you see it, so run it as written. If it appears unexpanded (a literal `${CLAUDE_PLUGIN_ROOT}`), read the package root from the `hypo@hypomnema` installPath in `~/.claude/plugins/installed_plugins.json` rather than guessing from 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 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
- The script path below resolves via `${CLAUDE_PLUGIN_ROOT}`, which the plugin harness expands to this package's absolute path before you see it, so run it as written. If it appears unexpanded (a literal `${CLAUDE_PLUGIN_ROOT}`), read the package root from the `hypo@hypomnema` installPath in `~/.claude/plugins/installed_plugins.json` rather than guessing from 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 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
- The script path below resolves via `${CLAUDE_PLUGIN_ROOT}`, which the plugin harness expands to this package's absolute path before you see it, so run it as written. If it appears unexpanded (a literal `${CLAUDE_PLUGIN_ROOT}`), read the package root from the `hypo@hypomnema` installPath in `~/.claude/plugins/installed_plugins.json` rather than guessing from 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 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
- The script path below resolves via `${CLAUDE_PLUGIN_ROOT}`, which the plugin harness expands to this package's absolute path before you see it, so run it as written. If it appears unexpanded (a literal `${CLAUDE_PLUGIN_ROOT}`), read the package root from the `hypo@hypomnema` installPath in `~/.claude/plugins/installed_plugins.json` rather than guessing from 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 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
- The plugin harness expands `${CLAUDE_PLUGIN_ROOT}` to this package's absolute path before you see it, so run the command as written. If it appears unexpanded (a literal `${CLAUDE_PLUGIN_ROOT}`), read the package root from the `hypo@hypomnema` installPath in `~/.claude/plugins/installed_plugins.json` rather than guessing from 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 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
- The script path below resolves via `${CLAUDE_PLUGIN_ROOT}`, which the plugin harness expands to this package's absolute path before you see it, so run it as written. If it appears unexpanded (a literal `${CLAUDE_PLUGIN_ROOT}`), read the package root from the `hypo@hypomnema` installPath in `~/.claude/plugins/installed_plugins.json` rather than guessing from 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 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
 
@@ -20,13 +20,15 @@ Say:
20
20
 
21
21
  Default: yes (dry-run first)
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.
24
+
23
25
  ---
24
26
 
25
27
  ## Step 2 — Dry run
26
28
 
27
29
  Run:
28
30
  ```
29
- node scripts/uninstall.mjs
31
+ node ${CLAUDE_PLUGIN_ROOT}/scripts/uninstall.mjs
30
32
  ```
31
33
 
32
34
  Show the output to the user. Ask:
@@ -39,7 +41,7 @@ If no → abort and confirm nothing was changed.
39
41
  ## Step 3 — Apply (if confirmed)
40
42
 
41
43
  ```
42
- node scripts/uninstall.mjs --apply
44
+ node ${CLAUDE_PLUGIN_ROOT}/scripts/uninstall.mjs --apply
43
45
  ```
44
46
 
45
47
  If the user also wants Codex hooks removed, append `--codex`.
@@ -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
- The script path below resolves via `${CLAUDE_PLUGIN_ROOT}`, which the plugin harness expands to this package's absolute path before you see it, so run it as written. If it appears unexpanded (a literal `${CLAUDE_PLUGIN_ROOT}`), read the package root from the `hypo@hypomnema` installPath in `~/.claude/plugins/installed_plugins.json` rather than guessing from 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 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
 
@@ -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
- The script path below resolves via `${CLAUDE_PLUGIN_ROOT}`, which the plugin harness expands to this package's absolute path before you see it, so run it as written. If it appears unexpanded (a literal `${CLAUDE_PLUGIN_ROOT}`), read the package root from the `hypo@hypomnema` installPath in `~/.claude/plugins/installed_plugins.json` rather than guessing from 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 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>]
@@ -335,7 +335,7 @@ journal/weekly/<YYYY-Www>.md ← committed report (heuristic v0, s
335
335
 
336
336
  `session-audit.mjs` reads transcripts from two locations, in priority order:
337
337
 
338
- 1. **Primary:** `<hypo-root>/.cache/sessions/index.jsonl` — written by the Stop hook `hypo-session-record.mjs`. Each line: `{ session_id, transcript_path, recorded_at, cwd, device }`. `device` (PRAC-17) is `os.hostname()` for multi-machine audit; the index lives under `.cache/` (gitignored in a normal vault) so it is a local-only per-session record.
338
+ 1. **Primary:** `<hypo-root>/.cache/sessions/index.jsonl` — written by the Stop hook `hypo-session-record.mjs`. Each line: `{ session_id, transcript_path, recorded_at, cwd, device }`. `device` is `os.hostname()` for multi-machine audit; the index lives under `.cache/` (gitignored in a normal vault) so it is a local-only per-session record.
339
339
  2. **Fallback:** `~/.claude/projects/<encoded>/*.jsonl` — scanned when the index is missing or empty (legacy / freshly-installed wikis).
340
340
 
341
341
  ### Classification
@@ -366,7 +366,7 @@ The score is a **proxy, not ground truth**. The four-week baseline plan (capture
366
366
 
367
367
  The observability pipeline reads but never republishes raw transcripts. Weekly reports only emit `session_id` plus aggregate counts — no transcript content, no URLs, no tool inputs. Transcripts themselves live under `~/.claude/projects/` or `.cache/sessions/` which `.hypoignore` already excludes from any sync.
368
368
 
369
- **PRAC-17 audit fields (`device`, `session_id`).** `device` (`os.hostname()`) is recorded in two places with different sync boundaries, by design. In `.cache/sessions/index.jsonl` it is per-session-accurate but **local-only** (`.cache/` is gitignored in a normal vault). In the seeded session-log daily-shard frontmatter it is an **intentional synced field** — a creator-only stamp (the session/machine that first created the day's shard), carried across machines so the wiki itself records which machine opened each day. `session_id` (the Claude session UUID) is seeded into that same synced frontmatter, but only on the Stop-chain close path that passes `--session-id`; on manual/skill closes it is omitted. This deliberately puts a hostname (and, on the automated path, the session UUID) into git-tracked, synced content; it is acceptable for a single-user private vault where multi-machine identification is the goal. Operators who do not want a hostname synced can override `os.hostname()` at the OS level or treat the field as a pseudonymous label.
369
+ **Audit fields (`device`, `session_id`).** `device` (`os.hostname()`) is recorded in two places with different sync boundaries, by design. In `.cache/sessions/index.jsonl` it is per-session-accurate but **local-only** (`.cache/` is gitignored in a normal vault). In the seeded session-log daily-shard frontmatter it is an **intentional synced field** — a creator-only stamp (the session/machine that first created the day's shard), carried across machines so the wiki itself records which machine opened each day. `session_id` (the Claude session UUID) is seeded into that same synced frontmatter, but only on the Stop-chain close path that passes `--session-id`; on manual/skill closes it is omitted. This deliberately puts a hostname (and, on the automated path, the session UUID) into git-tracked, synced content; it is acceptable for a single-user private vault where multi-machine identification is the goal. Operators who do not want a hostname synced can override `os.hostname()` at the OS level or treat the field as a pseudonymous label.
370
370
 
371
371
  ### Growth metrics (Lane B)
372
372
 
@@ -124,7 +124,8 @@ npm run lint # scripts/lint.mjs — frontmatter + wikilink validation + W8
124
124
  npm run fix:verify # Phase 1 of learned_behavior #6 — verifies fix #N status claims in
125
125
  # a wiki spec against `// @fix #N: <test-name>` anchors in
126
126
  # tests/runner.mjs. Maintainer dogfood; needs a local wiki at
127
- # $HYPO_DIR or ~/hypomnema. Does NOT grep ADR core decision lines.
127
+ # $HYPO_DIR or ~/hypomnema (source checkout only; not shipped in
128
+ # the npm package). Does NOT grep ADR core decision lines.
128
129
  ```
129
130
 
130
131
  > **`fix:verify` needs an explicit `--spec`.** The default path
@@ -217,13 +218,20 @@ Before opening:
217
218
  4. If you changed a template or `init` flow, a fresh `init` produces the expected vault structure.
218
219
  5. README / ARCHITECTURE / docs are updated to match.
219
220
 
220
- Open the PR against `main`. Include:
221
+ Open the PR against `main`. The body is **bilingual**: write the full description twice, once under a `# English` heading and once under a `# 한국어` heading, each carrying the same sections. The `## Changelog` and `## Checklist` blocks are language-neutral, so they appear once, after both language blocks. Do **not** add any tool-attribution footer (no "Generated with ..." line) to the body.
222
+
223
+ Each language block includes:
224
+
225
+ - **What changed** / **변경 내용**: short summary
226
+ - **Why** / **이유**: the user-visible motivation
227
+ - **How** / **방법**: approach notes that aren't obvious from the diff
228
+ - **Manual verification** / **수동 검증**: anything the test suite cannot cover
229
+ - **Migration notes** / **마이그레이션 노트**: if upgrading existing installs needs special handling (else "None")
230
+
231
+ After both blocks, language-neutral:
221
232
 
222
- - **What changed** — short summary
223
- - **Why** — the user-visible motivation
224
- - **Manual verification steps** — anything the test suite cannot cover
225
233
  - **Changelog**: one English line plus one Korean line if the change is user-visible (see CHANGELOG conventions below)
226
- - **Migration notes** if upgrading existing installs needs special handling
234
+ - **Checklist**: the boxes from the PR template
227
235
 
228
236
  ---
229
237
 
@@ -235,7 +243,7 @@ Open the PR against `main`. Include:
235
243
 
236
244
  - **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).
237
245
  - **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.
238
- - Internal tracker ids (`FEAT-`, `IMPR-`, `ISSUE-`, `PRAC-`) may appear in your local notes and in source or workflow comments (an internal reference, like an ADR anchor), but 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 identifier that ships in those is the PR number `#N`. `check-tracker-ids --tag` gates the tag body; the migration keeps the CHANGELOG body clean.
246
+ - Internal tracker ids (`FEAT-`, `IMPR-`, `ISSUE-`, `PRAC-`) 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 identifier that ships in those is the PR number `#N`. `check-tracker-ids` gates every surface (`--all`/`--staged` for files, `--commit-msg` for messages, `--tag` for the tag body); the migration keeps the CHANGELOG body clean.
239
247
 
240
248
  ### The `## Changelog` block
241
249
 
@@ -265,9 +273,9 @@ At and after the v1.2.0 cutoff, each non-empty gated section (`New Features`, `B
265
273
  ```
266
274
  ### New Features
267
275
  #### English
268
- - Feedback pages accept an optional `failure_type`. (#141)
276
+ - Feedback pages accept an optional `failure_type`. ([#141](https://github.com/sk-lim19f/Hypomnema/pull/141))
269
277
  #### 한국어
270
- - 피드백 페이지가 선택적 `failure_type`를 받습니다. (#141)
278
+ - 피드백 페이지가 선택적 `failure_type`를 받습니다. ([#141](https://github.com/sk-lim19f/Hypomnema/pull/141))
271
279
  ```
272
280
 
273
281
  Versions below the v1.2.0 cutoff (1.0.0, 1.0.1, 1.1.0) are English-only: the English body sits directly under the section header with no `####` sub-blocks. Korean is not back-filled for those releases.
@@ -276,12 +284,12 @@ The `### Changelog` index is language-neutral, one merged PR per line:
276
284
 
277
285
  ```
278
286
  ### Changelog
279
- - #141 feedback failure_type classification
280
- - #140 invalid-YAML lint guard
287
+ - [#141](https://github.com/sk-lim19f/Hypomnema/pull/141) feedback failure_type classification
288
+ - [#140](https://github.com/sk-lim19f/Hypomnema/pull/140) invalid-YAML lint guard
281
289
  Contributors: @handle
282
290
  ```
283
291
 
284
- Each line is `- #N <short title>`, no em dash. The `Contributors:` line lists that version's PR authors, de-duplicated; the release collector fills the handles from the GitHub API.
292
+ Each line is `- [#N](<pr-url>) <short title>`, no em dash: the collector links each `#N` to its GitHub PR (a repo markdown file does not autolink a bare `#N`), deriving the URL from the `repository` field in `package.json`. The `Contributors:` line lists that version's PR authors, de-duplicated; the release collector fills the handles from the GitHub API.
285
293
 
286
294
  A `### Known Issues` or `### Notes` block, when a version has one, is a trailing note (a caveat, not a change). It is not one of the gated sections, so it carries no `####` sub-blocks even at or after the cutoff; bilingual text there is recommended, not enforced.
287
295
 
@@ -14,7 +14,7 @@ function hasRemote() {
14
14
  }
15
15
 
16
16
  // Stage + commit via the shared helper (same .hypoignore filter the apply path
17
- // uses — ADR 0056). A real commit failure short-circuits before sync, exactly as
17
+ // uses). A real commit failure short-circuits before sync, exactly as
18
18
  // the inline logic did; "nothing to commit" is success and falls through to sync.
19
19
  const result = commitWikiChanges(HYPO_DIR);
20
20
  if (!result.committed) {
@@ -26,7 +26,7 @@ if (hasRemote()) {
26
26
  // pull/push failures must not stop the session, but they can no longer be
27
27
  // swallowed silently — syncRemote records each to .cache/sync-state.json and,
28
28
  // on a merge conflict, aborts the merge so the tree is never left half-merged
29
- // (FEAT-17 hardening). session-start + doctor surface the result next session.
29
+ // (part of the v1.4 sync hardening). session-start + doctor surface the result next session.
30
30
  syncRemote(HYPO_DIR);
31
31
  }
32
32
 
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * hypo-auto-minimal-crystallize.mjs — Stop hook (ADR 0022 Layer 3)
3
+ * hypo-auto-minimal-crystallize.mjs — Stop hook (Layer 3)
4
4
  *
5
5
  * Last hook in the Stop chain: a final-line defense that blocks `Stop` when
6
6
  * the current session did substantial work (mutation, or a high-volume
@@ -8,7 +8,7 @@
8
8
  * session-close. Forces Claude to run minimal session-close before the
9
9
  * conversation context evaporates.
10
10
  *
11
- * Decision flow (see ADR 0022 amendment 2026-05-19 Q1+Q2 + 2nd amendment Q-close-gate):
11
+ * Decision flow (see amendment 2026-05-19 Q1+Q2 + 2nd amendment Q-close-gate):
12
12
  *
13
13
  * 1. stop_hook_active === true → continue (loop guard; PoC 2026-05-14)
14
14
  * 2. wiki absent → continue (fail-open)
@@ -24,7 +24,7 @@
24
24
  * Close-intent gate (added after PR-C dogfooding revealed every-turn block —
25
25
  * codex 2-worker debate 2026-05-19, both REQUEST_CHANGES). Stop fires after
26
26
  * EVERY assistant turn, not at session end; blocking on "mutation + no marker"
27
- * alone nags the user on every turn of a long mutating session. ADR 0022's
27
+ * alone nags the user on every turn of a long mutating session. The hook's
28
28
  * real intent is "block when the session is ENDING and close is incomplete".
29
29
  * We approximate the end signal by reusing isClosePattern() over recent
30
30
  * user-message text (the same low-false-positive signal PreCompact uses):
@@ -59,22 +59,34 @@ function emitContinue() {
59
59
  }
60
60
 
61
61
  function emitBlock(sessionId, transcriptPath, gate = null) {
62
- // One-line, skill-first. /hypo:crystallize is the documented session-close
63
- // alias; passing --session-id there writes the per-session marker that clears
64
- // this block. CLI fallback + bypass live in commands/crystallize.md, not here
65
- // keep the Stop reason terse so the actionable instruction stands out.
66
- // Surface the transcript path so the close can pass --transcript-path=<path>,
67
- // which scopes the marker's lint gate to this session's own files (Bug A
68
- // coherence: a marker written without lint would only let Stop pass for
69
- // /compact to immediately re-block on the same errors).
70
- const transcriptHint = transcriptPath ? ` --transcript-path=${transcriptPath}` : '';
71
- const markCmd = `crystallize --mark-session-closed --session-id=${sessionId}${transcriptHint}`;
62
+ // One-line recovery action. The gate-precise branches below name an EXACT
63
+ // command so "only the marker is missing" stays a one-shot fix — but that
64
+ // command must be a runnable `node <pkg>/scripts/crystallize.mjs` invocation,
65
+ // never a bare `crystallize` (a package.json `bin` that is NOT on PATH in a
66
+ // plugin install `command not found`). When PKG_ROOT is
67
+ // unresolved we cannot build that path, so fall back to the /hypo:crystallize
68
+ // skill, which resolves its own package root (the same alias the generic
69
+ // branch uses). Passing --session-id writes the per-session marker that clears
70
+ // this block. Surface the transcript path so the close can pass
71
+ // --transcript-path=<path>, which scopes the marker's lint gate to this
72
+ // session's own files (Bug A coherence: a marker written without lint would
73
+ // only let Stop pass for /compact to immediately re-block on the same errors).
74
+ // Quote the paths so the printed command stays copy-paste runnable even when
75
+ // an install/transcript path contains spaces (display text only — never exec'd
76
+ // here).
77
+ const transcriptHint = transcriptPath ? ` --transcript-path="${transcriptPath}"` : '';
78
+ const cliBase = PKG_ROOT ? `node "${join(PKG_ROOT, 'scripts', 'crystallize.mjs')}"` : null;
79
+ const markCmd = cliBase
80
+ ? `${cliBase} --mark-session-closed --session-id=${sessionId}${transcriptHint}`
81
+ : `/hypo:crystallize (session_id=${sessionId}${transcriptHint})`;
72
82
  // The log-only escape hatch for a non-project (wiki/tooling-only)
73
83
  // session. Offered ONLY as an explicit alternative when a close blocker is
74
84
  // present — never as the default recovery, so a real project session is not
75
- // taught to bypass the ADR 0043 close invariant (codex design Finding 3).
76
- const logOnlyCmd = `crystallize --mark-session-closed --log-only --session-id=${sessionId}${transcriptHint}`;
77
- // ADR 0047: refine the message with the read-only /compact gate result.
85
+ // taught to bypass the close invariant (codex design Finding 3).
86
+ const logOnlyCmd = cliBase
87
+ ? `${cliBase} --mark-session-closed --log-only --session-id=${sessionId}${transcriptHint}`
88
+ : `/hypo:crystallize --log-only (session_id=${sessionId}${transcriptHint})`;
89
+ // Refine the message with the read-only /compact gate result.
78
90
  // - gate green → the close is compact-ready and ONLY the marker is missing
79
91
  // (the hand-edit close case: files Written + committed directly, bypassing
80
92
  // the marker writer). Say so precisely + give the one command, instead of
@@ -100,7 +112,7 @@ function emitBlock(sessionId, transcriptPath, gate = null) {
100
112
  JSON.stringify({
101
113
  decision: 'block',
102
114
  reason,
103
- stopReason: 'session-close incomplete (ADR 0022 Layer 3)',
115
+ stopReason: 'session-close incomplete (Layer 3)',
104
116
  }),
105
117
  );
106
118
  }
@@ -174,7 +186,7 @@ process.stdin.on('end', () => {
174
186
  return;
175
187
  }
176
188
 
177
- // ADR 0047: read-only /compact gate (same precompactGateStatus the real
189
+ // Read-only /compact gate (same precompactGateStatus the real
178
190
  // PreCompact hook uses) sharpens the block message — distinguishes "close
179
191
  // is compact-ready, only the marker is missing" from "there are real
180
192
  // blockers". The hook NEVER writes the marker here (file-header invariant);
@@ -2,7 +2,7 @@
2
2
  /**
3
3
  * hypo-compact-guard.mjs — UserPromptSubmit hook
4
4
  *
5
- * Scope: detects "/compact" or "/clear" typed in chat only (ADR 0022 Layer 2).
5
+ * Scope: detects "/compact" or "/clear" typed in chat only (Layer 2).
6
6
  * The CLI built-in /compact does NOT fire UserPromptSubmit — use personal-wiki-check.mjs
7
7
  * (PreCompact hook) as the hard gate for that path. /clear has no PreCompact event, so
8
8
  * this hook is the only chat-side gate that can prompt session-close before context wipe.
@@ -42,7 +42,7 @@ process.stdin.on('end', () => {
42
42
  const gitStatus = hypoIsClean();
43
43
  const hotStatus = hotMdIsClean();
44
44
 
45
- // ADR 0056: block on uncommitted (real unsaved work); unpushed commits (ahead)
45
+ // Block on uncommitted (real unsaved work); unpushed commits (ahead)
46
46
  // are a soft, auto-synced state and must not block /compact or /clear — mirrors
47
47
  // the precompactGateStatus demote so the chat-side gate stays consistent.
48
48
  if (hasSession && !gitStatus.uncommitted && hotStatus.clean) {