hypomnema 1.6.1 → 1.7.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 (62) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.ko.md +39 -14
  4. package/README.md +39 -14
  5. package/commands/capture.md +8 -6
  6. package/commands/crystallize.md +39 -20
  7. package/docs/ARCHITECTURE.md +49 -14
  8. package/docs/CONTRIBUTING.md +31 -29
  9. package/hooks/base-store.mjs +265 -0
  10. package/hooks/hooks.json +7 -1
  11. package/hooks/hypo-auto-minimal-crystallize.mjs +77 -31
  12. package/hooks/hypo-auto-stage.mjs +30 -0
  13. package/hooks/hypo-cwd-change.mjs +31 -2
  14. package/hooks/hypo-file-watch.mjs +21 -2
  15. package/hooks/hypo-first-prompt.mjs +19 -3
  16. package/hooks/hypo-lookup.mjs +86 -29
  17. package/hooks/hypo-personal-check.mjs +24 -3
  18. package/hooks/hypo-session-record.mjs +2 -3
  19. package/hooks/hypo-session-start.mjs +89 -7
  20. package/hooks/hypo-shared.mjs +944 -147
  21. package/hooks/proposal-store.mjs +513 -0
  22. package/package.json +40 -14
  23. package/scripts/capture.mjs +556 -37
  24. package/scripts/crystallize.mjs +639 -108
  25. package/scripts/doctor.mjs +304 -9
  26. package/scripts/feedback-sync.mjs +515 -44
  27. package/scripts/graph.mjs +9 -2
  28. package/scripts/init.mjs +230 -34
  29. package/scripts/lib/extensions.mjs +656 -1
  30. package/scripts/lib/hypo-ignore.mjs +54 -6
  31. package/scripts/lib/hypo-root.mjs +56 -6
  32. package/scripts/lib/page-usage.mjs +15 -2
  33. package/scripts/lib/pkg-json.mjs +40 -0
  34. package/scripts/lib/plugin-detect.mjs +96 -6
  35. package/scripts/lib/wd-match.mjs +23 -5
  36. package/scripts/lib/wikilink.mjs +32 -6
  37. package/scripts/lint.mjs +20 -4
  38. package/scripts/proposal.mjs +1032 -0
  39. package/scripts/query.mjs +25 -4
  40. package/scripts/resume.mjs +34 -12
  41. package/scripts/stats.mjs +28 -8
  42. package/scripts/uninstall.mjs +141 -6
  43. package/scripts/upgrade.mjs +197 -15
  44. package/skills/crystallize/SKILL.md +44 -7
  45. package/skills/debate/SKILL.md +88 -0
  46. package/skills/debate/references/orchestration-patterns.md +83 -0
  47. package/templates/.hyposcanignore +10 -0
  48. package/templates/SCHEMA.md +12 -0
  49. package/templates/gitignore +5 -0
  50. package/templates/hypo-config.md +1 -1
  51. package/templates/hypo-guide.md +6 -0
  52. package/scripts/.gitkeep +0 -0
  53. package/scripts/check-bilingual.mjs +0 -153
  54. package/scripts/check-readme-version.mjs +0 -126
  55. package/scripts/check-tracker-ids.mjs +0 -426
  56. package/scripts/check-versions.mjs +0 -171
  57. package/scripts/install-git-hooks.mjs +0 -293
  58. package/scripts/lib/changelog-classify.mjs +0 -216
  59. package/scripts/lib/check-bilingual.mjs +0 -244
  60. package/scripts/lib/check-tracker-ids.mjs +0 -217
  61. package/scripts/lib/pre-commit-format.mjs +0 -251
  62. package/scripts/pre-commit-format.mjs +0 -198
@@ -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.6.1",
14
+ "version": "1.7.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.6.1",
3
+ "version": "1.7.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,22 +25,22 @@ Andrej Karpathy의 "LLM 네이티브 위키" 스케치에서 출발했습니다.
25
25
 
26
26
  ### 지금 어디까지 자동인가
27
27
 
28
- 현재 릴리스는 v1.6.1입니다. 조건부나 유예된 세션 마무리에서 미커밋이나 진행 중인 작업이 남아 문구가 모호할 때 조기 nag 대신 지금 닫을지 되묻고, `working_dir` provenance를 강화해 앵커가 없던 프로젝트도 `cwd`-first resume이 앵커하도록 고쳤습니다. 그 앞 v1.6.0은 역방향 확장 capture를 완성했습니다. `hypomnema capture`(그리고 `/hypo:capture` 슬래시 커맨드)가 `~/.claude/` 아래에 보통 방식으로 만든 command·agent·hook을 위키로 끌어와, forward-sync가 다른 머신에 원래 이름 그대로 전파합니다. 손으로 만든 확장의 "한 머신에서 등록, 다른 머신에서 동기화" 갭을 데몬 없이 메웁니다. 그 앞 v1.5.1은 위키가 자기 신선도를 알리는 방식을 다듬었습니다. `verify_by_date`가 지난 페이지는 주입되는 순간 `[STALE ...]`로 표시되고, lookup 사용을 로컬에서 추적해 링크됐지만 한 번도 주입되지 않은 페이지를 crystallize가 cold 후보로 표면화하며, 표준 `session | project: title` 콜론 로그 포맷을 stale로 오진하던 세션 마무리도 견고해졌습니다. 다시 그 앞 v1.5.0은 머신 간 안정성에 집중했습니다. git 동기화 볼트의 `cwd`-first resume와 코드 레포 세션에 위키 위치를 안내하는 것이었습니다.
29
-
30
28
  위키 작업(자료 정리·검색·세션 마무리)은 아직 `/hypo:*` 명령이나 자연어로 시작합니다. v2의 목표는 Claude가 시키지 않아도 위키를 읽고 쓰고 합성하는 완전 자율 동작이고, 지금 그쪽으로 가는 중입니다.
31
29
 
32
- 이미 자동으로 도는 영역은 다음과 같습니다. v1.1.0은 위키가 한 세션에서 얼마나 쓰였는지 재는 관측성 점수를 넣었고, v1.2.0은 영역을 자동화했습니다.
30
+ 이미 자동으로 도는 영역은 다음과 같습니다. 위키가 한 세션에서 얼마나 쓰였는지 재는 관측성 점수가 있고, 여러 영역이 요청 없이 돌아갑니다.
33
31
 
32
+ - 신선도 신호. `verify_by_date`가 지난 페이지는 세션 시작 주입이나 lookup 주입 시 `[STALE ...]`로 표시되고, lookup 사용을 로컬에서 추적해 최근 90일간 lookup으로 주입되지 않은 페이지를 crystallize가 cold 후보로 표면화합니다.
34
33
  - 피드백 한 곳만 고치면 나머지는 따라옵니다. `pages/feedback/`에 한 번 적으면 위키가 `MEMORY.md`와 `~/.claude/CLAUDE.md`의 `<learned_behaviors>` 블록을 자동으로 갱신합니다(단방향 projection).
