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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.ko.md +1 -1
- package/README.md +1 -1
- package/commands/audit.md +1 -1
- package/commands/crystallize.md +8 -8
- package/commands/doctor.md +1 -1
- package/commands/feedback.md +2 -2
- package/commands/graph.md +1 -1
- package/commands/ingest.md +1 -1
- package/commands/init.md +2 -2
- package/commands/lint.md +1 -1
- package/commands/query.md +1 -1
- package/commands/rename.md +1 -1
- package/commands/resume.md +1 -1
- package/commands/stats.md +1 -1
- package/commands/uninstall.md +4 -2
- package/commands/upgrade.md +1 -1
- package/commands/verify.md +1 -1
- package/docs/ARCHITECTURE.md +2 -2
- package/docs/CONTRIBUTING.md +8 -7
- package/hooks/hypo-auto-commit.mjs +2 -2
- package/hooks/hypo-auto-minimal-crystallize.mjs +7 -7
- package/hooks/hypo-compact-guard.mjs +2 -2
- package/hooks/hypo-cwd-change.mjs +27 -37
- package/hooks/hypo-personal-check.mjs +37 -22
- package/hooks/hypo-session-end.mjs +1 -1
- package/hooks/hypo-session-record.mjs +2 -2
- package/hooks/hypo-session-start.mjs +34 -40
- package/hooks/hypo-shared.mjs +271 -71
- package/hooks/version-check.mjs +1 -1
- package/package.json +5 -1
- package/scripts/check-tracker-ids.mjs +69 -31
- package/scripts/crystallize.mjs +82 -38
- package/scripts/doctor.mjs +7 -7
- package/scripts/feedback-sync.mjs +5 -5
- package/scripts/feedback.mjs +9 -10
- package/scripts/init.mjs +7 -7
- package/scripts/lib/check-tracker-ids.mjs +90 -32
- package/scripts/lib/design-history-stale.mjs +1 -1
- package/scripts/lib/extensions.mjs +7 -7
- package/scripts/lib/failure-type.mjs +1 -1
- package/scripts/lib/feedback-scope.mjs +1 -1
- package/scripts/lib/project-create.mjs +2 -2
- package/scripts/lib/template-schema-version.mjs +1 -1
- package/scripts/lib/wd-match.mjs +181 -0
- package/scripts/lib/wikilink.mjs +1 -1
- package/scripts/lint.mjs +5 -5
- package/scripts/rename.mjs +1 -1
- package/scripts/resume.mjs +20 -22
- package/scripts/session-audit.mjs +1 -1
- package/scripts/stats.mjs +3 -3
- package/scripts/uninstall.mjs +3 -3
- package/scripts/upgrade.mjs +17 -18
- package/skills/crystallize/SKILL.md +7 -7
- package/skills/graph/SKILL.md +2 -2
- package/skills/ingest/SKILL.md +4 -4
- package/skills/lint/SKILL.md +2 -2
- package/skills/query/SKILL.md +2 -2
- package/skills/verify/SKILL.md +2 -2
- package/templates/SCHEMA.md +1 -1
- package/templates/hypo-config.md +1 -1
- package/templates/hypo-guide.md +20 -14
- package/templates/projects/_template/hot.md +1 -1
- package/scripts/fix-status-verify.mjs +0 -256
- package/scripts/lib/adr-corpus.mjs +0 -79
- package/scripts/lib/fix-manifest.mjs +0 -109
- package/scripts/lib/fix-status-verify.mjs +0 -439
package/README.ko.md
CHANGED
|
@@ -25,7 +25,7 @@ Andrej Karpathy의 "LLM 네이티브 위키" 스케치에서 출발했습니다.
|
|
|
25
25
|
|
|
26
26
|
### 지금 어디까지 자동인가
|
|
27
27
|
|
|
28
|
-
현재 릴리스는 v1.
|
|
28
|
+
현재 릴리스는 v1.5.0이고, 머신 간 안정성과 일상의 마찰로 초점을 옮겼습니다: git 동기화로 여러 머신이 공유하는 볼트에서 `cwd`-first resume가 올바른 프로젝트를 찾고, 코드 레포에서 작업하는 세션이 매번 위키를 다시 찾는 대신 위키 위치를 안내받으며, 세션 마무리가 무관한 lint 부채를 다시 나열하거나 수백 개 경고를 컨텍스트에 쏟지 않습니다. 그 앞 v1.4.x는 세션 마무리 경로를 견고하게 다듬었습니다. 위키 작업(자료 정리·검색·세션 마무리)은 아직 `/hypo:*` 명령어를 직접 입력해 시작합니다. v2의 목표는 Claude가 시키지 않아도 위키를 읽고 쓰고 합성하는 완전 자율 동작이고, 지금은 그쪽으로 가는 중입니다. v1.1.0이 위키가 한 세션에서 얼마나 쓰였는지 재는 관측성 점수를 넣었습니다. v1.2.0은 자동으로 동작하는 영역 네 가지를 추가했습니다.
|
|
29
29
|
|
|
30
30
|
- 피드백 한 곳만 고치면 나머지는 따라옵니다. `pages/feedback/`에 한 번 적으면 위키가 `MEMORY.md`와 `~/.claude/CLAUDE.md`의 `<learned_behaviors>` 블록을 자동으로 갱신합니다(단방향 projection).
|
|
31
31
|
- 확장 파일도 함께 동기화. 위키 안 `~/hypomnema/extensions/{agents,commands,hooks,skills}/`에 둔 파일을 `~/.claude/`에 자동 반영합니다. `--codex`를 붙이면 `hooks`·`commands`는 `~/.codex/`에도 반영하지만, `agents`와 `skills`는 Claude 전용이라 건너뜁니다.
|
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ New to the terms below? Keep the [Term decoder](#term-decoder) open in another t
|
|
|
25
25
|
|
|
26
26
|
### Where automation stands today
|
|
27
27
|
|
|
28
|
-
The current release is v1.
|
|
28
|
+
The current release is v1.5.0, which turns to cross-machine reliability and everyday friction: `cwd`-first resume now finds the right project on a git-synced vault shared across machines, a session working in a code repo is told where the wiki lives instead of re-discovering it each time, and session close stops re-listing unrelated lint debt or dumping hundreds of warnings into context. The v1.4.x line hardened the session-close path before it. Wiki work (ingest, query, session-close) still runs on explicit `/hypo:*` commands. The v2 goal is full autonomy: Claude reading, writing, and synthesizing the wiki without being asked, which is the direction this is heading. v1.1.0 shipped the observability score that measures how often the wiki is actually used per session. v1.2.0 added four autonomous lanes on top:
|
|
29
29
|
|
|
30
30
|
- Edit feedback in one place and the rest follows. `pages/feedback/` is the single source of truth for behavior corrections, and Hypomnema derives `MEMORY.md` and the `<learned_behaviors>` block inside `~/.claude/CLAUDE.md` from it automatically.
|
|
31
31
|
- Extensions sync alongside. Anything under `~/hypomnema/extensions/{agents,commands,hooks,skills}/` is mirrored into `~/.claude/` automatically. The `--codex` flag also mirrors `hooks` and `commands` into `~/.codex/`; `agents` and `skills` are Claude-only and skipped on purpose.
|
package/commands/audit.md
CHANGED
|
@@ -16,7 +16,7 @@ Definition: [[pages/observability/_index]].
|
|
|
16
16
|
|
|
17
17
|
## Step 1 — Run script
|
|
18
18
|
|
|
19
|
-
|
|
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 Hypomnema directory, pass it as `--hypo-dir="<path>"`. Otherwise omit the flag.
|
|
22
22
|
|
package/commands/crystallize.md
CHANGED
|
@@ -17,7 +17,7 @@ If `/hypo:crystallize` was invoked to close a session (via an explicit close sig
|
|
|
17
17
|
|
|
18
18
|
## Step 1a — Session-close advisory reflections
|
|
19
19
|
|
|
20
|
-
Before composing the payload (Step 2), run these four reflections and surface each to the user. Every one is **advisory** (
|
|
20
|
+
Before composing the payload (Step 2), run these four reflections and surface each to the user. 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.
|
|
21
21
|
|
|
22
22
|
1. **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를 건너뛸까요?"* and proceed only if the user wants a 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.
|
|
23
23
|
2. **ADR-candidate check (#41)** — Did this session make an architectural or design decision (a new pattern, a tradeoff chosen, a convention established)? If yes, ask whether it warrants an ADR and, if so, capture that intent in the `sessionLog` entry you compose in Step 2. If nothing rose to ADR level, you may record `ADR 없음 — <one-line reason>` in that same `sessionLog` entry — but gate it on #42's bar: the marker is machine-read and W8 excludes an entry carrying `ADR 없음` (with no ADR reference) from the design-history staleness check. Write it only when the session had **no design change at all**; a sub-ADR design shift takes #42a (append) instead, since the marker would suppress the W8 nudge it needs. **Never auto-write an ADR file** — recording the decision (or its absence) in the session-log payload is the only action here.
|
|
@@ -30,9 +30,9 @@ These are judgment calls; when uncertain, surface the question rather than skip
|
|
|
30
30
|
|
|
31
31
|
## Step 2 — Compose the session-close payload
|
|
32
32
|
|
|
33
|
-
The session-close path is **payload-driven**. 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
|
|
33
|
+
The session-close path is **payload-driven**. 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 the `--apply-session-close` apply path (the Step 3 command), which performs idempotent atomic writes and gates the result with lint.
|
|
34
34
|
|
|
35
|
-
Payload shape (`project` + 5 content fields required, 1 conditional, per Spec §5.2.7 / §8.3
|
|
35
|
+
Payload shape (`project` + 5 content fields required, 1 conditional, per Spec §5.2.7 / §8.3):
|
|
36
36
|
|
|
37
37
|
```json
|
|
38
38
|
{
|
|
@@ -75,7 +75,7 @@ Content guidance for each slot:
|
|
|
75
75
|
|
|
76
76
|
## Step 3 — Apply the payload
|
|
77
77
|
|
|
78
|
-
|
|
78
|
+
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.
|
|
79
79
|
|
|
80
80
|
```bash
|
|
81
81
|
node ${CLAUDE_PLUGIN_ROOT}/scripts/crystallize.mjs \
|
|
@@ -114,12 +114,12 @@ synthesis (no session-close intent) — the marker is then simply not written.
|
|
|
114
114
|
|
|
115
115
|
**Two lint gates run automatically, scoped to the files this close writes:**
|
|
116
116
|
|
|
117
|
-
Both gates judge only the **payload files** (the 5 mandatory close files + `open-questions.md`). Lint debt
|
|
117
|
+
Both gates judge only the **payload files** (the 5 mandatory close files + `open-questions.md`). Lint debt this close did not author is never gated — so an unrelated broken page elsewhere cannot block your close. It is reported as a non-blocking notice, scoped to the close-target project: debt under `projects/<project>/` is listed by file in `notices[]`; debt elsewhere (other projects, shared `pages/`, root files) folds into the `otherDebtCount` integer so the same untouched-file debt does not re-list its filenames on every close (run `/hypo:lint` for the full list).
|
|
118
118
|
|
|
119
|
-
1. **Preflight** — `lint
|
|
119
|
+
1. **Preflight** — an internal `lint --json` preflight runs **before** any payload bytes are written. Errors in overwrite targets (sessionState / projectHot / rootHot / openQuestions) are filtered (about to be replaced). Errors in an **append target** (session-log / log.md) still block (appending can't repair existing corruption) → exit 1 with `stage='preflight-lint'`. Errors outside the payload files → `notices[]`, apply proceeds.
|
|
120
120
|
2. **Post-apply** — lint re-runs after the writes. Blocks only on **errors** in payload files (a payload-introduced malformed body / bad frontmatter); pre-existing errors elsewhere → `notices[]`. A lint crash (unparseable output) always blocks. Broken wikilinks are lint **warnings** (W4 — forward references to planned pages are normal) and are not gated here. Surfaces as `stage='post-apply-lint'` (or `'post-apply-verification+lint'` if freshness also fails).
|
|
121
121
|
|
|
122
|
-
> **Manual close (direct Write tool calls)** clears the Stop-chain block via `--mark-session-closed --session-id=<id>`. Both marker writers apply a **user-close hard gate
|
|
122
|
+
> **Manual close (direct Write tool calls)** clears the Stop-chain block via `--mark-session-closed --session-id=<id>`. Both marker writers apply a **user-close hard gate**: the marker is written only when the session's transcript carries a genuine user close signal — an NL close phrase, a `/compact`, or an accepted AskUserQuestion [세션 마무리] answer. The transcript is resolved **strictly from `--session-id`** (a globally-unique id, globbed under `~/.claude/projects/`), never from a CLI arg, so a model that runs the writer on its own — without the user ever signalling close — is refused, and a forged path cannot point the gate at someone else's close-intent. The lint scope is widened from that same resolved transcript. `--transcript-path` is **not** consulted by the marker gate; it survives only to scope `--check-session-close`'s lint (which writes no marker).
|
|
123
123
|
|
|
124
124
|
---
|
|
125
125
|
|
|
@@ -154,7 +154,7 @@ If the user says stop, end here. Otherwise continue to Step 5.
|
|
|
154
154
|
|
|
155
155
|
## Step 5 — Surface synthesis candidates
|
|
156
156
|
|
|
157
|
-
|
|
157
|
+
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.
|
|
158
158
|
|
|
159
159
|
```bash
|
|
160
160
|
node ${CLAUDE_PLUGIN_ROOT}/scripts/crystallize.mjs [--hypo-dir="<path>"] [--min-group=2]
|
package/commands/doctor.md
CHANGED
|
@@ -18,7 +18,7 @@ You are running `/hypo:doctor`. Verify the health of the current Hypomnema wiki
|
|
|
18
18
|
|
|
19
19
|
## Step 1 — Resolve Hypomnema directory
|
|
20
20
|
|
|
21
|
-
|
|
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 Hypomnema directory, pass it as `--hypo-dir="<path>"`. Otherwise omit the flag and the script resolves the Hypomnema root automatically: `HYPO_DIR` env → `hypo-config.md` scan → `~/hypomnema` default.
|
|
24
24
|
|
package/commands/feedback.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
description: Record an AI behavior correction or preference into the wiki. Use when the user corrects how you work or states a lasting preference to remember.
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
-
You are running `/hypo:feedback`. Capture a behavior correction or preference into `pages/feedback/` — the **single source of truth** for learned behaviors
|
|
5
|
+
You are running `/hypo:feedback`. Capture a behavior correction or preference into `pages/feedback/` — the **single source of truth** for learned behaviors.
|
|
6
6
|
|
|
7
7
|
## What this does
|
|
8
8
|
|
|
@@ -36,7 +36,7 @@ If **claude-learned** is among the targets, the page must be `scope: global` + `
|
|
|
36
36
|
|
|
37
37
|
## Step 2 — List existing feedback (optional)
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
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.
|
|
40
40
|
|
|
41
41
|
To check for an existing topic, run:
|
|
42
42
|
|
package/commands/graph.md
CHANGED
|
@@ -14,7 +14,7 @@ You are running `/hypo:graph`. Generate a link dependency graph from wiki pages.
|
|
|
14
14
|
|
|
15
15
|
## Step 1 — Run script
|
|
16
16
|
|
|
17
|
-
|
|
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 Hypomnema directory, pass it as `--hypo-dir="<path>"`. Otherwise omit the flag.
|
|
20
20
|
|
package/commands/ingest.md
CHANGED
|
@@ -27,7 +27,7 @@ Do **not** fetch the URL or read the file yet — the privacy guard in Step 2 mu
|
|
|
27
27
|
|
|
28
28
|
## Step 2 — Privacy guard (`.hypoignore`)
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
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.
|
|
31
31
|
|
|
32
32
|
Refuse to ingest secrets (`.env`, SSH keys, credentials) before they ever reach `sources/`. Run the guard for **both** the input path and the destination path:
|
|
33
33
|
|
package/commands/init.md
CHANGED
|
@@ -43,7 +43,7 @@ Ask the following questions **one at a time**. Use the default if the user press
|
|
|
43
43
|
|
|
44
44
|
## Step 2a — From Remote (skip wizard) {#step-2a}
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
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.
|
|
47
47
|
|
|
48
48
|
If the user provided `--from-remote <url>`, run:
|
|
49
49
|
|
|
@@ -64,7 +64,7 @@ node ${CLAUDE_PLUGIN_ROOT}/scripts/init.mjs \
|
|
|
64
64
|
|
|
65
65
|
## Step 2 — Run the init script (new wiki)
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
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.
|
|
68
68
|
Then run:
|
|
69
69
|
|
|
70
70
|
```bash
|
package/commands/lint.md
CHANGED
|
@@ -16,7 +16,7 @@ You are running `/hypo:lint`. Validate all wiki pages for frontmatter correctnes
|
|
|
16
16
|
|
|
17
17
|
## Step 1 — Run script
|
|
18
18
|
|
|
19
|
-
|
|
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 Hypomnema directory, pass it as `--hypo-dir="<path>"`. Otherwise omit the flag and the script resolves the Hypomnema root automatically via `HYPO_DIR` → `hypo-config.md` scan → `~/hypomnema`.
|
|
22
22
|
|
package/commands/query.md
CHANGED
|
@@ -20,7 +20,7 @@ Ask the user what they want to know if it was not provided in the command invoca
|
|
|
20
20
|
|
|
21
21
|
## Step 2 — Search
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema upgrade --apply` or reinstall instead of guessing the cache layout.
|
|
24
24
|
|
|
25
25
|
Run full-text search:
|
|
26
26
|
|
package/commands/rename.md
CHANGED
|
@@ -16,7 +16,7 @@ You are running `/hypo:rename`. Move a page or directory and content-aware rewri
|
|
|
16
16
|
|
|
17
17
|
## Step 1 — Run script
|
|
18
18
|
|
|
19
|
-
|
|
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 Hypomnema directory, pass it as `--hypo-dir="<path>"`. Otherwise omit the flag.
|
|
22
22
|
|
package/commands/resume.md
CHANGED
|
@@ -20,7 +20,7 @@ If the user named a project in the command invocation, use that. Otherwise, run:
|
|
|
20
20
|
node ${CLAUDE_PLUGIN_ROOT}/scripts/resume.mjs [--hypo-dir="<path>"] [--project=<name>]
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema upgrade --apply` or reinstall instead of guessing the cache layout.
|
|
24
24
|
|
|
25
25
|
When `--project` is omitted, the script prefers the project whose `working_dir` contains the current directory (cwd-first); if nothing under the current directory matches, it falls back to the most recently active project from `hot.md`.
|
|
26
26
|
|
package/commands/stats.md
CHANGED
|
@@ -15,7 +15,7 @@ You are running `/hypo:stats`. Display a summary of wiki health and activity.
|
|
|
15
15
|
|
|
16
16
|
## Step 1 — Run script
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
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.
|
|
19
19
|
|
|
20
20
|
If the user specified a Hypomnema directory, pass it as `--hypo-dir="<path>"`. Otherwise omit the flag.
|
|
21
21
|
|
package/commands/uninstall.md
CHANGED
|
@@ -20,13 +20,15 @@ Say:
|
|
|
20
20
|
|
|
21
21
|
Default: yes (dry-run first)
|
|
22
22
|
|
|
23
|
+
Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema upgrade --apply` or reinstall instead of guessing the cache layout.
|
|
24
|
+
|
|
23
25
|
---
|
|
24
26
|
|
|
25
27
|
## Step 2 — Dry run
|
|
26
28
|
|
|
27
29
|
Run:
|
|
28
30
|
```
|
|
29
|
-
node scripts/uninstall.mjs
|
|
31
|
+
node ${CLAUDE_PLUGIN_ROOT}/scripts/uninstall.mjs
|
|
30
32
|
```
|
|
31
33
|
|
|
32
34
|
Show the output to the user. Ask:
|
|
@@ -39,7 +41,7 @@ If no → abort and confirm nothing was changed.
|
|
|
39
41
|
## Step 3 — Apply (if confirmed)
|
|
40
42
|
|
|
41
43
|
```
|
|
42
|
-
node scripts/uninstall.mjs --apply
|
|
44
|
+
node ${CLAUDE_PLUGIN_ROOT}/scripts/uninstall.mjs --apply
|
|
43
45
|
```
|
|
44
46
|
|
|
45
47
|
If the user also wants Codex hooks removed, append `--codex`.
|
package/commands/upgrade.md
CHANGED
|
@@ -14,7 +14,7 @@ You are running `/hypo:upgrade`. Check if the installed Hypomnema wiki is out of
|
|
|
14
14
|
|
|
15
15
|
## Step 1 — Run script
|
|
16
16
|
|
|
17
|
-
|
|
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 Hypomnema directory, pass it as `--hypo-dir="<path>"`. Otherwise omit the flag.
|
|
20
20
|
|
package/commands/verify.md
CHANGED
|
@@ -13,7 +13,7 @@ You are running `/hypo:verify`. Audit wiki pages for overdue or missing `verify_
|
|
|
13
13
|
|
|
14
14
|
## Step 1 — Run
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
Bundled scripts here run via `${CLAUDE_PLUGIN_ROOT}/scripts/`. To resolve that package root: if `${CLAUDE_PLUGIN_ROOT}` is already an absolute path, use it; otherwise read `pkgRoot` from `~/.claude/hypo-pkg.json` (only when non-empty and the target script exists under it); otherwise use the `hypo@hypomnema` (or legacy `hypomnema@hypomnema`) installPath in `~/.claude/plugins/installed_plugins.json`; if none resolve, stop and tell the user to run `hypomnema upgrade --apply` or reinstall instead of guessing the cache layout.
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
19
|
node ${CLAUDE_PLUGIN_ROOT}/scripts/verify.mjs [--hypo-dir="<path>"] [--file=<path>]
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -335,7 +335,7 @@ journal/weekly/<YYYY-Www>.md ← committed report (heuristic v0, s
|
|
|
335
335
|
|
|
336
336
|
`session-audit.mjs` reads transcripts from two locations, in priority order:
|
|
337
337
|
|
|
338
|
-
1. **Primary:** `<hypo-root>/.cache/sessions/index.jsonl` — written by the Stop hook `hypo-session-record.mjs`. Each line: `{ session_id, transcript_path, recorded_at, cwd, device }`. `device`
|
|
338
|
+
1. **Primary:** `<hypo-root>/.cache/sessions/index.jsonl` — written by the Stop hook `hypo-session-record.mjs`. Each line: `{ session_id, transcript_path, recorded_at, cwd, device }`. `device` is `os.hostname()` for multi-machine audit; the index lives under `.cache/` (gitignored in a normal vault) so it is a local-only per-session record.
|
|
339
339
|
2. **Fallback:** `~/.claude/projects/<encoded>/*.jsonl` — scanned when the index is missing or empty (legacy / freshly-installed wikis).
|
|
340
340
|
|
|
341
341
|
### Classification
|
|
@@ -366,7 +366,7 @@ The score is a **proxy, not ground truth**. The four-week baseline plan (capture
|
|
|
366
366
|
|
|
367
367
|
The observability pipeline reads but never republishes raw transcripts. Weekly reports only emit `session_id` plus aggregate counts — no transcript content, no URLs, no tool inputs. Transcripts themselves live under `~/.claude/projects/` or `.cache/sessions/` which `.hypoignore` already excludes from any sync.
|
|
368
368
|
|
|
369
|
-
**
|
|
369
|
+
**Audit fields (`device`, `session_id`).** `device` (`os.hostname()`) is recorded in two places with different sync boundaries, by design. In `.cache/sessions/index.jsonl` it is per-session-accurate but **local-only** (`.cache/` is gitignored in a normal vault). In the seeded session-log daily-shard frontmatter it is an **intentional synced field** — a creator-only stamp (the session/machine that first created the day's shard), carried across machines so the wiki itself records which machine opened each day. `session_id` (the Claude session UUID) is seeded into that same synced frontmatter, but only on the Stop-chain close path that passes `--session-id`; on manual/skill closes it is omitted. This deliberately puts a hostname (and, on the automated path, the session UUID) into git-tracked, synced content; it is acceptable for a single-user private vault where multi-machine identification is the goal. Operators who do not want a hostname synced can override `os.hostname()` at the OS level or treat the field as a pseudonymous label.
|
|
370
370
|
|
|
371
371
|
### Growth metrics (Lane B)
|
|
372
372
|
|
package/docs/CONTRIBUTING.md
CHANGED
|
@@ -124,7 +124,8 @@ npm run lint # scripts/lint.mjs — frontmatter + wikilink validation + W8
|
|
|
124
124
|
npm run fix:verify # Phase 1 of learned_behavior #6 — verifies fix #N status claims in
|
|
125
125
|
# a wiki spec against `// @fix #N: <test-name>` anchors in
|
|
126
126
|
# tests/runner.mjs. Maintainer dogfood; needs a local wiki at
|
|
127
|
-
# $HYPO_DIR or ~/hypomnema
|
|
127
|
+
# $HYPO_DIR or ~/hypomnema (source checkout only; not shipped in
|
|
128
|
+
# the npm package). Does NOT grep ADR core decision lines.
|
|
128
129
|
```
|
|
129
130
|
|
|
130
131
|
> **`fix:verify` needs an explicit `--spec`.** The default path
|
|
@@ -242,7 +243,7 @@ After both blocks, language-neutral:
|
|
|
242
243
|
|
|
243
244
|
- **PR title**: Conventional Commits plus a scope, e.g. `feat(feedback): add failure_type enum`. The type drives the CHANGELOG section (see the classification table below).
|
|
244
245
|
- **Merge commit**: the squash-merge subject carries the PR number (`#123`). That is where `#N` comes from, not the PR title. The two conventions stay separate.
|
|
245
|
-
- Internal tracker ids (`FEAT-`, `IMPR-`, `ISSUE-`, `PRAC-`) may appear in your local notes and in
|
|
246
|
+
- Internal tracker ids (`FEAT-`, `IMPR-`, `ISSUE-`, `PRAC-`) may appear in your local notes and in `tests/` / `qa-runs/` (where they aid test-to-issue traceability and never reach an installed user), but not in shipped code or workflow comments, and never on the published changelog and release surface: not in the CHANGELOG body, not in the PR `## Changelog` block, not in a tag annotation, not in a GitHub Release. The only identifier that ships in those is the PR number `#N`. `check-tracker-ids` gates every surface (`--all`/`--staged` for files, `--commit-msg` for messages, `--tag` for the tag body); the migration keeps the CHANGELOG body clean.
|
|
246
247
|
|
|
247
248
|
### The `## Changelog` block
|
|
248
249
|
|
|
@@ -272,9 +273,9 @@ At and after the v1.2.0 cutoff, each non-empty gated section (`New Features`, `B
|
|
|
272
273
|
```
|
|
273
274
|
### New Features
|
|
274
275
|
#### English
|
|
275
|
-
- Feedback pages accept an optional `failure_type`. (#141)
|
|
276
|
+
- Feedback pages accept an optional `failure_type`. ([#141](https://github.com/sk-lim19f/Hypomnema/pull/141))
|
|
276
277
|
#### 한국어
|
|
277
|
-
- 피드백 페이지가 선택적 `failure_type`를 받습니다. (#141)
|
|
278
|
+
- 피드백 페이지가 선택적 `failure_type`를 받습니다. ([#141](https://github.com/sk-lim19f/Hypomnema/pull/141))
|
|
278
279
|
```
|
|
279
280
|
|
|
280
281
|
Versions below the v1.2.0 cutoff (1.0.0, 1.0.1, 1.1.0) are English-only: the English body sits directly under the section header with no `####` sub-blocks. Korean is not back-filled for those releases.
|
|
@@ -283,12 +284,12 @@ The `### Changelog` index is language-neutral, one merged PR per line:
|
|
|
283
284
|
|
|
284
285
|
```
|
|
285
286
|
### Changelog
|
|
286
|
-
- #141 feedback failure_type classification
|
|
287
|
-
- #140 invalid-YAML lint guard
|
|
287
|
+
- [#141](https://github.com/sk-lim19f/Hypomnema/pull/141) feedback failure_type classification
|
|
288
|
+
- [#140](https://github.com/sk-lim19f/Hypomnema/pull/140) invalid-YAML lint guard
|
|
288
289
|
Contributors: @handle
|
|
289
290
|
```
|
|
290
291
|
|
|
291
|
-
Each line is `- #N <short title>`, no em dash. The `Contributors:` line lists that version's PR authors, de-duplicated; the release collector fills the handles from the GitHub API.
|
|
292
|
+
Each line is `- [#N](<pr-url>) <short title>`, no em dash: the collector links each `#N` to its GitHub PR (a repo markdown file does not autolink a bare `#N`), deriving the URL from the `repository` field in `package.json`. The `Contributors:` line lists that version's PR authors, de-duplicated; the release collector fills the handles from the GitHub API.
|
|
292
293
|
|
|
293
294
|
A `### Known Issues` or `### Notes` block, when a version has one, is a trailing note (a caveat, not a change). It is not one of the gated sections, so it carries no `####` sub-blocks even at or after the cutoff; bilingual text there is recommended, not enforced.
|
|
294
295
|
|
|
@@ -14,7 +14,7 @@ function hasRemote() {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
// Stage + commit via the shared helper (same .hypoignore filter the apply path
|
|
17
|
-
// uses
|
|
17
|
+
// uses). A real commit failure short-circuits before sync, exactly as
|
|
18
18
|
// the inline logic did; "nothing to commit" is success and falls through to sync.
|
|
19
19
|
const result = commitWikiChanges(HYPO_DIR);
|
|
20
20
|
if (!result.committed) {
|
|
@@ -26,7 +26,7 @@ if (hasRemote()) {
|
|
|
26
26
|
// pull/push failures must not stop the session, but they can no longer be
|
|
27
27
|
// swallowed silently — syncRemote records each to .cache/sync-state.json and,
|
|
28
28
|
// on a merge conflict, aborts the merge so the tree is never left half-merged
|
|
29
|
-
// (
|
|
29
|
+
// (part of the v1.4 sync hardening). session-start + doctor surface the result next session.
|
|
30
30
|
syncRemote(HYPO_DIR);
|
|
31
31
|
}
|
|
32
32
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* hypo-auto-minimal-crystallize.mjs — Stop hook (
|
|
3
|
+
* hypo-auto-minimal-crystallize.mjs — Stop hook (Layer 3)
|
|
4
4
|
*
|
|
5
5
|
* Last hook in the Stop chain: a final-line defense that blocks `Stop` when
|
|
6
6
|
* the current session did substantial work (mutation, or a high-volume
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* session-close. Forces Claude to run minimal session-close before the
|
|
9
9
|
* conversation context evaporates.
|
|
10
10
|
*
|
|
11
|
-
* Decision flow (see
|
|
11
|
+
* Decision flow (see amendment 2026-05-19 Q1+Q2 + 2nd amendment Q-close-gate):
|
|
12
12
|
*
|
|
13
13
|
* 1. stop_hook_active === true → continue (loop guard; PoC 2026-05-14)
|
|
14
14
|
* 2. wiki absent → continue (fail-open)
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
* Close-intent gate (added after PR-C dogfooding revealed every-turn block —
|
|
25
25
|
* codex 2-worker debate 2026-05-19, both REQUEST_CHANGES). Stop fires after
|
|
26
26
|
* EVERY assistant turn, not at session end; blocking on "mutation + no marker"
|
|
27
|
-
* alone nags the user on every turn of a long mutating session.
|
|
27
|
+
* alone nags the user on every turn of a long mutating session. The hook's
|
|
28
28
|
* real intent is "block when the session is ENDING and close is incomplete".
|
|
29
29
|
* We approximate the end signal by reusing isClosePattern() over recent
|
|
30
30
|
* user-message text (the same low-false-positive signal PreCompact uses):
|
|
@@ -82,11 +82,11 @@ function emitBlock(sessionId, transcriptPath, gate = null) {
|
|
|
82
82
|
// The log-only escape hatch for a non-project (wiki/tooling-only)
|
|
83
83
|
// session. Offered ONLY as an explicit alternative when a close blocker is
|
|
84
84
|
// present — never as the default recovery, so a real project session is not
|
|
85
|
-
// taught to bypass the
|
|
85
|
+
// taught to bypass the close invariant (codex design Finding 3).
|
|
86
86
|
const logOnlyCmd = cliBase
|
|
87
87
|
? `${cliBase} --mark-session-closed --log-only --session-id=${sessionId}${transcriptHint}`
|
|
88
88
|
: `/hypo:crystallize --log-only (session_id=${sessionId}${transcriptHint})`;
|
|
89
|
-
//
|
|
89
|
+
// Refine the message with the read-only /compact gate result.
|
|
90
90
|
// - gate green → the close is compact-ready and ONLY the marker is missing
|
|
91
91
|
// (the hand-edit close case: files Written + committed directly, bypassing
|
|
92
92
|
// the marker writer). Say so precisely + give the one command, instead of
|
|
@@ -112,7 +112,7 @@ function emitBlock(sessionId, transcriptPath, gate = null) {
|
|
|
112
112
|
JSON.stringify({
|
|
113
113
|
decision: 'block',
|
|
114
114
|
reason,
|
|
115
|
-
stopReason: 'session-close incomplete (
|
|
115
|
+
stopReason: 'session-close incomplete (Layer 3)',
|
|
116
116
|
}),
|
|
117
117
|
);
|
|
118
118
|
}
|
|
@@ -186,7 +186,7 @@ process.stdin.on('end', () => {
|
|
|
186
186
|
return;
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
-
//
|
|
189
|
+
// Read-only /compact gate (same precompactGateStatus the real
|
|
190
190
|
// PreCompact hook uses) sharpens the block message — distinguishes "close
|
|
191
191
|
// is compact-ready, only the marker is missing" from "there are real
|
|
192
192
|
// blockers". The hook NEVER writes the marker here (file-header invariant);
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* hypo-compact-guard.mjs — UserPromptSubmit hook
|
|
4
4
|
*
|
|
5
|
-
* Scope: detects "/compact" or "/clear" typed in chat only (
|
|
5
|
+
* Scope: detects "/compact" or "/clear" typed in chat only (Layer 2).
|
|
6
6
|
* The CLI built-in /compact does NOT fire UserPromptSubmit — use personal-wiki-check.mjs
|
|
7
7
|
* (PreCompact hook) as the hard gate for that path. /clear has no PreCompact event, so
|
|
8
8
|
* this hook is the only chat-side gate that can prompt session-close before context wipe.
|
|
@@ -42,7 +42,7 @@ process.stdin.on('end', () => {
|
|
|
42
42
|
const gitStatus = hypoIsClean();
|
|
43
43
|
const hotStatus = hotMdIsClean();
|
|
44
44
|
|
|
45
|
-
//
|
|
45
|
+
// Block on uncommitted (real unsaved work); unpushed commits (ahead)
|
|
46
46
|
// are a soft, auto-synced state and must not block /compact or /clear — mirrors
|
|
47
47
|
// the precompactGateStatus demote so the chat-side gate stays consistent.
|
|
48
48
|
if (hasSession && !gitStatus.uncommitted && hotStatus.clean) {
|
|
@@ -6,8 +6,7 @@
|
|
|
6
6
|
* project hot.md. Skips if still within the same project subtree.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { readFileSync, writeFileSync, existsSync,
|
|
10
|
-
import { homedir } from 'os';
|
|
9
|
+
import { readFileSync, writeFileSync, existsSync, realpathSync } from 'fs';
|
|
11
10
|
import { join } from 'path';
|
|
12
11
|
import {
|
|
13
12
|
HYPO_DIR,
|
|
@@ -19,6 +18,9 @@ import {
|
|
|
19
18
|
buildProjectSuggestionLine,
|
|
20
19
|
recordSuggestionCooldown,
|
|
21
20
|
sanitizeProjForPrompt,
|
|
21
|
+
pickProjectByCwd,
|
|
22
|
+
collectProjectWorkingDirs,
|
|
23
|
+
buildVaultOrientation,
|
|
22
24
|
} from './hypo-shared.mjs';
|
|
23
25
|
|
|
24
26
|
const PROJECTS_DIR = join(HYPO_DIR, 'projects');
|
|
@@ -33,42 +35,26 @@ function readIfNotIgnored(path, patterns) {
|
|
|
33
35
|
return readFileSync(path, 'utf-8').slice(0, MAX_CHARS);
|
|
34
36
|
}
|
|
35
37
|
|
|
36
|
-
function parseFrontmatterField(content, key) {
|
|
37
|
-
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
38
|
-
if (!match) return null;
|
|
39
|
-
const line = match[1].split('\n').find((l) => l.startsWith(`${key}:`));
|
|
40
|
-
if (!line) return null;
|
|
41
|
-
return line
|
|
42
|
-
.slice(key.length + 1)
|
|
43
|
-
.trim()
|
|
44
|
-
.replace(/^['"]|['"]$/g, '');
|
|
45
|
-
}
|
|
46
|
-
|
|
47
38
|
function findProjectHot(cwd) {
|
|
48
39
|
if (!existsSync(PROJECTS_DIR)) return null;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const content = readFileSync(indexPath, 'utf-8');
|
|
55
|
-
const workingDir = parseFrontmatterField(content, 'working_dir');
|
|
56
|
-
if (!workingDir) continue;
|
|
57
|
-
const resolved = workingDir.startsWith('~/')
|
|
58
|
-
? join(homedir(), workingDir.slice(2))
|
|
59
|
-
: workingDir;
|
|
60
|
-
if (cwd === resolved || cwd.startsWith(resolved + '/')) {
|
|
61
|
-
const hotPath = join(projDir, 'hot.md');
|
|
62
|
-
const statePath = join(projDir, 'session-state.md');
|
|
63
|
-
return {
|
|
64
|
-
proj,
|
|
65
|
-
hotPath: existsSync(hotPath) ? hotPath : null,
|
|
66
|
-
statePath: existsSync(statePath) ? statePath : null,
|
|
67
|
-
resolved,
|
|
68
|
-
};
|
|
69
|
-
}
|
|
40
|
+
let realpathCwd = null;
|
|
41
|
+
try {
|
|
42
|
+
realpathCwd = realpathSync(cwd);
|
|
43
|
+
} catch {
|
|
44
|
+
realpathCwd = null;
|
|
70
45
|
}
|
|
71
|
-
|
|
46
|
+
// Two-tier match (absolute prefix, then cross-machine unique basename) so a
|
|
47
|
+
// vault synced from another machine still resolves the cwd to its project.
|
|
48
|
+
const proj = pickProjectByCwd(collectProjectWorkingDirs(HYPO_DIR), cwd, { realpathCwd });
|
|
49
|
+
if (!proj) return null;
|
|
50
|
+
const projDir = join(PROJECTS_DIR, proj);
|
|
51
|
+
const hotPath = join(projDir, 'hot.md');
|
|
52
|
+
const statePath = join(projDir, 'session-state.md');
|
|
53
|
+
return {
|
|
54
|
+
proj,
|
|
55
|
+
hotPath: existsSync(hotPath) ? hotPath : null,
|
|
56
|
+
statePath: existsSync(statePath) ? statePath : null,
|
|
57
|
+
};
|
|
72
58
|
}
|
|
73
59
|
|
|
74
60
|
let raw = '';
|
|
@@ -123,10 +109,14 @@ process.stdin.on('end', () => {
|
|
|
123
109
|
);
|
|
124
110
|
}
|
|
125
111
|
}
|
|
112
|
+
// Same vault orientation as session-start: when the new cwd is a project
|
|
113
|
+
// working_dir distinct from the vault, surface where wiki files live.
|
|
114
|
+
const vaultOrientation = buildVaultOrientation(newCwd);
|
|
115
|
+
const orientPrefix = vaultOrientation ? `${vaultOrientation}\n\n` : '';
|
|
126
116
|
console.log(
|
|
127
117
|
JSON.stringify(
|
|
128
118
|
buildOutput(
|
|
129
|
-
|
|
119
|
+
`${orientPrefix}[WIKI: cwd changed → project=${sanitizeProjForPrompt(newHit.proj)}]\n\n${content}`,
|
|
130
120
|
{
|
|
131
121
|
continue: true,
|
|
132
122
|
suppressOutput: true,
|
|
@@ -137,7 +127,7 @@ process.stdin.on('end', () => {
|
|
|
137
127
|
return;
|
|
138
128
|
}
|
|
139
129
|
|
|
140
|
-
// MISS: cwd matches no project.
|
|
130
|
+
// MISS: cwd matches no project. Offer to create one
|
|
141
131
|
// when the trigger conditions hold. Same nudge-only model as session-start.
|
|
142
132
|
let suggestPrefix = '';
|
|
143
133
|
if (shouldSuggestProjectCreation(newCwd, HYPO_DIR)) {
|