hypomnema 1.4.2 → 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 (68) 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 +8 -8
  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 +8 -7
  22. package/hooks/hypo-auto-commit.mjs +2 -2
  23. package/hooks/hypo-auto-minimal-crystallize.mjs +7 -7
  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 +271 -71
  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 +82 -38
  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 +2 -2
  45. package/scripts/lib/template-schema-version.mjs +1 -1
  46. package/scripts/lib/wd-match.mjs +181 -0
  47. package/scripts/lib/wikilink.mjs +1 -1
  48. package/scripts/lint.mjs +5 -5
  49. package/scripts/rename.mjs +1 -1
  50. package/scripts/resume.mjs +20 -22
  51. package/scripts/session-audit.mjs +1 -1
  52. package/scripts/stats.mjs +3 -3
  53. package/scripts/uninstall.mjs +3 -3
  54. package/scripts/upgrade.mjs +17 -18
  55. package/skills/crystallize/SKILL.md +7 -7
  56. package/skills/graph/SKILL.md +2 -2
  57. package/skills/ingest/SKILL.md +4 -4
  58. package/skills/lint/SKILL.md +2 -2
  59. package/skills/query/SKILL.md +2 -2
  60. package/skills/verify/SKILL.md +2 -2
  61. package/templates/SCHEMA.md +1 -1
  62. package/templates/hypo-config.md +1 -1
  63. package/templates/hypo-guide.md +20 -14
  64. package/templates/projects/_template/hot.md +1 -1
  65. package/scripts/fix-status-verify.mjs +0 -256
  66. package/scripts/lib/adr-corpus.mjs +0 -79
  67. package/scripts/lib/fix-manifest.mjs +0 -109
  68. package/scripts/lib/fix-status-verify.mjs +0 -439
@@ -26,10 +26,10 @@
26
26
  * --json Output as JSON
27
27
  */
28
28
 
29
- import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
29
+ import { existsSync, readFileSync, readdirSync, statSync, realpathSync } from 'fs';
30
30
  import { join } from 'path';
31
- import { homedir } from 'os';
32
31
  import { resolveHypoRoot, expandHome } from './lib/hypo-root.mjs';
32
+ import { pickProjectByCwd, collectProjectWorkingDirs } from './lib/wd-match.mjs';
33
33
 
34
34
  // ── arg parsing ──────────────────────────────────────────────────────────────
35
35
 