35
- - 확장 파일도 함께 동기화. 위키 `~/hypomnema/extensions/{agents,commands,hooks,skills}/`에 파일을 `~/.claude/`에 자동 반영합니다. `--codex`를 붙이면 `hooks`·`commands`는 `~/.codex/`에도 반영하지만, `agents`와 `skills`는 Claude 전용이라 건너뜁니다.
36
- - 역방향 capture는 반대로 갑니다. `hypomnema capture`(또는 `/hypo:capture`)는 `~/.claude/{commands,agents}/`에 보통 방식으로 만든 command·agent나 `~/.claude/settings.json`에 등록한 정규형 hook을 위키로 끌어와, 이후 다른 머신에 원래 이름 그대로 동기화되게 합니다. hook은 정규형으로 무손실 라운드트립되는 것만 capture하고, 나머지는 이유와 함께 건너뜁니다. capture는 명시적이며 내용이 다른 위키 파일은 덮어쓰지 않습니다. skill은 아직 capture 대상이 아닙니다.
34
+ - `cd`만 해도 프로젝트를 알아보고 재개 정보를 띄웁니다. git 동기화된 프로젝트 디렉터리로 이동하면 대응하는 위키 프로젝트의 `hot.md`가 자동으로 주입되어, 코드 레포 세션이 위키 위치와 최근 상황을 바로 압니다.
37
35
  - 프로젝트 자동 생성. 프로젝트 표식(`package.json`·`Cargo.toml` 등)이 있는 git 저장소로 `cd` 했는데 대응하는 위키 프로젝트가 없으면 만들지 물어봅니다.
38
- - 세션 종료 자동 정리와 `/clear` 복구. 의미 있는 세션이 끝나면 "마무리 메모를 짧게 남길까요?"가 자동으로 뜹니다. 마무리하지 않은 채 `/clear`를 입력해도 다음 세션 시작 때 이어서 정리할 수 있습니다.
36
+ - 세션 종료 자동 정리와 `/clear` 복구. 의미 있는 세션이 끝나면 "마무리 메모를 짧게 남길까요?"가 자동으로 뜹니다. 단 진행 중인 작업(백그라운드 셸 작업이나 예약된 wake 포함)이 남아 있으면 강제로 닫거나 매 턴 다시 묻지 않고, 지금 닫을지만 되묻습니다. 마무리하지 않은 채 `/clear`를 입력해도 다음 세션 시작 때 이어서 정리할 수 있습니다.
39
37
 
40
38
  버전별로 무엇이 바뀌었는지는 [CHANGELOG](CHANGELOG.md)에 있습니다.
41
39
 
42
40
  업그레이드 정책 하나만 미리 알아 두세요. `hypomnema upgrade --apply`는 사용자가 직접 편집한 `SCHEMA.md`를 덮어쓰지 않습니다. 스키마가 올라가면 위키 루트에 마이그레이션 보고서만 써 주고, 실제 반영은 사용자가 직접 결정합니다(코드에서 Option C라고 부르는 정책).
43
41
 
42
+ 자동처럼 보이지만 실제로는 직접 실행해야 하는 기능이 두 가지 있습니다. extensions sync와 역방향 capture입니다. 위키 안 `~/hypomnema/extensions/{agents,commands,hooks,skills}/`에 둔 파일은 `~/.claude/`에 반영되는데(`--codex`를 붙이면 `hooks`·`commands`는 `~/.codex/`에도), 직접 `hypomnema init`, `hypomnema upgrade --apply`, 또는 dry-run이 아닌 `hypomnema capture`를 돌려야만 반영됩니다. 저절로 밀어 넣는 트리거는 없습니다. `hypomnema capture`(또는 `/hypo:capture`)는 반대 방향으로, 직접 만든 command·agent·hook·skill을 위키로 가져오는 경로인데 이것도 마찬가지로 명시적으로 불러야 동작합니다. 자세한 내용은 아래 명령어 표를 보세요.
43
+
44
44
  ---
45
45
 
46
46
  ## 빠른 시작
@@ -126,14 +126,14 @@ Hypomnema ───► 합성 · 마크다운 · git · 훅 · 로컬
126
126
  | 워크플로 통합 | Claude Code 네이티브 | 별도 앱 | 별도 앱 / 브라우저 | 별도 서비스 | 별도 앱 | 별도 사이트 |
127
127
  | 포맷 | 평문 마크다운 + frontmatter | 마크다운 | 독자 포맷 | 벡터 스토어 | 독자 포맷 | HTML |
128
128
  | 행동 튜닝 | `/hypo:feedback` → 영구 규칙 | 없음 | 없음 | 없음 | 일부 | 없음 |
129
- | 자동 동작 | `/hypo:*` 호출 + 관측성 점수(v1.1) + 자동화 4영역(v1.2); v2 목표는 완전 자율 | 없음 | 없음 | 없음 | 블랙박스 | 없음 |
129
+ | 자동 동작 | `/hypo:*` 호출 + 관측성 점수 + 자동 동작 영역; v2 목표는 완전 자율 | 없음 | 없음 | 없음 | 블랙박스 | 없음 |
130
130
  | 셋업 비용 | 명령 1개 | 설치 1번 | 가입 | 파이프라인 구축 | 가입 | 레포 연결 |
131
131
  | 락인 | 0 (마크다운 + git) | 낮음 | 높음 | 중간 | 높음 | 중간 |
132
132
 
133
133
  ### 이 선택이 가져다주는 것
134
134
 
135
135
  - 저장이 아니라 합성. 끝까지 읽지도 못한 글이 무덤처럼 쌓이지 않습니다. `/hypo:ingest`는 매번 구조화된 페이지를 만듭니다. 같은 주제를 다음에 인제스트하면 새 페이지를 만들지 않고 기존 페이지를 갱신합니다.
136
- - 오래 쓸수록 밀도가 빠르게 높아집니다. 소스 100개가 단절된 페이지 100개로 끝나면 의미가 없습니다. 쓰다 보면 페이지 수는 소스 증가보다 천천히 늘지만 교차 링크는 더 빠르게 늘어납니다.
136
+ - 밀도가 쌓이도록 설계했습니다. 소스 100개가 단절된 페이지 100개로 끝나면 의미가 없습니다. ingest가 소스를 새 페이지로 만들지 않고 기존 페이지에 합치는 구조라, 페이지 수는 소스 증가보다 천천히 늘고 교차 링크는 더 빠르게 늘어나도록 설계했습니다.
137
137
  - 맥락 전환이 없습니다. 어차피 Claude Code 안에서 일하는 중입니다. 위키는 슬래시 명령 한 줄로 닿습니다. 새 탭도, 다른 앱도, 추가 로그인도 없습니다.
138
138
  - 저장 형식이 오래 살아남습니다. 평문 마크다운 + git은 20년 뒤에도 읽힙니다. 오프라인에서도 `grep`이 됩니다. 언제든 다른 도구로 옮길 수 있고, 아직 나오지 않은 미래의 AI 도우미도 변환 없이 그대로 읽습니다.
139
139
 
@@ -149,14 +149,15 @@ Hypomnema ───► 합성 · 마크다운 · git · 훅 · 로컬
149
149
  | wikilink(위키링크) | `[[페이지-슬러그]]` 형태의 페이지 간 교차 참조. `lint`가 유효성을 확인합니다 |
150
150
  | ADR | "Architecture Decision Record". 자명하지 않은 설계 결정을 왜 했는지 짧게 적는 마크다운 페이지 |
151
151
  | schema(스키마) | `SCHEMA.md`에 정의된 타입 분류와 필수 항목 규칙. 페이지가 유효한지 판단하는 기준 |
152
- | lint | 읽기 전용 검증기(`hypomnema lint`). 프런트매터·위키링크·스키마를 한꺼번에 점검 |
152
+ | lint | 검증기(`/hypo:lint`, 또는 `npm run lint`). 프런트매터·위키링크·스키마를 점검 |
153
153
  | projection(투영) | 한 방향 자동 파생. `pages/feedback/*.md` → `MEMORY.md`와 CLAUDE.md `<learned_behaviors>` |
154
154
  | 단일 원천(source of truth, SoT) | 사용자가 편집하는 단 하나의 파일. projection은 거기서만 파생되고 역방향은 막습니다 |
155
155
  | 훅(hook) | Claude Code가 라이프사이클 이벤트(`SessionStart`, `Stop` 등)에 자동으로 실행하는 스크립트 |
