hypomnema 1.7.4 → 1.8.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/CHANGELOG.md +1202 -0
- package/README.ko.md +4 -4
- package/README.md +5 -5
- package/commands/audit.md +1 -1
- package/commands/capture.md +1 -1
- package/commands/crystallize.md +3 -3
- package/commands/doctor.md +15 -1
- package/commands/feedback.md +1 -1
- 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 +1 -1
- package/commands/upgrade.md +2 -2
- package/commands/verify.md +1 -1
- package/docs/ARCHITECTURE.md +8 -3
- package/docs/CONTRIBUTING.md +2 -1
- package/hooks/base-store.mjs +198 -0
- package/hooks/close-gate-store.mjs +3 -2
- package/hooks/hypo-auto-minimal-crystallize.mjs +10 -0
- package/hooks/hypo-compact-guard.mjs +5 -3
- package/hooks/hypo-cwd-change.mjs +8 -0
- package/hooks/hypo-personal-check.mjs +127 -67
- package/hooks/hypo-session-start.mjs +91 -10
- package/hooks/hypo-shared.mjs +241 -19
- package/hooks/version-check.mjs +44 -6
- package/package.json +9 -3
- package/scripts/capture.mjs +49 -17
- package/scripts/crystallize.mjs +20 -2092
- package/scripts/doctor.mjs +334 -14
- package/scripts/init.mjs +106 -81
- package/scripts/lib/crystallize-args.mjs +50 -0
- package/scripts/lib/crystallize-close-apply.mjs +1830 -0
- package/scripts/lib/crystallize-close-check.mjs +238 -0
- package/scripts/lib/crystallize-close-gate.mjs +58 -0
- package/scripts/lib/crystallize-helpers.mjs +55 -0
- package/scripts/lib/extensions.mjs +175 -49
- package/scripts/lib/git-hooks-dir.mjs +88 -3
- package/scripts/lib/plugin-detect.mjs +176 -21
- package/scripts/lint.mjs +12 -6
- package/scripts/proposal.mjs +3 -3
- package/scripts/upgrade.mjs +243 -20
- package/skills/crystallize/SKILL.md +15 -13
- package/skills/graph/SKILL.md +3 -3
- package/skills/ingest/SKILL.md +2 -2
- package/skills/lint/SKILL.md +3 -3
- package/skills/query/SKILL.md +3 -3
- package/skills/verify/SKILL.md +3 -3
- package/templates/hypo-automation.md +59 -17
- package/templates/hypo-config.md +1 -1
- package/templates/hypo-guide.md +12 -9
- package/templates/hypo-help.md +1 -1
package/README.ko.md
CHANGED
|
@@ -39,7 +39,7 @@ Andrej Karpathy의 "LLM 네이티브 위키" 스케치에서 출발했습니다.
|
|
|
39
39
|
|
|
40
40
|
업그레이드 정책 하나만 미리 알아 두세요. `hypomnema upgrade --apply`는 사용자가 직접 편집한 `SCHEMA.md`를 덮어쓰지 않습니다. 스키마가 올라가면 위키 루트에 마이그레이션 보고서만 써 주고, 실제 반영은 사용자가 직접 합니다(코드에서 Option C라고 부르는 정책).
|
|
41
41
|
|
|
42
|
-
같은 보류 방식이 세션 마무리 파일을 쓸 때에도 적용됩니다. 세션 종료 쓰기의 대상 파일이 이 세션이 읽은 뒤에 바뀌었다면 Hypomnema는 덮어쓰지 않습니다. 그 내용을 `<위키>/.cache/proposals/` 아래에 보류하고 알려 줍니다. 검토와 적용은 직접 하세요. `hypomnema proposal list`로 목록을 보고 `hypomnema proposal apply <id>` 또는 `hypomnema proposal discard <id>`를 씁니다. 자동 적용은 없습니다.
|
|
42
|
+
같은 보류 방식이 세션 마무리 파일을 쓸 때에도 적용됩니다. 세션 종료 쓰기의 대상 파일이 이 세션이 읽은 뒤에 바뀌었다면 Hypomnema는 덮어쓰지 않습니다. 그 내용을 `<위키>/.cache/proposals/` 아래에 보류하고 알려 줍니다. 검토와 적용은 직접 하세요. `hypomnema proposal list`로 목록을 보고 터미널에서 `hypomnema proposal apply <id>` 또는 `hypomnema proposal discard <id>`를 씁니다. 세션 안에서는 타이핑할 터미널이 없으므로 같은 승인을 대화에서 받습니다. `hypomnema proposal challenge`가 diff를 보여 주고, 거기 적힌 줄을 직접 입력하면, `hypomnema proposal resolve`가 본 그대로를 씁니다. 자동 적용은 없습니다.
|
|
43
43
|
|
|
44
44
|
자동처럼 보이지만 실제로는 직접 실행해야 하는 기능이 두 가지 있습니다. extensions sync와 역방향 capture입니다. 위키 안 `~/hypomnema/extensions/{agents,commands,hooks,skills}/`에 둔 파일은 `~/.claude/`에 반영되는데(`--codex`를 붙이면 `hooks`·`commands`는 `~/.codex/`에도), 직접 `hypomnema init`, `hypomnema upgrade --apply`, 또는 dry-run이 아닌 `hypomnema capture`를 돌려야만 반영됩니다. 저절로 밀어 넣는 트리거는 없습니다. `hypomnema capture`(또는 `/hypo:capture`)는 반대 방향으로, 직접 만든 command·agent·hook·skill을 위키로 가져오는 경로인데 이것도 마찬가지로 명시적으로 불러야 동작합니다. 자세한 내용은 아래 명령어 표를 보세요.
|
|
45
45
|
|
|
@@ -203,7 +203,7 @@ Hypomnema는 청크가 아니라 페이지를 지식 단위로 봅니다. 새
|
|
|
203
203
|
|---|---|
|
|
204
204
|
| `SessionStart` | "어디까지 했더라?" `hot.md` / `session-state.md` 다시 읽기 |
|
|
205
205
|
| `UserPromptSubmit` | "이거 이미 정리해 뒀나?" BM25 룩업, top-3 주입 |
|
|
206
|
-
| `PreCompact` | "세션 정리는 했나?" 체크리스트
|
|
206
|
+
| `PreCompact` | "세션 정리는 했나?" 체크리스트 알림 (권고일 뿐, `/compact`를 막지 않음) |
|
|
207
207
|
| `PostToolUse` (Write/Edit/MultiEdit) | `git add` |
|
|
208
208
|
| `Stop` | `git commit && git pull --no-rebase && git push` |
|
|
209
209
|
|
|
@@ -261,7 +261,7 @@ Hypomnema는 청크가 아니라 페이지를 지식 단위로 봅니다. 새
|
|
|
261
261
|
| `hypo-auto-stage.mjs` | `PostToolUse(Write/Edit/MultiEdit)` | 위키 파일 자동 stage |
|
|
262
262
|
| `hypo-auto-commit.mjs` | `Stop` | 자동 commit + pull + push |
|
|
263
263
|
| `hypo-hot-rebuild.mjs` | `Stop` | 루트 `hot.md` 포인터 테이블 재생성 (구조와 날짜) |
|
|
264
|
-
| `hypo-personal-check.mjs` | `PreCompact` | session-close 미완, 위키 커밋/푸시 누락, hot.md 구조 위반, lint
|
|
264
|
+
| `hypo-personal-check.mjs` | `PreCompact` | session-close 미완, 위키 커밋/푸시 누락, hot.md 구조 위반, lint 블로커를 `systemMessage`로 알림. `/compact`는 여기서 막지 않음. 세션 마무리 강제는 다른 자리에 남아 있음: Stop 훅(`hypo-auto-minimal-crystallize.mjs`)이 마무리 확인 없는 의미 있는 세션을 막고, `--mark-session-closed`는 여전히 red 게이트에서 마커를 거부함 |
|
|
265
265
|
| `hypo-session-end.mjs` | `SessionEnd` | SessionEnd 마커 기록. 다음 SessionStart가 `source=clear` 복구를 감지하게 함 |
|
|
266
266
|
| `hypo-session-record.mjs` | `Stop` | observability 점수 + auto-resume 신호용 세션 메타데이터 기록 |
|
|
267
267
|
| `hypo-auto-minimal-crystallize.mjs` | `Stop` | 사용자가 마무리를 신호한 뒤, 의미 있는 작업을 했는데 세션 마무리가 확인되지 않으면 Stop을 막고 `crystallize.mjs --mark-session-closed` 명령을 돌려줌. 미커밋 변경이나 진행 중인 작업이 있으면 명령 대신 지금 닫을지 되물음 |
|
|
@@ -392,7 +392,7 @@ E. 멈춘 프로젝트 재개.
|
|
|
392
392
|
|
|
393
393
|
세 번째 프라이버시 축은 경로가 아니라 페이지 단위로 걸립니다. 페이지 프런트매터에 `visibility_scope: machine:<device>`를 넣으면 그 기기에서만 노출됩니다. `<device>`의 기본값은 hostname이며, hostname이 고정적이지 않은 환경이라면 `HYPO_DEVICE`로 못박으세요. 그러지 않으면 페이지가 자기 기기와도 매치되지 않습니다. 필드를 생략하면 shared입니다.
|
|
394
394
|
|
|
395
|
-
`HYPO_SKIP_GATE=1`은 세션 중간에 사용자를 막을 수 있는
|
|
395
|
+
`HYPO_SKIP_GATE=1`은 세션 중간에 사용자를 막을 수 있는 게이트가 존중합니다. `hypo-compact-guard`, `hypo-close-guard`, `hypo-auto-minimal-crystallize`, 그리고 `hypo-web-fetch-ingest`의 ingest 안내가 여기 해당합니다. `hypo-personal-check`(PreCompact)도 이 플래그를 읽지만, 이 훅은 이제 `/compact`를 막지 않으므로 플래그는 반복될 뻔한 미완성-마무리 `systemMessage`만 억제합니다. 기록할 필요 없는 가벼운 세션에 씁니다.
|
|
396
396
|
|
|
397
397
|
> 모델 제공사에게 전송되는 범위: Hypomnema 훅은 위키 본문을 Claude Code의 추가 컨텍스트(`additionalContext`)에 실어 보내고, 이 내용은 프롬프트의 일부로 Claude 모델 제공사로 전송됩니다. `.hypoignore`에 등록된 경로는 모든 주입 훅(`hypo-file-watch`, `hypo-session-start`, `hypo-cwd-change`, `hypo-lookup`)과 `ingest`에서 제외되지만, 등록하지 않은 파일은 전송 대상입니다. (`hypo-auto-stage`/`hypo-auto-commit`은 git 스테이징용 훅이라 컨텍스트를 주입하지는 않지만, 스테이징 판단에도 `.hypoignore`를 참고합니다.) 비밀 정보는 위키에 두지 마시고, `HYPO_DIR` 아래에 민감한 내용을 저장하기 전에 `.hypoignore` 패턴을 먼저 점검하세요.
|
|
398
398
|
|
package/README.md
CHANGED
|
@@ -201,7 +201,7 @@ Friction kills personal knowledge systems. If saving a thought takes three click
|
|
|
201
201
|
|---|---|
|
|
202
202
|
| `SessionStart` | "Where did I leave off?" reading `hot.md` / `session-state.md` |
|
|
203
203
|
| `UserPromptSubmit` | "Do I already know this?" a BM25 lookup, top-3 inject |
|
|
204
|
-
| `PreCompact` | "Did I close the session?"
|
|
204
|
+
| `PreCompact` | "Did I close the session?" a checklist notice (advisory only; it does not block `/compact`) |
|
|
205
205
|
| `PostToolUse` (Write/Edit/MultiEdit) | `git add` |
|
|
206
206
|
| `Stop` | `git commit && git pull --no-rebase && git push` |
|
|
207
207
|
|
|
@@ -257,7 +257,7 @@ Nine commands cover the full capture → retrieval → consolidation cycle.
|
|
|
257
257
|
| `hypo-auto-stage.mjs` | `PostToolUse(Write/Edit/MultiEdit)` | Auto-stage wiki-file edits |
|
|
258
258
|
| `hypo-auto-commit.mjs` | `Stop` | Auto commit + pull + push |
|
|
259
259
|
| `hypo-hot-rebuild.mjs` | `Stop` | Rebuild the root `hot.md` pointer table (structure + dates) |
|
|
260
|
-
| `hypo-personal-check.mjs` | `PreCompact` |
|
|
260
|
+
| `hypo-personal-check.mjs` | `PreCompact` | Surfaces an unfinished session-close, an uncommitted or unpushed wiki, malformed `hot.md`, or lint blockers as a `systemMessage`; it never blocks `/compact`. Session close is still enforced elsewhere: the Stop hook (`hypo-auto-minimal-crystallize.mjs`) blocks on a substantial session with no verified close, and `--mark-session-closed` still refuses the marker on a red gate |
|
|
261
261
|
| `hypo-session-end.mjs` | `SessionEnd` | Write a SessionEnd marker so SessionStart can detect `source=clear` recovery |
|
|
262
262
|
| `hypo-session-record.mjs` | `Stop` | Record session metadata for the observability score and auto-resume signaling |
|
|
263
263
|
| `hypo-auto-minimal-crystallize.mjs` | `Stop` | After the user signals wrap-up, blocks Stop on a substantial session with no verified close and hands back the `crystallize.mjs --mark-session-closed` command. With uncommitted changes or work still in flight it asks whether to close now instead |
|
|
@@ -384,15 +384,15 @@ Place a `hypo-config.md` at the wiki root to make it portable across machines wi
|
|
|
384
384
|
|
|
385
385
|
`.hypoignore` controls which paths the hooks ignore (default: `*.pdf`, `*.zip`, `*.pem`, `.env*`, `*credentials*`, `*secret*`, `*token*`, `*password*`, `*passwd*`, …). Edit it directly; there is no privacy mode flag. One file, one source of truth.
|
|
386
386
|
|
|
387
|
-
> `init` also installs a pre-commit hook into the wiki repo at `<wiki>/.git/hooks/pre-commit`, marked `# hypo-managed:pre-commit:*`. It refuses any commit that stages a path matching `.hypoignore`; unstage the file, or override with `git commit --no-verify`. The hook embeds the absolute path of the installed package, so if you move or reinstall Hypomnema, re-run `hypomnema init`
|
|
387
|
+
> `init` also installs a pre-commit hook into the wiki repo at `<wiki>/.git/hooks/pre-commit`, marked `# hypo-managed:pre-commit:*`. It refuses any commit that stages a path matching `.hypoignore`; unstage the file, or override with `git commit --no-verify`. The hook embeds the absolute path of the installed package, so if you move or reinstall Hypomnema, this path goes stale and every wiki commit fails. `hypomnema upgrade --apply` (or `/hypo:upgrade`, confirming the apply step, on a plugin install) repoints it in most cases, since a plugin-channel version bump is exactly the case it is built to detect; re-run `hypomnema init` when `upgrade --apply` cannot, which happens for a manual/npm install running alongside an enabled plugin whose active root it cannot positively resolve, or when the vault itself (not just the package) has moved, since `upgrade` only ever corrects the embedded root, never the embedded `--hypo-dir`. `uninstall --apply` removes this hook, resolving the vault the same way `init` does (`--hypo-dir=<path>` to override); it only removes a hook that still carries the marker and no content outside it, so a hook you have since edited by hand is left in place. Pass `--keep-wiki-hook` to skip this wiki hook cleanup (the rest of the uninstall still runs).
|
|
388
388
|
|
|
389
389
|
> The credential-style patterns above are substring globs, so an ordinary page such as `pages/oauth-token-refresh.md` is matched too: it is never injected by any hook, and the wiki pre-commit hook refuses to commit it. If you write about these topics, narrow the patterns or rename the page.
|
|
390
390
|
|
|
391
391
|
`.hyposcanignore` is a different file with a narrower job. `init` writes one at the root of every new vault, and it only excludes paths from catalog scans (`lint`, `stats`, `query`, `verify`, `doctor`). It is not a privacy boundary: a path matched by `.hyposcanignore` is still committed and still readable by hooks. To actually keep something out of injection and commits, use `.hypoignore`.
|
|
392
392
|
|
|
393
|
-
> Gate bypass: `HYPO_SKIP_GATE=1` is honored by every gate that can stop you mid-session
|
|
393
|
+
> Gate bypass: `HYPO_SKIP_GATE=1` is honored by every gate that can stop you mid-session: `hypo-compact-guard`, `hypo-close-guard`, `hypo-auto-minimal-crystallize`, and the ingest nudge in `hypo-web-fetch-ingest`. `hypo-personal-check` (PreCompact) also reads the flag, but since that hook never blocks `/compact` anymore, the flag only suppresses the incomplete-close `systemMessage` it would otherwise repeat. Set it for a trivial session you do not want recorded.
|
|
394
394
|
|
|
395
|
-
> If a session-close write finds its target changed since this session read it, Hypomnema does not overwrite it. It parks the bytes under `<wiki>/.cache/proposals/` and tells you. Review and apply them yourself: `hypomnema proposal list`, then `hypomnema proposal apply <id>` or `hypomnema proposal discard <id>`. There is no auto-apply.
|
|
395
|
+
> If a session-close write finds its target changed since this session read it, Hypomnema does not overwrite it. It parks the bytes under `<wiki>/.cache/proposals/` and tells you. Review and apply them yourself: `hypomnema proposal list`, then `hypomnema proposal apply <id>` (at a terminal) or `hypomnema proposal discard <id>`. Inside a session, where there is no terminal to type at, the same approval is collected in the conversation: `hypomnema proposal challenge` prints the diffs, you type the line it gives you, and `hypomnema proposal resolve` writes exactly what you saw. There is no auto-apply.
|
|
396
396
|
|
|
397
397
|
> Provider transmission disclaimer: Hypomnema hooks emit wiki content into Claude Code's `additionalContext`, which is transmitted to the Claude model provider as part of the prompt. `.hypoignore` is enforced at every content-injection hook (`hypo-file-watch`, `hypo-session-start`, `hypo-cwd-change`, `hypo-lookup`) and at `ingest`, but any file _not_ matched by `.hypoignore` is fair game for transmission. (`hypo-auto-stage` and `hypo-auto-commit` are git-staging hooks, not injection points, and also honor `.hypoignore` for their staging decisions.) Keep secrets out of the wiki, and review `.hypoignore` patterns before storing anything sensitive under `HYPO_DIR`.
|
|
398
398
|
|
package/commands/audit.md
CHANGED
|
@@ -16,7 +16,7 @@ Definition: [[pages/observability/_index]].
|
|
|
16
16
|
|
|
17
17
|
## Step 1 — Run script
|
|
18
18
|
|
|
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.
|
|
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 `/hypo:upgrade` on a plugin install) 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/capture.md
CHANGED
|
@@ -39,4 +39,4 @@ A flat capture stores the file in the wiki as `extensions/<type>/hypo-ext-<name>
|
|
|
39
39
|
|
|
40
40
|
## Step 4: Commit the wiki, then sync elsewhere
|
|
41
41
|
|
|
42
|
-
Show the script output verbatim. When something was captured, remind the user to commit and push the wiki, then run `hypomnema upgrade --apply` on their other machine to install the captured extension under its original name.
|
|
42
|
+
Show the script output verbatim. When something was captured, remind the user to commit and push the wiki, then run `hypomnema upgrade --apply` (or `/hypo:upgrade` on a plugin install) on their other machine to install the captured extension under its original name.
|
package/commands/crystallize.md
CHANGED
|
@@ -21,7 +21,7 @@ Before composing the payload (Step 2), run these four reflections and surface ea
|
|
|
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. This check carries no `decisions/` directory precondition: run it whether or not that directory exists.
|
|
24
|
-
3. **design-history staleness check (#42).** Two branches, so a stale W8 never blocks a clean close: (a) if this session changed design decisions `projects/<name>/design-history.md` does not yet reflect (including sub-ADR background, tradeoff, or differentiation shifts), recommend appending now: the W8 lint warning flags this mechanically, and an active-project W8
|
|
24
|
+
3. **design-history staleness check (#42).** Two branches, so a stale W8 never blocks a clean close: (a) if this session changed design decisions `projects/<name>/design-history.md` does not yet reflect (including sub-ADR background, tradeoff, or differentiation shifts), recommend appending now: the W8 lint warning flags this mechanically, and an active-project W8 still counts as a `--check-session-close` blocker even though the PreCompact hook no longer stops `/compact` on it, so append before you commit. **If the file does not exist yet and this session had a design change, recommend creating it now** with that change as the first entry; lint separately flags a missing-but-needed file as W14, a warning that never blocks. (b) only if the session made **no** design change does the `ADR 없음` marker (#41) exempt the entry from W8; do not touch design-history, and do not create the file just to satisfy this branch's check. `ADR 없음` means "no design change," a stricter bar than "no ADR-level decision." Never auto-write the file yourself in either branch: recommend it, and let the user decide.
|
|
25
25
|
4. **Ingest check (#43)** — Did this session consume trustworthy external knowledge (a fetched URL, official docs, or code you verified directly)? If so, recommend running `/hypo:ingest` to capture it under `sources/`. Proceed only on the user's confirmation.
|
|
26
26
|
|
|
27
27
|
These are judgment calls; when uncertain, surface the question rather than skip it. None of the four blocks the close or writes on its own.
|
|
@@ -78,7 +78,7 @@ Content guidance for each slot:
|
|
|
78
78
|
|
|
79
79
|
## Step 3 — Apply the payload
|
|
80
80
|
|
|
81
|
-
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.
|
|
81
|
+
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 `/hypo:upgrade` on a plugin install) or reinstall instead of guessing the cache layout.
|
|
82
82
|
|
|
83
83
|
```bash
|
|
84
84
|
node ${CLAUDE_PLUGIN_ROOT}/scripts/crystallize.mjs \
|
|
@@ -179,7 +179,7 @@ If the user says stop, end here. Otherwise continue to Step 5.
|
|
|
179
179
|
|
|
180
180
|
## Step 5 — Surface synthesis candidates
|
|
181
181
|
|
|
182
|
-
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.
|
|
182
|
+
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 `/hypo:upgrade` on a plugin install) or reinstall instead of guessing the cache layout.
|
|
183
183
|
|
|
184
184
|
```bash
|
|
185
185
|
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
|
-
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.
|
|
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 `/hypo:upgrade` on a plugin install) 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
|
|
|
@@ -52,6 +52,15 @@ For any `✗` failures:
|
|
|
52
52
|
- Missing Hypomnema root or required directories/files → run `/hypo:init`.
|
|
53
53
|
- 0 hook files installed → run `/hypo:init`.
|
|
54
54
|
- 0 hook registrations in settings.json → run `/hypo:init`.
|
|
55
|
+
- `Hook execution smoke test` failed → show the stderr excerpt in the detail
|
|
56
|
+
verbatim; it names the cause. The repair depends on the channel, and
|
|
57
|
+
`/hypo:init` is only right on one of them. If `Hook files installed` says
|
|
58
|
+
"provided by the plugin loader", the install is plugin-managed and
|
|
59
|
+
`/hypo:init` deliberately leaves core hooks alone: tell the user to
|
|
60
|
+
re-install the plugin. Otherwise (npm or manual install) `/hypo:init`
|
|
61
|
+
repairs an incomplete copy. When the excerpt is not a missing module, have
|
|
62
|
+
the user run the hook directly in their shell
|
|
63
|
+
(`echo '{}' | node <the path in the detail>`) and read the error there.
|
|
55
64
|
|
|
56
65
|
For `⚠` warnings:
|
|
57
66
|
- Missing `hypo-config.md` → run `/hypo:init` — it creates the config marker.
|
|
@@ -59,6 +68,11 @@ For `⚠` warnings:
|
|
|
59
68
|
- Partial hook files (some missing) → run `/hypo:init` to install missing hooks.
|
|
60
69
|
- Partial settings.json registrations → run `/hypo:init` to merge missing entries.
|
|
61
70
|
- Missing git remote → `git -C <hypo-dir> remote add origin <url>`.
|
|
71
|
+
- `Installed cache vs marketplace HEAD` says the install is behind → the plugin
|
|
72
|
+
version string did not move, so the update left the old cache in place. Wait
|
|
73
|
+
for the next version bump, or swap the cache directory by hand knowing the
|
|
74
|
+
next real update overwrites it. If it says *ahead* instead, that is a local
|
|
75
|
+
checkout running in front of the marketplace and there is nothing to do.
|
|
62
76
|
- Broken `[[links]]` → list the affected files and ask if the user wants to fix them now.
|
|
63
77
|
- Overdue `verify_by_date` → offer to open the affected pages for review.
|
|
64
78
|
- Missing `verify_by` question → suggest adding a `verify_by` field to the listed pages.
|
package/commands/feedback.md
CHANGED
|
@@ -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
|
-
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.
|
|
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 `/hypo:upgrade` on a plugin install) 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
|
-
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
|
+
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 `/hypo:upgrade` on a plugin install) 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
|
-
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.
|
|
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 `/hypo:upgrade` on a plugin install) 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
|
-
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.
|
|
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 `/hypo:upgrade` on a plugin install) 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
|
-
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.
|
|
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 `/hypo:upgrade` on a plugin install) 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
|
-
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
|
+
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 `/hypo:upgrade` on a plugin install) 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
|
-
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.
|
|
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 `/hypo:upgrade` on a plugin install) 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
|
-
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
|
+
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 `/hypo:upgrade` on a plugin install) 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
|
-
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.
|
|
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 `/hypo:upgrade` on a plugin install) 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
|
-
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
|
+
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 `/hypo:upgrade` on a plugin install) 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
|
@@ -25,7 +25,7 @@ Say:
|
|
|
25
25
|
|
|
26
26
|
Default: yes (dry-run first)
|
|
27
27
|
|
|
28
|
-
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.
|
|
28
|
+
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 `/hypo:upgrade` on a plugin install) or reinstall instead of guessing the cache layout.
|
|
29
29
|
|
|
30
30
|
---
|
|
31
31
|
|
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
|
-
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
|
+
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 `/hypo:upgrade` on a plugin install) 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
|
|
|
@@ -28,7 +28,7 @@ node ${CLAUDE_PLUGIN_ROOT}/scripts/upgrade.mjs [--hypo-dir="<path>"]
|
|
|
28
28
|
|
|
29
29
|
Show the output verbatim.
|
|
30
30
|
|
|
31
|
-
> **Plugin installs**: if the output begins with `ℹ Plugin install detected`, the core hooks, slash commands, and `settings.json` wiring are managed by the Claude Code plugin loader — **not** by `/hypo:upgrade`. Do **not** run `--apply` expecting it to update them (it intentionally skips those to avoid double-registering every hook). To upgrade the plugin itself: `/plugin marketplace update hypomnema` then `/reload-plugins`. `--apply` in plugin mode applies vault-side migrations (SCHEMA, `.hypoignore`), refreshes package metadata, and still syncs any vault extensions — but does **not** install the core hooks/commands/settings (the plugin provides those).
|
|
31
|
+
> **Plugin installs**: if the output begins with `ℹ Plugin install detected`, the core hooks, slash commands, and `settings.json` wiring are managed by the Claude Code plugin loader — **not** by `/hypo:upgrade`. Do **not** run `--apply` expecting it to update them (it intentionally skips those to avoid double-registering every hook). To upgrade the plugin itself: `/plugin marketplace update hypomnema` then `/reload-plugins`. **Then run `--apply` anyway**: the vault's git pre-commit hook holds an absolute path baked in at init time, and reloading the plugin does not move it, so without that step every wiki commit keeps running the install you had when you first ran init. `--apply` in plugin mode applies vault-side migrations (SCHEMA, `.hypoignore`), refreshes package metadata, and still syncs any vault extensions — but does **not** install the core hooks/commands/settings (the plugin provides those).
|
|
32
32
|
|
|
33
33
|
> **Note**: A major SCHEMA bump is only **detected** in this step. The informational `MIGRATION-vX.Y.md` file is written later by `--apply` (Step 4) and only on a major bump. `SCHEMA.md` is never auto-overwritten.
|
|
34
34
|
|
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
|
-
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.
|
|
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 `/hypo:upgrade` on a plugin install) 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
|
@@ -117,7 +117,7 @@ Hooks run automatically at Claude Code lifecycle events. They are deployed to `~
|
|
|
117
117
|
| `hypo-first-prompt` | Marker-based one-shot `hot.md` injection on first user prompt (10-min TTL) — for sessions that bypass `SessionStart` |
|
|
118
118
|
| `hypo-lookup` | BM25 search over the wiki on every prompt. **HIT** → inject top-3 page snippets (≤2000 chars each; a page whose `verify_by_date` is overdue gets a `[STALE verify_by_date=…]` marker prepended). **MISS** → emit closest-slug signal that prompts Claude to research + `/hypo:ingest` |
|
|
119
119
|
| `hypo-compact-guard` | Detect `/compact` invocations → enforce session-close checklist before allowing compact |
|
|
120
|
-
| `hypo-personal-check` | PreCompact
|
|
120
|
+
| `hypo-personal-check` | PreCompact detection: lint blockers, uncommitted changes, missing session-log entries surface as a `systemMessage`; `/compact` is never blocked here |
|
|
121
121
|
| `hypo-auto-stage` | After Write/Edit on a wiki path, run `git add` (skips paths matching `.hypoignore`) |
|
|
122
122
|
| `hypo-hot-rebuild` | At session stop, regenerate root `hot.md` from recent activity; emit growth metrics + cache for next SessionStart |
|
|
123
123
|
| `hypo-session-record` | At session stop, append `{session_id, transcript_path, recorded_at, cwd, device}` to `.cache/sessions/index.jsonl` (primary source for the observability audit) |
|
|
@@ -218,7 +218,8 @@ Hooks inline this logic in `hypo-shared.mjs`. Scripts use `scripts/lib/hypo-root
|
|
|
218
218
|
2. Creates the vault directory structure: `pages/`, `projects/`, `sources/`, `journal/{daily,weekly,monthly}/`.
|
|
219
219
|
3. Copies `templates/` files: 6 root files (`hypo-config.md`, `index.md`, `hot.md`, `log.md`, `SCHEMA.md`, `hypo-guide.md`) + 4 helpers (`Home.md`, `Overview.md`, `hypo-automation.md`, `hypo-help.md`) + `pages/_index.md` + `projects/_template/`.
|
|
220
220
|
4. Deploys 10 hooks to `~/.claude/hooks/` and merges entries into `~/.claude/settings.json` (idempotent; preserves non-hypo hooks).
|
|
221
|
-
5. Writes `~/.claude/hypo-pkg.json` with `pkgRoot` for upgrade tracking
|
|
221
|
+
5. Writes `~/.claude/hypo-pkg.json` with `pkgRoot` for upgrade tracking (skipped when the
|
|
222
|
+
plugin channel judgment fails; see `init.mjs`'s `resolveDurableRoot`).
|
|
222
223
|
6. `git init` + first commit `init: hypomnema wiki`. Pushes to remote when one is provided.
|
|
223
224
|
|
|
224
225
|
`--dry-run` previews; `--no-hooks` and `--no-git-init` are also supported. Re-running `init` is idempotent — existing files are skipped, never overwritten.
|
|
@@ -448,7 +449,11 @@ does not matter.
|
|
|
448
449
|
|
|
449
450
|
## CI / Release
|
|
450
451
|
|
|
451
|
-
### `ci.yml` —
|
|
452
|
+
### `ci.yml` — independent jobs
|
|
453
|
+
|
|
454
|
+
The table below is not exhaustive and the count is deliberately left out: it went
|
|
455
|
+
stale once already. `.github/workflows/ci.yml` is the source of truth for which
|
|
456
|
+
jobs exist.
|
|
452
457
|
|
|
453
458
|
| Job | Purpose |
|
|
454
459
|
|---|---|
|
package/docs/CONTRIBUTING.md
CHANGED
|
@@ -247,7 +247,7 @@ After both blocks, language-neutral:
|
|
|
247
247
|
|
|
248
248
|
- **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).
|
|
249
249
|
- **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.
|
|
250
|
-
- Internal tracker ids (`FEAT-`, `IMPR-`, `ISSUE-`, `PRAC-`, `fix #N`) may appear in your local notes and in `tests/`
|
|
250
|
+
- Internal tracker ids (`FEAT-`, `IMPR-`, `ISSUE-`, `PRAC-`, `fix #N`) may appear in your local notes and in `tests/` (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 tracker identifier that ships in those is the PR number `#N`; the lone exception is the ADR carve-out noted below. `check-tracker-ids` gates the file, message, and tag surfaces (`--all`/`--staged` for files, `--commit-msg` for messages, `--tag` for the tag body), and `check-pr-surface` gates the PR title and body; the migration keeps the CHANGELOG body clean. `ADR NNNN` / `decisions/NNNN` are exempt on the changelog surfaces (the CHANGELOG body, the tag body, and the PR `## Changelog` block), where a release line legitimately cites the decision behind it.
|
|
251
251
|
|
|
252
252
|
### The `## Changelog` block
|
|
253
253
|
|
|
@@ -384,6 +384,7 @@ npm run check:bilingual # each gated CHANGELOG section has #### English + ###
|
|
|
384
384
|
npm run smoke:plugin # plugin manifest + hooks/commands/skills load-valid
|
|
385
385
|
npm run smoke-pack # packed tarball installs and resolves
|
|
386
386
|
npm run check:tracker-ids # no private-tracker pointers leaked into shipped files
|
|
387
|
+
npm run format:check # every checked file matches Prettier's output
|
|
387
388
|
|
|
388
389
|
# 4. Commit every file the bump touched, plus the lockfile.
|
|
389
390
|
git add package.json package-lock.json .claude-plugin/ templates/hypo-config.md \
|
package/hooks/base-store.mjs
CHANGED
|
@@ -250,6 +250,204 @@ export function advanceBaseForWrite(hypoDir, sessionId, relPath, absPath, knownH
|
|
|
250
250
|
}
|
|
251
251
|
}
|
|
252
252
|
|
|
253
|
+
// ── observed set ───────────────────────────────────────────────────────────
|
|
254
|
+
//
|
|
255
|
+
// `targets` never moves except through the two invariants above, so a session
|
|
256
|
+
// that outlives its first snapshot by days sees every intervening legitimate
|
|
257
|
+
// write from OTHER sessions as drift and parks all four overwrite targets.
|
|
258
|
+
// The observed set is a second, additive record: what this session was
|
|
259
|
+
// actually SHOWN by a later SessionStart (resume/compact), kept separate from
|
|
260
|
+
// `targets` so it can only ever widen what a close may write, never narrow or
|
|
261
|
+
// replace the original base. `readBaseEntry`'s shape and `targets`' meaning
|
|
262
|
+
// are unchanged; a consumer that never calls the functions below sees
|
|
263
|
+
// identical behavior to before this section existed.
|
|
264
|
+
//
|
|
265
|
+
// Two guards keep the widening bounded to "what this session was just shown":
|
|
266
|
+
//
|
|
267
|
+
// - Generation. `observedGeneration` is a per-session counter, bumped once
|
|
268
|
+
// per SessionStart by `beginObservedGeneration` (BEFORE the first read of
|
|
269
|
+
// that SessionStart, so it covers everything that SessionStart injects).
|
|
270
|
+
// `recordObserved` stamps each entry with the CURRENT generation but never
|
|
271
|
+
// advances it — advancing on every record would put the two files a HIT
|
|
272
|
+
// SessionStart injects (hot.md, session-state.md) into different
|
|
273
|
+
// generations, since they are recorded one call apart, and the second call
|
|
274
|
+
// would expire the first. `readObservedHash` only returns a hash whose
|
|
275
|
+
// `generation` equals the CURRENT `observedGeneration`; a SessionStart
|
|
276
|
+
// that bumps the generation and then injects nothing (ignored, scoped out,
|
|
277
|
+
// absent, no session_id) leaves every existing entry one generation stale,
|
|
278
|
+
// so it reads back as null everywhere. This is what makes "only the most
|
|
279
|
+
// recent SessionStart's injection licenses a write" true without an
|
|
280
|
+
// explicit expiry pass: staleness falls out of the generation compare.
|
|
281
|
+
// - Tracked-key scoping. Exactly like `advanceBaseForWrite`, `recordObserved`
|
|
282
|
+
// is a no-op for a key that is not already in `targets` — it cannot mint a
|
|
283
|
+
// new guarded target, only add provenance to one that was already
|
|
284
|
+
// snapshotted for this session.
|
|
285
|
+
//
|
|
286
|
+
// One entry per path, not an array: a later observation of the SAME path
|
|
287
|
+
// simply overwrites the old `{generation, hash}` pair, so there is no
|
|
288
|
+
// unbounded growth to cap or dedup.
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Bump this session's observed generation. Call once per SessionStart
|
|
292
|
+
* invocation, before the first `recordObserved` of that invocation — this is
|
|
293
|
+
* what makes an injection-free SessionStart (resume that hit no target, a
|
|
294
|
+
* scoped-out file, .hypoignore) expire every prior observation instead of
|
|
295
|
+
* leaving it licensed forever.
|
|
296
|
+
*
|
|
297
|
+
* No-op when the session has no snapshot yet: there is nothing to bump.
|
|
298
|
+
*
|
|
299
|
+
* @returns {boolean} true when base.json was updated
|
|
300
|
+
*/
|
|
301
|
+
export function beginObservedGeneration(hypoDir, sessionId) {
|
|
302
|
+
if (!sessionId) return false;
|
|
303
|
+
const parsed = readBaseFile(hypoDir, sessionId);
|
|
304
|
+
if (!parsed) return false;
|
|
305
|
+
const current = typeof parsed.observedGeneration === 'number' ? parsed.observedGeneration : 0;
|
|
306
|
+
parsed.observedGeneration = current + 1;
|
|
307
|
+
try {
|
|
308
|
+
atomicWrite(basePath(hypoDir, sessionId), JSON.stringify(parsed, null, 2));
|
|
309
|
+
return true;
|
|
310
|
+
} catch {
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Record that this session was just SHOWN `hash` for `relPath` (the exact
|
|
317
|
+
* bytes a SessionStart injection read, not a fresh disk re-read — the caller
|
|
318
|
+
* must pass the hash of the bytes it actually displayed).
|
|
319
|
+
*
|
|
320
|
+
* `truncated`: true when the injection sliced the file (2000-char HOT_CHARS /
|
|
321
|
+
* STATE_CHARS) before showing it, i.e. the caller only passed `hash` of a
|
|
322
|
+
* prefix's worth of trust even though `hash` itself is the FULL file's hash.
|
|
323
|
+
* A truncated observation is stored, not dropped, so a parked close can name
|
|
324
|
+
* the reason (`base-mismatch-truncated-observation`) instead of the plain
|
|
325
|
+
* `base-mismatch` a bare no-op would produce — but `readObservedHash` below
|
|
326
|
+
* refuses to hand it out as a licence: seeing 5% of a file is not seeing it.
|
|
327
|
+
*
|
|
328
|
+
* No-op, in order: no snapshot for this session; `relPath` is not one of the
|
|
329
|
+
* four tracked overwrite targets (mirrors `advanceBaseForWrite`'s scoping —
|
|
330
|
+
* this must not be able to mint a new guarded key). Stamped with the CURRENT
|
|
331
|
+
* `observedGeneration`, never advancing it: the caller advances once via
|
|
332
|
+
* `beginObservedGeneration`, not once per recorded target.
|
|
333
|
+
*
|
|
334
|
+
* @returns {boolean} true when base.json was updated
|
|
335
|
+
*/
|
|
336
|
+
/**
|
|
337
|
+
* A safe integer, or null. base.json is on disk and another writer (an older
|
|
338
|
+
* release, a half-finished write, a hand edit) can leave any shape in it, so a
|
|
339
|
+
* generation counter is only trusted when it is exactly that: `NaN`, `Infinity`,
|
|
340
|
+
* `1.5` and `"1"` all read as absent rather than as a value to compare against.
|
|
341
|
+
*/
|
|
342
|
+
function safeGeneration(v) {
|
|
343
|
+
return Number.isSafeInteger(v) ? v : null;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export function recordObserved(hypoDir, sessionId, relPath, hash, truncated = false) {
|
|
347
|
+
if (!sessionId) return false;
|
|
348
|
+
const parsed = readBaseFile(hypoDir, sessionId);
|
|
349
|
+
if (!parsed) return false;
|
|
350
|
+
if (!Object.prototype.hasOwnProperty.call(parsed.targets, relPath)) return false;
|
|
351
|
+
if (typeof hash !== 'string') return false;
|
|
352
|
+
const generation = typeof parsed.observedGeneration === 'number' ? parsed.observedGeneration : 0;
|
|
353
|
+
if (!parsed.observed || typeof parsed.observed !== 'object' || Array.isArray(parsed.observed)) {
|
|
354
|
+
parsed.observed = {};
|
|
355
|
+
}
|
|
356
|
+
parsed.observed[relPath] = { generation, hash, truncated: !!truncated };
|
|
357
|
+
try {
|
|
358
|
+
atomicWrite(basePath(hypoDir, sessionId), JSON.stringify(parsed, null, 2));
|
|
359
|
+
return true;
|
|
360
|
+
} catch {
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* The hash this session was shown for `relPath`, but ONLY when it was shown
|
|
367
|
+
* IN FULL during the CURRENT observed generation — the actual enforcement
|
|
368
|
+
* point of "only the most recent SessionStart's injection licenses a write".
|
|
369
|
+
* Returns null for: no snapshot, a session whose observed-generation counter
|
|
370
|
+
* was never created (see below), no observed entry, a malformed entry, a
|
|
371
|
+
* generation that does not match (stale — superseded by a later SessionStart,
|
|
372
|
+
* or never refreshed by one that injected nothing), or an entry the injection
|
|
373
|
+
* itself marked `truncated`.
|
|
374
|
+
*
|
|
375
|
+
* The `observedGeneration` check is deliberately ASYMMETRIC with how
|
|
376
|
+
* `recordObserved` reads the same field: that function normalizes a missing
|
|
377
|
+
* counter to `0` only to pick a generation to STAMP an entry with. Doing the
|
|
378
|
+
* same here — treating "no counter" as "generation 0" — would make a session
|
|
379
|
+
* whose `beginObservedGeneration` call never ran (never bumped past 0) match
|
|
380
|
+
* an entry `recordObserved` also stamped at 0, and the observed set would
|
|
381
|
+
* license writes despite the expiry mechanism that is supposed to gate it
|
|
382
|
+
* never having run at all. So here, "no counter" reads as "no current
|
|
383
|
+
* generation for anything to match" — null, not 0.
|
|
384
|
+
*
|
|
385
|
+
* @returns {string|null}
|
|
386
|
+
*/
|
|
387
|
+
export function readObservedHash(hypoDir, sessionId, relPath) {
|
|
388
|
+
if (!sessionId) return null;
|
|
389
|
+
const parsed = readBaseFile(hypoDir, sessionId);
|
|
390
|
+
if (!parsed) return null;
|
|
391
|
+
const current = safeGeneration(parsed.observedGeneration);
|
|
392
|
+
if (current === null) return null;
|
|
393
|
+
const observed = parsed.observed;
|
|
394
|
+
if (!observed || typeof observed !== 'object' || Array.isArray(observed)) return null;
|
|
395
|
+
const entry = observed[relPath];
|
|
396
|
+
if (!entry || typeof entry !== 'object' || typeof entry.hash !== 'string' || !entry.hash) {
|
|
397
|
+
return null;
|
|
398
|
+
}
|
|
399
|
+
if (safeGeneration(entry.generation) !== current) return null;
|
|
400
|
+
// `truncated` is checked for a STRICT boolean, and any other shape refuses the
|
|
401
|
+
// licence rather than falling through to `=== true` being false. A corrupt
|
|
402
|
+
// `"true"` string used to pass that comparison and hand out a licence for a
|
|
403
|
+
// sliced observation — the one thing this field exists to deny. Corruption
|
|
404
|
+
// parks; it never widens.
|
|
405
|
+
if (entry.truncated !== false) return null;
|
|
406
|
+
return entry.hash;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Whether this session has a CURRENT-generation observed entry for `relPath`
|
|
411
|
+
* that exists but was marked `truncated` by `recordObserved` — the one bit
|
|
412
|
+
* `readObservedHash`'s null collapses away. Consulted only to pick a park
|
|
413
|
+
* reason (`base-mismatch-truncated-observation` vs plain `base-mismatch`),
|
|
414
|
+
* never to license a write; a caller must keep treating `readObservedHash`'s
|
|
415
|
+
* null as "no licence" regardless of what this returns.
|
|
416
|
+
*
|
|
417
|
+
* @returns {boolean}
|
|
418
|
+
*/
|
|
419
|
+
export function wasObservedTruncated(hypoDir, sessionId, relPath) {
|
|
420
|
+
if (!sessionId) return false;
|
|
421
|
+
const parsed = readBaseFile(hypoDir, sessionId);
|
|
422
|
+
if (!parsed) return false;
|
|
423
|
+
const current = safeGeneration(parsed.observedGeneration);
|
|
424
|
+
if (current === null) return false;
|
|
425
|
+
const observed = parsed.observed;
|
|
426
|
+
if (!observed || typeof observed !== 'object' || Array.isArray(observed)) return false;
|
|
427
|
+
const entry = observed[relPath];
|
|
428
|
+
if (!entry || typeof entry !== 'object') return false;
|
|
429
|
+
if (safeGeneration(entry.generation) !== current) return false;
|
|
430
|
+
// Mirrors readObservedHash's strict check: anything that is not exactly `false`
|
|
431
|
+
// counts as truncated here. This only picks the park REASON (readObservedHash
|
|
432
|
+
// has already refused the licence), so erring toward "truncated" names a
|
|
433
|
+
// narrower cause than the generic mismatch and never unblocks a write.
|
|
434
|
+
return entry.truncated !== false;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// The four whole-file overwrite targets are prose-and-table markdown documents, and
|
|
438
|
+
// a base-mismatch on one of them always parks. Five predicates lived here that tried
|
|
439
|
+
// to skip the park when a payload "provably" lost nothing, and four rounds of review
|
|
440
|
+
// broke all five against the real vault. The last one accepted an insertion between a
|
|
441
|
+
// table's header and its separator, which keeps every byte and stops the table from
|
|
442
|
+
// being a table. They all failed the same way: a markdown document's meaning comes
|
|
443
|
+
// from block context that begins far above the line under judgement, and a hook that
|
|
444
|
+
// may use Node built-ins only is not the place to own a block parser.
|
|
445
|
+
//
|
|
446
|
+
// The pointer table this was built for should stop being a shared whole-file
|
|
447
|
+
// overwrite target and become a locally generated projection of the per-project files
|
|
448
|
+
// that already own those facts. Then two machines never contend over it, and nothing
|
|
449
|
+
// here needs to prove anything.
|
|
450
|
+
|
|
253
451
|
/**
|
|
254
452
|
* The four overwrite targets crystallize replaces wholesale. `project` may be
|
|
255
453
|
* null when cwd resolves to no project; the two project-scoped paths are then
|