hypomnema 1.1.0 → 1.2.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 (72) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.ko.md +4 -2
  4. package/README.md +4 -2
  5. package/commands/audit.md +2 -2
  6. package/commands/crystallize.md +113 -23
  7. package/commands/feedback.md +40 -26
  8. package/commands/ingest.md +31 -9
  9. package/commands/upgrade.md +2 -2
  10. package/docs/ARCHITECTURE.md +1 -1
  11. package/docs/CONTRIBUTING.md +1 -1
  12. package/hooks/hooks.json +30 -1
  13. package/hooks/hypo-auto-commit.mjs +10 -4
  14. package/hooks/hypo-auto-minimal-crystallize.mjs +145 -0
  15. package/hooks/hypo-auto-stage.mjs +4 -3
  16. package/hooks/hypo-compact-guard.mjs +33 -24
  17. package/hooks/hypo-cwd-change.mjs +107 -24
  18. package/hooks/hypo-file-watch.mjs +23 -10
  19. package/hooks/hypo-first-prompt.mjs +37 -23
  20. package/hooks/hypo-hot-rebuild.mjs +22 -10
  21. package/hooks/hypo-lookup.mjs +171 -65
  22. package/hooks/hypo-personal-check.mjs +207 -112
  23. package/hooks/hypo-pre-commit.mjs +46 -0
  24. package/hooks/hypo-session-end.mjs +58 -0
  25. package/hooks/hypo-session-record.mjs +11 -5
  26. package/hooks/hypo-session-start.mjs +298 -52
  27. package/hooks/hypo-shared.mjs +793 -37
  28. package/hooks/hypo-web-fetch-ingest.mjs +121 -0
  29. package/hooks/version-check-fetch.mjs +74 -0
  30. package/hooks/version-check.mjs +184 -0
  31. package/package.json +17 -3
  32. package/scripts/crystallize.mjs +623 -18
  33. package/scripts/doctor.mjs +730 -47
  34. package/scripts/feedback-sync.mjs +974 -0
  35. package/scripts/feedback.mjs +253 -44
  36. package/scripts/graph.mjs +35 -22
  37. package/scripts/ingest.mjs +89 -16
  38. package/scripts/init.mjs +398 -113
  39. package/scripts/lib/design-history-stale.mjs +83 -0
  40. package/scripts/lib/extensions.mjs +749 -0
  41. package/scripts/lib/frontmatter.mjs +5 -1
  42. package/scripts/lib/hypo-ignore.mjs +12 -10
  43. package/scripts/lib/pkg-json.mjs +23 -5
  44. package/scripts/lib/project-create.mjs +225 -0
  45. package/scripts/lib/schema-vocab.mjs +96 -0
  46. package/scripts/lint.mjs +238 -31
  47. package/scripts/query.mjs +26 -10
  48. package/scripts/resume.mjs +11 -5
  49. package/scripts/session-audit.mjs +37 -27
  50. package/scripts/smoke-pack.mjs +224 -0
  51. package/scripts/stats.mjs +24 -10
  52. package/scripts/uninstall.mjs +363 -49
  53. package/scripts/upgrade.mjs +706 -202
  54. package/scripts/verify.mjs +24 -14
  55. package/scripts/weekly-report.mjs +59 -25
  56. package/skills/crystallize/SKILL.md +20 -7
  57. package/skills/ingest/SKILL.md +25 -5
  58. package/templates/.hypoignore +16 -2
  59. package/templates/Home.md +2 -0
  60. package/templates/SCHEMA.md +61 -6
  61. package/templates/extensions/agents/.gitkeep +0 -0
  62. package/templates/extensions/commands/.gitkeep +0 -0
  63. package/templates/extensions/hooks/.gitkeep +0 -0
  64. package/templates/extensions/skills/.gitkeep +0 -0
  65. package/templates/gitignore +5 -0
  66. package/templates/hot.md +2 -0
  67. package/templates/hypo-config.md +1 -1
  68. package/templates/hypo-guide.md +42 -2
  69. package/templates/hypo-help.md +1 -1
  70. package/templates/pages/observability/_index.md +77 -0
  71. package/templates/projects/_template/index.md +2 -2
  72. package/templates/projects/_template/prd.md +1 -1
@@ -11,7 +11,7 @@
11
11
  "name": "hypomnema",
12
12
  "source": "./",
13
13
  "description": "LLM-native personal wiki — session-aware knowledge base for Claude Code",
14
- "version": "1.1.0",
14
+ "version": "1.2.0",
15
15
  "homepage": "https://github.com/sk-lim19f/Hypomnema"
16
16
  }
17
17
  ]
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hypomnema",
3
- "version": "1.1.0",
3
+ "version": "1.2.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
@@ -180,7 +180,7 @@ v1.0에서는 `personal / shared / public` 3-mode를 만들었습니다. 현실
180
180
  |---|---|---|
181
181
  | `/hypo:ingest` | 원본을 `sources/`에 보관하고 Claude가 `pages/`에 구조화된 페이지를 합성. 셸 헬퍼(`scripts/ingest.mjs`)는 read-only — 아직 ingest되지 않은 소스를 *목록만* 출력 | 보관할 가치가 있는 글을 읽었을 때 |