156
156
  | 라이프사이클 이벤트 | Claude Code가 플러그인에 알리는 시점. 세션 시작·프롬프트 제출·도구 사용·compact 요청·세션 종료 등 |
157
157
  | `hot.md` | 프로젝트별 캐시. "방금 무엇을 했는지"(직전 세션의 핵심) |
158
158
  | `session-state.md` | 프로젝트별 캐시. "다음에 무엇을 할지"(다음 세션 시작 때 주입되는 이어받기 데이터) |
159
- | `.hypoignore` | 모든 콘텐츠 주입 훅과 `ingest`에서 제외할 경로(글롭 패턴) |
159
+ | `.hypoignore` | 모든 콘텐츠 주입 훅과 `ingest`에서 제외할 경로(글롭 패턴). 프라이버시 경계 |
160
+ | `.hyposcanignore` | 카탈로그 스캔(`lint`, `graph`, `stats`, `query`, `verify`, `doctor`)에서만 제외할 경로. 프라이버시 경계가 아니며, 매치돼도 여전히 커밋되고 훅이 읽음 |
160
161
  | 관측성 점수(observability score) | 세션별 측정값(search·ingest·feedback 활동). 위키가 실제로 쓰였는지 보여줍니다 |
161
162
  | manifest | 설치 스크립트가 쓰는 작은 JSON. 어떤 파일을 어떤 SHA로 설치했는지 기록 |
162
163
  | `additionalContext` | Claude Code 훅이 프롬프트에 컨텍스트를 끼워 넣는 필드. 콘텐츠 주입 훅의 출력 위치 |
@@ -214,7 +215,7 @@ Hypomnema는 청크가 아니라 페이지를 지식 단위로 봅니다. 새
214
215
 
215
216
  ### 7. 왜 privacy mode 플래그가 아니라 `.hypoignore`인가
216
217
 
217
- v1.0에서는 `personal / shared / public` 3-mode를 만들었습니다. 현실과 부딪히자 바로 무너졌습니다. 모든 privacy 결정은 결국 경로 단위 질문이었고, 그건 단일 파일(`.hypoignore`) 하나로 처리됩니다. v1.1에서 mode 개념을 통째로 지웠습니다. 파일 하나가 모든 결정을 담는 단일 원천 구조입니다.
218
+ 처음에는 `personal / shared / public` 3-mode를 만들었습니다. 현실과 부딪히자 바로 무너졌습니다. 모든 privacy 결정은 결국 경로 단위 질문이었고, 그건 단일 파일(`.hypoignore`) 하나로 처리됩니다. 그래서 mode 개념을 통째로 지웠습니다. 파일 하나가 모든 결정을 담는 단일 원천 구조입니다.
218
219
 
219
220
  ---
220
221
 
@@ -267,12 +268,13 @@ v1.0에서는 `personal / shared / public` 3-mode를 만들었습니다. 현실
267
268
  | `/hypo:uninstall` | 훅 및 등록 정보 제거 |
268
269
  | `/hypo:stats` | 위키 통계 |
269
270
  | `/hypo:audit` | observability 감사 (세션별 메트릭, 주간 보고서) |
271
+ | `hypomnema capture`(`/hypo:capture`) | `~/.claude/{commands,agents}/`에 직접 만든 command·agent, `settings.json`에 등록한 정규형 hook, 또는 `~/.claude/skills/`의 skill을 위키로 가져와 다른 기기에 원래 이름으로 동기화. 명시적 호출만 동작하고, 내용이 다른 위키 파일은 덮어쓰지 않으며, 손실 없이 원형 복원되는 것만 대상 |
270
272
 
271
273
  > 업데이트 알림: `SessionStart` 훅이 npm 레지스트리와 Claude Code 플러그인 마켓플레이스를 백그라운드에서 확인합니다(세션 시작을 막지 않습니다). 새 버전이 올라와 있으면 다음 세션 시작 때 "Update available!" 안내가 한 줄 뜹니다. `HYPO_NO_UPDATE_CHECK=1`, `NO_UPDATE_NOTIFIER=1`을 주거나 `CI=true` 환경이면 건너뜁니다.
272
274
 
273
275
  ### Claude Agent Skills
274
276
 
275
- 합성이 핵심인 명령어(`ingest`, `query`, `crystallize`, `lint`, `verify`, `graph`)는 `skills/<name>/SKILL.md`로도 등록돼 있습니다. 대화 내용이 해당 스킬의 `description`과 맞으면 Claude Agent Skills 메커니즘이 슬래시 명령 없이도 자동으로 호출합니다. 명령어를 정확히 몰라도 하려는 일을 말로 적으면 됩니다.
277
+ 합성이 핵심인 명령어 6개(`ingest`, `query`, `crystallize`, `lint`, `verify`, `graph`)는 `skills/<name>/SKILL.md`로도 등록돼 있습니다. 대화 내용이 해당 스킬의 `description`과 맞으면 Claude Agent Skills 메커니즘이 슬래시 명령 없이도 자동으로 호출합니다. 일곱 번째 스킬인 `debate`는 대응하는 슬래시 커맨드가 따로 없습니다. 위키 주장을 다시 검증하거나 되돌리기 힘든 결정을 ADR로 굳힐 때, 두 단계로 나뉜 구조화된 검토를 실행합니다. 명령어를 정확히 몰라도 하려는 일을 말로 적으면 됩니다.
276
278
 
277
279
  | 이렇게 말하면 | 트리거되는 스킬 |
278
280
  |---|---|
@@ -335,6 +337,7 @@ E. 멈춘 프로젝트 재개.
335
337
  ├── SCHEMA.md ← 타입 시스템 참조
336
338
  ├── hypo-guide.md ← 운영 가이드
337
339
  ├── .hypoignore ← 훅에서 제외할 글로브 패턴
340
+ ├── .hyposcanignore ← 카탈로그 스캔에서만 제외할 글로브 패턴 (프라이버시 경계 아님)
338
341
  ├── pages/ ← 영구 지식 페이지
339
342
  │ └── feedback/ ← AI 행동 교정
340
343
  ├── projects/ ← 프로젝트 아티팩트 및 세션 로그
@@ -363,9 +366,11 @@ E. 멈춘 프로젝트 재개.
363
366
 
364
367
  `.hypoignore`는 훅이 무시할 경로를 정합니다 (기본: `*.pdf`, `*.zip`, `*.pem`, `*.env` 등). 직접 편집하면 됩니다. privacy mode 플래그는 없습니다. 파일 하나가 모든 결정을 담는 단일 원천 구조입니다.
365
368
 
