hypomnema 1.3.4 → 1.4.1
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 +151 -153
- package/README.md +121 -123
- package/commands/audit.md +4 -4
- package/commands/crystallize.md +18 -5
- package/commands/doctor.md +3 -3
- package/commands/feedback.md +9 -3
- package/commands/graph.md +3 -3
- package/commands/ingest.md +6 -4
- package/commands/init.md +5 -3
- package/commands/lint.md +3 -3
- package/commands/query.md +3 -3
- package/commands/rename.md +4 -4
- package/commands/resume.md +4 -2
- package/commands/stats.md +3 -3
- package/commands/upgrade.md +4 -4
- package/commands/verify.md +3 -3
- package/docs/ARCHITECTURE.md +4 -2
- package/docs/CONTRIBUTING.md +148 -25
- package/hooks/hypo-auto-commit.mjs +6 -12
- package/hooks/hypo-session-record.mjs +5 -0
- package/hooks/hypo-session-start.mjs +7 -0
- package/hooks/hypo-shared.mjs +68 -1
- package/package.json +10 -2
- package/scripts/check-bilingual.mjs +49 -11
- package/scripts/check-readme-version.mjs +126 -0
- package/scripts/check-tracker-ids.mjs +60 -1
- package/scripts/check-versions.mjs +171 -0
- package/scripts/crystallize.mjs +49 -34
- package/scripts/doctor.mjs +13 -4
- package/scripts/feedback.mjs +68 -1
- package/scripts/graph.mjs +5 -32
- package/scripts/init.mjs +2 -1
- package/scripts/lib/changelog-classify.mjs +216 -0
- package/scripts/lib/check-bilingual.mjs +125 -22
- package/scripts/lib/check-tracker-ids.mjs +19 -0
- package/scripts/lib/failure-type.mjs +33 -0
- package/scripts/lib/frontmatter.mjs +23 -2
- package/scripts/lib/schema-vocab.mjs +35 -0
- package/scripts/lib/template-schema-version.mjs +21 -0
- package/scripts/lib/wikilink.mjs +156 -0
- package/scripts/lint.mjs +86 -47
- package/scripts/rename.mjs +6 -30
- package/scripts/stats.mjs +22 -2
- package/scripts/upgrade.mjs +11 -3
- package/scripts/weekly-report.mjs +9 -3
- package/skills/crystallize/SKILL.md +21 -1
- package/templates/SCHEMA.md +25 -1
- package/templates/hypo-config.md +1 -1
- package/scripts/bump-version.mjs +0 -63
- package/scripts/smoke-pack.mjs +0 -261
package/commands/feedback.md
CHANGED
|
@@ -26,6 +26,7 @@ If the user did not provide them, ask. The classification fields are required so
|
|
|
26
26
|
6. **Targets**: "Where should this project?" → `project-memory` (MEMORY.md) and/or `claude-learned` (global CLAUDE.md). Default `project-memory`.
|
|
27
27
|
7. **Priority** (1–5, higher sorts first; default 3).
|
|
28
28
|
8. **Sensitivity**: `public` (default) or `sanitized` (redacted secrets/paths). `private` is not allowed — the wiki is git-pushed.
|
|
29
|
+
9. **Failure type** (optional): if this correction came from a real failure incident, classify it — `hallucination` | `false-completion` | `process-stall` | `over-caution` | `overreach` | `incompleteness` | `instruction-miss` | `convention-violation`. Omit it for a pure preference or a brand-new convention ("always do X"). When several fit, take the most specific (the list is in precedence order; see SCHEMA §3.1).
|
|
29
30
|
|
|
30
31
|
If **claude-learned** is among the targets, the page must be `scope: global` + `tier: L1`, and you must also collect:
|
|
31
32
|
- **Global summary**: a one-line summary for the CLAUDE.md learned-behaviors entry.
|
|
@@ -35,10 +36,12 @@ If **claude-learned** is among the targets, the page must be `scope: global` + `
|
|
|
35
36
|
|
|
36
37
|
## Step 2 — List existing feedback (optional)
|
|
37
38
|
|
|
38
|
-
|
|
39
|
+
The script path below resolves via `${CLAUDE_PLUGIN_ROOT}`, which the plugin harness expands to this package's absolute path before you see it, so run it as written. If it appears unexpanded (a literal `${CLAUDE_PLUGIN_ROOT}`), read the package root from the `hypo@hypomnema` installPath in `~/.claude/plugins/installed_plugins.json` rather than guessing from the cache layout.
|
|
40
|
+
|
|
41
|
+
To check for an existing topic, run:
|
|
39
42
|
|
|
40
43
|
```bash
|
|
41
|
-
node
|
|
44
|
+
node ${CLAUDE_PLUGIN_ROOT}/scripts/feedback.mjs --list [--hypo-dir="<path>"]
|
|
42
45
|
```
|
|
43
46
|
|
|
44
47
|
If a matching topic exists, appending adds a dated entry and bumps `updated:` (classification frontmatter is preserved).
|
|
@@ -50,7 +53,7 @@ If a matching topic exists, appending adds a dated entry and bumps `updated:` (c
|
|
|
50
53
|
Run with `--dry-run` first to preview the generated page, then without it to write. Pass every collected field:
|
|
51
54
|
|
|
52
55
|
```bash
|
|
53
|
-
node
|
|
56
|
+
node ${CLAUDE_PLUGIN_ROOT}/scripts/feedback.mjs \
|
|
54
57
|
--topic="<slug>" \
|
|
55
58
|
--entry="<one-line rule>" \
|
|
56
59
|
--scope="global|project:<project-id>" \
|
|
@@ -61,6 +64,7 @@ node <package-root>/scripts/feedback.mjs \
|
|
|
61
64
|
--memory-summary="<one-line MEMORY.md summary>" \
|
|
62
65
|
--reason="<why this rule exists>" \
|
|
63
66
|
[--global-summary="<one-line CLAUDE.md summary>" --promote-to-global] \
|
|
67
|
+
[--failure-type="<enum>"] \
|
|
64
68
|
[--source="session:<date>"] \
|
|
65
69
|
[--hypo-dir="<path>"] \
|
|
66
70
|
[--dry-run]
|
|
@@ -68,6 +72,8 @@ node <package-root>/scripts/feedback.mjs \
|
|
|
68
72
|
|
|
69
73
|
When `--targets` includes `claude-learned`, `--global-summary` and `--promote-to-global` are required (and `--scope=global --tier=L1`).
|
|
70
74
|
|
|
75
|
+
`--failure-type` is optional (one of the eight values above). On **append** to an existing topic it is set only if the page has none; if the page already carries a different `failure_type` the command errors (a page holds a single failure_type — use a separate topic for a different one). Without the flag, an append leaves the frontmatter untouched as before.
|
|
76
|
+
|
|
71
77
|
> **`scope: project:<project-id>` 주의.** `<project-id>`는 `feedback-sync`가 resolve한 project-id와 정확히 일치해야 한다 (default: cwd의 `/`,`.` → `-` 치환; `--project-id=<id>` 로 override). 일치하지 않으면 그 페이지는 해당 project의 MEMORY로 projection되지 **않는다** (silent skip — lint error 아님). v1.3.0부터 scope regex(`^(global|project:[A-Za-z0-9_-]+)$`)가 cwd-derived id 형식(`-Users-...`)을 그대로 허용하므로 lint 통과를 위해 `--project-id=<slug>`를 override할 필요는 없다. 단 cwd에 공백 등 `[A-Za-z0-9_-]` 밖 문자가 있으면 그 id는 여전히 거부되니 그때만 `--project-id=<id>`로 override한다.
|
|
72
78
|
|
|
73
79
|
On a real (non-dry-run) write, the script automatically runs `feedback-sync --write` to refresh MEMORY.md / CLAUDE.md. If that post-step reports drift it prints a one-line warning — the page is still saved; reconcile with `hypomnema feedback-sync --check`.
|
package/commands/graph.md
CHANGED
|
@@ -12,9 +12,9 @@ You are running `/hypo:graph`. Generate a link dependency graph from wiki pages.
|
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
15
|
-
## Step 1 —
|
|
15
|
+
## Step 1 — Run script
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
The script path below resolves via `${CLAUDE_PLUGIN_ROOT}`, which the plugin harness expands to this package's absolute path before you see it, so run it as written. If it appears unexpanded (a literal `${CLAUDE_PLUGIN_ROOT}`), read the package root from the `hypo@hypomnema` installPath in `~/.claude/plugins/installed_plugins.json` rather than guessing from 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
|
|
|
@@ -23,7 +23,7 @@ If the user specified a Hypomnema directory, pass it as `--hypo-dir="<path>"`. O
|
|
|
23
23
|
## Step 2 — Run the graph script
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
|
-
node
|
|
26
|
+
node ${CLAUDE_PLUGIN_ROOT}/scripts/graph.mjs \
|
|
27
27
|
[--hypo-dir="<path>"] \
|
|
28
28
|
[--format=json|mermaid|dot] \
|
|
29
29
|
[--min-edges=<n>]
|
package/commands/ingest.md
CHANGED
|
@@ -27,18 +27,20 @@ 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
|
+
The script paths below resolve via `${CLAUDE_PLUGIN_ROOT}`, which the plugin harness expands to this package's absolute path before you see it, so run them as written. If one appears unexpanded (a literal `${CLAUDE_PLUGIN_ROOT}`), read the package root from the `hypo@hypomnema` installPath in `~/.claude/plugins/installed_plugins.json` rather than guessing from the cache layout.
|
|
31
|
+
|
|
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:
|
|
31
33
|
|
|
32
34
|
1. **If the source is a file path**, check it (use an absolute path):
|
|
33
35
|
|
|
34
36
|
```bash
|
|
35
|
-
node
|
|
37
|
+
node ${CLAUDE_PLUGIN_ROOT}/scripts/ingest.mjs [--hypo-dir="<path>"] --check="<absolute-input-path>"
|
|
36
38
|
```
|
|
37
39
|
|
|
38
40
|
2. **Always** check the destination `sources/<slug>.<ext>`:
|
|
39
41
|
|
|
40
42
|
```bash
|
|
41
|
-
node
|
|
43
|
+
node ${CLAUDE_PLUGIN_ROOT}/scripts/ingest.mjs [--hypo-dir="<path>"] --check="sources/<slug>.<ext>"
|
|
42
44
|
```
|
|
43
45
|
|
|
44
46
|
If either command exits non-zero, **stop**: surface the `Refused: ...` message to the user and do not fetch, read, or save the source. The slug check matters because a user could rename a `.env` to an innocuous slug — the destination must still be blocked.
|
|
@@ -50,7 +52,7 @@ If either command exits non-zero, **stop**: surface the `Refused: ...` message t
|
|
|
50
52
|
Run the ingest helper to surface existing orphaned sources:
|
|
51
53
|
|
|
52
54
|
```bash
|
|
53
|
-
node
|
|
55
|
+
node ${CLAUDE_PLUGIN_ROOT}/scripts/ingest.mjs [--hypo-dir="<path>"]
|
|
54
56
|
```
|
|
55
57
|
|
|
56
58
|
If there are orphaned sources already in `sources/`, ask: "There are N unprocessed sources — do you want to ingest one of those instead?"
|
package/commands/init.md
CHANGED
|
@@ -43,10 +43,12 @@ 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
|
+
The script path below resolves via `${CLAUDE_PLUGIN_ROOT}`, which the plugin harness expands to this package's absolute path before you see it, so run it as written. If it appears unexpanded (a literal `${CLAUDE_PLUGIN_ROOT}`), read the package root from the `hypo@hypomnema` installPath in `~/.claude/plugins/installed_plugins.json` rather than guessing from the cache layout.
|
|
47
|
+
|
|
46
48
|
If the user provided `--from-remote <url>`, run:
|
|
47
49
|
|
|
48
50
|
```bash
|
|
49
|
-
node
|
|
51
|
+
node ${CLAUDE_PLUGIN_ROOT}/scripts/init.mjs \
|
|
50
52
|
--from-remote="<url>" \
|
|
51
53
|
--hypo-dir="<hypo-dir>" \
|
|
52
54
|
[--no-hooks] \
|
|
@@ -62,11 +64,11 @@ node <package-root>/scripts/init.mjs \
|
|
|
62
64
|
|
|
63
65
|
## Step 2 — Run the init script (new wiki)
|
|
64
66
|
|
|
65
|
-
|
|
67
|
+
The script path below resolves via `${CLAUDE_PLUGIN_ROOT}`, which the plugin harness expands to this package's absolute path before you see it, so run it as written. If it appears unexpanded (a literal `${CLAUDE_PLUGIN_ROOT}`), read the package root from the `hypo@hypomnema` installPath in `~/.claude/plugins/installed_plugins.json` rather than guessing from the cache layout.
|
|
66
68
|
Then run:
|
|
67
69
|
|
|
68
70
|
```bash
|
|
69
|
-
node
|
|
71
|
+
node ${CLAUDE_PLUGIN_ROOT}/scripts/init.mjs \
|
|
70
72
|
--hypo-dir="<hypo-dir>" \
|
|
71
73
|
[--no-hooks] \
|
|
72
74
|
[--codex] \
|
package/commands/lint.md
CHANGED
|
@@ -14,9 +14,9 @@ You are running `/hypo:lint`. Validate all wiki pages for frontmatter correctnes
|
|
|
14
14
|
|
|
15
15
|
---
|
|
16
16
|
|
|
17
|
-
## Step 1 —
|
|
17
|
+
## Step 1 — Run script
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
The script path below resolves via `${CLAUDE_PLUGIN_ROOT}`, which the plugin harness expands to this package's absolute path before you see it, so run it as written. If it appears unexpanded (a literal `${CLAUDE_PLUGIN_ROOT}`), read the package root from the `hypo@hypomnema` installPath in `~/.claude/plugins/installed_plugins.json` rather than guessing from 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
|
|
|
@@ -25,7 +25,7 @@ If the user specified a Hypomnema directory, pass it as `--hypo-dir="<path>"`. O
|
|
|
25
25
|
## Step 2 — Run lint
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
|
-
node
|
|
28
|
+
node ${CLAUDE_PLUGIN_ROOT}/scripts/lint.mjs [--hypo-dir="<path>"] [--json] [--fix]
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
Options:
|
package/commands/query.md
CHANGED
|
@@ -18,14 +18,14 @@ Ask the user what they want to know if it was not provided in the command invoca
|
|
|
18
18
|
|
|
19
19
|
---
|
|
20
20
|
|
|
21
|
-
## Step 2 —
|
|
21
|
+
## Step 2 — Search
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
The script path below resolves via `${CLAUDE_PLUGIN_ROOT}`, which the plugin harness expands to this package's absolute path before you see it, so run it as written. If it appears unexpanded (a literal `${CLAUDE_PLUGIN_ROOT}`), read the package root from the `hypo@hypomnema` installPath in `~/.claude/plugins/installed_plugins.json` rather than guessing from the cache layout.
|
|
24
24
|
|
|
25
25
|
Run full-text search:
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
|
-
node
|
|
28
|
+
node ${CLAUDE_PLUGIN_ROOT}/scripts/query.mjs \
|
|
29
29
|
--q="<query terms>" \
|
|
30
30
|
[--hypo-dir="<path>"] \
|
|
31
31
|
[--limit=10]
|
package/commands/rename.md
CHANGED
|
@@ -14,9 +14,9 @@ You are running `/hypo:rename`. Move a page or directory and content-aware rewri
|
|
|
14
14
|
|
|
15
15
|
---
|
|
16
16
|
|
|
17
|
-
## Step 1 —
|
|
17
|
+
## Step 1 — Run script
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
The script path below resolves via `${CLAUDE_PLUGIN_ROOT}`, which the plugin harness expands to this package's absolute path before you see it, so run it as written. If it appears unexpanded (a literal `${CLAUDE_PLUGIN_ROOT}`), read the package root from the `hypo@hypomnema` installPath in `~/.claude/plugins/installed_plugins.json` rather than guessing from 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
|
|
|
@@ -27,7 +27,7 @@ If the user specified a Hypomnema directory, pass it as `--hypo-dir="<path>"`. O
|
|
|
27
27
|
Always run the dry-run (no `--apply`) before writing anything:
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
|
-
node
|
|
30
|
+
node ${CLAUDE_PLUGIN_ROOT}/scripts/rename.mjs \
|
|
31
31
|
[--hypo-dir="<path>"] \
|
|
32
32
|
--from=<slug|rel|dir> \
|
|
33
33
|
--to=<slug|rel|dir> \
|
|
@@ -61,7 +61,7 @@ If the result is `ok: false`:
|
|
|
61
61
|
Once the user confirms the dry-run looks right, re-run with `--apply`:
|
|
62
62
|
|
|
63
63
|
```bash
|
|
64
|
-
node
|
|
64
|
+
node ${CLAUDE_PLUGIN_ROOT}/scripts/rename.mjs \
|
|
65
65
|
[--hypo-dir="<path>"] \
|
|
66
66
|
--from=<slug|rel|dir> \
|
|
67
67
|
--to=<slug|rel|dir> \
|
package/commands/resume.md
CHANGED
|
@@ -14,12 +14,14 @@ You are running `/hypo:resume`. Load the session state for an active project and
|
|
|
14
14
|
|
|
15
15
|
## Step 1 — Resolve project
|
|
16
16
|
|
|
17
|
-
If the user named a project in the command invocation, use that. Otherwise,
|
|
17
|
+
If the user named a project in the command invocation, use that. Otherwise, run:
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
node
|
|
20
|
+
node ${CLAUDE_PLUGIN_ROOT}/scripts/resume.mjs [--hypo-dir="<path>"] [--project=<name>]
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
+
The plugin harness expands `${CLAUDE_PLUGIN_ROOT}` to this package's absolute path before you see it, so run the command as written. If it appears unexpanded (a literal `${CLAUDE_PLUGIN_ROOT}`), read the package root from the `hypo@hypomnema` installPath in `~/.claude/plugins/installed_plugins.json` rather than guessing from the cache layout.
|
|
24
|
+
|
|
23
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`.
|
|
24
26
|
|
|
25
27
|
---
|
package/commands/stats.md
CHANGED
|
@@ -13,9 +13,9 @@ You are running `/hypo:stats`. Display a summary of wiki health and activity.
|
|
|
13
13
|
|
|
14
14
|
---
|
|
15
15
|
|
|
16
|
-
## Step 1 —
|
|
16
|
+
## Step 1 — Run script
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
The script path below resolves via `${CLAUDE_PLUGIN_ROOT}`, which the plugin harness expands to this package's absolute path before you see it, so run it as written. If it appears unexpanded (a literal `${CLAUDE_PLUGIN_ROOT}`), read the package root from the `hypo@hypomnema` installPath in `~/.claude/plugins/installed_plugins.json` rather than guessing from 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
|
|
|
@@ -24,7 +24,7 @@ If the user specified a Hypomnema directory, pass it as `--hypo-dir="<path>"`. O
|
|
|
24
24
|
## Step 2 — Run the stats script
|
|
25
25
|
|
|
26
26
|
```bash
|
|
27
|
-
node
|
|
27
|
+
node ${CLAUDE_PLUGIN_ROOT}/scripts/stats.mjs [--hypo-dir="<path>"] [--json]
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
---
|
package/commands/upgrade.md
CHANGED
|
@@ -12,9 +12,9 @@ You are running `/hypo:upgrade`. Check if the installed Hypomnema wiki is out of
|
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
15
|
-
## Step 1 —
|
|
15
|
+
## Step 1 — Run script
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
The script path below resolves via `${CLAUDE_PLUGIN_ROOT}`, which the plugin harness expands to this package's absolute path before you see it, so run it as written. If it appears unexpanded (a literal `${CLAUDE_PLUGIN_ROOT}`), read the package root from the `hypo@hypomnema` installPath in `~/.claude/plugins/installed_plugins.json` rather than guessing from 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
|
|
|
@@ -23,7 +23,7 @@ If the user specified a Hypomnema directory, pass it as `--hypo-dir="<path>"`. O
|
|
|
23
23
|
## Step 2 — Run upgrade check
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
|
-
node
|
|
26
|
+
node ${CLAUDE_PLUGIN_ROOT}/scripts/upgrade.mjs [--hypo-dir="<path>"]
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
Show the output verbatim.
|
|
@@ -54,7 +54,7 @@ If there is anything to update, ask the user:
|
|
|
54
54
|
|
|
55
55
|
- If **yes** → run with `--apply`:
|
|
56
56
|
```bash
|
|
57
|
-
node
|
|
57
|
+
node ${CLAUDE_PLUGIN_ROOT}/scripts/upgrade.mjs [--hypo-dir="<path>"] --apply
|
|
58
58
|
```
|
|
59
59
|
- If **no** → tell the user they can apply later by running `/hypo:upgrade` again.
|
|
60
60
|
|
package/commands/verify.md
CHANGED
|
@@ -11,12 +11,12 @@ You are running `/hypo:verify`. Audit wiki pages for overdue or missing `verify_
|
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
14
|
-
## Step 1 —
|
|
14
|
+
## Step 1 — Run
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
The script path below resolves via `${CLAUDE_PLUGIN_ROOT}`, which the plugin harness expands to this package's absolute path before you see it, so run it as written. If it appears unexpanded (a literal `${CLAUDE_PLUGIN_ROOT}`), read the package root from the `hypo@hypomnema` installPath in `~/.claude/plugins/installed_plugins.json` rather than guessing from the cache layout.
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
|
-
node
|
|
19
|
+
node ${CLAUDE_PLUGIN_ROOT}/scripts/verify.mjs [--hypo-dir="<path>"] [--file=<path>]
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
Options:
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -115,7 +115,7 @@ Hooks run automatically at Claude Code lifecycle events. They are deployed to `~
|
|
|
115
115
|
| `hypo-personal-check` | PreCompact validation: lint blockers, uncommitted changes, missing session-log entries → block compact |
|
|
116
116
|
| `hypo-auto-stage` | After Write/Edit on a wiki path, run `git add` (skips paths matching `.hypoignore`) |
|
|
117
117
|
| `hypo-hot-rebuild` | At session stop, regenerate root `hot.md` from recent activity; emit growth metrics + cache for next SessionStart |
|
|
118
|
-
| `hypo-session-record` | At session stop, append `{session_id, transcript_path, recorded_at, cwd}` to `.cache/sessions/index.jsonl` (primary source for the observability audit) |
|
|
118
|
+
| `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) |
|
|
119
119
|
| `hypo-auto-commit` | At session stop, filter changed paths through `.hypoignore`, commit non-ignored changes, `git pull --no-rebase` + `git push` (silent fail on missing remote) |
|
|
120
120
|
| `hypo-cwd-change` | When working directory changes, re-resolve the active project and inject its `hot.md` |
|
|
121
121
|
| `hypo-file-watch` | Notify on external wiki edits so the in-session view stays consistent |
|
|
@@ -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 }`.
|
|
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` (PRAC-17) 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,6 +366,8 @@ 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
|
+
**PRAC-17 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
|
+
|
|
369
371
|
### Growth metrics (Lane B)
|
|
370
372
|
|
|
371
373
|
A separate, lightweight counter — distinct from the audit pipeline — runs at every Stop / SessionStart pair:
|
package/docs/CONTRIBUTING.md
CHANGED
|
@@ -222,69 +222,192 @@ Open the PR against `main`. Include:
|
|
|
222
222
|
- **What changed** — short summary
|
|
223
223
|
- **Why** — the user-visible motivation
|
|
224
224
|
- **Manual verification steps** — anything the test suite cannot cover
|
|
225
|
+
- **Changelog**: one English line plus one Korean line if the change is user-visible (see CHANGELOG conventions below)
|
|
225
226
|
- **Migration notes** — if upgrading existing installs needs special handling
|
|
226
227
|
|
|
227
228
|
---
|
|
228
229
|
|
|
230
|
+
## CHANGELOG conventions
|
|
231
|
+
|
|
232
|
+
`CHANGELOG.md` is the source of truth for release history. It follows a fixed section model, and contributors feed it through the `## Changelog` block in the PR template rather than editing `CHANGELOG.md` directly. The release collector and the maintainer assemble the final entries at release time.
|
|
233
|
+
|
|
234
|
+
### PR title vs. merge commit
|
|
235
|
+
|
|
236
|
+
- **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).
|
|
237
|
+
- **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.
|
|
238
|
+
- Internal tracker ids (`FEAT-`, `IMPR-`, `ISSUE-`, `PRAC-`) may appear in your local notes and in source or workflow comments (an internal reference, like an ADR anchor), but 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 --tag` gates the tag body; the migration keeps the CHANGELOG body clean.
|
|
239
|
+
|
|
240
|
+
### The `## Changelog` block
|
|
241
|
+
|
|
242
|
+
If a change is user-visible, fill the `## Changelog` block in the PR body with one English line and one Korean line:
|
|
243
|
+
|
|
244
|
+
```
|
|
245
|
+
## Changelog
|
|
246
|
+
|
|
247
|
+
- EN: Feedback pages accept an optional `failure_type` so recurring mistakes are visible.
|
|
248
|
+
- KO: 피드백 페이지에 선택적 `failure_type`를 달아 반복되는 실수를 집계로 볼 수 있습니다.
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
- Reference the PR by number only (`#123`). No internal tracker ids on this surface.
|
|
252
|
+
- No em dashes; use a colon, comma, or parentheses.
|
|
253
|
+
- Internal-only changes (a refactor with no user-visible effect, test-only, CI plumbing) write `None` and skip the lines.
|
|
254
|
+
|
|
255
|
+
### Section model
|
|
256
|
+
|
|
257
|
+
Each version block in `CHANGELOG.md` is ordered, top to bottom:
|
|
258
|
+
|
|
259
|
+
1. Optional `> [!IMPORTANT]` migration or breaking callout.
|
|
260
|
+
2. `### New Features`, `### Bug Fixes`, `### Chores`: present only when that version has a user-relevant entry of that kind. An empty section is omitted. Keep each entry to one compact line; split or trim anything that runs to multiple sentences. Describe only what a user sees: purely internal work (release tooling, contributor docs, a refactor with no user-visible effect) gets no prose entry and lives in the `### Changelog` index alone.
|
|
261
|
+
3. `### Changelog`: the PR-link index plus contributors. Always present. Language-neutral. It lists every merged PR, internal ones included.
|
|
262
|
+
|
|
263
|
+
At and after the v1.2.0 cutoff, each non-empty gated section (`New Features`, `Bug Fixes`, `Chores`) splits its content into `#### English` then `#### 한국어`, in that order:
|
|
264
|
+
|
|
265
|
+
```
|
|
266
|
+
### New Features
|
|
267
|
+
#### English
|
|
268
|
+
- Feedback pages accept an optional `failure_type`. (#141)
|
|
269
|
+
#### 한국어
|
|
270
|
+
- 피드백 페이지가 선택적 `failure_type`를 받습니다. (#141)
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
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.
|
|
274
|
+
|
|
275
|
+
The `### Changelog` index is language-neutral, one merged PR per line:
|
|
276
|
+
|
|
277
|
+
```
|
|
278
|
+
### Changelog
|
|
279
|
+
- #141 feedback failure_type classification
|
|
280
|
+
- #140 invalid-YAML lint guard
|
|
281
|
+
Contributors: @handle
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
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.
|
|
285
|
+
|
|
286
|
+
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.
|
|
287
|
+
|
|
288
|
+
### Classification
|
|
289
|
+
|
|
290
|
+
The section is decided by change kind, with the Conventional Commit type as the default and content as the override:
|
|
291
|
+
|
|
292
|
+
| Input | Section |
|
|
293
|
+
|---|---|
|
|
294
|
+
| `feat:` / `FEAT-` | New Features |
|
|
295
|
+
| `fix:` / `ISSUE-` | Bug Fixes |
|
|
296
|
+
| `chore:` / `refactor:` / `docs:` / `ci:` / `perf:` / `IMPR-` / `PRAC-` | Chores |
|
|
297
|
+
|
|
298
|
+
Chores is defined by kind, not by visibility: improvements (`IMPR`), refactors, and release or internal cleanup all land in Chores even when user-visible. The external changelog highlights only New Features and Bug Fixes; everything else is a Chore.
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
229
302
|
## Release process
|
|
230
303
|
|
|
231
304
|
Hypomnema uses semver. Releases are automated via `release.yml` on `v*` tag push.
|
|
232
305
|
|
|
233
306
|
### Cutting a release
|
|
234
307
|
|
|
235
|
-
|
|
308
|
+
The maintainer drives this checklist with a personal `/ship` command (it lives in
|
|
309
|
+
`~/.claude/commands/`, alongside `qa-before-ship` — it is maintainer tooling, not
|
|
310
|
+
a shipped plugin command, since an OSS user has no reason to release Hypomnema
|
|
311
|
+
itself). The sequence below is that same checklist, and is the authoritative
|
|
312
|
+
in-repo reference; follow it whether or not you have the `/ship` convenience
|
|
313
|
+
command.
|
|
314
|
+
|
|
315
|
+
Every Hypomnema release must carry Korean alongside the English body
|
|
236
316
|
in **both** the CHANGELOG section AND the git tag annotation. The release
|
|
237
|
-
workflow enforces this with `scripts/check-bilingual.mjs`; lightweight
|
|
238
|
-
a missing
|
|
317
|
+
workflow enforces this with `scripts/check-bilingual.mjs`; a lightweight tag, or
|
|
318
|
+
a gated CHANGELOG section missing its `#### 한국어` sub-block, will block `npm publish`. It also requires the
|
|
319
|
+
release version to appear in **both** `README.md` and `README.ko.md`
|
|
320
|
+
(`scripts/check-readme-version.mjs`) — the floor that stops the README reconcile
|
|
321
|
+
from being dropped.
|
|
322
|
+
|
|
323
|
+
Prerequisite: the changelog collector in step 2 reads merged-PR data through the GitHub CLI, so have `gh` installed and authenticated (`gh auth status`) before you start. It is a maintainer-only release script and is not shipped to npm.
|
|
239
324
|
|
|
240
325
|
```bash
|
|
241
326
|
# 1. Bump the version across package.json, plugin.json, marketplace.json,
|
|
242
327
|
# and templates/hypo-config.md. Takes a concrete semver (not patch/minor/major).
|
|
243
328
|
node scripts/bump-version.mjs <new-semver> # e.g. 1.2.2 or 1.3.0-rc.1
|
|
244
329
|
|
|
245
|
-
#
|
|
246
|
-
#
|
|
330
|
+
# 1b. bump-version does NOT touch package-lock.json — sync it so `npm ci` and the
|
|
331
|
+
# version-consistency gate stay green (the lock carries the version twice).
|
|
332
|
+
npm install --package-lock-only
|
|
333
|
+
|
|
334
|
+
# 2. Draft the new CHANGELOG.md section from the merged PRs' `## Changelog`
|
|
335
|
+
# blocks (see CHANGELOG conventions above). The collector prints a draft for
|
|
336
|
+
# the range since the last tag; paste it, then finalize the wording by hand,
|
|
337
|
+
# dropping any entry that is not user-relevant. --strict fails if any PR lacks
|
|
338
|
+
# a usable block.
|
|
339
|
+
node scripts/collect-changelog.mjs --strict # maintainer-only; not shipped to npm
|
|
340
|
+
# Each gated section (New Features / Bug Fixes / Chores) MUST carry both a
|
|
341
|
+
# "#### English" and a "#### 한국어" sub-block; check:bilingual enforces it.
|
|
247
342
|
$EDITOR CHANGELOG.md
|
|
248
343
|
|
|
249
|
-
# 3.
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
344
|
+
# 3. Reconcile BOTH READMEs — add a v<version> sentence to the rolling version
|
|
345
|
+
# narrative in README.md AND README.ko.md, and update the first-viewport
|
|
346
|
+
# "current release" pointer. (Dropped 3x historically; check:readme is the floor.)
|
|
347
|
+
$EDITOR README.md README.ko.md
|
|
348
|
+
|
|
349
|
+
# 4. Verify locally before tagging (the full gate set CI + prepublishOnly run)
|
|
350
|
+
npm test # unit suite
|
|
351
|
+
npm run lint # vault linter
|
|
352
|
+
npm run check:versions # all version-carrying files (incl. package-lock) agree
|
|
353
|
+
npm run check:bilingual # each gated CHANGELOG section has #### English + #### 한국어
|
|
354
|
+
npm run check:readme # v<version> present in BOTH READMEs
|
|
355
|
+
npm run smoke:plugin # plugin manifest + hooks/commands/skills load-valid
|
|
356
|
+
npm run smoke-pack # packed tarball installs and resolves
|
|
357
|
+
npm run check:tracker-ids # no private-tracker pointers leaked into shipped files
|
|
358
|
+
|
|
359
|
+
# 5. Commit — include EVERY file the bump touched, the READMEs, and the lockfile.
|
|
360
|
+
git add package.json package-lock.json .claude-plugin/ templates/hypo-config.md \
|
|
361
|
+
CHANGELOG.md README.md README.ko.md
|
|
254
362
|
git commit -m "chore: release v<version>"
|
|
363
|
+
# Then open the release PR, pass review + green CI, and squash-merge to main.
|
|
255
364
|
|
|
256
|
-
#
|
|
365
|
+
# 6. Tag the merge commit with an ANNOTATED tag, never a lightweight tag.
|
|
257
366
|
# Annotation body shape: English summary, then "---" on its own line,
|
|
258
|
-
# then a Korean summary block.
|
|
367
|
+
# then a Korean summary block. The GitHub Release republishes this body
|
|
368
|
+
# verbatim, so keep it on the same public surface as the CHANGELOG: PR
|
|
369
|
+
# numbers (#N) only, no internal tracker ids (check-tracker-ids --tag gates it).
|
|
259
370
|
git tag -a v<version> -m "$(cat <<'EOF'
|
|
260
|
-
Hypomnema v<version
|
|
371
|
+
Hypomnema v<version>: <one-line English summary>
|
|
261
372
|
|
|
262
|
-
<a few lines of English body
|
|
373
|
+
<a few lines of English body: what shipped, links, etc.>
|
|
263
374
|
|
|
264
375
|
---
|
|
265
376
|
|
|
266
|
-
Hypomnema v<version
|
|
377
|
+
Hypomnema v<version>: <한 줄 한글 요약>
|
|
267
378
|
|
|
268
379
|
<몇 줄의 한글 요약 본문.>
|
|
269
380
|
EOF
|
|
270
381
|
)"
|
|
271
382
|
|
|
272
|
-
#
|
|
383
|
+
# 7. Rehearse the release locally (same checks CI runs against the tag)
|
|
273
384
|
node scripts/check-bilingual.mjs --tag v<version>
|
|
385
|
+
node scripts/check-versions.mjs --tag v<version>
|
|
386
|
+
node scripts/check-tracker-ids.mjs --tag v<version> # no tracker ids leak into the tag body
|
|
387
|
+
npm publish --dry-run --access public # packs + prepublishOnly, NO registry PUT
|
|
274
388
|
|
|
275
|
-
#
|
|
276
|
-
|
|
389
|
+
# 8. Push the SPECIFIC tag alone — NOT --tags (that would push stale local tags
|
|
390
|
+
# and could trigger releases for versions you did not intend).
|
|
391
|
+
git push origin v<version>
|
|
277
392
|
```
|
|
278
393
|
|
|
279
394
|
The `release.yml` workflow then:
|
|
280
395
|
|
|
281
|
-
1. Verifies the tag matches
|
|
282
|
-
|
|
283
|
-
(`scripts/check-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
`
|
|
396
|
+
1. Verifies the tag matches the version in EVERY version-carrying file —
|
|
397
|
+
`package.json`, `package-lock.json`, `.claude-plugin/{plugin,marketplace}.json`,
|
|
398
|
+
and `templates/hypo-config.md` (`scripts/check-versions.mjs --tag`).
|
|
399
|
+
2. Smokes the plugin channel — manifest, `hooks/hooks.json` targets, and
|
|
400
|
+
command/skill component files (`scripts/smoke-plugin.mjs`).
|
|
401
|
+
3. Validates the CHANGELOG section AND the tag annotation are bilingual
|
|
402
|
+
(`scripts/check-bilingual.mjs`), and that the version is present in both
|
|
403
|
+
READMEs (`scripts/check-readme-version.mjs`).
|
|
404
|
+
4. Runs `npm test` and `npm run lint`.
|
|
405
|
+
5. Publishes to npm with `npm publish --access public --provenance` and creates
|
|
406
|
+
the GitHub Release from the tag body.
|
|
407
|
+
|
|
408
|
+
`NPM_TOKEN` must be set as a repository secret. After rotating it, run the
|
|
409
|
+
`release.yml` `workflow_dispatch` "publish credential pre-check" — it verifies the
|
|
410
|
+
token authenticates without publishing anything.
|
|
288
411
|
|
|
289
412
|
### Versioning rules
|
|
290
413
|
|
|
@@ -6,14 +6,10 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { spawnSync } from 'child_process';
|
|
9
|
-
import { HYPO_DIR,
|
|
10
|
-
|
|
11
|
-
function git(...args) {
|
|
12
|
-
return spawnSync('git', ['-C', HYPO_DIR, ...args], { encoding: 'utf-8', timeout: 30000 });
|
|
13
|
-
}
|
|
9
|
+
import { HYPO_DIR, syncRemote, commitWikiChanges } from './hypo-shared.mjs';
|
|
14
10
|
|
|
15
11
|
function hasRemote() {
|
|
16
|
-
const r =
|
|
12
|
+
const r = spawnSync('git', ['-C', HYPO_DIR, 'remote'], { encoding: 'utf-8', timeout: 30000 });
|
|
17
13
|
return (r.stdout || '').trim().length > 0;
|
|
18
14
|
}
|
|
19
15
|
|
|
@@ -28,12 +24,10 @@ if (!result.committed) {
|
|
|
28
24
|
|
|
29
25
|
if (hasRemote()) {
|
|
30
26
|
// pull/push failures must not stop the session, but they can no longer be
|
|
31
|
-
// swallowed silently —
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const push = git('push');
|
|
36
|
-
if (push.status !== 0) appendSyncFailure(HYPO_DIR, 'push', push.stderr || push.stdout);
|
|
27
|
+
// swallowed silently — syncRemote records each to .cache/sync-state.json and,
|
|
28
|
+
// on a merge conflict, aborts the merge so the tree is never left half-merged
|
|
29
|
+
// (FEAT-17 hardening). session-start + doctor surface the result next session.
|
|
30
|
+
syncRemote(HYPO_DIR);
|
|
37
31
|
}
|
|
38
32
|
|
|
39
33
|
console.log(JSON.stringify({ continue: true, suppressOutput: true }));
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import { existsSync, mkdirSync, appendFileSync } from 'fs';
|
|
14
14
|
import { dirname, join } from 'path';
|
|
15
|
+
import { hostname } from 'os';
|
|
15
16
|
import { HYPO_DIR } from './hypo-shared.mjs';
|
|
16
17
|
|
|
17
18
|
const INDEX_PATH = join(HYPO_DIR, '.cache', 'sessions', 'index.jsonl');
|
|
@@ -50,6 +51,10 @@ process.stdin.on('end', () => {
|
|
|
50
51
|
transcript_path: transcriptPath,
|
|
51
52
|
recorded_at: new Date().toISOString(),
|
|
52
53
|
cwd: payload.cwd || process.cwd(),
|
|
54
|
+
// PRAC-17: machine identity for multi-machine audit. index.jsonl lives
|
|
55
|
+
// under .cache/ (gitignored in a normal vault), so this is a LOCAL-only
|
|
56
|
+
// per-session record — accurate for every session, no sync/privacy cost.
|
|
57
|
+
device: hostname() || 'unknown',
|
|
53
58
|
};
|
|
54
59
|
appendFileSync(INDEX_PATH, JSON.stringify(entry) + '\n');
|
|
55
60
|
} catch (err) {
|
|
@@ -243,6 +243,13 @@ function syncStateNotice(pullOk) {
|
|
|
243
243
|
return '';
|
|
244
244
|
}
|
|
245
245
|
const last = entries[entries.length - 1];
|
|
246
|
+
if (last.op === 'conflict') {
|
|
247
|
+
return (
|
|
248
|
+
`[WIKI: remote diverged — auto-merge was aborted to protect your edits ` +
|
|
249
|
+
`(your local work is committed and safe; the other machine's version is on the remote). ` +
|
|
250
|
+
`Resolve manually: \`git -C ${HYPO_DIR} pull --no-rebase\`, fix conflicts, then push.]`
|
|
251
|
+
);
|
|
252
|
+
}
|
|
246
253
|
return `[WIKI: last sync failed: ${last.op || '?'} — ${last.error || 'unknown'}]`;
|
|
247
254
|
}
|
|
248
255
|
const GLOBAL_HOT = join(HYPO_DIR, 'hot.md');
|