@@ -60,27 +60,25 @@ function parseFrontmatterField(content, key) {
60
60
  .replace(/^['"]|['"]$/g, '');
61
61
  }
62
62
 
63
- // Among `slugs`, return the one whose projects/<slug>/index.md `working_dir`
64
- // is the LONGEST prefix of cwd (so /repo/sub wins over /repo). Returns null
65
- // when cwd is falsy or matches none. resume gives this authority OVER recency
66
- // (ADR 0044): a cwd↔working_dir match wins regardless of which row is newest.
63
+ // Return the project (among `slugs`) that owns cwd: a longest-prefix
64
+ // working_dir match, or when no absolute path matches because the vault was
65
+ // synced from another machine a cwd ancestor whose directory name is a
66
+ // globally-unique project basename. Uniqueness is judged over EVERY project on
67
+ // disk (not just `slugs`), so a shared dirname declines and falls back to
68
+ // recency. resume gives this authority over recency: a cwd↔project match wins
69
+ // regardless of which hot.md row is newest.
67
70
  function pickByCwd(hypoDir, slugs, cwd) {
68
71
  if (!cwd) return null;
69
- let best = null;
70
- let bestLen = -1;
71
- for (const slug of slugs) {
72
- const indexPath = join(hypoDir, 'projects', slug, 'index.md');
73
- if (!existsSync(indexPath)) continue;
74
- const wd = parseFrontmatterField(readFileSync(indexPath, 'utf-8'), 'working_dir');
75
- if (!wd) continue;
76
- let resolved = wd.startsWith('~/') ? join(homedir(), wd.slice(2)) : wd;
77
- resolved = resolved.replace(/\/+$/, ''); // trailing-slash normalize
78
- if ((cwd === resolved || cwd.startsWith(resolved + '/')) && resolved.length > bestLen) {
79
- bestLen = resolved.length;
80
- best = slug;
81
- }
72
+ let realpathCwd = null;
73
+ try {
74
+ realpathCwd = realpathSync(cwd);
75
+ } catch {
76
+ realpathCwd = null;
82
77
  }
83
- return best;
78
+ return pickProjectByCwd(collectProjectWorkingDirs(hypoDir), cwd, {
79
+ eligible: slugs,
80
+ realpathCwd,
81
+ });
84
82
  }
85
83
 
86
84
  // When cwd is set but no project's working_dir matches it, resume falls back to
@@ -125,11 +123,11 @@ function resolveActiveProject(hypoDir, cwd = null) {
125
123
  ),
126
124
  ].map((m) => ({ name: m[1].trim(), date: m[2] || '', slug: m[3] }));
127
125
  if (wikiRows.length > 0) {
128
- // cwd-first (ADR 0044): a cwd↔working_dir match wins over recency, across
126
+ // cwd-first: a cwd↔working_dir match wins over recency, across
129
127
  // ALL rows (not just a same-date tie). The user is physically in that
130
128
  // project, so cwd is a stronger intent signal than "some other project was
131
129
  // touched more recently". This reverses the earlier tie-breaker-only
132
- // semantics now that resume=cwd-positive (ADR 0043). Tradeoff: a stale cwd
130
+ // semantics now that resume=cwd-positive. Tradeoff: a stale cwd
133
131
  // match can mask a genuinely newer project; `--project` overrides. close
134
132
  // callers pass null → recency path below (resume=cwd-positive / close=no-pick).
135
133
  if (cwd) {
@@ -6,7 +6,7 @@
6
6
  * (search count, ingest count, URLs mentioned, feedback count) so the
7
7
  * weekly observability report (Lane E) can compute autonomy scores.
8
8
  *
9
- * Transcript dual-source (ADR 0019):
9
+ * Transcript dual-source:
10
10
  * 1) Primary: <hypo-dir>/.cache/sessions/index.jsonl
11
11
  * Written by hooks/hypo-session-record.mjs (Stop hook).
12
12
  * 2) Fallback: ~/.claude/projects/<encoded>/*.jsonl
package/scripts/stats.mjs CHANGED
@@ -48,8 +48,8 @@ function collectMdFiles(dir, acc = [], hypoDir = '', ignorePatterns = []) {
48
48
  // Local top-level-only extractor, behavior-aligned with lib/frontmatter.mjs:
49
49
  // skip indented / list lines, first-wins, strip a trailing `# comment`. Without
50
50
  // the comment strip a `failure_type: overreach # note` would aggregate under the
51
- // literal "overreach # note" key (FEAT-1). Kept local (not the shared import) to
52
- // hold stats' dependency surface flat per the FEAT-1 scope split.
51
+ // literal "overreach # note" key. Kept local (not the shared import) to
52
+ // hold stats' dependency surface flat per the scope split.
53
53
  function parseFrontmatter(content) {
54
54
  const m = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
55
55
  if (!m) return null;
@@ -101,7 +101,7 @@ const sources = existsSync(join(args.hypoDir, 'sources'))
101
101
  : [];
102
102
 
103
103
  const typeCounts = {};
104
- const failureTypeCounts = {}; // FEAT-1: feedback pages carrying a failure_type
104
+ const failureTypeCounts = {}; // feedback pages carrying a failure_type
105
105
  let missingFrontmatter = 0;
106
106
 
107
107
  for (const f of pageFiles) {
@@ -15,7 +15,7 @@
15
15
  * --force-extensions Remove user-modified extension files (hypo-ext-*) instead of preserving them
16
16
  * --hooks-dir=<path> Override Claude hooks directory (default: ~/.claude/hooks)
17
17
  *
18
- * Extensions (ADR 0024): hypo-ext-* hard-copies under
18
+ * Extensions: hypo-ext-* hard-copies under
19
19
  * ~/.claude/{hooks,commands,skills,agents}/ and ~/.codex/{hooks,commands}/ (with
20
20
  * --codex) are removed when their on-disk SHA matches the recorded one in
21
21
  * ~/.claude/hypo-pkg.json#extensions.<target>. User-modified copies are preserved
@@ -94,7 +94,7 @@ function removeCommands(apply, force) {
94
94
  return { removed, skippedUserModified, skippedNonRegular };
95
95
  }
96
96
 
97
- // ── extensions removal (ADR 0024) ───────────────────────────────────
97
+ // ── extensions removal ───────────────────────────────────
98
98
 
99
99
  // Strip per-target extension SHA records from ~/.claude/hypo-pkg.json. Surgical:
100
100
  // we only touch the entries for keys we actually removed, so a `--force-extensions`
@@ -424,7 +424,7 @@ const hookResult = removeHookFiles(claudeHooksDir, hookFiles, args.apply);
424
424
  const settingsResult = stripSettingsJson(claudeSettings, claudeHooksDir, hookMap, args.apply);
425
425
  const commandResult = removeCommands(args.apply, args.forceCommands);
426
426
 
427
- // Extensions (ADR 0024). Order matters: remove files first, then strip
427
+ // Extensions. Order matters: remove files first, then strip
428
428
  // settings, then surgically clear the per-target SHA map. The SHA strip uses
429
429
  // removedKeys so a user-modified file we left in place keeps its recorded SHA
430
430
  // (doctor still has a baseline next run). Settings are path-based and run even
@@ -20,7 +20,7 @@
20
20
  * the user-extensions companion sync (E4).
21
21
  * --json Output results as JSON
22
22
  * --allow-downgrade Override the guard that refuses to overwrite a NEWER
23
- * active install with an older package (ADR 0038)
23
+ * active install with an older package
24
24
  * --allow-dual-install Override the dual-install guard: register the Claude core
25
25
  * surface even though the Hypomnema plugin is also enabled
26
26
  * (knowingly accept the double-registration risk)
@@ -235,7 +235,7 @@ function checkSchemaVersion(hypoDir) {
235
235
 
236
236
  // Target-aware: the same core-hook integrity check runs against ~/.claude/hooks/
237
237
  // for the default claude target, and against ~/.codex/hooks/ when --codex is set
238
- // (ADR 0024). The function reads only apply happens in applyHookFiles.
238
+ // The function reads only (apply happens in applyHookFiles).
239
239
  function checkHookFiles(hooksDir) {
240
240
  const results = [];
241
241
 
@@ -473,15 +473,15 @@ function writeMigrationReport(hypoDir, fromVersion, toVersion, { pluginMode = fa
473
473
  // Don't overwrite an existing report
474
474
  if (existsSync(dest)) return dest;
475
475
 
476
- // v1 → v2 specific guidance for the ADR 0031 feedback classification bump.
477
- // Other major jumps fall back to the generic body. ADR 0034 reserves the
478
- // right to keep SCHEMA.md as user-owned (Option C); auto-stub of the 9 new
479
- // fields is rejected because scope/tier/targets/sensitivity/reason/source
480
- // are semantic decisions whose wrong defaults would project wrong behavior.
481
- // Fire for any 1.x → 2.x major crossing, not just 1.0 → 2.0 exactly: the ADR
482
- // 0031 feedback-field backfill that this body explains was introduced at 2.0
476
+ // v1 → v2 specific guidance for the feedback classification bump. Other major
477
+ // jumps fall back to the generic body. The schema deliberately keeps SCHEMA.md
478
+ // user-owned (Option C); auto-stub of the 9 new fields is rejected because
479
+ // scope/tier/targets/sensitivity/reason/source are semantic decisions whose
480
+ // wrong defaults would project wrong behavior.
481
+ // Fire for any 1.x → 2.x major crossing, not just 1.0 → 2.0 exactly: the
482
+ // feedback-field backfill that this body explains was introduced at 2.0
483
483
  // and still applies to a user landing on any later 2.x (e.g. 2.1's additive
484
- // failure_type — FEAT-1). Keying on the exact target string would silently
484
+ // failure_type). Keying on the exact target string would silently
485
485
  // drop this guidance the moment the template minor moves.
486
486
  const fromV = parseVersion(fromVersion);
487
487
  const toV = parseVersion(toVersion);
@@ -489,8 +489,7 @@ function writeMigrationReport(hypoDir, fromVersion, toVersion, { pluginMode = fa
489
489
  const specificBody = isV1ToV2
490
490
  ? `## What changed in SCHEMA 2.0
491
491
 
492
- ADR 0031 (\`projects/hypomnema/decisions/0031-feedback-as-sot-external-memory-projection.md\`)
493
- made \`feedback\` pages the single source of truth for behavior corrections and added
492
+ This release made \`feedback\` pages the single source of truth for behavior corrections and added
494
493
  **9 hard-required frontmatter fields** for every \`feedback\` page:
495
494
 
496
495
  - \`status\` (active | superseded | archived)
@@ -510,8 +509,8 @@ that does not satisfy all four is rejected by both \`hypomnema lint\` and
510
509
  \`hypomnema feedback\` (see \`scripts/lint.mjs\` and \`scripts/feedback.mjs\`
511
510
  enforcement).
512
511
 
513
- ADR 0034 records this schema bump and the reasoning (semver-major because
514
- existing feedback pages without these fields now fail \`hypomnema lint\`).
512
+ This schema bump is semver-major because existing feedback pages without these
513
+ fields now fail \`hypomnema lint\`.
515
514
 
516
515
  ## Action items — existing wiki
517
516
 
@@ -799,7 +798,7 @@ function writePluginModeMetadata() {
799
798
  const args = parseArgs(process.argv);
800
799
 
801
800
  // Target paths. Claude is always checked; codex is checked only when --codex is set
802
- // (ADR 0024) so users without codex installed see no false drift.
801
+ // so users without codex installed see no false drift.
803
802
  const claudeHooksDir = join(HOME, '.claude', 'hooks');
804
803
  const claudeSettingsPath = join(HOME, '.claude', 'settings.json');
805
804
  const codexHooksDir = join(HOME, '.codex', 'hooks');
@@ -858,7 +857,7 @@ const hooksCodex = args.codex ? checkHookFiles(codexHooksDir) : null;
858
857
  const settingsCodex = args.codex ? checkSettingsJson(codexSettingsPath, codexHooksDir) : null;
859
858
  const oldHookRefsCodex = args.codex ? checkOldHookNames(codexSettingsPath) : null;
860
859
 
861
- // Extensions companion (ADR 0024). Read-only check; the apply
860
+ // Extensions companion. Read-only check; the apply
862
861
  // happens below, AFTER applyCommands, so the per-target SHA map merges into the
863
862
  // hypo-pkg.json that applyCommands writes (rather than being clobbered by it).
864
863
  const extSettingsPath = claudeSettingsPath;
@@ -935,7 +934,7 @@ let appliedExtensions = null;
935
934
  let appliedExtensionsCodex = null;
936
935
 
937
936
  if (args.apply) {
938
- // Downgrade guard (ADR 0038, P): an `--apply` from an OLDER package than the
937
+ // Downgrade guard: an `--apply` from an OLDER package than the
939
938
  // active install would overwrite newer hooks (upgrade.mjs:287 copyFileSync) and
940
939
  // rewrite hypo-pkg.json to the older version. Refuse before the first mutation.
941
940
  // A dev workspace re-running its own --apply (incl. the post-commit sync hook)
@@ -1370,7 +1369,7 @@ if (hypoignore.status === 'no-file') {
1370
1369
  for (const e of hypoignore.missing) lines.push(` + ${e.pattern}`);
1371
1370
  }
1372
1371
 
1373
- // Extensions companion (ADR 0024; conflict/drift gating E3, #31). Shared by the
1372
+ // Extensions companion (conflict/drift gating E3, #31). Shared by the
1374
1373
  // claude target and, under --codex, the codex target (E4, #32) — the label keeps
1375
1374
  // the two blocks distinguishable in the report.
1376
1375
  function pushExtSummary(check, label) {
@@ -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.
@@ -67,11 +67,11 @@ When uncertain, surface the question rather than skip it. None of the four block
67
67
  After completing the checklist, verify it before reporting:
68
68
 
69
69
  ```bash
70
- 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>"]
71
71
  ```
72
72
 
73
- This runs the **full** PreCompact gate via the shared `precompactGateStatus`
74
- (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
75
75
  design-history, and feedback projection over-cap/conflict. Fix every `✗` it
76
76
  reports and re-run until it prints **"Compact-ready"** — that is the signal the
77
77
  session is closed. A close-files-only pass is not enough; the real `/compact`
@@ -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`
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  title: Hypomnema Config
3
3
  type: config
4
- version: "1.4.2"
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,13 +82,13 @@ 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
@@ -99,13 +99,13 @@ Ask: *"이 작업이 마무리되었나요? 세션을 정리(crystallize)할까
99
99
  diagnostic, JSON `scope: "project"`): green there means only that slug is
100
100
  close-complete, **not** that `/compact` is globally unblocked. Use the plain
101
101
  check for the go/no-go signal.
102
- 7. Record the session-closed marker (ADR 0047). The Stop hook blocks until this
102
+ 7. Record the session-closed marker. The Stop hook blocks until this
103
103
  session's per-session marker exists, and a hand-edit close (writing the files
104
104
  directly + committing) never writes it; the marker is written only by the
105
- crystallize writer, never by the hook (ADR 0022 bypass guard). Normal path:
106
- 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>`),
107
107
  which writes the marker once the gate is green. Hand-edit recovery: after
108
- 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>`).
109
109
  Both writers gate the marker on the SAME `precompactGateStatus` as `/compact`,
110
110
  so the marker only lands when step 6 would print **"Compact-ready"**.
111
111
 
@@ -153,7 +153,7 @@ projects/<name>/
153
153
 
154
154
  Add to root `hot.md` active projects table.
155
155
 
156
- #### Auto-project offer (ADR 0023)
156
+ #### Auto-project offer
157
157
 
158
158
  When SessionStart / CwdChanged injects a line like
159
159
  `[WIKI: cwd '<name>'에 매칭되는 프로젝트가 없습니다. 자동 생성할까요? (Y/n)]`,
@@ -166,8 +166,14 @@ ignore it:**
166
166
  2. **On Yes** — run the scaffold helper once (it substitutes tokens, creates
167
167
  the project files, adds the root `hot.md` row, and logs the entry):
168
168
  ```
169
- 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)"
170
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.
171
177
  Then tell the user: "Created project `<name>` at
172
178
  `~/hypomnema/projects/<name>/`. Edit `index.md` to refine." Do **not** hand-write
173
179
  the five files — the helper keeps substitution and registration consistent.
@@ -229,8 +235,8 @@ Hypomnema v1.1.0 ships an **autonomy score** so you can see whether the wiki is
229
235
  - `staleness-skip` — session older than the audit window (default 30d)
230
236
  - **Where it lives:**
231
237
  - Per-session transcript index: `<hypo-root>/.cache/sessions/index.jsonl` (written by the Stop hook `hypo-session-record.mjs`).
232
- - Fallback source: `~/.claude/projects/<encoded>/*.jsonl` (used when the index is empty — see ADR 0019 if present in your wiki).
233
- - 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.
234
240
  - **Definitions:** the 0% / 100% endpoints, the formal score sketch, and open questions live in `pages/observability/_index.md`.
235
241
 
236
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