hypomnema 1.8.0 → 1.8.2

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 (41) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +46 -0
  4. package/README.ko.md +8 -6
  5. package/README.md +8 -6
  6. package/commands/crystallize.md +32 -6
  7. package/commands/graph.md +7 -4
  8. package/commands/lint.md +8 -1
  9. package/commands/query.md +6 -4
  10. package/commands/resume.md +1 -0
  11. package/commands/verify.md +12 -1
  12. package/docs/ARCHITECTURE.md +26 -12
  13. package/docs/CONTRIBUTING.md +15 -6
  14. package/hooks/hypo-compact-guard.mjs +126 -23
  15. package/hooks/hypo-cwd-change.mjs +20 -19
  16. package/hooks/hypo-first-prompt.mjs +31 -16
  17. package/hooks/hypo-lookup.mjs +10 -5
  18. package/hooks/hypo-session-start.mjs +180 -2
  19. package/hooks/hypo-shared.mjs +410 -83
  20. package/hooks/hypo-web-fetch-ingest.mjs +9 -13
  21. package/package.json +2 -1
  22. package/scripts/doctor.mjs +32 -3
  23. package/scripts/graph.mjs +22 -2
  24. package/scripts/init.mjs +5 -1
  25. package/scripts/lib/crystallize-args.mjs +38 -2
  26. package/scripts/lib/crystallize-close-apply.mjs +745 -450
  27. package/scripts/lint.mjs +242 -39
  28. package/scripts/query.mjs +22 -2
  29. package/scripts/resume.mjs +177 -2
  30. package/scripts/upgrade.mjs +2 -2
  31. package/scripts/verify.mjs +22 -2
  32. package/templates/SCHEMA.md +23 -1
  33. package/templates/hypo-automation.md +4 -2
  34. package/templates/hypo-config.md +1 -1
  35. package/templates/hypo-guide.md +1 -1
  36. package/skills/crystallize/SKILL.md +0 -189
  37. package/skills/graph/SKILL.md +0 -58
  38. package/skills/ingest/SKILL.md +0 -107
  39. package/skills/lint/SKILL.md +0 -59
  40. package/skills/query/SKILL.md +0 -62
  41. package/skills/verify/SKILL.md +0 -96
@@ -11,7 +11,7 @@
11
11
  "name": "hypo",
12
12
  "source": "./",
13
13
  "description": "LLM-native personal wiki — session-aware knowledge base for Claude Code",
14
- "version": "1.8.0",
14
+ "version": "1.8.2",
15
15
  "homepage": "https://github.com/sk-lim19f/Hypomnema"
16
16
  }
17
17
  ]
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hypo",
3
- "version": "1.8.0",
3
+ "version": "1.8.2",
4
4
  "description": "LLM-native personal wiki system — session-aware knowledge base for Claude Code",
