hypomnema 1.4.1 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.ko.md +1 -1
  4. package/README.md +1 -1
  5. package/commands/audit.md +1 -1
  6. package/commands/crystallize.md +11 -9
  7. package/commands/doctor.md +1 -1
  8. package/commands/feedback.md +2 -2
  9. package/commands/graph.md +1 -1
  10. package/commands/ingest.md +1 -1
  11. package/commands/init.md +2 -2
  12. package/commands/lint.md +1 -1
  13. package/commands/query.md +1 -1
  14. package/commands/rename.md +1 -1
  15. package/commands/resume.md +1 -1
  16. package/commands/stats.md +1 -1
  17. package/commands/uninstall.md +4 -2
  18. package/commands/upgrade.md +1 -1
  19. package/commands/verify.md +1 -1
  20. package/docs/ARCHITECTURE.md +2 -2
  21. package/docs/CONTRIBUTING.md +20 -12
  22. package/hooks/hypo-auto-commit.mjs +2 -2
  23. package/hooks/hypo-auto-minimal-crystallize.mjs +30 -18
  24. package/hooks/hypo-compact-guard.mjs +2 -2
  25. package/hooks/hypo-cwd-change.mjs +27 -37
  26. package/hooks/hypo-personal-check.mjs +37 -22
  27. package/hooks/hypo-session-end.mjs +1 -1
  28. package/hooks/hypo-session-record.mjs +2 -2
  29. package/hooks/hypo-session-start.mjs +34 -40
  30. package/hooks/hypo-shared.mjs +364 -82
  31. package/hooks/version-check.mjs +1 -1
  32. package/package.json +5 -1
  33. package/scripts/check-tracker-ids.mjs +69 -31
  34. package/scripts/crystallize.mjs +322 -70
  35. package/scripts/doctor.mjs +7 -7
  36. package/scripts/feedback-sync.mjs +5 -5
  37. package/scripts/feedback.mjs +9 -10
  38. package/scripts/init.mjs +7 -7
  39. package/scripts/lib/check-tracker-ids.mjs +90 -32
  40. package/scripts/lib/design-history-stale.mjs +1 -1
  41. package/scripts/lib/extensions.mjs +7 -7
  42. package/scripts/lib/failure-type.mjs +1 -1
  43. package/scripts/lib/feedback-scope.mjs +1 -1
  44. package/scripts/lib/project-create.mjs +25 -7
  45. package/scripts/lib/schema-vocab.mjs +105 -1
  46. package/scripts/lib/template-schema-version.mjs +1 -1
  47. package/scripts/lib/wd-match.mjs +181 -0
  48. package/scripts/lib/wikilink.mjs +1 -1
  49. package/scripts/lint.mjs +14 -6
  50. package/scripts/rename.mjs +1 -1
  51. package/scripts/resume.mjs +20 -22
  52. package/scripts/session-audit.mjs +1 -1
  53. package/scripts/stats.mjs +3 -3
  54. package/scripts/uninstall.mjs +3 -3
  55. package/scripts/upgrade.mjs +17 -18
  56. package/skills/crystallize/SKILL.md +16 -8
  57. package/skills/graph/SKILL.md +2 -2
  58. package/skills/ingest/SKILL.md +4 -4
  59. package/skills/lint/SKILL.md +2 -2
  60. package/skills/query/SKILL.md +2 -2
  61. package/skills/verify/SKILL.md +2 -2
  62. package/templates/SCHEMA.md +12 -4
  63. package/templates/hypo-config.md +1 -1
  64. package/templates/hypo-guide.md +24 -15
  65. package/templates/projects/_template/hot.md +1 -1
  66. package/scripts/fix-status-verify.mjs +0 -256
  67. package/scripts/lib/adr-corpus.mjs +0 -79
  68. package/scripts/lib/fix-manifest.mjs +0 -109
  69. package/scripts/lib/fix-status-verify.mjs +0 -439