182
182
  | `/hypo:query` | BM25 검색 + LLM 합성 + `[[wikilink]]` 인용 | 자기 노트에 근거한 답변이 필요할 때 |
183
- | `/hypo:crystallize` | 초안 합성 + 11단계 session-close 체크리스트 | 비자명한 세션 종료 시 |
183
+ | `/hypo:crystallize` | session-close 체크리스트(steps 1~6) + 요청 초안 합성(steps 7~11) | 비자명한 세션 종료 시 |
184
184
  | `/hypo:resume` | 활성 프로젝트의 가장 최근 세션 상태 로드 | 일시 중단된 프로젝트로 돌아올 때 |
185
185
  | `/hypo:feedback` | AI 행동 교정 기록, 영구 규칙으로 승격 가능 | Claude가 잘못 하거나 정확히 잘 했을 때 |
186
186
  | `/hypo:verify` | `verify_by` frontmatter 페이지 감사 | 시간 제약 지식이 노후화됐을 가능성이 있을 때 |
@@ -196,7 +196,7 @@ v1.0에서는 `personal / shared / public` 3-mode를 만들었습니다. 현실
196
196
  | `hypo-lookup.mjs` | `UserPromptSubmit` | BM25 top-3 HIT 주입 / MISS → 가까운 슬러그 신호 |
197
197
  | `hypo-compact-guard.mjs` | `UserPromptSubmit` | `/compact` 감지 → session-close 체크리스트 강제 |
198
198
  | `hypo-cwd-change.mjs` | `CwdChanged` | cwd에 매칭되는 프로젝트 `hot.md` 주입 |
199
- | `hypo-file-watch.mjs` | `FileChanged` | 위키 파일 변경 알림 |
199
+ | `hypo-file-watch.mjs` | `FileChanged` | 위키 파일 변경 알림 (`.hypoignore` 준수 — 매칭 경로는 LLM 컨텍스트로 재주입되지 않음) |
200
200
  | `hypo-auto-stage.mjs` | `PostToolUse(Write/Edit)` | 위키 파일 자동 stage |
201
201
  | `hypo-auto-commit.mjs` | `Stop` | 자동 commit + pull + push |
202
202
  | `hypo-hot-rebuild.mjs` | `Stop` | `hot.md` 재생성 |
@@ -298,6 +298,8 @@ Claude가 잘못하거나 정확히 맞았을 때마다 `/hypo:feedback`을 실
298
298
 
299
299
  `.hypoignore`는 훅이 무시할 경로를 정의합니다 (기본: `*.pdf`, `*.zip`, `*.pem`, `*.env` 등). 직접 편집하면 됩니다 — privacy mode 플래그는 없습니다. 단일 파일, 단일 진실 소스.
300
300
 