5
5
  "author": {
6
6
  "name": "sk-lim19f",
package/CHANGELOG.md CHANGED
@@ -5,6 +5,52 @@ 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.8.2] - 2026-09-10
9
+
10
+ ### Bug Fixes
11
+
12
+ #### English
13
+
14
+ - Hook notices reach the model again. They were emitted with a top-level `additionalContext`, which Claude Code silently ignores, so the close gate, the resume contract, and lookup results never arrived. Measured after the fix: a canary planted in a project's `hot.md` came back verbatim from a session with every file tool blocked, and the same canary read as absent on the previous build. ([#287](https://github.com/sk-lim19f/Hypomnema/pull/287))
15
+ - The session-close gate reads a background-task notification the same way on every delivery path, so a task finishing after you asked to close no longer cancels the close. A refusal also reports `gateReason`, naming which check refused. ([#289](https://github.com/sk-lim19f/Hypomnema/pull/289))
16
+ - `lint`, `verify`, `graph`, `query` and `crystallize` exit 2 on an unrecognised flag or an empty `--hypo-dir=`, instead of silently dropping it and falling back to the default vault. Their command docs no longer print placeholder syntax as runnable arguments. ([#286](https://github.com/sk-lim19f/Hypomnema/pull/286))
17
+ - The `/compact` and `/clear` notice no longer orders a session close, no longer fires on another project's uncommitted files, and no longer goes silent when the vault is broken enough to be worth reporting (`decisions/0101`). ([#285](https://github.com/sk-lim19f/Hypomnema/pull/285))
18
+ - `/hypo:doctor` no longer accepts a session-closed marker as proof for a project the close gate never evaluated. The marker records the set the gate actually checked, and the audit requires an artifact's project to be in that set as well as in the marker's attribution. ([#284](https://github.com/sk-lim19f/Hypomnema/pull/284))
19
+
20
+ #### 한국어
21
+
22
+ - 훅 알림이 다시 모델에 닿습니다. 최상단 `additionalContext`로 나가고 있었는데 Claude Code가 그것을 조용히 무시해서 close 게이트, 세션 재개 계약, lookup 결과가 도착한 적이 없었습니다. 고친 뒤 실측했습니다. 프로젝트 `hot.md`에 심은 난수를 파일 도구를 전부 막은 세션이 그대로 답했고, 같은 난수를 이전 빌드는 못 읽었습니다. ([#287](https://github.com/sk-lim19f/Hypomnema/pull/287))
23
+ - 세션 마무리 게이트가 background task 알림을 어느 전달 경로로 오든 같게 읽습니다. 마무리를 요청한 뒤 작업이 끝나도 그 마무리가 취소되지 않습니다. 거절할 때는 `gateReason`으로 어느 검사가 거절했는지도 함께 알립니다. ([#289](https://github.com/sk-lim19f/Hypomnema/pull/289))
24
+ - `lint`·`verify`·`graph`·`query`·`crystallize`가 못 알아보는 플래그와 빈 `--hypo-dir=`를 조용히 버리고 기본 볼트로 가는 대신 exit 2를 냅니다. 명령 문서도 placeholder 문법을 실행 가능한 인자처럼 찍지 않습니다. ([#286](https://github.com/sk-lim19f/Hypomnema/pull/286))
25
+ - `/compact`·`/clear` 알림이 세션 마무리를 지시하지 않고, 다른 프로젝트의 미커밋 파일로 뜨지 않으며, 보고할 값어치가 있을 만큼 볼트가 망가졌을 때 조용해지지 않습니다 (`decisions/0101`). ([#285](https://github.com/sk-lim19f/Hypomnema/pull/285))
26
+ - `/hypo:doctor`가 close 게이트가 한 번도 평가하지 않은 프로젝트에 대해 session-closed 마커를 증거로 받지 않습니다. 마커가 게이트의 실제 검사 집합을 기록하고, 감사가 산출물의 프로젝트를 귀속뿐 아니라 그 집합에서도 요구합니다. ([#284](https://github.com/sk-lim19f/Hypomnema/pull/284))
27
+
28
+ ### Chores
29
+
30
+ #### English
31
+
32
+ - The shipped `SCHEMA.md` template defines `updated` as the day content changed, not the day the file was written, and states plainly that nothing enforces it. ([#288](https://github.com/sk-lim19f/Hypomnema/pull/288))
33
+
34
+ #### 한국어
35
+
36
+ - 출하되는 `SCHEMA.md` 템플릿이 `updated`를 파일을 쓴 날이 아니라 내용이 바뀐 날로 정의하고, 그것을 집행하는 장치가 없다는 사실도 함께 적습니다. ([#288](https://github.com/sk-lim19f/Hypomnema/pull/288))
37
+
38
+ ## [1.8.1] - 2026-09-07
39
+
40
+ ### Bug Fixes
41
+
42
+ #### English
43
+
44
+ - Six slash names (`crystallize`, `graph`, `ingest`, `lint`, `query`, `verify`) shipped as both a `commands/*.md` file and a `skills/*/SKILL.md` directory, so each registered twice as a component and the command palette repeated them. Only the `commands/` half ever loaded, so the duplicates are removed with no behavior change, and `smoke:plugin` now fails on a name present in both directories (decisions/0100). ([#282](https://github.com/sk-lim19f/Hypomnema/pull/282))
45
+ - A session close now checks that the root files it writes still carry a usable frontmatter block, and refuses a close that would leave invalid YAML in one of them. ([#281](https://github.com/sk-lim19f/Hypomnema/pull/281))
46
+ - `/hypo:resume` and SessionStart now surface uncommitted vault work that belongs to another project, so a session does not silently adopt it. ([#280](https://github.com/sk-lim19f/Hypomnema/pull/280))
47
+
48
+ #### 한국어
49
+
50
+ - 여섯 슬래시 이름(`crystallize`, `graph`, `ingest`, `lint`, `query`, `verify`)이 `commands/*.md` 파일과 `skills/*/SKILL.md` 디렉터리로 둘 다 출하되어 component 가 두 번씩 등록되고 명령 팔레트가 그만큼 반복됐습니다. 실제로 로드된 것은 `commands/` 쪽뿐이라 중복을 동작 변화 없이 지웠고, 이제 한 이름이 두 디렉터리에 다 있으면 `smoke:plugin` 이 실패합니다 (decisions/0100). ([#282](https://github.com/sk-lim19f/Hypomnema/pull/282))
51
+ - 세션 마무리가 자기가 쓰는 루트 파일에 쓸 만한 프론트매터 블록이 남는지 검사하고, 그중 하나에 깨진 YAML 을 남길 마무리를 거절합니다. ([#281](https://github.com/sk-lim19f/Hypomnema/pull/281))
52
+ - `/hypo:resume` 과 SessionStart 가 다른 프로젝트 소관의 미커밋 변경을 한 줄로 알려, 세션이 그것을 조용히 자기 일로 삼지 않게 합니다. ([#280](https://github.com/sk-lim19f/Hypomnema/pull/280))
53
+
8
54
  ## [1.8.0] - 2026-09-03
9
55
 
10
56
  ### New Features
package/README.ko.md CHANGED
@@ -253,11 +253,11 @@ Hypomnema는 청크가 아니라 페이지를 지식 단위로 봅니다. 새
253
253
  |---|---|---|
254
254
  | `hypo-close-guard.mjs` | `PreToolUse` | Write/Edit/MultiEdit가 세션 마무리 쓰기로 보이는데 트랜스크립트에 사용자의 마무리 신호가 없으면, 파일이 바뀌기 전에 확인을 받음 |
255
255
  | `hypo-session-start.mjs` | `SessionStart` | `hot.md` / `session-state.md` 주입 + `git pull --ff-only` |
256
- | `hypo-first-prompt.mjs` | `UserPromptSubmit` | 마커 기반 일회성 `hot.md` 주입 (10분 TTL) |
256
+ | `hypo-first-prompt.mjs` | `UserPromptSubmit` | 마커 기반으로 첫 프롬프트에 재개 한 줄을 요구한다(10분 TTL). `hot.md`를 다시 읽지는 않는다 |
257
257
  | `hypo-lookup.mjs` | `UserPromptSubmit` | BM25 top-3 HIT 주입 / MISS면 가까운 슬러그 신호 |
258
- | `hypo-compact-guard.mjs` | `UserPromptSubmit` | 채팅에 입력된 `/compact`나 `/clear`를 감지해 컨텍스트가 지워지기 전에 session-close 체크리스트를 강제 |
259
- | `hypo-cwd-change.mjs` | `CwdChanged` | cwd에 맞는 프로젝트 `hot.md` 주입 |
260
- | `hypo-file-watch.mjs` | `FileChanged` | 위키 파일 변경 알림 (`.hypoignore` 준수. 매칭 경로는 LLM 컨텍스트로 다시 주입하지 않음) |
258
+ | `hypo-compact-guard.mjs` | `UserPromptSubmit` | 채팅에 입력된 `/compact`나 `/clear`를 감지해 마무리가 덜 됐으면 알린다. compact 자체는 막지 않는다 |
259
+ | `hypo-cwd-change.mjs` | `CwdChanged` | cwd에 맞는 프로젝트 `hot.md`로 컨텍스트를 만든다. `CwdChanged`에는 Claude Code가 모델로 전달하는 출력 필드가 없어 지금은 닿지 않는다 |
260
+ | `hypo-file-watch.mjs` | `FileChanged` | 바뀐 위키 파일로 컨텍스트를 만든다(`.hypoignore` 준수). 이 패키지는 감시 경로를 등록하지 않아 이벤트가 발생할 계기가 없고, `FileChanged` 출력은 어차피 모델에 안 닿는다 |
261
261
  | `hypo-auto-stage.mjs` | `PostToolUse(Write/Edit/MultiEdit)` | 위키 파일 자동 stage |
262
262
  | `hypo-auto-commit.mjs` | `Stop` | 자동 commit + pull + push |
263
263
  | `hypo-hot-rebuild.mjs` | `Stop` | 루트 `hot.md` 포인터 테이블 재생성 (구조와 날짜) |
@@ -289,7 +289,9 @@ PostToolUse 훅 둘은 matcher 없이 등록되고 각자 tool_name으로 거릅
289
289
 
290
290
  ### Claude Agent Skills
291
291
 
292
- 합성이 핵심인 명령어 6개(`ingest`, `query`, `crystallize`, `lint`, `verify`, `graph`)는 `skills/<name>/SKILL.md`로도 등록돼 있습니다. 대화 내용이 해당 스킬의 `description`과 맞으면 Claude Agent Skills 메커니즘이 슬래시 명령 없이도 자동으로 호출합니다. 일곱 번째인 `debate`는 스킬로만 있습니다. `commands/` 아래에 파일이 없지만 플러그인이 `skills/`도 함께 싣기 때문에 `/hypo:debate`는 나머지와 똑같이 동작합니다. 세 단계(심문, 검증, 종합)로 구조화된 검토를 실행해 위키 주장을 재검증하거나 되돌리기 어려운 결정을 ADR로 굳힙니다. 위 명령어 표에 없는 것은 그 표가 `commands/` 아래 파일을 세기 때문이지 칠 수 있는 것을 세기 때문이 아닙니다. 명령어를 정확히 몰라도 하려는 일을 말로 적으면 됩니다.
292
+ `commands/` 아래의 모든 파일이 그 자체로 Claude Agent Skill입니다. 대화 내용이 그 `description`과 맞으면 슬래시 명령 없이도 자동으로 호출됩니다. (플러그인 설치에서 실측한 것입니다. npm 경로가 설치하는 `~/.claude/commands/hypo/` 사본도 같은 방식으로 자동 호출되는지는 재 보지 않았습니다.) 합성이 핵심인 여섯(`ingest`, `query`, `crystallize`, `lint`, `verify`, `graph`)에서 이 동작이 가장 잘 드러납니다. `debate`만 반대쪽에 있습니다. `commands/` 아래에 파일이 없고 `skills/debate/SKILL.md`에만 있는데, 플러그인이 `skills/`도 함께 싣기 때문에 `/hypo:debate`는 나머지와 똑같이 동작합니다. 세 단계(심문, 검증, 종합)로 구조화된 검토를 실행해 위키 주장을 재검증하거나 되돌리기 어려운 결정을 ADR로 굳힙니다. 위 명령어 표에 없는 것은 그 표가 `commands/` 아래 파일을 세기 때문이지 칠 수 있는 것을 세기 때문이 아닙니다. 명령어를 정확히 몰라도 하려는 일을 말로 적으면 됩니다.
293
+
294
+ 이름 하나는 두 디렉터리 중 한 곳에서만 출하됩니다. flat `commands/<name>.md`와 디렉터리 `skills/<name>/SKILL.md`는 둘 다 `/hypo:<name>`을 차지하고, 둘을 함께 실어도 사람용 표면과 모델용 표면으로 갈리지 않습니다. 한쪽만 이기고 나머지는 죽은 무게가 됩니다. 충돌이 있으면 `npm run smoke:plugin`이 실패합니다. 이 검사는 이 패키지의 `commands/`와 `skills/`만 봅니다. `~/hypomnema/extensions/`에 둔 직접 만든 확장은 검사하지 않으므로, 스스로 캡처한 `commands/mine.md`와 `skills/mine/`은 여전히 함께 동기화되어 같은 방식으로 충돌할 수 있습니다.
293
295
 
294
296
  | 이렇게 말하면 | 트리거되는 스킬 |
295
297
  |---|---|
@@ -394,7 +396,7 @@ E. 멈춘 프로젝트 재개.
394
396
 
395
397
  `HYPO_SKIP_GATE=1`은 세션 중간에 사용자를 막을 수 있는 게이트가 존중합니다. `hypo-compact-guard`, `hypo-close-guard`, `hypo-auto-minimal-crystallize`, 그리고 `hypo-web-fetch-ingest`의 ingest 안내가 여기 해당합니다. `hypo-personal-check`(PreCompact)도 이 플래그를 읽지만, 이 훅은 이제 `/compact`를 막지 않으므로 플래그는 반복될 뻔한 미완성-마무리 `systemMessage`만 억제합니다. 기록할 필요 없는 가벼운 세션에 씁니다.
396
398
 
397
- > 모델 제공사에게 전송되는 범위: Hypomnema 훅은 위키 본문을 Claude Code의 추가 컨텍스트(`additionalContext`)에 실어 보내고, 이 내용은 프롬프트의 일부로 Claude 모델 제공사로 전송됩니다. `.hypoignore`에 등록된 경로는 모든 주입 훅(`hypo-file-watch`, `hypo-session-start`, `hypo-cwd-change`, `hypo-lookup`)과 `ingest`에서 제외되지만, 등록하지 않은 파일은 전송 대상입니다. (`hypo-auto-stage`/`hypo-auto-commit`은 git 스테이징용 훅이라 컨텍스트를 주입하지는 않지만, 스테이징 판단에도 `.hypoignore`를 참고합니다.) 비밀 정보는 위키에 두지 마시고, `HYPO_DIR` 아래에 민감한 내용을 저장하기 전에 `.hypoignore` 패턴을 먼저 점검하세요.
399
+ > 모델 제공사에게 전송되는 범위: Hypomnema 훅은 위키 본문을 Claude Code의 추가 컨텍스트(`additionalContext`)에 실어 보내고, 이 내용은 프롬프트의 일부로 Claude 모델 제공사로 전송됩니다. 이 경로가 실제로 모델까지 닿는 것은 `hypo-session-start`와 `hypo-lookup`입니다. `hypo-cwd-change`와 `hypo-file-watch`는 `CwdChanged`와 `FileChanged` 이벤트에서 동작하는데, Claude Code가 이 두 이벤트에는 모델에 닿는 출력 필드를 두지 않아서, 두 훅이 만드는 `additionalContext`는 지금은 전송 전에 버려집니다. `.hypoignore`는 출력이 모델까지 닿는지와 무관하게 이 네 훅 모두와 `ingest`에서 적용됩니다. 등록하지 않은 파일은 모델에 닿는 두 훅이 그대로 전송합니다. (`hypo-auto-stage`/`hypo-auto-commit`은 git 스테이징용 훅이라 컨텍스트를 주입하지는 않지만, 스테이징 판단에도 `.hypoignore`를 참고합니다.) 비밀 정보는 위키에 두지 마시고, `HYPO_DIR` 아래에 민감한 내용을 저장하기 전에 `.hypoignore` 패턴을 먼저 점검하세요.
398
400
 
399
401
  > git sync 범위: Hypomnema는 `~/hypomnema/` 위키 자체만 git sync합니다. 단 `init` / `upgrade`는 `~/.claude/` 안의 관리 대상 영역(Hypomnema 자체 hook `~/.claude/hooks/`, 슬래시 커맨드 `~/.claude/commands/hypo/`, `settings.json` 등록)을 설치·SHA 추적하고, extensions companion sync로 위키의 `~/hypomnema/extensions/`에 둔 `agents/`·`commands/`·`hooks/`·`skills/`도 미러링합니다(직접 `init`·`upgrade --apply`·`capture`를 돌릴 때. `--codex`면 `hooks`·`commands` 부분집합만 `~/.codex/`로). 이 관리 대상 바깥에 있는 `~/.claude/` 콘텐츠는 일부러 관리하지 않습니다. 위키를 거치지 않는 기타 agent/skill, 머신 고유 `settings.local.json` 같은 일반 Claude Code 설정의 기기 간 동기화는 [chezmoi](https://www.chezmoi.io/) 같은 별도 dotfiles 매니저를 권합니다.
400
402
 
package/README.md CHANGED
@@ -249,11 +249,11 @@ Nine commands cover the full capture → retrieval → consolidation cycle.
249
249
  |---|---|---|
250
250
  | `hypo-close-guard.mjs` | `PreToolUse` | When a Write/Edit/MultiEdit looks like a session-close write and the transcript carries no close signal from you, ask before it lands |
251
251
  | `hypo-session-start.mjs` | `SessionStart` | Inject `hot.md` / `session-state.md` + `git pull --ff-only` |
252
- | `hypo-first-prompt.mjs` | `UserPromptSubmit` | Marker-based one-shot `hot.md` injection (10-min TTL) |
252
+ | `hypo-first-prompt.mjs` | `UserPromptSubmit` | Marker-based one-shot request for a resume line on the first prompt (10-min TTL). It does not re-read `hot.md` |
253
253
  | `hypo-lookup.mjs` | `UserPromptSubmit` | BM25 top-3 HIT inject / MISS → closest-slug signal |
254
- | `hypo-compact-guard.mjs` | `UserPromptSubmit` | Detect a typed `/compact` or `/clear`, and enforce the session-close checklist before context is wiped |
255
- | `hypo-cwd-change.mjs` | `CwdChanged` | Inject the matching project's `hot.md` |
256
- | `hypo-file-watch.mjs` | `FileChanged` | Notify on wiki-file changes (honors `.hypoignore`; matched paths are never re-emitted into LLM context) |
254
+ | `hypo-compact-guard.mjs` | `UserPromptSubmit` | Detect a typed `/compact` or `/clear` and report an incomplete session close. It never blocks the compact |
255
+ | `hypo-cwd-change.mjs` | `CwdChanged` | Build context from the matching project's `hot.md`. `CwdChanged` has no output field Claude Code forwards to the model, so this does not currently reach Claude |
256
+ | `hypo-file-watch.mjs` | `FileChanged` | Build context for a changed wiki file (honors `.hypoignore`). Nothing in this package registers watch paths, so the event has no trigger here, and `FileChanged` output does not reach Claude either way |
257
257
  | `hypo-auto-stage.mjs` | `PostToolUse(Write/Edit/MultiEdit)` | Auto-stage wiki-file edits |
258
258
  | `hypo-auto-commit.mjs` | `Stop` | Auto commit + pull + push |
259
259
  | `hypo-hot-rebuild.mjs` | `Stop` | Rebuild the root `hot.md` pointer table (structure + dates) |
@@ -287,7 +287,9 @@ Every maintenance command also runs from a plain shell, which is what you need w
287
287
 
288
288
  ### Claude Agent Skills
289
289
 
290
- The six synthesis-heavy commands (`ingest`, `query`, `crystallize`, `lint`, `verify`, `graph`) are also exposed as Claude Agent Skills in `skills/<name>/SKILL.md`, so they auto-trigger when the conversation matches their description, with no slash command required. A seventh, `debate`, is a skill only: it has no file under `commands/`, but the plugin loads `skills/` too, so `/hypo:debate` works like the rest. It runs a structured three-phase review (interrogate, verify, synthesize) to re-verify a wiki claim or harden a hard-to-reverse decision into an ADR. It is absent from the command tables above because those count the files under `commands/`, not what you can type.
290
+ Every file under `commands/` is itself a Claude Agent Skill, so any of them auto-triggers when the conversation matches its description, with no slash command required. (Measured on a plugin install; whether the npm path's `~/.claude/commands/hypo/` copies auto-trigger the same way has not been measured.) You will notice this most on the synthesis-heavy ones (`ingest`, `query`, `crystallize`, `lint`, `verify`, `graph`). `debate` is the one that lives the other way around, in `skills/debate/SKILL.md` with no file under `commands/`; the plugin loads `skills/` too, so `/hypo:debate` works like the rest. It runs a structured three-phase review (interrogate, verify, synthesize) to re-verify a wiki claim or harden a hard-to-reverse decision into an ADR. It is absent from the command tables above because those count the files under `commands/`, not what you can type.
291
+
292
+ Each name ships from exactly one of the two directories. A flat `commands/<name>.md` and a directory `skills/<name>/SKILL.md` both claim `/hypo:<name>`, and shipping the pair does not give you one surface for people and another for the model: only one wins and the other is dead weight. `npm run smoke:plugin` fails on a collision. That gate covers this package's own `commands/` and `skills/`; your own extensions under `~/hypomnema/extensions/` are not checked, so a `commands/mine.md` and a `skills/mine/` you captured yourself can still both sync and collide the same way.
291
293
 
292
294
  | Say this | Skill it triggers |
293
295
  |---|---|
@@ -394,7 +396,7 @@ Place a `hypo-config.md` at the wiki root to make it portable across machines wi
394
396
 
395
397
  > If a session-close write finds its target changed since this session read it, Hypomnema does not overwrite it. It parks the bytes under `<wiki>/.cache/proposals/` and tells you. Review and apply them yourself: `hypomnema proposal list`, then `hypomnema proposal apply <id>` (at a terminal) or `hypomnema proposal discard <id>`. Inside a session, where there is no terminal to type at, the same approval is collected in the conversation: `hypomnema proposal challenge` prints the diffs, you type the line it gives you, and `hypomnema proposal resolve` writes exactly what you saw. There is no auto-apply.
396
398
 
397
- > Provider transmission disclaimer: Hypomnema hooks emit wiki content into Claude Code's `additionalContext`, which is transmitted to the Claude model provider as part of the prompt. `.hypoignore` is enforced at every content-injection hook (`hypo-file-watch`, `hypo-session-start`, `hypo-cwd-change`, `hypo-lookup`) and at `ingest`, but any file _not_ matched by `.hypoignore` is fair game for transmission. (`hypo-auto-stage` and `hypo-auto-commit` are git-staging hooks, not injection points, and also honor `.hypoignore` for their staging decisions.) Keep secrets out of the wiki, and review `.hypoignore` patterns before storing anything sensitive under `HYPO_DIR`.
399
+ > Provider transmission disclaimer: Hypomnema hooks emit wiki content into Claude Code's `additionalContext`, which is transmitted to the Claude model provider as part of the prompt. That path actually reaches the model for `hypo-session-start` and `hypo-lookup`. `hypo-cwd-change` and `hypo-file-watch` fire on `CwdChanged` and `FileChanged`, two events for which Claude Code's hook output has no field that reaches the model, so the `additionalContext` those two hooks emit is currently dropped before transmission. `.hypoignore` is enforced at all four of these hooks (whether or not their output reaches the model) and at `ingest`, but any file _not_ matched by `.hypoignore` is fair game for transmission through the two hooks that do reach the model. (`hypo-auto-stage` and `hypo-auto-commit` are git-staging hooks, not injection points, and also honor `.hypoignore` for their staging decisions.) Keep secrets out of the wiki, and review `.hypoignore` patterns before storing anything sensitive under `HYPO_DIR`.
398
400
 
399
401
  > Scope of git sync: Hypomnema git-syncs only the `~/hypomnema/` wiki itself. `init` / `upgrade` do install and SHA-track a defined surface inside `~/.claude/`: Hypomnema's own hooks at `~/.claude/hooks/`, slash commands at `~/.claude/commands/hypo/`, and `settings.json` registrations. The extensions companion sync covers one more surface: whatever you put in `~/hypomnema/extensions/` (`agents/`, `commands/`, `hooks/`, `skills/`) lands in `~/.claude/`. With `--codex`, hooks and commands also go to `~/.codex/`. Anything _outside_ that defined surface in `~/.claude/` is intentionally not managed by Hypomnema. For general cross-machine sync of Claude Code config (other agents/skills not staged via the wiki, machine-specific `settings.local.json`, etc.), use a separate dotfiles manager such as [chezmoi](https://www.chezmoi.io/).
400
402
 
@@ -85,10 +85,12 @@ node ${CLAUDE_PLUGIN_ROOT}/scripts/crystallize.mjs \
85
85
  --apply-session-close \
86
86
  --payload=/tmp/hypo-session-close-<session-id>.json \
87
87
  --session-id=<current-session-id> \
88
- --hypo-dir="<path>" \
89
88
  --json
90
89
  ```
91
90
 
91
+ Add `--hypo-dir="<path>"` only when the user specified a Hypomnema directory explicitly;
92
+ otherwise omit it and the script resolves the root itself.
93
+
92
94
  **`--session-id` is required for any close that carries a `--payload`.** It is not a
93
95
  switch that turns a check on; omitting it fails the check. Before a single byte is
94
96
  written or committed, the apply resolves that session's transcript and looks for
@@ -99,7 +101,7 @@ and nothing on disk, in three cases:
99
101
  |---|---|
100
102
  | `session-id-required` | No `--session-id` was passed. |
101
103
  | `transcript-unresolved` | The id resolves to no transcript under `~/.claude/projects/`. |
102
- | `no-user-close-signal` | The transcript exists, and the user never asked to close. |
104
+ | `no-user-close-signal` | The transcript exists, but no close authority is in force right now. This one string collapses three different gate outcomes, and only one of them means the user never asked. Read `gateReason` (below) before deciding what to do about it. |
103
105
 
104
106
  A refusal is not a failure to route around. It means the close should not happen: ask
105
107
  the user, and re-run only after they say so.
@@ -129,6 +131,15 @@ no session id.
129
131
  | `--apply-session-close --payload=<path> --session-id=<id>` | The only apply path. Verifies close authority against that session's transcript **first**; on a refusal nothing is written. On success: per-field idempotent writes (no-op when bytes match), strict verification, lint gate, commit, and the per-session closed marker. Safe to re-run. |
130
132
  | `--apply-session-close --force` | Skips the probe early-exit. It does **not** skip the authority check, and `--payload` plus `--session-id` are still required to apply anything. |
131
133
 
134
+ When a refusal carries `reason: 'no-user-close-signal'`, the JSON also carries
135
+ `gateReason`, naming which of the three gate checks refused: `no-open` (the
136
+ transcript holds no user close signal at all), `transcript-rewrite-detected`
137
+ (the transcript changed under the gate), or `no-new-open-since-resolution` (the
138
+ close signal predates a resolution already recorded). The collapsed `reason`
139
+ stays the same string in all three cases, so read `gateReason` rather than
140
+ parsing the `Gate detail:` fragment out of `error`. The field is absent for
141
+ every other refusal.
142
+
132
143
  **Two lint gates run automatically, scoped to the files this close writes:**
133
144
 
134
145
  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).
@@ -169,7 +180,12 @@ If `markerWritten: true`: ask: "Session closed. Would you like to also run knowl
169
180
 
170
181
  - `session-id-required`: you omitted `--session-id`. Pass the main conversation's id and re-run.
171
182
  - `transcript-unresolved`: the id resolved no transcript, so it is almost certainly not the main conversation's (a background-task or Agent-thread uuid, most often). Get the right one and re-run.
172
- - `no-user-close-signal`: the transcript is this session's, and the user never asked to close in wording the gate recognizes (e.g. "세션 마무리까지 진행해줘" falls outside the close-signal set). Re-running the same id changes nothing, because the transcript is unchanged. Confirm intent once with `AskUserQuestion`, header "세션", a single option labelled **세션 마무리** (설명: "이 세션을 마무리하고 close 마커를 기록"). If the user picks it, that answer lands in the transcript as a recognized close signal, so re-running the exact same command now applies **everything**: the writes, the commit, and the marker. If the user declines, the session stays open and nothing is written. Do NOT touch the close-signal matcher itself, and do not hand-write the files to work around the refusal.
183
+ - `no-user-close-signal`: the transcript is this session's, but no close authority is in force. **Branch on `gateReason` before doing anything.** The three cases need three different responses, and treating them alike is what made this refusal look like it had a new cause every time it appeared.
184
+ - `no-open`: the user genuinely never asked to close in wording the gate recognizes (e.g. "세션 마무리까지 진행해줘" falls outside the close-signal set). Re-running the same id changes nothing, because the transcript is unchanged. Confirm intent once with `AskUserQuestion`, header "세션", a single option labelled **세션 마무리** (설명: "이 세션을 마무리하고 close 마커를 기록"). If the user picks it, that answer lands in the transcript as a recognized close signal, so re-running the exact same command now applies **everything**: the writes, the commit, and the marker. If the user declines, the session stays open and nothing is written.
185
+ - `no-new-open-since-resolution`: the user DID ask, and that request was already resolved by an earlier close. Asking again makes them answer a question they have already answered. Report that this session is already closed and stop; do not re-prompt.
186
+ - `transcript-rewrite-detected`: the transcript changed underneath the gate, so the earlier signal can no longer be attested. This is not a statement about what the user wants. Say what happened rather than asking them to repeat themselves, and let a human decide.
187
+
188
+ In all three: do NOT touch the close-signal matcher itself, and do not hand-write the files to work around the refusal.
173
189
 
174
190
  If the apply succeeded but `markerWritten: false`, do NOT say "session closed." Branch on `markerSkipReason` (`compact-gate-not-ok`, `commit-failed: …`, `marker-did-not-land`): surface the reason verbatim and address it (resolve the compact blocker, fix the git / disk issue) before re-running.
175
191
 
@@ -182,9 +198,14 @@ If the user says stop, end here. Otherwise continue to Step 5.
182
198
  Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema upgrade --apply` (or `/hypo:upgrade` on a plugin install) or reinstall instead of guessing the cache layout.
183
199
 
184
200
  ```bash
185
- node ${CLAUDE_PLUGIN_ROOT}/scripts/crystallize.mjs [--hypo-dir="<path>"] [--min-group=2]
201
+ node ${CLAUDE_PLUGIN_ROOT}/scripts/crystallize.mjs --min-group=2
186
202
  ```
187
203
 
204
+ Add `--hypo-dir="<path>"` only when the user specified a Hypomnema directory explicitly;
205
+ otherwise omit it.
206
+
207
+ An unrecognized flag exits 2 instead of being ignored.
208
+
188
209
  Show the output to the user. If no candidates are found, tell them Hypomnema looks well-connected and no crystallization is needed.
189
210
 
190
211
  ---
@@ -255,10 +276,15 @@ Show what was created or modified, and offer to run `/hypo:lint` to verify all n
255
276
  `--check-session-close` (read-only strict gate, same check PreCompact runs) is still supported as a probe-only verification. Use it when you only want to verify that today's session-close is complete without applying anything:
256
277
 
257
278
  ```bash
258
- node ${CLAUDE_PLUGIN_ROOT}/scripts/crystallize.mjs --check-session-close [--hypo-dir="<path>"]
279
+ node ${CLAUDE_PLUGIN_ROOT}/scripts/crystallize.mjs --check-session-close
259
280
  ```
260
281
 
261
- 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".)
282
+ Add `--hypo-dir="<path>"` only when the user specified a Hypomnema directory explicitly;
283
+ otherwise omit it.
284
+
285
+ An unrecognized flag exits 2 instead of being ignored.
286
+
287
+ It reports any file as `missing` or `stale`. For an actual close, prefer `--apply-session-close --payload=<path>` (Step 3): it bundles freshness and lint into one gate and is the documented dogfood path. `parseArgs` only accepts the `--payload=<path|->` spelling (a path, or `-` for stdin); a space-separated `--payload <path>` is rejected outright with exit 2, not silently dropped.
262
288
 
263
289
  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.
264
290
 
package/commands/graph.md CHANGED
@@ -23,12 +23,15 @@ If the user specified a Hypomnema directory, pass it as `--hypo-dir="<path>"`. O
23
23
  ## Step 2 — Run the graph script
24
24
 
25
25
  ```bash
26
- node ${CLAUDE_PLUGIN_ROOT}/scripts/graph.mjs \
27
- [--hypo-dir="<path>"] \
28
- [--format=json|mermaid|dot] \
29
- [--min-edges=<n>]
26
+ node ${CLAUDE_PLUGIN_ROOT}/scripts/graph.mjs
30
27
  ```
31
28
 
29
+ Add `--hypo-dir="<path>"` only when the user gave one (per Step 1). Add `--format=<fmt>`
30
+ and `--min-edges=<n>` only with one concrete value from the Options list below, for
31
+ example `--format=mermaid`; the defaults (`json`, `0`) apply when they are omitted.
32
+
33
+ An unrecognized flag exits 2 instead of being ignored.
34
+
32
35
  Options:
33
36
  - `--format=json` (default) — adjacency list with in/out degree counts
34
37
  - `--format=mermaid` — Mermaid `graph TD` diagram (paste into a Markdown code block)
package/commands/lint.md CHANGED
@@ -25,10 +25,17 @@ If the user specified a Hypomnema directory, pass it as `--hypo-dir="<path>"`. O
25
25
  ## Step 2 — Run lint
26
26
 
27
27
  ```bash
28
- node ${CLAUDE_PLUGIN_ROOT}/scripts/lint.mjs [--hypo-dir="<path>"] [--json] [--fix]
28
+ node ${CLAUDE_PLUGIN_ROOT}/scripts/lint.mjs --json
29
29
  ```
30
30
 
31
+ Add `--hypo-dir="<path>"` only when the user gave one (per Step 1); otherwise leave it
32
+ out and the script resolves the root itself.
33
+
34
+ An unrecognized flag exits 2 instead of being ignored. Do not add `--fix` on this first
35
+ pass; Step 4 covers when to offer it and re-run with it only after the user agrees.
36
+
31
37
  Options:
38
+ - `--hypo-dir=<path>` (optional): Hypomnema root, pass only when the user specified one explicitly
32
39
  - `--json` — output results as JSON (useful for tooling)
33
40
  - `--fix` — auto-add missing `updated` field (safe repairs only; no other fields are modified)
34
41
 
package/commands/query.md CHANGED
@@ -22,15 +22,17 @@ Ask the user what they want to know if it was not provided in the command invoca
22
22
 
23
23
  Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema upgrade --apply` (or `/hypo:upgrade` on a plugin install) or reinstall instead of guessing the cache layout.
24
24
 
25
+ If the user specified a Hypomnema directory, pass it as `--hypo-dir="<path>"`. Otherwise
26
+ omit the flag.
27
+
25
28
  Run full-text search:
26
29
 
27
30
  ```bash
28
- node ${CLAUDE_PLUGIN_ROOT}/scripts/query.mjs \
29
- --q="<query terms>" \
30
- [--hypo-dir="<path>"] \
31
- [--limit=10]
31
+ node ${CLAUDE_PLUGIN_ROOT}/scripts/query.mjs --q="<query terms>" --limit=10
32
32
  ```
33
33
 
34
+ An unrecognized flag exits 2 instead of being ignored.
35
+
34
36
  ---
35
37
 
36
38
  ## Step 3 — Read relevant pages
@@ -29,6 +29,7 @@ When `--project` is omitted, the script prefers the project whose `working_dir`
29
29
  ## Step 2 — Present session state
30
30
 
31
31
  Show the output from the script:
32
+ - **`[WIKI: ...]` notice**, if present, first and verbatim. It names uncommitted changes elsewhere in the vault (another project, or unattributable to any project) that are not this session's scope. Do not fold this line into the project summary or fetch/edit the paths it names without the user's explicit instruction.
32
33
  - **Project** name
33
34
  - **Next tasks** from `session-state.md`
34
35
  - **Background** from `hot.md` (what was done last session, condensed)
@@ -15,11 +15,22 @@ You are running `/hypo:verify`. Audit wiki pages for overdue or missing `verify_
15
15
 
16
16
  Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema upgrade --apply` (or `/hypo:upgrade` on a plugin install) or reinstall instead of guessing the cache layout.
17
17
 
18
+ If the user specified a Hypomnema directory, pass it as `--hypo-dir="<path>"`. Otherwise
19
+ omit the flag.
20
+
18
21
  ```bash
19
- node ${CLAUDE_PLUGIN_ROOT}/scripts/verify.mjs [--hypo-dir="<path>"] [--file=<path>]
22
+ node ${CLAUDE_PLUGIN_ROOT}/scripts/verify.mjs
20
23
  ```
21
24
 
25
+ Add `--file="<path>"` only to check a single page (for example, right after editing it).
26
+ Omitting it scans the whole vault (`pages/` and `projects/`); the script skips a `--file`
27
+ path it cannot read rather than erroring, so passing one that does not exist reports
28
+ "nothing to verify" instead of failing loudly.
29
+
30
+ An unrecognized flag exits 2 instead of being ignored.
31
+
22
32
  Options:
33
+ - `--hypo-dir=<path>` (optional): Hypomnema root, pass only when the user specified one explicitly
23
34
  - `--file=<path>` — check a single page only (useful after editing a page)
24
35
 
25
36
  ---
@@ -47,7 +47,7 @@ hypomnema/
47
47
  │ ├── frontmatter.mjs
48
48
  │ ├── hypo-root.mjs
49
49
  │ └── hypo-ignore.mjs
50
- ├── skills/ ← Agent Skills — skills/<name>/SKILL.md
50
+ ├── skills/ ← directory-form Agent Skills (skills/<name>/SKILL.md)
51
51
  ├── templates/ ← baseline files copied on init
52
52
  │ ├── hypo-config.md, index.md, hot.md, log.md, SCHEMA.md, hypo-guide.md
53
53
  │ ├── Home.md, Overview.md, hypo-automation.md, hypo-help.md
@@ -79,15 +79,29 @@ Each user-facing operation is a pair: an LLM-facing prompt + a Node.js script.
79
79
 
80
80
  **Commands shipped in v1.1:** `init`, `doctor`, `upgrade`, `uninstall`, `ingest`, `query`, `crystallize`, `resume`, `feedback`, `verify`, `lint`, `stats`, `graph`.
81
81
 
82
- The synthesis-heavy commands (`ingest`, `query`, `crystallize`, `lint`, `verify`, `graph`) are also exposed as Agent Skills.
82
+ Every one of those files is itself an Agent Skill; see below.
83
83
 
84
84
  ---
85
85
 
86
86
  ## Agent Skills
87
87
 
88
- `skills/<name>/SKILL.md` follows the Claude Agent Skills convention. When a conversation matches the skill's description, Claude auto-loads it without needing the slash command.
88
+ A flat `commands/<name>.md` and a directory `skills/<name>/SKILL.md` are the same kind of
89
+ component to the plugin loader: both are Agent Skills, both claim `/hypo:<name>`, and both
90
+ auto-load when a conversation matches their `description`. (That is measured on a plugin
91
+ install. `init`/`upgrade` do not deploy the plugin's own `skills/` at all, so on the npm path
92
+ only `commands/` is installed — into `~/.claude/commands/hypo/` — and whether those copies
93
+ auto-trigger the same way has not been measured.) The directory form is what a skill needs when
94
+ it carries more than one file (`skills/debate/references/`); the flat form is everything else.
89
95
 
90
- > v1.0 originally planned flat `skills/*.md` files; v1.1 switched to `<name>/SKILL.md` for compatibility with the official Agent Skills loader.
96
+ **One name ships from one directory, never both** (in this package; the extensions channel
97
+ under `~/hypomnema/extensions/` is not covered by the gate and can still sync a co-named pair)**.** Shipping a pair does not give you a
98
+ human-facing surface and a model-facing surface: nothing in the frontmatter separates them, only
99
+ one wins, and the loser is dead weight that still costs always-on tokens and still appears in
100
+ `claude plugin details`. Six pairs shipped that way from v1.0.0 until 2026-09-04, and `commands/`
101
+ won every one of them. `scripts/smoke-plugin.mjs` now fails the build on a collision.
102
+
103
+ > v1.0 originally planned flat `skills/*.md` files and shipped `<name>/SKILL.md` instead; both
104
+ > forms have loaded since v1.0.0. The 2026-09-04 correction is that they are not two surfaces.
91
105
 
92
106
  ---
93
107
 
@@ -114,16 +128,16 @@ Hooks run automatically at Claude Code lifecycle events. They are deployed to `~
114
128
  | Hook | Responsibility |
115
129
  |---|---|
116
130
  | `hypo-session-start` | Inject `index.md`, root `hot.md`, project `hot.md`/`session-state.md`. Run `git pull --ff-only` (silent fail on missing remote) |
117
- | `hypo-first-prompt` | Marker-based one-shot `hot.md` injection on first user prompt (10-min TTL) — for sessions that bypass `SessionStart` |
131
+ | `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` |
118
132
  | `hypo-lookup` | BM25 search over the wiki on every prompt. **HIT** → inject top-3 page snippets (≤2000 chars each; a page whose `verify_by_date` is overdue gets a `[STALE verify_by_date=…]` marker prepended). **MISS** → emit closest-slug signal that prompts Claude to research + `/hypo:ingest` |
119
- | `hypo-compact-guard` | Detect `/compact` invocations → enforce session-close checklist before allowing compact |
133
+ | `hypo-compact-guard` | Detect `/compact` or `/clear` typed in chat and, if session close is incomplete, tell Claude so. It never blocks `/compact` |
120
134
  | `hypo-personal-check` | PreCompact detection: lint blockers, uncommitted changes, missing session-log entries surface as a `systemMessage`; `/compact` is never blocked here |
121
135
  | `hypo-auto-stage` | After Write/Edit on a wiki path, run `git add` (skips paths matching `.hypoignore`) |
122
136
  | `hypo-hot-rebuild` | At session stop, regenerate root `hot.md` from recent activity; emit growth metrics + cache for next SessionStart |
123
137
  | `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) |
124
138
  | `hypo-auto-commit` | At session stop, filter changed paths through `.hypoignore`, commit non-ignored changes, `git pull --no-rebase` + `git push` (silent fail on missing remote) |
125
- | `hypo-cwd-change` | When working directory changes, re-resolve the active project and inject its `hot.md` |
126
- | `hypo-file-watch` | Notify on external wiki edits so the in-session view stays consistent |
139
+ | `hypo-cwd-change` | When working directory changes, re-resolve the active project and build `additionalContext` with its `hot.md`; `CwdChanged` has no field Claude Code forwards to the model, so this does not currently reach Claude |
140
+ | `hypo-file-watch` | Build `additionalContext` for a changed wiki file. Two things keep this inert: nothing in this package returns `watchPaths`, so the event has no registered trigger, and `FileChanged` output does not reach the model. The event also fires regardless of who changed the file, so it is not an external-edit signal |
127
141
 
128
142
  ### Deployment constraint
129
143
 
@@ -149,7 +163,7 @@ Scripts in `scripts/` are not deployed — they run from the package install pat
149
163
  | `hypoIsClean()` | Check git status + unpushed commits |
150
164
  | `hotMdIsClean()` | Validate `hot.md` structure |
151
165
  | `isCompactCommand(prompt)` | Detect `/compact` invocations |
152
- | `buildOutput(...)` | Format hook output for Claude Code's `additionalContext` channel |
166
+ | `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 |
153
167
  | `SESSION_STATE_NEXT_HEADINGS` | Allowed headings for "next tasks" — `## 다음 이어받기` / `## 다음 작업`. Lint reuses this constant (DRY) |
154
168
 
155
169
  ---
@@ -284,7 +298,7 @@ SessionStart
284
298
  ├─► UserPromptSubmit (every prompt)
285
299
  │ ├─► hypo-first-prompt.mjs (one-shot, 10min TTL)
286
300
  │ ├─► hypo-lookup.mjs (BM25 inject)
287
- │ └─► hypo-compact-guard.mjs (block /compact when checklist incomplete)
301
+ │ └─► hypo-compact-guard.mjs (report incomplete checklist; never blocks /compact)
288
302
  │
289
303
  ├─► PostToolUse(Write/Edit)
290
304
  │ └─► hypo-auto-stage.mjs (git add)
@@ -293,7 +307,7 @@ SessionStart
293
307
  │ └─► hypo-personal-check.mjs (lint + session-close gate)
294
308
  │
295
309
  ├─► CwdChanged
296
- │ └─► hypo-cwd-change.mjs (re-inject project hot.md)
310
+ │ └─► hypo-cwd-change.mjs (builds hot.md context; does not currently reach Claude)
297
311
  │
298
312
  └─► Stop
299
313
  ├─► hypo-hot-rebuild.mjs (regenerate root hot.md + growth cache)
@@ -385,7 +399,7 @@ If `git status` shows no `.md` changes, the diff step is skipped — Stop hook f
385
399
 
386
400
  ### Citation convention
387
401
 
388
- The six writer-side skills (`crystallize`, `query`, `ingest`, `verify`, `graph`, `lint`) carry an identical footer instructing Claude to cite wiki pages inline as `[[page-slug]]`, which keeps them connected in the graph. The observability audit does not scan for these inline citations: it scores sessions on tool and command usage recorded by `hypo-session-record` (search / ingest / feedback counts). Counting citations as a signal is a possible future iteration, not current behavior.
402
+ **The convention is current.** It is stated in `commands/query.md`, in `commands/crystallize.md`, and in `templates/hypo-guide.md` §8, which `init` writes into a vault that does not already have it (an existing copy is left alone). What went away on 2026-09-04 is one particular copy of it: an identical footer written into six `skills/*/SKILL.md` files, which were the losing half of the name collision above and never reached anyone. Whether to repeat that footer in the remaining `commands/*.md` is open. The observability audit never depended on it either way: it scores sessions on tool and command usage recorded by `hypo-session-record` (search / ingest / feedback counts), not on inline citations.
389
403
 
390
404
  ---
391
405
 
@@ -45,12 +45,12 @@ See [ARCHITECTURE.md](ARCHITECTURE.md) for the full breakdown. Short version:
45
45
 
46
46
  | Path | What lives there |
47
47
  |---|---|
48
- | `commands/` | LLM-facing prompts for `/hypo:*` slash commands |
48
+ | `commands/` | LLM-facing prompts for `/hypo:*` slash commands — each is also an Agent Skill (auto-triggers on its `description`) |
49
49
  | `scripts/` | Node.js implementations called by the commands |
50
50
  | `scripts/lib/` | Shared helpers for scripts (`frontmatter`, `hypo-root`, `hypo-ignore`) |
51
51
  | `hooks/` | Lifecycle hooks + `hooks.json` registry |
52
52
  | `hooks/hypo-shared.mjs` | Shared hook utilities — read the deployment constraint below |
53
- | `skills/<name>/SKILL.md` | Agent Skills (auto-trigger via description match) |
53
+ | `skills/<name>/SKILL.md` | Directory-form Agent Skills — only for a skill needing more than one file (`skills/debate/references/`). The name must not collide with `commands/` |
54
54
  | `templates/` | Files copied into new wiki vaults on init |
55
55
  | `tests/` | Test suite (no external deps): `harness.mjs`, `helpers.mjs`, `runner.mjs` (entry), and one `<area>.test.mjs` per production area |
56
56
  | `docs/` | ARCHITECTURE, CONTRIBUTING |
@@ -77,7 +77,7 @@ Scripts under `scripts/` are not deployed and may freely import from `scripts/li
77
77
 
78
78
  1. Edit `commands/<name>.md` — the LLM-facing prompt.
79
79
  2. Edit `scripts/<name>.mjs` — the Node.js logic.
80
- 3. If the command is new and synthesis-heavy, add `skills/<name>/SKILL.md`.
80
+ 3. Do **not** add `skills/<name>/SKILL.md` for it. A `commands/*.md` file is already an Agent Skill and auto-triggers on its `description`; a same-named directory skill claims the same `/hypo:<name>`, and `npm run smoke:plugin` fails on the collision. To change when it auto-triggers, edit that `description`.
81
81
  4. Update the command table in `README.md` and `README.ko.md`.
82
82
  5. Add coverage to the matching `tests/<area>.test.mjs` (a new command usually means a new area file).
83
83
 
@@ -109,10 +109,17 @@ If you need to share new logic, prefer extending an existing helper over adding
109
109
 
110
110
  ### Adding an Agent Skill
111
111
 
112
- 1. Create `skills/<name>/SKILL.md`.
113
- 2. The frontmatter must include `name`, `description`, and the trigger criteria.
112
+ Use the directory form only when the skill needs more than one file (`skills/debate/references/`).
113
+ Everything else belongs in `commands/`, which loads the same way.
114
+
115
+ 1. Create `skills/<name>/SKILL.md`. **The name must not collide with any `commands/<name>.md`** —
116
+ both claim `/hypo:<name>`, only one wins, and `npm run smoke:plugin` fails on the pair.
117
+ 2. The frontmatter must include a `description` carrying the trigger criteria. `name` is
118
+ optional — omit it and the directory name is the invocation name (`skills/debate/SKILL.md`
119
+ does this). If you do set it, it must equal the directory name; `smoke:plugin` fails otherwise.
114
120
  3. The skill body is the LLM prompt — keep it focused on the synthesis task.
115
- 4. Add to the skills inventory in `README.md`.
121
+ 4. Describe it in the **Claude Agent Skills** section of `README.md` and `README.ko.md`, the way
122
+ `debate` is described there. (There is no separate skills inventory table.)
116
123
 
117
124
  ---
118
125
 
@@ -122,6 +129,8 @@ If you need to share new logic, prefer extending an existing helper over adding
122
129
  npm test # tests/*.test.mjs, sharded across processes — unit + smoke + contract
123
130
  npm run lint # scripts/lint.mjs — frontmatter + wikilink validation + W8 (design-history stale
124
131
  # vs session-log) + W14 (design-history missing but session-log implies one)
132
+ # + W15 (synthesis older than the newest page in its sources_consulted)
133
+ # + W16 (sources_consulted names W15 could not resolve)
125
134
  npm run fix:verify # Phase 1 of learned_behavior #6 — verifies fix #N status claims in
126
135
  # a wiki spec against `// @fix #N: <test-name>` anchors, read as a
127
136
  # union across every tests/*.mjs. Maintainer dogfood; needs a wiki at