@@ -11,14 +11,14 @@ When invoked to close a session — via an explicit close signal ("세션 종료
11
11
 
12
12
  ## What this does
13
13
 
14
- - **Close mode**: walks the checklist (session-state, project hot.md, root hot.md, session-log, open-questions(변경 시), log.md) plus a lint step, then writes via `crystallize.mjs --apply-session-close --payload=<path>` — which runs the lint gate automatically, **scoped to the files it writes** (debt elsewhere is a non-blocking notice). `--check-session-close` is a read-only dry-run of the **full** PreCompact gate (ADR 0046) — close files + scoped lint + design-history + feedback projection sharing one function (`precompactGateStatus`) with the gate. A green check means no gate blocker needs a human fix, so it is the signal to declare the session closed (pass `--transcript-path` to widen the lint scope to this session's edited files exactly as the interactive hook does). It is not a hard guarantee: the live `/compact` can still differ on a context-≥70% prompt, `HYPO_SKIP_GATE`, or a transcript-scoped lint error the check did not see.
14
+ - **Close mode**: walks the checklist (session-state, project hot.md, root hot.md, session-log, open-questions(변경 시), log.md) plus a lint step, then writes via `/hypo:crystallize` in `--apply-session-close --payload=<path>` mode — which runs the lint gate automatically, **scoped to the files it writes** (debt elsewhere is a non-blocking notice). `--check-session-close` is a read-only dry-run of the **full** PreCompact gate: close files + scoped lint + design-history + feedback projection, sharing one function (`precompactGateStatus`) with the gate. A green check means no gate blocker needs a human fix, so it is the signal to declare the session closed (pass `--transcript-path` to widen the lint scope to this session's edited files exactly as the interactive hook does). It is not a hard guarantee: the live `/compact` can still differ on a context-≥70% prompt, `HYPO_SKIP_GATE`, or a transcript-scoped lint error the check did not see.
15
15
  - **Synthesis mode**: finds tag clusters (≥ N pages), orphan pages (no outbound `[[wikilinks]]`), and draft / stub pages, then guides consolidation into `pages/syntheses/<topic>.md` with back-links and `index.md` updates.
16
16
 
17
17
  ---
18
18
 
19
19
  ## Step 1 — Locate package root
20
20
 
21
- Locate the Hypomnema package root (the directory two levels above this file (`skills/<name>/SKILL.md` package root)).
21
+ Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema upgrade --apply` or reinstall instead of guessing the cache layout.
22
22
 
23
23
  If the user specified a wiki directory, pass it as `--wiki-dir="<path>"`. Otherwise omit the flag and the script resolves the wiki root automatically via `HYPO_DIR` → `hypo-config.md` scan → `~/hypomnema`.
24
24
 
@@ -27,7 +27,7 @@ If the user specified a wiki directory, pass it as `--wiki-dir="<path>"`. Otherw
27
27
  ## Step 2 — Run crystallize scan
28
28
 
29
29
  ```bash
30
- node <package-root>/scripts/crystallize.mjs \
30
+ node ${CLAUDE_PLUGIN_ROOT}/scripts/crystallize.mjs \
31
31
  [--wiki-dir="<path>"] \
32
32
  [--min-group=<n>] \
33
33
  [--json]
@@ -47,7 +47,7 @@ If `/hypo:crystallize` was invoked as a session-close action, run through this c
47
47
 
48
48
  ### Advisory reflections (run before the checklist below — advisory only)
49
49
 
50
- Surface each of these four to the user first. Every one is **advisory** (ADR 0029 identity guard): the user confirms or declines, and none performs an automatic action, writes a file on its own, or bypasses the mandatory gate.
50
+ Surface each of these four to the user first. Every one is **advisory** (identity guard): the user confirms or declines, and none performs an automatic action, writes a file on its own, or bypasses the mandatory gate.
51
51
 
52
52
  - **Trivial-session check (#44)** — Was this session trivial (a single bug fix, a single-file edit, or Q&A with no durable artifact)? If so, recommend skipping session-close: *"이 세션은 trivial해 보입니다 — session-close를 건너뛸까요?"* A trivial skip is a recommendation, **not** a bypass: it must not mark the session closed, must not run `--mark-session-closed`, and must not claim `/compact` can pass. Any real close still requires all 5 mandatory files.
53
53
  - **ADR-candidate check (#41)** — Did this session make an architectural or design decision (a new pattern, a tradeoff, a convention)? If yes, ask whether it warrants an ADR and capture that intent in the session-log entry. If nothing rose to ADR level, you may record the literal marker `ADR 없음 — <one-line reason>` in that same session-log entry — but gate it on #42's bar, not this one: the marker is machine-read and W8 treats a session-log entry carrying `ADR 없음` (and no ADR reference) as a *no-design* session, excluding it from the design-history staleness check. So write `ADR 없음` only when the session had no design change at all. If it had a sub-ADR design shift (background / tradeoff / differentiation), append to design-history (#42a) instead — writing the marker there would suppress the W8 nudge that shift needs. **Never auto-write an ADR file** — the session-log note is the only action here.
@@ -61,21 +61,29 @@ When uncertain, surface the question rather than skip it. None of the four block
61
61
  3. **hot.md (root)** — update `<wiki-root>/hot.md` active-projects pointer table: set the `Last Session` date for this project to today.
62
62
  4. **session-log** — append a session entry to `projects/<name>/session-log/YYYY-MM-DD.md` (daily shard; the apply path creates today's file with seeded frontmatter if it does not exist yet).
63
63
  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.
64
- 6. **log.md** — append a `session` entry to `<wiki-root>/log.md`.
64
+
65
+ > Do **not** hand-write the root `log.md` session entry, and do **not** put a `log` field in the apply payload. The `## [date] session | <project>` entry is a derivable artifact: `--apply-session-close` calls `deriveRootLogEntries` after writing the session-log, reconstructing the canonical line from the session-log heading. (Supply `log` only for a deliberately custom line; it is now an optional payload field.)
65
66
 
66
67
  After completing the checklist, verify it before reporting:
67
68
 
68
69
  ```bash
69
- node <package-root>/scripts/crystallize.mjs --check-session-close [--hypo-dir="<path>"]
70
+ node ${CLAUDE_PLUGIN_ROOT}/scripts/crystallize.mjs --check-session-close [--hypo-dir="<path>"]
70
71
  ```
71
72
 
72
- This runs the **full** PreCompact gate via the shared `precompactGateStatus`
73
- (ADR 0046): close files (`missing` / `stale`) plus lint blockers, stale
73
+ This runs the **full** PreCompact gate via the shared `precompactGateStatus`:
74
+ close files (`missing` / `stale`) plus lint blockers, stale
74
75
  design-history, and feedback projection over-cap/conflict. Fix every `✗` it
75
76
  reports and re-run until it prints **"Compact-ready"** — that is the signal the
76
77
  session is closed. A close-files-only pass is not enough; the real `/compact`
77
78
  also blocks on those other checks.
78
79
 
80
+ Optional `--project=<slug>` narrows the check to ONE project (close status plus
81
+ lint scope). It is a project-scoped **diagnostic**, not the compact-ready
82
+ signal. A green `--project` result (JSON `scope: "project"`) means only that
83
+ project is close-complete; another today-active project can still block
84
+ `/compact`. Use the plain `--check-session-close` (no `--project`) for the
85
+ go/no-go close signal.
86
+
79
87
  **When using `--apply-session-close --session-id=<id>`** (the payload-driven
80
88
  path), the `--session-id` must be the main conversation's session id. Do NOT
81
89
  extract it from a background task or Agent output path (e.g., a UUID from
@@ -14,7 +14,7 @@ You are running `/hypo:graph`. Build a wikilink dependency graph from all pages
14
14
 
15
15
  ## Step 1 — Locate package root
16
16
 
17
- Locate the Hypomnema package root (the directory two levels above this file (`skills/<name>/SKILL.md` package root)).
17
+ Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema upgrade --apply` or reinstall instead of guessing the cache layout.
18
18
 
19
19
  If the user specified a wiki directory, pass it as `--wiki-dir="<path>"`. Otherwise omit the flag and the script resolves the wiki root automatically via `HYPO_DIR` → `hypo-config.md` scan → `~/hypomnema`.
20
20
 
@@ -37,7 +37,7 @@ Optionally ask:
37
37
  ## Step 3 — Run graph
38
38
 
39
39
  ```bash
40
- node <package-root>/scripts/graph.mjs \
40
+ node ${CLAUDE_PLUGIN_ROOT}/scripts/graph.mjs \
41
41
  [--wiki-dir="<path>"] \
42
42
  [--format=json|mermaid|dot] \
43
43
  [--min-edges=<n>]
@@ -14,7 +14,7 @@ You are running `/hypo:ingest`. Add a new source document to `sources/` and crea
14
14
 
15
15
  ## Step 1 — Locate package root
16
16
 
17
- Locate the Hypomnema package root (the directory two levels above this file (`skills/<name>/SKILL.md` package root)).
17
+ Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema upgrade --apply` or reinstall instead of guessing the cache layout.
18
18
 
19
19
  If the user specified a wiki directory, pass it as `--hypo-dir="<path>"`. Otherwise omit the flag and the script resolves the wiki root automatically via `HYPO_DIR` → `hypo-config.md` scan → `~/hypomnema`.
20
20
 
@@ -23,7 +23,7 @@ If the user specified a wiki directory, pass it as `--hypo-dir="<path>"`. Otherw
23
23
  ## Step 2 — Run ingest status check
24
24
 
25
25
  ```bash
26
- node <package-root>/scripts/ingest.mjs [--hypo-dir="<path>"] [--json]
26
+ node ${CLAUDE_PLUGIN_ROOT}/scripts/ingest.mjs [--hypo-dir="<path>"] [--json]
27
27
  ```
28
28
 
29
29
  Options:
@@ -40,13 +40,13 @@ Before touching any source content, refuse to ingest secrets (`.env`, SSH keys,
40
40
  1. **If the user provided a file path**, check it (use an absolute path):
41
41
 
42
42
  ```bash
43
- node <package-root>/scripts/ingest.mjs [--hypo-dir="<path>"] --check="<absolute-input-path>"
43
+ node ${CLAUDE_PLUGIN_ROOT}/scripts/ingest.mjs [--hypo-dir="<path>"] --check="<absolute-input-path>"
44
44
  ```
45
45
 
46
46
  2. **Always** check the destination `sources/<slug>.<ext>`:
47
47
 
48
48
  ```bash
49
- node <package-root>/scripts/ingest.mjs [--hypo-dir="<path>"] --check="sources/<slug>.<ext>"
49
+ node ${CLAUDE_PLUGIN_ROOT}/scripts/ingest.mjs [--hypo-dir="<path>"] --check="sources/<slug>.<ext>"
50
50
  ```
51
51
 
52
52
  If either command exits non-zero, **stop**: surface the `Refused: ...` message to the user and do not download, 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.
@@ -16,7 +16,7 @@ You are running `/hypo:lint`. Validate all wiki pages for frontmatter correctnes
16
16
 
17
17
  ## Step 1 — Locate package root
18
18
 
19
- Locate the Hypomnema package root (the directory two levels above this file (`skills/<name>/SKILL.md` package root)).
19
+ Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema upgrade --apply` or reinstall instead of guessing the cache layout.
20
20
 
21
21
  If the user specified a wiki directory, pass it as `--wiki-dir="<path>"`. Otherwise omit the flag and the script resolves the wiki root automatically via `HYPO_DIR` → `hypo-config.md` scan → `~/hypomnema`.
22
22
 
@@ -25,7 +25,7 @@ If the user specified a wiki directory, pass it as `--wiki-dir="<path>"`. Otherw
25
25
  ## Step 2 — Run lint
26
26
 
27
27
  ```bash
28
- node <package-root>/scripts/lint.mjs [--wiki-dir="<path>"] [--json] [--fix]
28
+ node ${CLAUDE_PLUGIN_ROOT}/scripts/lint.mjs [--wiki-dir="<path>"] [--json] [--fix]
29
29
  ```
30
30
 
31
31
  Options:
@@ -14,7 +14,7 @@ You are running `/hypo:query`. Full-text search across all wiki pages and projec
14
14
 
15
15
  ## Step 1 — Locate package root
16
16
 
17
- Locate the Hypomnema package root (the directory two levels above this file (`skills/<name>/SKILL.md` package root)).
17
+ Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema upgrade --apply` or reinstall instead of guessing the cache layout.
18
18
 
19
19
  If the user specified a wiki directory, pass it as `--wiki-dir="<path>"`. Otherwise omit the flag and the script resolves the wiki root automatically via `HYPO_DIR` → `hypo-config.md` scan → `~/hypomnema`.
20
20
 
@@ -31,7 +31,7 @@ Use the search terms from the user's message. If no query was provided, ask:
31
31
  ## Step 3 — Run query
32
32
 
33
33
  ```bash
34
- node <package-root>/scripts/query.mjs \
34
+ node ${CLAUDE_PLUGIN_ROOT}/scripts/query.mjs \
35
35
  --q="<search terms>" \
36
36
  [--wiki-dir="<path>"] \
37
37
  [--limit=<n>] \
@@ -14,7 +14,7 @@ You are running `/hypo:verify`. Check all wiki pages for `verify_by` and `verify
14
14
 
15
15
  ## Step 1 — Locate package root
16
16
 
17
- Locate the Hypomnema package root (the directory two levels above this file (`skills/<name>/SKILL.md` package root)).
17
+ Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema upgrade --apply` or reinstall instead of guessing the cache layout.
18
18
 
19
19
  If the user specified a wiki directory, pass it as `--wiki-dir="<path>"`. Otherwise omit the flag and the script resolves the wiki root automatically via `HYPO_DIR` → `hypo-config.md` scan → `~/hypomnema`.
20
20
 
@@ -23,7 +23,7 @@ If the user specified a wiki directory, pass it as `--wiki-dir="<path>"`. Otherw
23
23
  ## Step 2 — Run verify
24
24
 
25
25
  ```bash
26
- node <package-root>/scripts/verify.mjs \
26
+ node ${CLAUDE_PLUGIN_ROOT}/scripts/verify.mjs \
27
27
  [--wiki-dir="<path>"] \
28
28
  [--file="<path>"] \
29
29
  [--json]
@@ -96,7 +96,7 @@ verify_by: <question to re-check at next review>
96
96
  verify_by_date: YYYY-MM-DD
97
97
  ```
98
98
 
99
- ### 3.1. `feedback` type — projection fields (ADR 0031)
99
+ ### 3.1. `feedback` type — projection fields
100
100
 
101
101
  Feedback pages are the single source of truth for behavior corrections;
102
102
  `hypomnema feedback-sync` projects them one-way into Claude Code's `MEMORY.md`
@@ -154,9 +154,11 @@ Edit the feedback page only — never hand-edit the generated
154
154
 
155
155
  ## 4. Tag Vocabulary
156
156
 
157
- Use lowercase, hyphenated tags. Vocabulary is locked — `lint` blocks unknown tags
158
- and forbidden patterns (PascalCase, plurals, whitespace, generic words).
159
- Extend this list (and `~/hypomnema/SCHEMA.md`) before introducing a new tag.
157
+ Use lowercase, hyphenated tags. `lint` hard-blocks forbidden patterns (PascalCase,
158
+ plurals, whitespace, generic words); an unknown but well-formed tag is a warning,
159
+ and a session close auto-registers it into the `### Pending` section below so the
160
+ next lint accepts it. Promote a pending tag into a category here (and in
161
+ `~/hypomnema/SCHEMA.md`) once it has settled.
160
162
 
161
163
  **Meta**: `wiki`, `index`, `pages`, `home`, `overview`, `guide`, `operations`, `schema`, `reference`, `hypo`, `commands`, `hot-cache`, `migration`
162
164
  **Workflow**: `automation`, `hooks`, `observability`, `autonomy`, `wiki-health`, `weekly`
@@ -165,6 +167,12 @@ Extend this list (and `~/hypomnema/SCHEMA.md`) before introducing a new tag.
165
167
  **Status**: `active`, `completed`, `archived`, `draft`, `stable`, `deprecated`, `needs-review`, `proposed`, `superseded`
166
168
  **Content classification**: `learning`, `tip`, `feedback`, `gotcha`, `concept`, `pattern`
167
169
 
170
+ ### Pending (auto-registered)
171
+
172
+ Tags auto-registered by a crystallize close because they were not yet in the
173
+ vocabulary above. Review periodically: promote each into a category and delete it
174
+ here, or drop the tag from the page.
175
+
168
176
  ### Forbidden patterns
169
177
 
170
178
  | Pattern | Reason | Use instead |
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  title: Hypomnema Config
3
3
  type: config
4
- version: "1.4.1"
4
+ version: "1.5.0"
5
5
  created: YYYY-MM-DD
6
6
  ---
7
7
 
@@ -67,7 +67,7 @@ Trigger: explicit close mention, `/compact` request, or context limit approachin
67
67
 
68
68
  **Auto-trigger rule**: when the user sends a natural-language close signal — "세션 마무리하자", "오늘 여기까지", "이만 종료", "wrap up", "signing off", or equivalent — run the session close checklist immediately without waiting for an explicit `/compact` or `/hypo:crystallize`. If the intent is ambiguous, confirm with a single question before proceeding.
69
69
 
70
- **Proactive close offer** (ADR 0022 Layer 1): even when the user gives no close signal, offer to wrap up once a task is truly done — don't make them remember to. Fire `AskUserQuestion` **at the end of the assistant turn in which you report a task complete or verified**, when both hold:
70
+ **Proactive close offer** (Layer 1): even when the user gives no close signal, offer to wrap up once a task is truly done — don't make them remember to. Fire `AskUserQuestion` **at the end of the assistant turn in which you report a task complete or verified**, when both hold:
71
71
 
72
72
  1. The session did substantial work — file mutations or multi-step changes, not Q&A only.
73
73
  2. The current user request did not already include a next task or tell you not to close.
@@ -82,27 +82,30 @@ Ask: *"이 작업이 마무리되었나요? 세션을 정리(crystallize)할까
82
82
  2. Update `projects/<name>/hot.md` (what was done, ≤500 words, overwrite)
83
83
  3. Append to `projects/<name>/session-log/YYYY-MM-DD.md` (daily shard, narrative entry, append-only)
84
84
  4. Update root `hot.md` pointer table + date
85
- 5. Run `scripts/lint.mjs` and fix errors in files **you** touched — debt in other
85
+ 5. Run `/hypo:lint` and fix errors in files **you** touched — debt in other
86
86
  projects / shared pages you did not author is reported as a non-blocking
87
- notice, not a gate. (The documented `crystallize.mjs --apply-session-close`
88
- path runs this lint automatically, scoped to the files it writes.)
89
- 6. Verify with `scripts/crystallize.mjs --check-session-close`: a dry-run of the
87
+ notice, not a gate. (The documented `/hypo:crystallize` session-close path
88
+ runs this lint automatically, scoped to the files it writes.)
89
+ 6. Verify with `/hypo:crystallize` in its `--check-session-close` mode: a dry-run of the
90
90
  **full** PreCompact gate (close files + lint + design-history + feedback
91
- projection, ADR 0046), sharing one function with the gate. Only declare the
91
+ projection), sharing one function with the gate. Only declare the
92
92
  session closed once it prints **"Compact-ready"**. A "close files updated"
93
93
  check alone is not enough — the real `/compact` gate also blocks on a lint
94
94
  error in a close file or a feedback projection over-cap. (Not a hard
95
95
  guarantee: the live gate can still differ on a context-≥70% prompt,
96
96
  `HYPO_SKIP_GATE`, or a transcript-scoped lint error — pass `--transcript-path`
97
97
  to include the last.) Pass `--session-id=<id>` to also see `marker_present`
98
- (step 7).
99
- 7. Record the session-closed marker (ADR 0047). The Stop hook blocks until this
98
+ (step 7). `--project=<slug>` narrows the check to one project (a scoped
99
+ diagnostic, JSON `scope: "project"`): green there means only that slug is
100
+ close-complete, **not** that `/compact` is globally unblocked. Use the plain
101
+ check for the go/no-go signal.
102
+ 7. Record the session-closed marker. The Stop hook blocks until this
100
103
  session's per-session marker exists, and a hand-edit close (writing the files
101
104
  directly + committing) never writes it; the marker is written only by the
102
- crystallize writer, never by the hook (ADR 0022 bypass guard). Normal path:
103
- close via `crystallize.mjs --apply-session-close --session-id=<id> --transcript-path=<path>`,
105
+ crystallize writer, never by the hook (bypass guard). Normal path:
106
+ close via `/hypo:crystallize` (`--apply-session-close --session-id=<id> --transcript-path=<path>`),
104
107
  which writes the marker once the gate is green. Hand-edit recovery: after
105
- committing the files, run `crystallize.mjs --mark-session-closed --session-id=<id> --transcript-path=<path>`.
108
+ committing the files, run `/hypo:crystallize` (`--mark-session-closed --session-id=<id> --transcript-path=<path>`).
106
109
  Both writers gate the marker on the SAME `precompactGateStatus` as `/compact`,
107
110
  so the marker only lands when step 6 would print **"Compact-ready"**.
108
111
 
@@ -150,7 +153,7 @@ projects/<name>/
150
153
 
151
154
  Add to root `hot.md` active projects table.
152
155
 
153
- #### Auto-project offer (ADR 0023)
156
+ #### Auto-project offer
154
157
 
155
158
  When SessionStart / CwdChanged injects a line like
156
159
  `[WIKI: cwd '<name>'에 매칭되는 프로젝트가 없습니다. 자동 생성할까요? (Y/n)]`,
@@ -163,8 +166,14 @@ ignore it:**
163
166
  2. **On Yes** — run the scaffold helper once (it substitutes tokens, creates
164
167
  the project files, adds the root `hot.md` row, and logs the entry):
165
168
  ```
166
- node <pkg-root>/scripts/lib/project-create.mjs --name <slug> --working-dir "$(pwd)"
169
+ node ${CLAUDE_PLUGIN_ROOT}/scripts/lib/project-create.mjs --name <slug> --working-dir "$(pwd)"
167
170
  ```
171
+ To resolve that package root from this guide: if `${CLAUDE_PLUGIN_ROOT}` is
172
+ already an absolute path, use it; otherwise read `pkgRoot` from
173
+ `~/.claude/hypo-pkg.json` (only when non-empty and the script exists under it);
174
+ otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath
175
+ in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the
176
+ user to run `hypomnema upgrade --apply` or reinstall instead of guessing.
168
177
  Then tell the user: "Created project `<name>` at
169
178
  `~/hypomnema/projects/<name>/`. Edit `index.md` to refine." Do **not** hand-write
170
179
  the five files — the helper keeps substitution and registration consistent.
@@ -226,8 +235,8 @@ Hypomnema v1.1.0 ships an **autonomy score** so you can see whether the wiki is
226
235
  - `staleness-skip` — session older than the audit window (default 30d)
227
236
  - **Where it lives:**
228
237
  - Per-session transcript index: `<hypo-root>/.cache/sessions/index.jsonl` (written by the Stop hook `hypo-session-record.mjs`).
229
- - Fallback source: `~/.claude/projects/<encoded>/*.jsonl` (used when the index is empty — see ADR 0019 if present in your wiki).
230
- - Reports: `journal/weekly/<YYYY-Www>.md` (spec §6.4 SoT), generated by `node scripts/weekly-report.mjs --write`.
238
+ - Fallback source: `~/.claude/projects/<encoded>/*.jsonl` (used when the index is empty).
239
+ - Reports: `journal/weekly/<YYYY-Www>.md` (spec §6.4 SoT), generated by the `/hypo:audit` weekly report flow.
231
240
  - **Definitions:** the 0% / 100% endpoints, the formal score sketch, and open questions live in `pages/observability/_index.md`.
232
241
 
233
242
  Use `/hypo:audit` for a quick read of recent sessions; pass `--write` to commit the weekly report into the wiki.
@@ -21,7 +21,7 @@ tags: [hot-cache, project]
21
21
  ## Key Decisions
22
22
 
23
23
  <!-- Brief pointers to important ADRs or design choices -->
24
- <!-- [[decisions/0001-topic]] — decision summary -->
24
+ <!-- [[decisions/NNNN-topic]] — decision summary -->
25
25
 
26
26
  ## Blockers / Open Questions
27
27
 
@@ -1,256 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * fix-status-verify (CLI) — verify fix→test linkage + ADR-line evidence
4
- * against wiki spec claims.
5
- *
6
- * Phase 1: test-green half (anchors × spec status × runner results).
7
- * Phase 2 (A-sot): manifest validation + manifest↔anchor drift + ADR core
8
- * decision grep against the production corpus. See scripts/lib/fix-manifest.mjs
9
- * and scripts/lib/fix-status-verify.mjs headers.
10
- *
11
- * Usage:
12
- * node scripts/fix-status-verify.mjs [--hypo-dir <path>]
13
- * [--spec <path>]
14
- * [--runner <path>]
15
- * [--test-command "<cmd>"]
16
- * [--json]
17
- *
18
- * Exit 0 if no error-level findings, 1 otherwise.
19
- */
20
-
21
- import { readFileSync, existsSync } from 'node:fs';
22
- import { join, dirname, resolve } from 'node:path';
23
- import { homedir } from 'node:os';
24
- import { spawnSync } from 'node:child_process';
25
- import { fileURLToPath, pathToFileURL } from 'node:url';
26
- import {
27
- parseAnchors,
28
- parseStatus,
29
- parseRunnerOutput,
30
- verifyMatrix,
31
- isReferenceStub,
32
- validateManifest,
33
- checkManifestCoverage,
34
- checkAdrLines,
35
- FIX_MANIFEST,
36
- NO_ADR,
37
- } from './lib/fix-status-verify.mjs';
38
- import { buildCorpusSearch } from './lib/adr-corpus.mjs';
39
-
40
- const REPO = resolve(dirname(fileURLToPath(import.meta.url)), '..');
41
-
42
- // Production-code corpus for the ADR-line grep (spec §A amendment 2026-06-07:
43
- // templates/ ships via npm `files`, so prompt-driven fixes are verifiable).
44
- const CORPUS_DIRS = ['scripts', 'hooks', 'commands', 'skills', 'templates'];
45
- // MUST exclude the manifest itself — it holds every adrKeyLine as a literal and
46
- // would self-match, making ADR_LINE_MISSING impossible to ever fire.
47
- const CORPUS_EXCLUDE = ['scripts/lib/fix-manifest.mjs'];
48
-
49
- function parseArgs(argv) {
50
- const out = {
51
- hypoDir: process.env.HYPO_DIR || join(homedir(), 'hypomnema'),
52
- spec: null,
53
- runner: join(REPO, 'tests/runner.mjs'),
54
- testCommand: 'npm test',
55
- json: false,
56
- manifest: null,
57
- };
58
- for (let i = 0; i < argv.length; i++) {
59
- const a = argv[i];
60
- if (a === '--hypo-dir') out.hypoDir = argv[++i];
61
- else if (a === '--spec') out.spec = argv[++i];
62
- else if (a === '--runner') out.runner = argv[++i];
63
- else if (a === '--test-command') out.testCommand = argv[++i];
64
- else if (a === '--manifest') out.manifest = argv[++i];
65
- else if (a === '--json') out.json = true;
66
- else if (a === '--help' || a === '-h') {
67
- printHelp();
68
- process.exit(0);
69
- } else {
70
- console.error(`unknown arg: ${a}`);
71
- process.exit(2);
72
- }
73
- }
74
- if (!out.spec) {
75
- out.spec = join(out.hypoDir, 'projects/hypomnema/spec-v1.2.md');
76
- }
77
- return out;
78
- }
79
-
80
- function printHelp() {
81
- console.log(
82
- [
83
- 'fix-status-verify — Phase 1 (test-green half) of learned_behavior #6',
84
- '',
85
- 'Options:',
86
- ' --hypo-dir <path> Wiki root (default: $HYPO_DIR or ~/hypomnema)',
87
- ' --spec <path> Override spec-v1.2.md path',
88
- ' --runner <path> Override tests/runner.mjs path',
89
- ' --test-command "<cmd>" Test invocation (default: "npm test")',
90
- ' --json Emit machine-readable JSON report',
91
- '',
92
- 'Exit 0 if no error findings, 1 otherwise.',
93
- '',
94
- 'NOTE: The default --spec is a `type: reference` redirect stub (the real',
95
- 'spec moved to archive/). Running without --spec fails with STUB_SPEC by',
96
- 'design — pass --spec <real spec> to verify against actual claims.',
97
- '',
98
- 'Phase 2 (A-sot): also greps each manifest adrKeyLine against the',
99
- 'production corpus (scripts/ hooks/ commands/ skills/ templates/) and',
100
- 'checks manifest↔anchor drift. NO_ADR rows skip the grep (test-green only).',
101
- ].join('\n'),
102
- );
103
- }
104
-
105
- function runTests(testCommand) {
106
- // Parse simple command (no shell metacharacters supported in args; this is
107
- // a maintainer tool, not a security boundary).
108
- const parts = testCommand.split(/\s+/).filter(Boolean);
109
- const [cmd, ...args] = parts;
110
- const result = spawnSync(cmd, args, {
111
- cwd: REPO,
112
- encoding: 'utf-8',
113
- env: { ...process.env, FORCE_COLOR: '0' },
114
- maxBuffer: 64 * 1024 * 1024,
115
- });
116
- return {
117
- stdout: result.stdout || '',
118
- stderr: result.stderr || '',
119
- exitCode: result.status,
120
- };
121
- }
122
-
123
- function formatFinding(f) {
124
- const icon = f.level === 'error' ? '✗' : '⚠';
125
- // Some findings are not tied to a specific fix # (STUB_SPEC,
126
- // TEST_RUN_NONZERO_EXIT). Only render the `fix #N` segment when present so
127
- // they don't print `fix #undefined`.
128
- const ref = f.fixNum != null ? ` fix #${f.fixNum}` : '';
129
- return ` ${icon} [${f.class}]${ref}` + (f.testName ? ` (${f.testName})` : '') + `: ${f.detail}`;
130
- }
131
-
132
- async function main() {
133
- const opts = parseArgs(process.argv.slice(2));
134
-
135
- // Manifest source: built-in code constant by default (ADR 0036). --manifest
136
- // <path.mjs> overrides for tests, which inject a fixture manifest matching
137
- // their synthetic fixes so the real manifest does not couple to fixtures.
138
- let manifest = FIX_MANIFEST;
139
- if (opts.manifest) {
140
- if (!existsSync(opts.manifest)) {
141
- console.error(`manifest not found: ${opts.manifest}`);
142
- process.exit(2);
143
- }
144
- const mod = await import(pathToFileURL(resolve(opts.manifest)).href);
145
- manifest = mod.FIX_MANIFEST;
146
- }
147
-
148
- if (!existsSync(opts.spec)) {
149
- console.error(`spec not found: ${opts.spec}`);
150
- console.error('hint: pass --hypo-dir <path> or set $HYPO_DIR');
151
- process.exit(2);
152
- }
153
- if (!existsSync(opts.runner)) {
154
- console.error(`runner not found: ${opts.runner}`);
155
- process.exit(2);
156
- }
157
-
158
- const specText = readFileSync(opts.spec, 'utf-8');
159
- const runnerText = readFileSync(opts.runner, 'utf-8');
160
-
161
- const anchors = parseAnchors(runnerText);
162
- const status = parseStatus(specText);
163
- const specIsStub = isReferenceStub(specText);
164
-
165
- if (!opts.json) {
166
- console.log(`fix-status-verify (Phase 1)`);
167
- console.log(` spec: ${opts.spec}`);
168
- console.log(` runner: ${opts.runner}`);
169
- console.log(` ${status.size} positive status claim(s), ${anchors.size} anchor(s)`);
170
- console.log(` running: ${opts.testCommand}`);
171
- }
172
-
173
- const testRun = runTests(opts.testCommand);
174
- const testResults = parseRunnerOutput(testRun.stdout + '\n' + testRun.stderr);
175
-
176
- if (!opts.json) {
177
- const passes = [...testResults.values()].filter((v) => v === 'pass').length;
178
- const fails = [...testResults.values()].filter((v) => v === 'fail').length;
179
- console.log(` test run: ${passes} pass, ${fails} fail (exit ${testRun.exitCode})`);
180
- }
181
-
182
- const matrixResult = verifyMatrix({ anchors, status, testResults, specIsStub });
183
- const findings = [...matrixResult.findings];
184
-
185
- // Phase 2 (A-sot): manifest validation + ADR-line grep. validateManifest and
186
- // checkAdrLines are spec-independent (manifest/code health) and run always;
187
- // checkManifestCoverage keys off the spec status (a no-op under STUB_SPEC,
188
- // where status is empty).
189
- const needsCorpus = manifest.some((r) => r.adrKeyLine !== NO_ADR);
190
- const adrSearch = needsCorpus
191
- ? buildCorpusSearch({ repoRoot: REPO, includeDirs: CORPUS_DIRS, excludePaths: CORPUS_EXCLUDE })
192
- : () => false;
193
- const adrExists = (adrPath) => existsSync(join(opts.hypoDir, 'projects/hypomnema', adrPath));
194
- findings.push(...validateManifest(manifest));
195
- findings.push(...checkManifestCoverage({ manifest, anchors, status }));
196
- findings.push(...checkAdrLines({ manifest, searchFn: adrSearch, adrExistsFn: adrExists }));
197
-
198
- // CLI-level error: if the test command itself exited nonzero, the test run
199
- // is not green even if the anchored tests happen to all pass in the parsed
200
- // output. Surface as a synthetic error finding so `ok` flips false.
201
- if (testRun.exitCode !== 0) {
202
- findings.push({
203
- level: 'error',
204
- class: 'TEST_RUN_NONZERO_EXIT',
205
- detail: `test command "${opts.testCommand}" exited ${testRun.exitCode}`,
206
- exitCode: testRun.exitCode,
207
- });
208
- }
209
- const ok = !findings.some((f) => f.level === 'error') && testRun.exitCode === 0;
210
-
211
- const MANDATORY_NOTE =
212
- 'test-linkage + green + ADR-line grep (Phase 2): manifest evidence checked against production corpus';
213
-
214
- if (opts.json) {
215
- console.log(
216
- JSON.stringify(
217
- {
218
- ok,
219
- spec: opts.spec,
220
- runner: opts.runner,
221
- statusClaims: status.size,
222
- anchorCount: anchors.size,
223
- testsRan: testResults.size,
224
- testExitCode: testRun.exitCode,
225
- findings,
226
- note: MANDATORY_NOTE,
227
- },
228
- null,
229
- 2,
230
- ),
231
- );
232
- } else {
233
- const errors = findings.filter((f) => f.level === 'error');
234
- const warns = findings.filter((f) => f.level === 'warn');
235
- if (errors.length === 0 && warns.length === 0) {
236
- console.log(` ✓ all ${status.size} claimed-merged fix(es) verified`);
237
- } else {
238
- if (errors.length) {
239
- console.log(`\nerrors (${errors.length}):`);
240
- for (const f of errors) console.log(formatFinding(f));
241
- }
242
- if (warns.length) {
243
- console.log(`\nwarnings (${warns.length}):`);
244
- for (const f of warns) console.log(formatFinding(f));
245
- }
246
- }
247
- console.log(`\n(${MANDATORY_NOTE})`);
248
- }
249
-
250
- process.exit(ok ? 0 : 1);
251
- }
252
-
253
- main().catch((e) => {
254
- console.error(e);
255
- process.exit(2);
256
- });