369
+ `.hyposcanignore`는 역할이 다른 별도 파일입니다. `init`이 모든 신규 볼트 루트에 자동으로 만들며, 카탈로그 스캔(`lint`, `graph`, `stats`, `query`, `verify`, `doctor`)에서만 경로를 뺍니다. 프라이버시 경계가 아닙니다. `.hyposcanignore`에 매치돼도 여전히 커밋되고 훅이 읽을 수 있습니다. 주입과 커밋을 실제로 막으려면 `.hypoignore`를 쓰세요.
370
+
366
371
  > 모델 사업자에게 전송되는 범위: 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` 패턴을 먼저 점검하세요.
367
372
 
368
- > git sync 범위: Hypomnema는 `~/hypomnema/` 위키 자체만 git sync합니다. 단 `init` / `upgrade`는 `~/.claude/` 안의 관리 대상 영역(Hypomnema 자체 hook `~/.claude/hooks/`, 슬래시 커맨드 `~/.claude/commands/hypo/`, `settings.json` 등록)을 설치·SHA 추적하고, v1.2.0 extensions companion sync로 위키의 `~/hypomnema/extensions/`에 둔 `agents/`·`commands/`·`hooks/`·`skills/`도 자동 미러링합니다(`--codex`면 `hooks`·`commands` 부분집합만 `~/.codex/`로). 이 관리 대상 바깥에 있는 `~/.claude/` 콘텐츠는 일부러 관리하지 않습니다. 위키를 거치지 않는 기타 agent/skill, 머신 고유 `settings.local.json` 같은 일반 Claude Code 설정의 기기 간 동기화는 [chezmoi](https://www.chezmoi.io/) 같은 별도 dotfiles 매니저를 권합니다.
373
+ > 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/`도 자동 미러링합니다(`--codex`면 `hooks`·`commands` 부분집합만 `~/.codex/`로). 이 관리 대상 바깥에 있는 `~/.claude/` 콘텐츠는 일부러 관리하지 않습니다. 위키를 거치지 않는 기타 agent/skill, 머신 고유 `settings.local.json` 같은 일반 Claude Code 설정의 기기 간 동기화는 [chezmoi](https://www.chezmoi.io/) 같은 별도 dotfiles 매니저를 권합니다.
369
374
 
370
375
  ### `/hypo:*` 커맨드는 어디서 오는가
371
376
 
@@ -388,11 +393,25 @@ E. 멈춘 프로젝트 재개.
388
393
  ## 상태
389
394
 
390
395
  - 테스트: `npm test` 참조. 테스트가 추가될 때마다 개수가 바뀌므로, 정확한 기준은 테스트 러너입니다
391
- - CI: 7개 독립 job (test matrix, lint, init/upgrade snapshots, replay, hypo-absent, uninstall-smoke)
396
+ - CI: 독립 job 약 12개 (test matrix, lint, tracker-ids, ship-surface, init/upgrade snapshots, replay, hypo-absent, uninstall-smoke). 정확한 목록은 `.github/workflows/ci.yml` 참고
392
397
  - 릴리스: `v*` 태그 push 시 `npm publish --provenance` 자동 실행
393
398
 
394
399
  ---
395
400
 
401
+ ## AI 어시스턴트에게
402
+
403
+ Claude(또는 다른 코딩 에이전트)로서 Hypomnema 위키를 운영한다면 아래가 기본 규칙입니다.
404
+
405
+ 위키가 단일 SoT입니다. 사용자가 기억해 달라고 한 것은 전부 위키 루트 아래 마크다운으로 있고, 기억에 의존해 답하기 전에 그것부터 읽습니다. 한 프로젝트의 배경은 `projects/<name>/hot.md`에, 다음에 할 일은 `projects/<name>/session-state.md`에 있으며 `/hypo:resume`이 둘 다 불러옵니다. 위키 루트의 `hot.md`는 다른 파일입니다. 어떤 프로젝트가 활성인지 가리키는 포인터 테이블입니다.
406
+
407
+ 그 상태를 위키 밖에 또 두지 마세요. `MEMORY.md`와 `~/.claude/CLAUDE.md`의 `<learned_behaviors>` 블록은 당신의 메모리처럼 보이지만, Hypomnema가 `pages/feedback/`에서 뽑아내는 단방향 projection입니다. 교정은 `/hypo:feedback`으로 적고 동기화가 다시 만들게 두면 됩니다. projection을 손으로 고치면 다음 동기화가 그것을 병합하지 않고 conflict로 멈춥니다. 어느 쪽이 진실인지 판단할 근거가 없기 때문이고, 그때부터 feedback 페이지로 되돌려 정리하는 일은 당신 몫이 됩니다. 위키 사실을 당신만 보는 곳에 챙겨두면 사용자의 다른 머신에는 아예 닿지 않습니다.
408
+
409
+ `sources/`는 불변입니다. 그 아래는 무엇도 고치지 않습니다. 대신 `pages/`로 합성하고 출처를 링크로 되짚습니다.
410
+
411
+ 운영 절차는 위키 안에 함께 깔리므로 따로 받아올 필요가 없습니다. 위키 루트의 `hypo-guide.md`가 운영 가이드이고, `SCHEMA.md`가 페이지 타입과 frontmatter를 정의하며, `hypo-help.md`가 명령어 목록입니다. 세션에서 첫 위키 작업을 할 때 `SCHEMA.md`, `hypo-guide.md` 순으로 읽습니다.
412
+
413
+ ---
414
+
396
415
  ## 문서
397
416
 
398
417
  - [ARCHITECTURE.md](docs/ARCHITECTURE.md): 내부 구조, 컴포넌트 맵, 데이터 흐름
@@ -401,6 +420,12 @@ E. 멈춘 프로젝트 재개.
401
420
 
402
421
  ---
403
422
 
423
+ ## 지원
424
+
425
+ 버그·기능 요청은 [GitHub Issues](https://github.com/sk-lim19f/Hypomnema/issues)로 남겨 주세요. 개인 프로젝트라 best-effort로 관리하며 응답 시간을 보장하지는 않습니다.
426
+
427
+ ---
428
+
404
429
  ## 라이선스
405
430
 
406
431
  MIT
package/README.md CHANGED
@@ -25,22 +25,22 @@ 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.6.1. It fixes session close so a conditional or deferred wrap-up ("once X is done, wrap up") that still has uncommitted or in-flight work asks you whether to close now instead of nagging prematurely, and it hardens `working_dir` provenance so `cwd`-first resume can anchor a project that was missing its anchor. The v1.6.0 release before it completed reverse extension capture: `hypomnema capture` (and the `/hypo:capture` slash command) pulls a command, agent, or hook you created the normal way under `~/.claude/` into the wiki, so forward-sync propagates it to your other machines under its original name. That closes the "register on one machine, sync on another" gap for extensions you author by hand, without a daemon. The v1.5.1 release before it sharpened how the wiki signals its own freshness: a page whose `verify_by_date` has passed is flagged `[STALE ...]` the moment it is injected, lookup usage is tracked locally so crystallize can surface a linked-but-never-injected page as a cold candidate, and session close was made robust to the standard `session | project: title` colon log format that had been misread as stale. The v1.5.0 line before that turned to cross-machine reliability: `cwd`-first resume on a git-synced vault and a code-repo session told where the wiki lives.
29
-
30
28
  Wiki work (ingest, query, session-close) still starts from an explicit `/hypo:*` command or plain language. The v2 goal is full autonomy: Claude reading, writing, and synthesizing the wiki without being asked, which is the direction this is heading.
31
29
 
32
- The lanes that already run on their own: v1.1.0 shipped the observability score that measures how often the wiki is used per session, and v1.2.0 added four automated areas on top.
30
+ The lanes that already run on their own: an observability score measures how often the wiki is used per session, and several areas run without a prompt.
33
31
 
32
+ - Freshness signaling. A page whose `verify_by_date` has passed is flagged `[STALE ...]` when it's injected at session start or via lookup, and lookup usage is tracked locally so crystallize can surface a page not injected via lookup in the last 90 days as a cold candidate.
34
33
  - 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.
35
- - 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.
36
- - Reverse capture goes the other way. `hypomnema capture` (or `/hypo:capture`) pulls a command or agent you made the normal way under `~/.claude/{commands,agents}/`, or a canonical hook you registered in `~/.claude/settings.json`, into the wiki, so it then syncs to your other machines under its original name. Hooks are captured only when they round-trip losslessly to the canonical form; others are skipped with a reason. Capture is explicit and never overwrites a differing wiki file. Skills are not captured yet.
34
+ - Project-aware resume on `cd`. Switching into a git-synced project directory injects that project's `hot.md` automatically, so a code-repo session already knows where the wiki lives and what happened last.
37
35
  - Auto-project creation. When you `cd` into a git repo with a project marker (`package.json`, `Cargo.toml`, etc.) and no matching wiki project exists, Hypomnema offers to scaffold one.
38
- - Session-close cleanup and `/clear` recovery. After a non-trivial session, a "save a minimal session-close note?" prompt appears automatically; a `/clear` after a forgotten close is detected and recovered at the next session start.
36
+ - Session-close cleanup and `/clear` recovery. After a non-trivial session, a "save a minimal session-close note?" prompt appears automatically, but not if work is still in flight (a running background shell task or a scheduled wake); in that case Hypomnema asks whether to close now instead of forcing it or nagging every turn. A `/clear` after a forgotten close is detected and recovered at the next session start.
39
37
 
40
38
  What changed per version lives in the [CHANGELOG](CHANGELOG.md).
41
39
 
42
40
  One upgrade policy worth knowing up front: `hypomnema upgrade --apply` never overwrites a `SCHEMA.md` you have edited. When the schema bumps, upgrade writes a migration report into the wiki root and leaves the actual changes for you to apply (the policy the code calls Option C).
43
41
 
42
+ Two things that look automatic but aren't: extensions sync and reverse capture. Anything under `~/hypomnema/extensions/{agents,commands,hooks,skills}/` is mirrored into `~/.claude/` (and, with `--codex`, `hooks`/`commands` into `~/.codex/` too), but only on a run you trigger yourself: `hypomnema init`, `hypomnema upgrade --apply`, or a non-dry-run `hypomnema capture`. No hook pushes it there on its own. `hypomnema capture` (or `/hypo:capture`) is also the reverse path, pulling your own commands/agents/hooks/skills into the wiki, and that too only runs when you call it. See the command table below.
43
+
44
44
  ---
45
45
 
46
46
  ## Quick Start
@@ -126,14 +126,14 @@ Hypomnema ───► synthesis · markdown · git · hooks · local
126
126
  | Workflow integration | Native to Claude Code | Separate app | Separate app / browser tab | Separate service | Separate app | Separate site |
127
127
  | Format | Plain markdown + frontmatter | Markdown | Proprietary | Vector store | Proprietary | HTML |
128
128
  | Behavior tuning | `/hypo:feedback` → permanent rules | None | None | None | Sometimes | None |
129
- | Auto-behavior | `/hypo:*` triggers + observability score (v1.1) + four autonomous lanes (v1.2); v2 target = fully autonomous | None | None | None | Black box | None |
129
+ | Auto-behavior | `/hypo:*` triggers + observability score + autonomous lanes; v2 target = fully autonomous | None | None | None | Black box | None |
130
130
  | Setup cost | One command | One install | Sign-up | Pipeline build | Sign-up | Repo connect |
131
131
  | Lock-in | Zero (markdown + git) | Low | High | Medium | High | Medium |
132
132
 
133
133
  ### What this trade-off buys you
134
134
 
135
135
  - Synthesis over storage. You don't end up with a graveyard of half-read articles. Each `/hypo:ingest` produces a structured page, and the next ingest on the same topic updates that page instead of adding a new one.
136
- - Compounding density. A wiki with 100 sources should not be 100 disconnected pages. After a few months of real use, page count grows sub-linearly while cross-links grow faster.
136
+ - Compounding density. A wiki with 100 sources should not be 100 disconnected pages. The design intent: since ingest merges a new source into an existing page instead of always creating one, page count should grow sub-linearly while cross-links grow faster.
137
137
  - No context switch. You're already in Claude Code. The wiki is one slash command away, with no extra tab, app, or login.
138
138
  - Future-proof storage. Plain markdown + git will still be readable in 20 years, greps offline, moves to another tool anytime, and stays usable by AI assistants that don't exist yet, with no conversion needed.
139
139
 
@@ -149,14 +149,15 @@ These are the recurring terms used in the rest of the README. Keep this table op
149
149
  | wikilink | A `[[page-slug]]` cross-reference between pages; resolved at lint time |
150
150
  | ADR | "Architecture Decision Record": a short markdown page recording _why_ a non-obvious design choice was made |
151
151
  | schema | The type taxonomy and required-field rules in `SCHEMA.md`: what makes a page valid |
152
- | lint | A read-only validator (`hypomnema lint`) that checks frontmatter, wikilinks, and schema |
152
+ | lint | A validator (`/hypo:lint`, or `npm run lint`) that checks frontmatter, wikilinks, and schema |
153
153
  | projection | A one-way automatic derivation: `pages/feedback/*.md` → `MEMORY.md` and CLAUDE.md `<learned_behaviors>` |
154
154
  | SoT ("source of truth") | The single file you edit; projections derive from it, never the other way around |
155
155
  | hook | A script Claude Code runs automatically on a lifecycle event (e.g. `SessionStart`, `Stop`) |
156
156
  | lifecycle event | A point Claude Code surfaces to plugins: session opens, prompt submitted, tool used, compact requested, session ends, etc. |
157
157
  | `hot.md` | Per-project cache: "what just happened" (most recent session highlights) |
158
158
  | `session-state.md` | Per-project cache: "what's next" (the resume payload for the next session) |
159
- | `.hypoignore` | Glob patterns that exclude paths from every content-injection hook and from `ingest` |
159
+ | `.hypoignore` | Glob patterns that exclude paths from every content-injection hook and from `ingest`; a privacy boundary |
160
+ | `.hyposcanignore` | Glob patterns that exclude paths from catalog scans only (`lint`, `graph`, `stats`, `query`, `verify`, `doctor`); not a privacy boundary, a matched path still gets committed and read by hooks |
160
161
  | observability score | A per-session metric (search / ingest / feedback activity) that measures whether the wiki is actually being used |
161
162
  | manifest | A small JSON the install scripts write to track exactly which files were installed and at what SHA |
162
163
  | `additionalContext` | The Claude Code hook field that injects extra context into the prompt: where content-injection hooks emit |
@@ -214,7 +215,7 @@ Every external dependency is a future failure: it breaks, gets bought, gets depr
214
215
 
215
216
  ### 7. Why `.hypoignore` instead of a privacy mode flag
216
217
 
217
- v1.0 had a `personal / shared / public` mode matrix. It didn't survive contact with reality: every privacy decision turned out to be a per-path question, and a single file (`.hypoignore`) handles per-path decisions natively. v1.1 deleted the mode concept entirely. One file, one source of truth.
218
+ Hypomnema started with a `personal / shared / public` mode matrix. It didn't survive contact with reality: every privacy decision turned out to be a per-path question, and a single file (`.hypoignore`) handles per-path decisions natively. The mode concept was deleted entirely. One file, one source of truth.
218
219
 
219
220
  ---
220
221
 
@@ -267,12 +268,13 @@ All hooks resolve the wiki root via `HYPO_DIR` env → `hypo-config.md` scan →
267
268
  | `/hypo:uninstall` | Remove hooks and registrations |
268
269
  | `/hypo:stats` | Wiki statistics |
269
270
  | `/hypo:audit` | Observability audit (per-session metrics, weekly report) |
271
+ | `hypomnema capture` (or `/hypo:capture`) | Pull a command/agent you created directly under `~/.claude/{commands,agents}/`, a canonical hook registered in `settings.json`, or a skill under `~/.claude/skills/`, into the wiki, so it syncs to your other machines under its original name. Explicit only, never overwrites a differing wiki file; only captures what round-trips losslessly |
270
272
 
271
273
  > Update notice: the `SessionStart` hook runs a non-blocking background check against npm and the Claude Code plugin marketplace, and prints an "Update available!" banner the next time a newer version has been published. Opt out with `HYPO_NO_UPDATE_CHECK=1`, `NO_UPDATE_NOTIFIER=1`, or by running under `CI=true`.
272
274
 
273
275
  ### Claude Agent Skills
274
276
 
275
- The 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. You don't need to know the exact command; just say what you want.
277
+ 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 skill, `debate`, has no slash-command counterpart: it runs a structured two-phase review to re-verify a wiki claim or harden a hard-to-reverse decision into an ADR. You don't need to know the exact command; just say what you want.
276
278
 
277
279
  | Say this | Skill it triggers |
278
280
  |---|---|
@@ -335,6 +337,7 @@ Do not store here:
335
337
  ├── SCHEMA.md ← type system reference
336
338
  ├── hypo-guide.md ← operations guide
337
339
  ├── .hypoignore ← glob patterns excluded from hooks
340
+ ├── .hyposcanignore ← glob patterns excluded from catalog scans only (not a privacy boundary)
338
341
  ├── pages/ ← permanent knowledge pages
339
342
  │ └── feedback/ ← AI behavior corrections
340
343
  ├── projects/ ← project artifacts and session logs
@@ -363,9 +366,11 @@ Place a `hypo-config.md` at the wiki root to make it portable across machines wi
363
366
 
364
367
  `.hypoignore` controls which paths the hooks ignore (default: `*.pdf`, `*.zip`, `*.pem`, `*.env`, …). Edit it directly; there is no privacy mode flag. One file, one source of truth.
365
368
 
369
+ `.hyposcanignore` is a different file with a narrower job. `init` writes one at the root of every new vault, and it only excludes paths from catalog scans (`lint`, `graph`, `stats`, `query`, `verify`, `doctor`). It is not a privacy boundary: a path matched by `.hyposcanignore` is still committed and still readable by hooks. To actually keep something out of injection and commits, use `.hypoignore`.
370
+
366
371
  > 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`.
367
372
 
368
- > 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), plus, via v1.2.0 extensions companion sync, any `agents/` · `commands/` · `hooks/` · `skills/` you ship inside `~/hypomnema/extensions/` (and with `--codex`, the `hooks` + `commands` subset into `~/.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/).
373
+ > 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), plus, via the extensions companion sync, any `agents/` · `commands/` · `hooks/` · `skills/` you ship inside `~/hypomnema/extensions/` (and with `--codex`, the `hooks` + `commands` subset into `~/.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/).
369
374
 
370
375
  ### Where do `/hypo:*` commands live?
371
376
 
@@ -388,11 +393,25 @@ No external services. No API keys. No vector databases.
388
393
  ## Status
389
394
 
390
395
  - Tests: see `npm test`. Exact totals shift as lanes ship, so the runner is the source of truth
391
- - CI: 7 independent jobs (test matrix, lint, init/upgrade snapshots, replay, hypo-absent, uninstall-smoke)
396
+ - CI: about a dozen independent jobs (test matrix, lint, tracker-ids, ship-surface, init/upgrade snapshots, replay, hypo-absent, uninstall-smoke, and more); see `.github/workflows/ci.yml` for the exact set
392
397
  - Release: `npm publish --provenance` on `v*` tag push
393
398
 
394
399
  ---
395
400
 
401
+ ## For AI assistants
402
+
403
+ If you are Claude (or another coding agent) operating a Hypomnema wiki, these are the ground rules.
404
+
405
+ The wiki is the single source of truth. Everything the user asked you to remember about their work is markdown under the wiki root, and you read it before answering from recollection. `projects/<name>/hot.md` holds the background for one project, `projects/<name>/session-state.md` holds what to do next, and `/hypo:resume` loads both. The `hot.md` at the wiki root is a different file: a pointer table of which projects are active.
406
+
407
+ Keep no second copy of that state. `MEMORY.md` and the `<learned_behaviors>` block inside `~/.claude/CLAUDE.md` look like your own memory, but they are one-way projections that Hypomnema derives from `pages/feedback/`. Record a correction with `/hypo:feedback` and let the sync regenerate them. Edit a projection by hand and the next sync stops on it as a conflict instead of merging it, because nothing can tell which side is now the truth; reconciling it back into the feedback page becomes your job. And a wiki fact you stash somewhere only you can see never reaches the user's other machines at all.
408
+
409
+ Captured sources are immutable. Never edit anything under `sources/`. Synthesize into `pages/` instead, and link back to the source.
410
+
411
+ The operating procedure ships inside the wiki, so you do not have to fetch anything. `hypo-guide.md` at the wiki root is the operations guide, `SCHEMA.md` defines the page types and frontmatter, and `hypo-help.md` lists the commands. Read `SCHEMA.md` then `hypo-guide.md` on your first wiki task of a session.
412
+
413
+ ---
414
+
396
415
  ## Docs
397
416
 
398
417
  - [ARCHITECTURE.md](docs/ARCHITECTURE.md): internals, component map, data flows
@@ -401,6 +420,12 @@ No external services. No API keys. No vector databases.
401
420
 
402
421
  ---
403
422
 
423
+ ## Support
424
+
425
+ Bugs and feature requests: [GitHub Issues](https://github.com/sk-lim19f/Hypomnema/issues). This is a personal project maintained on a best-effort basis; there's no response-time guarantee.
426
+
427
+ ---
428
+
404
429
  ## License
405
430
 
406
431
  MIT
@@ -1,10 +1,12 @@
1
1
  ---
2
- description: Pull extensions you made the normal way under ~/.claude/{commands,agents}, or a canonical hook registered in ~/.claude/settings.json, into the wiki so they sync to your other machines. Use when the user wants to capture, adopt, or back up a locally-authored slash command, agent, or hook into Hypomnema.
2
+ description: Pull extensions you made the normal way under ~/.claude/{commands,agents,skills}, or a canonical hook registered in ~/.claude/settings.json, into the wiki so they sync to your other machines. Use when the user wants to capture, adopt, or back up a locally-authored slash command, agent, skill, or hook into Hypomnema.
3
3
  ---
4
4
 
5
- You are running `/hypo:capture`. Bring a command or agent that the user created the normal way (directly under `~/.claude/commands/` or `~/.claude/agents/`), or a hook they registered in `~/.claude/settings.json`, into the wiki `extensions/` tree so the existing forward-sync propagates it to their other machines under its original name.
5
+ You are running `/hypo:capture`. Bring a command, agent, or skill that the user created the normal way (directly under `~/.claude/commands/`, `~/.claude/agents/`, or `~/.claude/skills/`), or a hook they registered in `~/.claude/settings.json`, into the wiki `extensions/` tree so the existing forward-sync propagates it to their other machines under its original name.
6
6
 
7
- Scope: commands, agents, and hooks. Commands and agents are enumerated from `~/.claude/{commands,agents}/`; hooks are read from the `~/.claude/settings.json` registration. A hook is captured only when its command is the canonical `node $HOME/.claude/hooks/<name>.mjs` form and it round-trips losslessly (original event, matcher, and timeout preserved); non-canonical or lossy registrations are skipped with a reason instead of being rewritten. Skills are not captured yet.
7
+ Scope: commands, agents, skills, and hooks. Commands and agents are enumerated from `~/.claude/{commands,agents}/`; a skill is a whole directory (`~/.claude/skills/<name>/SKILL.md` plus its subtree); hooks are read from the `~/.claude/settings.json` registration.
8
+
9
+ Hooks and skills are captured only when they round-trip losslessly, because what lands in the wiki is exactly what the far machine installs. A hook qualifies when its command is the canonical `node $HOME/.claude/hooks/<name>.mjs` form and its event, matcher, and timeout are preserved. A skill is refused whole (never captured as a partial subset) when its subtree holds anything that cannot survive the trip: a symlink, a hardlink, an empty directory (git cannot carry one), a VCS control directory, or more than 500 files / 5 MiB. That ceiling is what keeps a vendored skill with its own `node_modules` out of the vault. Content is reproduced byte for byte; the executable bit is not carried by sync yet, so a captured skill holding executable scripts prints a warning.
8
10
 
9
11
  ## Step 1: Resolve the package root
10
12
 
@@ -18,7 +20,7 @@ If the user specified a Hypomnema directory, pass it as `--hypo-dir="<path>"`. O
18
20
  node ${CLAUDE_PLUGIN_ROOT}/scripts/capture.mjs [--hypo-dir="<path>"]
19
21
  ```
20
22
 
21
- With no names and no `--all`, the script lists capturable candidates and stops. Candidates come from two sources: commands and agents are the unowned regular `.md` files under `~/.claude/{commands,agents}/`, and hooks are the canonical `node $HOME/.claude/hooks/<name>.mjs` entries registered in `~/.claude/settings.json`. It excludes anything already managed by the wiki, the reserved `hypo-*` namespace, symlinks and other non-regular files, core hooks, and any hook whose registration would not round-trip losslessly. Skipped hook registrations are printed with the reason; unowned commands and agents that fail these filters are simply omitted from the list, without a per-item reason.
23
+ With no names and no `--all`, the script lists capturable candidates and stops. Candidates come from three sources: commands and agents are the unowned regular `.md` files under `~/.claude/{commands,agents}/`; skills are the unowned directories under `~/.claude/skills/` that hold a real `SKILL.md`; hooks are the canonical `node $HOME/.claude/hooks/<name>.mjs` entries registered in `~/.claude/settings.json`. It excludes anything already managed by the wiki, the reserved `hypo-*` namespace, symlinks and other non-regular files, core hooks, and anything that would not round-trip losslessly. Skipped hooks and skills are printed with the reason; unowned commands and agents that fail these filters are simply omitted from the list, without a per-item reason.
22
24
 
23
25
  Show the list. Note that these are unowned candidates, not a provenance check: explicit selection is the trust boundary. A third-party tool's command, agent, or hook could appear here, so let the user pick deliberately.
24
26
 
@@ -31,9 +33,9 @@ node ${CLAUDE_PLUGIN_ROOT}/scripts/capture.mjs [--hypo-dir="<path>"] <name> [<na
31
33
  node ${CLAUDE_PLUGIN_ROOT}/scripts/capture.mjs [--hypo-dir="<path>"] --all
32
34
  ```
33
35
 
34
- A name is the filename (`mycmd.md`, or `myhook.mjs` for a hook), its stem (`mycmd`), or the `type/file` form (`commands/mycmd.md`, `hooks/myhook.mjs`). Add `--dry-run` to preview without writing.
36
+ A name is the filename (`mycmd.md`, or `myhook.mjs` for a hook), its stem (`mycmd`, and for a skill the directory name), or the `type/file` form (`commands/mycmd.md`, `skills/myskill`). When a bare name matches more than one candidate (a `mine` command and a `mine` skill), the script refuses it and asks for the type-qualified form. Add `--dry-run` to preview without writing, or `--type=skills` to narrow the scan.
35
37
 
36
- Each capture stores the file in the wiki as `extensions/<type>/hypo-ext-<name>.<ext>` (`.md` for commands and agents, `.mjs` for hooks) with a sidecar `hypo-ext-<name>.manifest.json` that records the original install name (and, for a hook, its event, matcher, and timeout), then adopts it so the currently-installed copy is left in place and tracked. A wiki file that already exists with different content (or a mismatched manifest) is refused, not overwritten.
38
+ A flat capture stores the file in the wiki as `extensions/<type>/hypo-ext-<name>.<ext>` (`.md` for commands and agents, `.mjs` for hooks). A skill is stored as a directory, `extensions/skills/hypo-ext-<name>/`, with its subtree intact. Either way a sidecar `hypo-ext-<name>.manifest.json` records the original install name (and, for a hook, its event, matcher, and timeout), and the capture is then adopted so the currently-installed copy is left in place and tracked. A wiki entry that already exists with different content (or a mismatched manifest) is refused, not overwritten.
37
39
 
38
40
  ## Step 4: Commit the wiki, then sync elsewhere
39
41
 
@@ -37,6 +37,7 @@ Payload shape (`project` + 4 content fields required; `log` optional/derived; `o
37
37
  ```json
38
38
  {
39
39
  "project": "<project-name>",
40
+ "sessionId": "<current session id — the SAME value you pass to --session-id>",
40
41
  "date": "YYYY-MM-DD",
41
42
  "sessionState": { "content": "<full body of projects/<name>/session-state.md>" },
42
43
  "projectHot": { "content": "<full body of projects/<name>/hot.md>" },
@@ -52,6 +53,7 @@ Payload shape (`project` + 4 content fields required; `log` optional/derived; `o
52
53
  Field rules:
53
54
 
54
55
  - `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.
56
+ - `sessionId`: recommended. The current session id, the same value you pass to `--session-id`. When present, apply refuses (`stage='session-id-mismatch'`, exit 1, nothing written) if it does not equal `--session-id`, so a payload file another session overwrote cannot be applied under this session's marker. It is a string self-check, not the transcript resolver; authority still comes from `--session-id`. Optional for backward-compat: omitting it or setting it to `null` skips the check (fail-open), but an empty string `""` counts as a mismatch and is refused. Omit only if you genuinely cannot determine the id.
55
57
  - `date` — optional. Defaults to today (local). Must be `YYYY-MM-DD` if supplied.
56
58
  - `openQuestions` — optional. Include only when `pages/open-questions.md` exists and changed this session.
57
59
  - `log`: optional. Omit it by default (apply derives the canonical `## [date] session | <project>` line from your `sessionLog` heading). Supply it only for a custom log.md line, which must still be a canonical `session | <project>` heading, or the apply fails at `stage='pre-apply-verification'`.
@@ -61,7 +63,7 @@ Notes:
61
63
 
62
64
  - `sessionState` / `projectHot` / `rootHot` / `openQuestions` are **overwrite** (full-file content). `sessionLog` / `log` are **append** (entry-level idempotency — exact-entry dedup, safe to re-run).
63
65
  - Frontmatter `updated:` is NOT auto-fixed. If your payload's `updated:` is stale, the post-apply verification gate will fail with `stage='post-apply-verification'` and you must fix the payload and retry.
64
- - Write the payload to a temp path, e.g. `/tmp/hypo-session-close-<YYYY-MM-DD>.json`.
66
+ - Write the payload to a **session-scoped** temp path, e.g. `/tmp/hypo-session-close-<session-id>.json`, using the same `<session-id>` you pass below. A date-based path (`<YYYY-MM-DD>`) collides when two sessions close on the same day: the second write silently overwrites the first, and the wrong payload gets applied. The `sessionId` field above is the second line of defense if a path is ever reused anyway.
65
67
 
66
68
  Content guidance for each slot:
67
69
 
@@ -81,20 +83,32 @@ Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that p
81
83
  ```bash
82
84
  node ${CLAUDE_PLUGIN_ROOT}/scripts/crystallize.mjs \
83
85
  --apply-session-close \
84
- --payload=/tmp/hypo-session-close-<YYYY-MM-DD>.json \
86
+ --payload=/tmp/hypo-session-close-<session-id>.json \
85
87
  --session-id=<current-session-id> \
86
88
  --hypo-dir="<path>" \
87
89
  --json
88
90
  ```
89
91
 
90
- **`--session-id`:** pass the current session's id whenever you
91
- know it most importantly when this close was triggered by a `[WIKI_AUTOCLOSE]`
92
- Stop-hook block (the block reason prints the exact `--session-id` to use). On a
93
- verified close (`ok: true` + clean git tree), it writes the per-session marker
94
- `HYPO_DIR/.cache/session-closed-<id>.marker`. That marker is what tells the
95
- Stop-chain Layer 3 hook (`hypo-auto-minimal-crystallize`) the session is closed,
96
- so it stops re-prompting. Omit it only when running crystallize purely for
97
- synthesis (no session-close intent) — the marker is then simply not written.
92
+ **`--session-id` is required for any close that carries a `--payload`.** It is not a
93
+ switch that turns a check on; omitting it fails the check. Before a single byte is
94
+ written or committed, the apply resolves that session's transcript and looks for
95
+ evidence the **user** asked to close. It refuses outright, exit 1 with `applied: []`
96
+ and nothing on disk, in three cases:
97
+
98
+ | `reason` | Meaning |
99
+ |---|---|
100
+ | `session-id-required` | No `--session-id` was passed. |
101
+ | `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. |
103
+
104
+ A refusal is not a failure to route around. It means the close should not happen: ask
105
+ the user, and re-run only after they say so.
106
+
107
+ On a verified close (`ok: true` + clean git tree) the apply also writes the per-session
108
+ marker `HYPO_DIR/.cache/session-closed-<id>.marker`, which tells the Stop-chain Layer 3
109
+ hook (`hypo-auto-minimal-crystallize`) that the session is closed so it stops
110
+ re-prompting. Running crystallize purely for **synthesis** carries no payload and needs
111
+ no session id.
98
112
 
99
113
  > **Source rule for `--session-id`:** use only the main conversation's session id
100
114
  > (the id shown in the `[WIKI_AUTOCLOSE]` block reason, or the injected
@@ -110,10 +124,10 @@ synthesis (no session-close intent) — the marker is then simply not written.
110
124
 
111
125
  | Invocation | Behavior |
112
126
  |---|---|
113
- | `--apply-session-close` (no `--payload`) | **Probe mode** — exits 0 with "오늘 이미 close 완료로 보임" if all 5 files are fresh today; exits 1 with "payload is required" otherwise. Cheap "already complete?" check. |
114
- | `--apply-session-close --payload=<path>` | **Always-apply** payload presence = explicit close intent. Per-field idempotent writes (no-op when bytes match), then strict verification + lint gate. Safe to re-run. |
115
- | `--apply-session-close --payload=<path> --session-id=<id>` | Same as above, **plus** writes the per-session closed marker on success (clean git required). The Stop-chain Layer 3 path. |
116
- | `--apply-session-close --force` | Skips the probe early-exit. `--payload` still required for any actual apply work. |
127
+ | `--apply-session-close` (no `--payload`) | **Probe mode** — exits 0 with "오늘 이미 close 완료로 보임" if all 5 files are fresh today; exits 1 with "payload is required" otherwise. Writes nothing, so it needs no session id. |
128
+ | `--apply-session-close --payload=<path>` | **Refused**, exit 1, `reason: 'session-id-required'`. Nothing is written and nothing is committed. |
129
+ | `--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
+ | `--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. |
117
131
 
118
132
  **Two lint gates run automatically, scoped to the files this close writes:**
119
133
 
@@ -132,6 +146,7 @@ The result JSON includes a `stage` field when `ok: false`. Branch on it:
132
146
 
133
147
  | `stage` | What broke | How to recover |
134
148
  |---|---|---|
149
+ | `session-id-mismatch` | The payload's `sessionId` is a non-empty string that does not equal `--session-id`, so this file was authored for a different session (a reused or overwritten temp path). Caught **before** any write. Absent or `null` `sessionId` fails open (no check); an empty string counts as a mismatch. | Rebuild the payload for THIS session and write it to a session-scoped path (`/tmp/hypo-session-close-<session-id>.json`), then re-run. No payload bytes were written. |
135
150
  | `pre-apply-verification` | A payload heading does not match the freshness contract the close gate enforces: `sessionLog.entry` has no dated `## [YYYY-MM-DD] …` heading, or an explicit `log.entry` is not the canonical `## [date] session | <project>` line. Caught **before** any write. | Fix the heading in the payload (the session-log entry needs a bracketed dated heading; the log line needs `session \| <project>` after the date, colon or space delimiter), then re-run. No payload bytes were written. |
136
151
  | `preflight-lint` | A payload file (append target: session-log / log.md) has a pre-existing blocking lint error. | Fix the lint error in that file, then re-run. No payload bytes were written. (Debt outside the payload files is a non-blocking notice, not this stage.) |
137
152
  | `post-apply-verification` | A mandatory file's `updated:` frontmatter is stale (≠ today) after apply. | Edit the payload's stale `content` (or supply correct `date`), then re-run. Writes are idempotent — re-applying a corrected payload is safe. |
@@ -148,13 +163,15 @@ Once `ok: true`, report:
148
163
  - ✓ post-apply lint clean
149
164
  - **marker written?** (required check): if `markerWritten: true`, report "session-close marker written"; if `markerWritten: false`, report "session-close marker NOT written (reason: `<markerSkipReason>`)" and do NOT declare the session "closed" or "complete". A missing marker means the Stop-chain is still open; recover per the `markerSkipReason` branch below.
150
165
 
151
- If `markerWritten: true` (or `--session-id` was not passed): ask: "Session closed. Would you like to also run knowledge synthesis now, or stop here?"
166
+ If `markerWritten: true`: ask: "Session closed. Would you like to also run knowledge synthesis now, or stop here?"
167
+
168
+ **If `ok: false` with an authority `reason`, the close did not happen at all.** Nothing was written, nothing was committed. Do not report a partial close, and do not go looking for another way in.
152
169
 
153
- If `markerWritten: false`, do NOT say "session closed." Branch on `markerSkipReason`:
170
+ - `session-id-required`: you omitted `--session-id`. Pass the main conversation's id and re-run.
171
+ - `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.
154
173
 
155
- - `no-user-close-signal`: the files applied cleanly and the `--session-id` resolved a transcript, but that transcript carries no close phrase the gate recognizes (the user asked to close in wording that fell outside the close-signal set, e.g. "세션 마무리까지 진행해줘" or "세션 마무리 진행"). Re-running with the same id will not help, since the transcript is unchanged. Instead, confirm intent once with `AskUserQuestion`, header "세션", a single option labelled **세션 마무리** (설명: "이 세션을 마무리하고 close 마커를 기록"). If the user picks 세션 마무리, that answer becomes a recognized close signal in the transcript, so re-run the exact same `--apply-session-close … --session-id` command (the writes are idempotent no-ops; the marker now lands). If the user declines, leave the session unmarked: the wiki record stands, but the session is not closed. Do NOT touch the close-signal matcher itself.
156
- - `transcript-unresolved` (or a background / agent id was passed): say "Files applied and verified (ok: true), but the session-close marker was not written (reason: `<markerSkipReason>`). The Stop-chain is still active. To fully close: re-run with the correct main-conversation `--session-id`."
157
- - any other reason (`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.
174
+ 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.
158
175
 
159
176
  If the user says stop, end here. Otherwise continue to Step 5.
160
177
 
@@ -243,4 +260,6 @@ node ${CLAUDE_PLUGIN_ROOT}/scripts/crystallize.mjs --check-session-close [--hypo
243
260
 
244
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".)
245
262
 
246
- 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.
263
+ 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
+
265
+ On the marker writer (`--mark-session-closed --project=<slug>`), `--project` names the project this session closed: it sets the marker's attribution slug and enters the close scope, so another session's incomplete close is reported as `close_debt` (a notice) instead of refusing your marker. Every other gate check stays global.