301
+ > **Provider 전송 디스클레이머.** Hypomnema 훅은 위키 콘텐츠를 Claude Code의 `additionalContext`로 emit하며, 이는 프롬프트의 일부로 Claude 모델 provider에 전송됩니다. `.hypoignore`는 모든 콘텐츠 주입 훅(`hypo-file-watch`, `hypo-session-start`, `hypo-cwd-change`, `hypo-lookup`) 및 `ingest`에서 강제되지만, `.hypoignore`에 매칭되지 *않는* 파일은 전송 대상입니다. (`hypo-auto-stage`/`hypo-auto-commit`은 git staging 훅이며 주입 지점이 아니지만, staging 판단에도 `.hypoignore`를 준수합니다.) 위키에 비밀을 두지 말고, `HYPO_DIR` 아래 민감 콘텐츠를 저장하기 전 `.hypoignore` 패턴을 검토하세요.
302
+
301
303
  > **git sync 범위.** Hypomnema는 `~/hypomnema/` 위키 자체만 git sync합니다. Claude Code 설정(`~/.claude/`)은 의도적으로 Hypomnema가 **관리하지 않습니다** — agent·skill·settings의 기기 간 동기화는 [chezmoi](https://www.chezmoi.io/) 같은 별도 dotfiles 매니저 사용을 권장합니다.
302
304
 
303
305
  ### `/hypo:*` 커맨드는 어디서 오는가?
package/README.md CHANGED
@@ -180,7 +180,7 @@ Eight commands cover the full capture → retrieval → consolidation cycle.
180
180
  |---|---|---|
181
181
  | `/hypo:ingest` | Saves the raw source under `sources/`; Claude synthesizes a structured page under `pages/`. The shell helper (`scripts/ingest.mjs`) is read-only — it only *lists* pending sources so you know what still needs ingesting | Anytime you read something worth keeping |
182
182
  | `/hypo:query` | BM25 retrieval + LLM synthesis with `[[wikilink]]` citations | When you need an answer grounded in your own notes |
183
- | `/hypo:crystallize` | Synthesizes drafts and runs the 11-step session-close checklist | End of a non-trivial session |
183
+ | `/hypo:crystallize` | Runs the session-close checklist (steps 1~6) and, on request, synthesizes drafts (steps 7~11) | End of a non-trivial session |
184
184
  | `/hypo:resume` | Loads the most recent session state for an active project | Coming back to a paused project |
185
185
  | `/hypo:feedback` | Records an AI behavior correction; eligible for promotion to permanent rules | Right when Claude does something wrong (or exactly right) |
186
186
  | `/hypo:verify` | Audits pages with `verify_by` frontmatter | When time-bound knowledge might have aged out |
@@ -196,7 +196,7 @@ Eight commands cover the full capture → retrieval → consolidation cycle.
196
196
  | `hypo-lookup.mjs` | `UserPromptSubmit` | BM25 top-3 HIT inject / MISS → closest-slug signal |
197
197
  | `hypo-compact-guard.mjs` | `UserPromptSubmit` | Detect `/compact` → enforce session-close checklist |
198
198
  | `hypo-cwd-change.mjs` | `CwdChanged` | Inject the matching project's `hot.md` |
199
- | `hypo-file-watch.mjs` | `FileChanged` | Notify on wiki-file changes |
199
+ | `hypo-file-watch.mjs` | `FileChanged` | Notify on wiki-file changes (honors `.hypoignore` — matched paths are never re-emitted into LLM context) |
200
200
  | `hypo-auto-stage.mjs` | `PostToolUse(Write/Edit)` | Auto-stage wiki-file edits |
201
201
  | `hypo-auto-commit.mjs` | `Stop` | Auto commit + pull + push |
202
202
  | `hypo-hot-rebuild.mjs` | `Stop` | Rebuild `hot.md` |
@@ -298,6 +298,8 @@ Place a `hypo-config.md` at the wiki root to make it portable across machines wi
298
298
 
299
299
  `.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.
300
300
 
301
+ > **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`.
302
+
301
303
  > **Scope of git sync.** Hypomnema git-syncs only the `~/hypomnema/` wiki itself. Your Claude Code configuration (`~/.claude/`) is intentionally **not** managed by Hypomnema — for cross-machine sync of agents, skills, and settings, the recommended pattern is a separate dotfiles manager such as [chezmoi](https://www.chezmoi.io/).
302
304
 
303
305
  ### Where do `/hypo:*` commands live?
package/commands/audit.md CHANGED
@@ -28,9 +28,9 @@ If the user specified a Hypomnema directory, pass it as `--hypo-dir="<path>"`. O
28
28
  ```bash
29
29
  node <package-root>/scripts/session-audit.mjs [--hypo-dir="<path>"] [--limit=20]
30
30
  ```
31
- - **Weekly report (when the user asks for "weekly", "score", or names a week)** — write the report to `pages/observability/<YYYY-WW>.md`:
31
+ - **Weekly report (when the user asks for "weekly", "score", or names a week)** — write the report to `journal/weekly/<YYYY-Www>.md` (spec §6.4 SoT):
32
32
  ```bash
33
- node <package-root>/scripts/weekly-report.mjs [--hypo-dir="<path>"] [--week=YYYY-WW] --write
33
+ node <package-root>/scripts/weekly-report.mjs [--hypo-dir="<path>"] [--week=YYYY-Www] --write
34
34
  ```
35
35
  - **JSON for tooling** — append `--json` to either script.
36
36
 
@@ -4,47 +4,125 @@ description: Crystallize draft notes into stable knowledge — also the session-
4
4
 
5
5
  You are running `/hypo:crystallize`. This command serves two purposes:
6
6
 
7
- 1. **Session close** — if invoked at the end of a session, run the session-close checklist first
7
+ 1. **Session close** — if invoked at the end of a session, run the session-close mechanical-apply path
8
8
  2. **Knowledge synthesis** — consolidate draft or scattered wiki pages into stable, well-linked pages
9
9
 
10
10
  ---
11
11
 
12
12
  ## Step 1 — Detect context
13
13
 
14
- If the user invoked `/hypo:crystallize` to close a session (phrases like "세션 종료", "오늘 작업 마무리", "session close", or "wrap up"), run Steps 2–3 (session-close checklist) **before** the synthesis scan. Otherwise skip to Step 4.
14
+ If the user invoked `/hypo:crystallize` to close a session (phrases like "세션 종료", "오늘 작업 마무리", "session close", or "wrap up"), run Steps 2–4 (session-close mechanical apply + recovery) **before** the synthesis scan. Otherwise skip to Step 5.
15
15
 
16
16
  ---
17
17
 
18
- ## Step 2 — Session-close checklist
18
+ ## Step 2 — Compose the session-close payload
19
19
 
20
- Work through each item in order. For an explicit session-close invocation, proceed automatically without asking for confirmation on each item.
20
+ The session-close path is **payload-driven** (fix #38). Instead of writing the 5 mandatory files one-by-one, you compose a single JSON payload that describes the full session-close state, then hand it to `crystallize.mjs --apply-session-close`, which performs idempotent atomic writes and gates the result with lint.
21
21
 
22
- 1. **session-state.md** update `projects/<name>/session-state.md` with the next tasks list for the upcoming session (what to tackle first next time).
23
- 2. **hot.md (project)** — update `projects/<name>/hot.md` with a session snapshot: what changed and decisions made. Keep under 500 words. Do not put next-step tasks here; those belong in session-state.md.
24
- 3. **hot.md (root)** — update `<hypo-root>/hot.md` active-projects pointer table: set the `Last Session` date for this project to today.
25
- 4. **session-log** — append a session entry to `projects/<name>/session-log/YYYY-MM.md` (create the file if it does not exist for this month).
26
- 5. **open-questions** — only if `pages/open-questions.md` exists and questions were raised or resolved this session: move resolved ones out; add newly raised ones. Skip if unchanged.
27
- 6. **log.md** — append a `session` entry to `<hypo-root>/log.md`.
22
+ Payload shape (5 required + 1 conditional, per Spec §5.2.7 / §8.3 + ADR 0029):
23
+
24
+ ```json
25
+ {
26
+ "project": "<project-name>",
27
+ "date": "YYYY-MM-DD",
28
+ "sessionState": { "content": "<full body of projects/<name>/session-state.md>" },
29
+ "projectHot": { "content": "<full body of projects/<name>/hot.md>" },
30
+ "rootHot": { "content": "<full body of <hypo-root>/hot.md>" },
31
+ "sessionLog": { "entry": "<entry to append to projects/<name>/session-log/YYYY-MM.md>" },
32
+ "log": { "entry": "<entry to append to <hypo-root>/log.md>" },
33
+ "openQuestions": { "content": "<full body of pages/open-questions.md>" }
34
+ }
35
+ ```
36
+
37
+ > **Important:** the JSON above is a literal template — do not add `//` or `#` comments when materializing it. `readPayload()` runs `JSON.parse`, which rejects comments and would fail the apply before any write.
38
+
39
+ Field rules:
40
+
41
+ - `project` — optional. Falls back to the active project from root `hot.md` pointer table.
42
+ - `date` — optional. Defaults to today (local). Must be `YYYY-MM-DD` if supplied.
43
+ - `openQuestions` — optional. Include only when `pages/open-questions.md` exists and changed this session.
44
+ - All other top-level fields are required.
45
+
46
+ Notes:
47
+
48
+ - `sessionState` / `projectHot` / `rootHot` / `openQuestions` are **overwrite** (full-file content). `sessionLog` / `log` are **append** (entry-level idempotency — exact-entry dedup, safe to re-run).
49
+ - 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.
50
+ - Write the payload to a temp path, e.g. `/tmp/hypo-session-close-<YYYY-MM-DD>.json`.
51
+
52
+ Content guidance for each slot:
53
+
54
+ 1. **sessionState** — next tasks list for the upcoming session (what to tackle first next time).
55
+ 2. **projectHot** — session snapshot under 500 words: what changed and decisions made. Do **not** put next-step tasks here; those belong in `sessionState`.
56
+ 3. **rootHot** — active-projects pointer table with this project's `Last Session` date set to today.
57
+ 4. **sessionLog** — one session entry to append to `projects/<name>/session-log/YYYY-MM.md`.
58
+ 5. **log** — one `session` entry to append to `<hypo-root>/log.md`.
59
+ 6. **openQuestions** (conditional) — only if `pages/open-questions.md` exists and questions were raised or resolved this session.
60
+
61
+ ---
62
+
63
+ ## Step 3 — Apply the payload
64
+
65
+ ```bash
66
+ node <package-root>/scripts/crystallize.mjs \
67
+ --apply-session-close \
68
+ --payload=/tmp/hypo-session-close-<YYYY-MM-DD>.json \
69
+ --session-id=<current-session-id> \
70
+ --hypo-dir="<path>" \
71
+ --json
72
+ ```
73
+
74
+ **`--session-id` (fix #27 PR-C):** pass the current session's id whenever you
75
+ know it — most importantly when this close was triggered by a `[WIKI_AUTOCLOSE]`
76
+ Stop-hook block (the block reason prints the exact `--session-id` to use). On a
77
+ verified close (`ok: true` + clean git tree), it writes the per-session marker
78
+ `HYPO_DIR/.cache/session-closed-<id>.marker`. That marker is what tells the
79
+ Stop-chain Layer 3 hook (`hypo-auto-minimal-crystallize`) the session is closed,
80
+ so it stops re-prompting. Omit it only when running crystallize purely for
81
+ synthesis (no session-close intent) — the marker is then simply not written.
82
+
83
+ **Behavior (fix #39 option D + fix #40 lint gates):**
84
+
85
+ | Invocation | Behavior |
86
+ |---|---|
87
+ | `--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. |
88
+ | `--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. |
89
+ | `--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. |
90
+ | `--apply-session-close --force` | Skips the probe early-exit. `--payload` still required for any actual apply work. |
91
+
92
+ **Two lint gates run automatically (fix #40):**
93
+
94
+ 1. **Preflight** — `lint.mjs --json` runs **before** any payload bytes are written. Errors in files this payload will overwrite (sessionState / projectHot / rootHot / openQuestions) are filtered (they're about to be replaced anyway). Errors in any other file → exit 1 with `stage='preflight-lint'`, no apply occurs.
95
+ 2. **Post-apply** — lint re-runs after the writes. Catches payloads that introduce a broken wikilink or malformed body. Surfaces as `stage='post-apply-lint'` (or `'post-apply-verification+lint'` if the freshness gate also fails).
28
96
 
29
97
  ---
30
98
 
31
- ## Step 3Session-close confirmation
99
+ ## Step 4Stage-based recovery
100
+
101
+ The result JSON includes a `stage` field when `ok: false`. Branch on it:
32
102
 
33
- After completing the checklist, report:
103
+ | `stage` | What broke | How to recover |
104
+ |---|---|---|
105
+ | `preflight-lint` | A non-payload file in the wiki has a blocking lint error. | Fix the lint error in that file (separate from session-close), then re-run. No payload bytes were written. |
106
+ | `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. |
107
+ | `post-apply-lint` | The payload itself introduced a lint blocker (broken wikilink, bad frontmatter). | Fix the offending content in the payload, then re-run. |
108
+ | `post-apply-verification+lint` | Both above. | Fix both; re-run. |
34
109
 
35
- - session-state.md updated
36
- - ✓ hot.md (project + root) updated
110
+ Once `ok: true`, report:
111
+
112
+ - ✓ session-state.md applied
113
+ - ✓ hot.md (project + root) applied
37
114
  - ✓ session-log entry appended
38
- - ✓ open-questions updated (or skipped if unchanged)
39
- - ✓ log.md updated
115
+ - ✓ open-questions applied (or skipped if unchanged)
116
+ - ✓ log.md entry appended
117
+ - ✓ post-apply lint clean
40
118
 
41
119
  Then ask: "Session closed. Would you like to also run knowledge synthesis now, or stop here?"
42
120
 
43
- If the user says stop, end here. Otherwise continue to Step 4.
121
+ If the user says stop, end here. Otherwise continue to Step 5.
44
122
 
45
123
  ---
46
124
 
47
- ## Step 4 — Surface synthesis candidates
125
+ ## Step 5 — Surface synthesis candidates
48
126
 
49
127
  Locate the Hypomnema package root (the directory containing this file's parent `commands/`).
50
128
 
@@ -56,7 +134,7 @@ Show the output to the user. If no candidates are found, tell them Hypomnema loo
56
134
 
57
135
  ---
58
136
 
59
- ## Step 5 — Choose what to crystallize
137
+ ## Step 6 — Choose what to crystallize
60
138
 
61
139
  If candidates exist, ask:
62
140
 
@@ -67,7 +145,7 @@ If candidates exist, ask:
67
145
 
68
146
  ---
69
147
 
70
- ## Step 5a — Tag cluster synthesis
148
+ ## Step 6a — Tag cluster synthesis
71
149
 
72
150
  For a tag cluster:
73
151
 
@@ -89,7 +167,7 @@ For a tag cluster:
89
167
 
90
168
  ---
91
169
 
92
- ## Step 5b — Draft upgrade
170
+ ## Step 6b — Draft upgrade
93
171
 
94
172
  For a draft page:
95
173
 
@@ -100,7 +178,7 @@ For a draft page:
100
178
 
101
179
  ---
102
180
 
103
- ## Step 5c — Cross-link unlinked pages
181
+ ## Step 6c — Cross-link unlinked pages
104
182
 
105
183
  For unlinked pages:
106
184
 
@@ -111,6 +189,18 @@ For unlinked pages:
111
189
 
112
190
  ---
113
191
 
114
- ## Step 6 — Report
192
+ ## Step 7 — Report
115
193
 
116
194
  Show what was created or modified, and offer to run `/hypo:lint` to verify all new links resolve.
195
+
196
+ ---
197
+
198
+ ## Appendix — Legacy `--check-session-close`
199
+
200
+ `--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:
201
+
202
+ ```bash
203
+ node <package-root>/scripts/crystallize.mjs --check-session-close [--hypo-dir="<path>"]
204
+ ```
205
+
206
+ 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".)
@@ -2,23 +2,34 @@
2
2
  description: Record an AI behavior correction or preference into the wiki
3
3
  ---
4
4
 
5
- You are running `/hypo:feedback`. Capture a behavior correction or preference into `pages/feedback/` for future sessions.
5
+ You are running `/hypo:feedback`. Capture a behavior correction or preference into `pages/feedback/` — the **single source of truth** for learned behaviors (ADR 0031 / fix #37).
6
6
 
7
7
  ## What this does
8
8
 
9
- - Creates or updates `pages/feedback/<topic>.md` with a dated entry
9
+ - Creates or updates `pages/feedback/<topic>.md` with a dated entry and full classification frontmatter
10
10
  - Appends a reference to `log.md`
11
- - Ensures the feedback is findable in future sessions
11
+ - **Automatically refreshes the projection** into `MEMORY.md` and the user's CLAUDE.md `<learned_behaviors>` via `feedback-sync --write`
12
+
13
+ > ⚠️ Do **not** hand-edit MEMORY.md or CLAUDE.md `<learned_behaviors>` for feedback. Those are one-way projections derived from the wiki page. Edit the wiki page; the projection follows.
12
14
 
13
15
  ---
14
16
 
15
17
  ## Step 1 — Gather feedback details
16
18
 
17
- If the user did not provide them, ask:
19
+ If the user did not provide them, ask. The classification fields are required so the page can project correctly:
20
+
21
+ 1. **Topic** (slug): "What topic does this feedback apply to? (e.g. `response-length`, `commit-style`)"
22
+ 2. **Rule** (entry): "State the rule or correction in one or two sentences."
23
+ 3. **Reason**: "What incident or reasoning prompted this?"
24
+ 4. **Scope**: "Does this apply globally (all projects) or to this project only?" → `global` | `project:<project-id>` (project-id must exact-match the resolved id; see Step 3 note)
25
+ 5. **Tier**: "Is this a hard rule (L1) or a softer preference (L2)?" → `L1` | `L2`
26
+ 6. **Targets**: "Where should this project?" → `project-memory` (MEMORY.md) and/or `claude-learned` (global CLAUDE.md). Default `project-memory`.
27
+ 7. **Priority** (1–5, higher sorts first; default 3).
28
+ 8. **Sensitivity**: `public` (default) or `sanitized` (redacted secrets/paths). `private` is not allowed — the wiki is git-pushed.
18
29
 
19
- 1. **Topic** (slug): "What topic does this feedback apply to? (e.g. `response-length`, `commit-style`, `code-comments`)"
20
- 2. **Rule**: "State the rule or correction in one or two sentences."
21
- 3. **Why** (optional): "What was the reason or incident that prompted this?"
30
+ If **claude-learned** is among the targets, the page must be `scope: global` + `tier: L1`, and you must also collect:
31
+ - **Global summary**: a one-line summary for the CLAUDE.md learned-behaviors entry.
32
+ - Confirm **promote to global** (the page is only projected to CLAUDE.md when promoted).
22
33
 
23
34
  ---
24
35
 
@@ -30,38 +41,41 @@ To check for an existing topic, locate the Hypomnema package root and run:
30
41
  node <package-root>/scripts/feedback.mjs --list [--hypo-dir="<path>"]
31
42
  ```
32
43
 
33
- If a matching topic exists, confirm with the user whether to append to it or create a new one.
44
+ If a matching topic exists, appending adds a dated entry and bumps `updated:` (classification frontmatter is preserved).
34
45
 
35
46
  ---
36
47
 
37
- ## Step 3 — Write the feedback entry
38
-
39
- Compose the entry text. Format:
40
-
41
- ```
42
- **Rule**: <one-line rule>
43
-
44
- **Why**: <reason or incident>
45
-
46
- **How to apply**: <when this kicks in>
47
- ```
48
+ ## Step 3 — Write the feedback page
48
49
 
49
- Then run:
50
+ Run with `--dry-run` first to preview the generated page, then without it to write. Pass every collected field:
50
51
 
51
52
  ```bash
52
53
  node <package-root>/scripts/feedback.mjs \
53
54
  --topic="<slug>" \
54
- --entry="<formatted entry text>" \
55
+ --entry="<one-line rule>" \
56
+ --scope="global|project:<project-id>" \
57
+ --tier="L1|L2" \
58
+ --targets="project-memory[,claude-learned]" \
59
+ --priority=<1-5> \
60
+ --sensitivity="public|sanitized" \
61
+ --memory-summary="<one-line MEMORY.md summary>" \
62
+ --reason="<why this rule exists>" \
63
+ [--global-summary="<one-line CLAUDE.md summary>" --promote-to-global] \
64
+ [--source="session:<date>"] \
55
65
  [--hypo-dir="<path>"] \
56
66
  [--dry-run]
57
67
  ```
58
68
 
59
- Run with `--dry-run` first to preview, then without it to write.
69
+ When `--targets` includes `claude-learned`, `--global-summary` and `--promote-to-global` are required (and `--scope=global --tier=L1`).
70
+
71
+ > **`scope: project:<project-id>` 주의 (v1.2.0).** `<project-id>`는 `feedback-sync`가 resolve한 project-id와 정확히 일치해야 한다 (default: cwd의 `/`,`.` → `-` 치환; `--project-id=<id>` 로 override). 일치하지 않으면 그 페이지는 해당 project의 MEMORY로 projection되지 **않는다** (silent skip — lint error 아님). 다만 현재 lint scope regex(`^project:[a-z0-9][a-z0-9-]*$`)는 cwd-derived id 형식(`-Users-...`)을 거부하므로, **`project:*` scope를 사용하려면 slug-safe id로 `--project-id=<slug>`를 override해서 wiki 디렉터리도 그 id에 맞추는 운영 패턴이 필요하다**. resolved-id ↔ slug 정합화는 v1.3.0 트랙에서 다룸.
72
+
73
+ On a real (non-dry-run) write, the script automatically runs `feedback-sync --write` to refresh MEMORY.md / CLAUDE.md. If that post-step reports drift it prints a one-line warning — the page is still saved; reconcile with `hypomnema feedback-sync --check`.
60
74
 
61
75
  ---
62
76
 
63
- ## Step 4 — Confirm and cross-reference
77
+ ## Step 4 — Confirm
64
78
 
65
- After writing:
66
- - Tell the user: "Saved to `pages/feedback/<topic>.md`."
67
- - If this feedback should also update the project's `session-state.md` or the user's CLAUDE.md `<learned_behaviors>`, ask: "Should I also add this to your CLAUDE.md learned behaviors?"
79
+ After writing, tell the user:
80
+ - "Saved to `pages/feedback/<topic>.md` and refreshed the MEMORY/CLAUDE projection."
81
+ - If the projection post-step warned (over-cap, conflict, unresolved project-id), surface that and suggest `hypomnema feedback-sync --check`.
@@ -21,13 +21,33 @@ Ask the user:
21
21
  2. **Slug**: "What slug should this source have? (e.g. `openai-swarm-paper`, `team-retro-2026-04`)"
22
22
  - Default: derive from title or filename
23
23
 
24
- If a URL is provided, fetch the content. If a file path is provided, read it.
24
+ Do **not** fetch the URL or read the file yet — the privacy guard in Step 2 must run first.
25
25
 
26
26
  ---
27
27
 
28
- ## Step 2 — Check for orphaned sources
28
+ ## Step 2 — Privacy guard (`.hypoignore`)
29
29
 
30
- Locate the Hypomnema package root. Run the ingest helper to surface existing orphaned sources:
30
+ Refuse to ingest secrets (`.env`, SSH keys, credentials) before they ever reach `sources/`. Locate the Hypomnema package root and run the guard for **both** the input path and the destination path:
31
+
32
+ 1. **If the source is a file path**, check it (use an absolute path):
33
+
34
+ ```bash
35
+ node <package-root>/scripts/ingest.mjs [--hypo-dir="<path>"] --check="<absolute-input-path>"
36
+ ```
37
+
38
+ 2. **Always** check the destination `sources/<slug>.<ext>`:
39
+
40
+ ```bash
41
+ node <package-root>/scripts/ingest.mjs [--hypo-dir="<path>"] --check="sources/<slug>.<ext>"
42
+ ```
43
+
44
+ If either command exits non-zero, **stop**: surface the `Refused: ...` message to the user and do not fetch, read, or save the source. The slug check matters because a user could rename a `.env` to an innocuous slug — the destination must still be blocked.
45
+
46
+ ---
47
+
48
+ ## Step 3 — Check for orphaned sources
49
+
50
+ Run the ingest helper to surface existing orphaned sources:
31
51
 
32
52
  ```bash
33
53
  node <package-root>/scripts/ingest.mjs [--hypo-dir="<path>"]
@@ -35,9 +55,11 @@ node <package-root>/scripts/ingest.mjs [--hypo-dir="<path>"]
35
55
 
36
56
  If there are orphaned sources already in `sources/`, ask: "There are N unprocessed sources — do you want to ingest one of those instead?"
37
57
 
58
+ Once the guard has passed: if a URL is provided, fetch the content; if a file path is provided, read it.
59
+
38
60
  ---
39
61
 
40
- ## Step 3 — Save raw source
62
+ ## Step 4 — Save raw source
41
63
 
42
64
  Save the raw content to `sources/<slug>.<ext>` (use `.md` for text, `.txt` for plain text, `.pdf` or original extension for documents).
43
65
 
@@ -45,13 +67,13 @@ Do **not** modify or summarize in the sources file — save it as-is.
45
67
 
46
68
  ---
47
69
 
48
- ## Step 4 — Synthesize
70
+ ## Step 5 — Synthesize
49
71
 
50
72
  Read and synthesize the source:
51
73
 
52
74
  1. **Check index.md** — does a page on this topic already exist?
53
75
  - If yes: update the existing page (merge new information, mark `updated:` today)
54
- - If no: create a new page in `pages/` with `type: source-summary` and `source: <slug>`
76
+ - If no: create a new page in `pages/` with `type: source-summary` and `sources: [<slug>]`
55
77
 
56
78
  2. **Frontmatter** for new pages:
57
79
  ```yaml
@@ -60,7 +82,7 @@ Read and synthesize the source:
60
82
  type: source-summary
61
83
  updated: YYYY-MM-DD
62
84
  tags: [<relevant tags>]
63
- source: <slug>
85
+ sources: [<slug>]
64
86
  confidence: high | medium | low
65
87
  evidence_strength: direct
66
88
  ---
@@ -70,14 +92,14 @@ Read and synthesize the source:
70
92
 
71
93
  ---
72
94
 
73
- ## Step 5 — Update index.md and log.md
95
+ ## Step 6 — Update index.md and log.md
74
96
 
75
97
  - Append a line to `index.md`: `- [[pages/<slug>]] — <one-line description>`
76
98
  - Append to `log.md`: `- YYYY-MM-DD ingest: [[pages/<slug>]] from sources/<slug>.<ext>`
77
99
 
78
100
  ---
79
101
 
80
- ## Step 6 — Report
102
+ ## Step 7 — Report
81
103
 
82
104
  Show:
83
105
  - ✓ Saved source: `sources/<slug>.<ext>`
@@ -28,7 +28,7 @@ node <package-root>/scripts/upgrade.mjs [--hypo-dir="<path>"]
28
28
 
29
29
  Show the output verbatim.
30
30
 
31
- > **Note**: If a major SCHEMA bump is detected, this step generates a `MIGRATION-vX.Y.md` file in the Hypomnema root. This is a new informational file no existing files are overwritten.
31
+ > **Note**: A major SCHEMA bump is only **detected** in this step. The informational `MIGRATION-vX.Y.md` file is written later by `--apply` (Step 4) and only on a major bump. `SCHEMA.md` is never auto-overwritten.
32
32
 
33
33
  ---
34
34
 
@@ -38,7 +38,7 @@ Show the output verbatim.
38
38
  - `⚠` — minor update available (stale hook or missing settings entry)
39
39
  - `✗` — major version bump or missing hook files (action required)
40
40
 
41
- For a **major SCHEMA bump**: point the user to the generated `MIGRATION-vX.Y.md` file in their Hypomnema root and ask them to review it before applying.
41
+ For a **major SCHEMA bump**: warn the user that `--apply` will additionally write a `MIGRATION-vX.Y.md` informational file in their Hypomnema root and that they must manually merge the SCHEMA diff after applying.
42
42
 
43
43
  ---
44
44
 
@@ -328,7 +328,7 @@ scripts/session-audit.mjs ← per-session metrics + classificat
328
328
  scripts/weekly-report.mjs ← aggregated weekly autonomy score
329
329
 
330
330
 
331
- pages/observability/<YYYY-WW>.md ← committed report (heuristic v0)
331
+ journal/weekly/<YYYY-Www>.md ← committed report (heuristic v0, spec §6.4 SoT)
332
332
  ```
333
333
 
334
334
  ### Transcript dual-source (ADR 0019)
@@ -120,7 +120,7 @@ If you need to share new logic, prefer extending an existing helper over adding
120
120
 
121
121
  ```bash
122
122
  npm test # tests/runner.mjs — unit + smoke + contract tests
123
- npm run lint # scripts/lint.mjs — frontmatter + wikilink validation
123
+ npm run lint # scripts/lint.mjs — frontmatter + wikilink validation + W8 (design-history stale vs session-log)
124
124
  ```
125
125
 
126
126
  The test runner uses only Node.js built-ins. Tests create scoped temp directories and clean up after themselves; you can run the suite without any environment setup.
package/hooks/hooks.json CHANGED
@@ -11,6 +11,17 @@
11
11
  ]
12
12
  }
13
13
  ],
14
+ "SessionEnd": [
15
+ {
16
+ "hooks": [
17
+ {
18
+ "type": "command",
19
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/hypo-session-end.mjs",
20
+ "timeout": 10
21
+ }
22
+ ]
23
+ }
24
+ ],
14
25
  "UserPromptSubmit": [
15
26
  {
16
27
  "hooks": [
@@ -60,6 +71,15 @@
60
71
  "timeout": 10
61
72
  }
62
73
  ]
74
+ },
75
+ {
76
+ "hooks": [
77
+ {
78
+ "type": "command",
79
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/hypo-web-fetch-ingest.mjs",
80
+ "timeout": 10
81
+ }
82
+ ]
63
83
  }
64
84
  ],
65
85
  "Stop": [
@@ -89,6 +109,15 @@
89
109
  "timeout": 60
90
110
  }
91
111
  ]
112
+ },
113
+ {
114
+ "hooks": [
115
+ {
116
+ "type": "command",
117
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/hooks/hypo-auto-minimal-crystallize.mjs",
118
+ "timeout": 10
119
+ }
120
+ ]
92
121
  }
93
122
  ],
94
123
  "CwdChanged": [
@@ -114,5 +143,5 @@
114
143
  }
115
144
  ]
116
145
  },
117
- "shared": ["hypo-shared.mjs"]
146
+ "shared": ["hypo-shared.mjs", "version-check.mjs", "version-check-fetch.mjs"]
118
147
  }
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import { spawnSync } from 'child_process';
9
- import { HYPO_DIR, loadHypoIgnore, isIgnored } from './hypo-shared.mjs';
9
+ import { HYPO_DIR, loadHypoIgnore, isIgnored, appendSyncFailure } from './hypo-shared.mjs';
10
10
  import { join } from 'path';
11
11
 
12
12
  function git(...args) {
@@ -27,7 +27,8 @@ for (const line of (porcelain.stdout || '').split('\n')) {
27
27
  if (!line) continue;
28
28
  const file = line.slice(3).replace(/^"|"$/g, '').split(' -> ').pop().trim();
29
29
  if (!file) continue;
30
- if (ignorePatterns.length > 0 && isIgnored(join(HYPO_DIR, file), HYPO_DIR, ignorePatterns)) continue;
30
+ if (ignorePatterns.length > 0 && isIgnored(join(HYPO_DIR, file), HYPO_DIR, ignorePatterns))
31
+ continue;
31
32
  paths.push(file);
32
33
  }
33
34
  if (paths.length > 0) git('add', '--', ...paths);
@@ -42,8 +43,13 @@ if (staged) {
42
43
  }
43
44
 
44
45
  if (hasRemote()) {
45
- git('pull', '--no-rebase', '-q');
46
- git('push');
46
+ // fix #9: pull/push failures must not stop the session, but they can no
47
+ // longer be swallowed silently — record each to .cache/sync-state.json so
48
+ // session-start (#10) and doctor (#11) can surface them next session.
49
+ const pull = git('pull', '--no-rebase', '-q');
50
+ if (pull.status !== 0) appendSyncFailure(HYPO_DIR, 'pull', pull.stderr || pull.stdout);
51
+ const push = git('push');
52
+ if (push.status !== 0) appendSyncFailure(HYPO_DIR, 'push', push.stderr || push.stdout);
47
53
  }
48
54
 
49
55
  console.log(JSON.stringify({ continue: true, suppressOutput: true }));