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.
Files changed (52) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.ko.md +151 -153
  4. package/README.md +121 -123
  5. package/commands/audit.md +4 -4
  6. package/commands/crystallize.md +18 -5
  7. package/commands/doctor.md +3 -3
  8. package/commands/feedback.md +9 -3
  9. package/commands/graph.md +3 -3
  10. package/commands/ingest.md +6 -4
  11. package/commands/init.md +5 -3
  12. package/commands/lint.md +3 -3
  13. package/commands/query.md +3 -3
  14. package/commands/rename.md +4 -4
  15. package/commands/resume.md +4 -2
  16. package/commands/stats.md +3 -3
  17. package/commands/upgrade.md +4 -4
  18. package/commands/verify.md +3 -3
  19. package/docs/ARCHITECTURE.md +4 -2
  20. package/docs/CONTRIBUTING.md +148 -25
  21. package/hooks/hypo-auto-commit.mjs +6 -12
  22. package/hooks/hypo-session-record.mjs +5 -0
  23. package/hooks/hypo-session-start.mjs +7 -0
  24. package/hooks/hypo-shared.mjs +68 -1
  25. package/package.json +10 -2
  26. package/scripts/check-bilingual.mjs +49 -11
  27. package/scripts/check-readme-version.mjs +126 -0
  28. package/scripts/check-tracker-ids.mjs +60 -1
  29. package/scripts/check-versions.mjs +171 -0
  30. package/scripts/crystallize.mjs +49 -34
  31. package/scripts/doctor.mjs +13 -4
  32. package/scripts/feedback.mjs +68 -1
  33. package/scripts/graph.mjs +5 -32
  34. package/scripts/init.mjs +2 -1
  35. package/scripts/lib/changelog-classify.mjs +216 -0
  36. package/scripts/lib/check-bilingual.mjs +125 -22
  37. package/scripts/lib/check-tracker-ids.mjs +19 -0
  38. package/scripts/lib/failure-type.mjs +33 -0
  39. package/scripts/lib/frontmatter.mjs +23 -2
  40. package/scripts/lib/schema-vocab.mjs +35 -0
  41. package/scripts/lib/template-schema-version.mjs +21 -0
  42. package/scripts/lib/wikilink.mjs +156 -0
  43. package/scripts/lint.mjs +86 -47
  44. package/scripts/rename.mjs +6 -30
  45. package/scripts/stats.mjs +22 -2
  46. package/scripts/upgrade.mjs +11 -3
  47. package/scripts/weekly-report.mjs +9 -3
  48. package/skills/crystallize/SKILL.md +21 -1
  49. package/templates/SCHEMA.md +25 -1
  50. package/templates/hypo-config.md +1 -1
  51. package/scripts/bump-version.mjs +0 -63
  52. package/scripts/smoke-pack.mjs +0 -261
@@ -1,261 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * scripts/smoke-pack.mjs — pre-publish smoke test
4
- *
5
- * Runs `npm pack`, installs the resulting tarball into an isolated temp
6
- * directory, and exercises the installed CLI to verify the published
7
- * artifact actually works. Catches mistakes that local tests miss:
8
- * - `files:` / `.npmignore` excluding required assets
9
- * - `bin` entry pointing to a missing file
10
- * - runtime requires on paths not shipped in the tarball
11
- *
12
- * Usage:
13
- * node scripts/smoke-pack.mjs [--keep]
14
- *
15
- * --keep Leave the temp directory in place for inspection on success.
16
- */
17
-
18
- import { spawnSync } from 'node:child_process';
19
- import {
20
- mkdtempSync,
21
- rmSync,
22
- readFileSync,
23
- writeFileSync,
24
- existsSync,
25
- readdirSync,
26
- renameSync,
27
- } from 'node:fs';
28
- import { join } from 'node:path';
29
- import { tmpdir } from 'node:os';
30
- import { fileURLToPath } from 'node:url';
31
-
32
- const REPO = join(fileURLToPath(new URL('.', import.meta.url)), '..');
33
- const KEEP = process.argv.includes('--keep');
34
-
35
- // When this script runs inside `npm publish --dry-run` (the release workflow's
36
- // publish-credential pre-check), npm exports `npm_config_dry_run=true` into the
37
- // lifecycle environment. spawnSync inherits process.env, so the nested
38
- // `npm pack` below would ALSO run in dry-run mode — reporting a tarball
39
- // filename/size it never actually writes to disk — and the subsequent
40
- // `npm install <tarball>` then fails with ENOENT (npm maps errno -2 → exit 254).
41
- // Strip the flag so the nested npm commands always perform real writes,
42
- // matching the real tag-push publish job (which has no outer `--dry-run`). The
43
- // outer workflow stays dry-run and still skips the registry PUT.
44
- // (This was the precheck-254 root cause; the earlier "missing NODE_AUTH_TOKEN"
45
- // theory was wrong — it is file absence, not auth.)
46
- const npmEnv = { ...process.env };
47
- for (const key of Object.keys(npmEnv)) {
48
- if (key.toLowerCase().replaceAll('-', '_') === 'npm_config_dry_run') {
49
- delete npmEnv[key];
50
- }
51
- }
52
-
53
- const PKG = JSON.parse(readFileSync(join(REPO, 'package.json'), 'utf-8'));
54
- const PKG_NAME = PKG.name;
55
-
56
- function run(cmd, args, opts = {}) {
57
- const res = spawnSync(cmd, args, { stdio: 'pipe', encoding: 'utf-8', ...opts });
58
- if (res.status !== 0) {
59
- process.stderr.write(res.stdout || '');
60
- process.stderr.write(res.stderr || '');
61
- throw new Error(`${cmd} ${args.join(' ')} exited ${res.status}`);
62
- }
63
- return res;
64
- }
65
-
66
- function step(msg) {
67
- console.log(`\n▸ ${msg}`);
68
- }
69
-
70
- const work = mkdtempSync(join(tmpdir(), 'hypo-smoke-'));
71
- const sandboxHome = join(work, 'home');
72
- const installRoot = join(work, 'install');
73
- const wikiDir = join(work, 'wiki');
74
- let cleanupOk = false;
75
-
76
- try {
77
- // Capture pre-commit hook contents (if any) BEFORE pack so we can prove
78
- // `npm pack` didn't mutate it. The `prepare` lifecycle script runs during
79
- // `npm pack` and could theoretically touch .git/hooks/pre-commit; the
80
- // installer's CI/lifecycle guards must prevent that.
81
- const preCommitPath = join(REPO, '.git', 'hooks', 'pre-commit');
82
- const preCommitBefore = existsSync(preCommitPath) ? readFileSync(preCommitPath, 'utf-8') : null;
83
-
84
- step('npm pack');
85
- const pack = run('npm', ['pack', '--json'], { cwd: REPO, env: npmEnv });
86
- const meta = JSON.parse(pack.stdout)[0];
87
- const tarball = join(REPO, meta.filename);
88
- console.log(` → ${meta.filename} (${meta.size} bytes, ${meta.entryCount} entries)`);
89
-
90
- step(`install into ${installRoot}`);
91
- run('mkdir', ['-p', installRoot]);
92
- writeFileSync(
93
- join(installRoot, 'package.json'),
94
- JSON.stringify({
95
- name: 'hypo-smoke-host',
96
- version: '0.0.0',
97
- private: true,
98
- }) + '\n',
99
- );
100
- // No `--silent`: run() only prints npm's stdout/stderr on a non-zero exit, so
101
- // a real nested-install failure must not be muted (the precheck-254 error was
102
- // masked by `--silent` for two release cycles).
103
- run('npm', ['install', '--no-audit', '--no-fund', tarball], { cwd: installRoot, env: npmEnv });
104
-
105
- // Move tarball into the work dir so it's not left in the repo.
106
- renameSync(tarball, join(work, meta.filename));
107
-
108
- const cliBin = join(installRoot, 'node_modules', '.bin', 'hypomnema');
109
- if (!existsSync(cliBin)) {
110
- throw new Error(`bin not installed at ${cliBin}`);
111
- }
112
-
113
- step('hypomnema --help (installed)');
114
- const help = run(cliBin, ['--help']);
115
- if (!help.stdout.includes('Usage: hypomnema')) {
116
- throw new Error('--help output did not match expected banner');
117
- }
118
-
119
- step('hypomnema init --dry-run (installed)');
120
- const dryEnv = { ...process.env, HOME: sandboxHome, HYPO_DIR: wikiDir };
121
- const dry = run(
122
- cliBin,
123
- [
124
- 'init',
125
- '--dry-run',
126
- `--hypo-dir=${wikiDir}`,
127
- '--no-hooks',
128
- '--no-commands',
129
- '--no-git-init',
130
- '--no-shell',
131
- ],
132
- { env: dryEnv },
133
- );
134
- if (!/dry[\s-]?run/i.test(dry.stdout + dry.stderr)) {
135
- console.log(dry.stdout);
136
- throw new Error('init --dry-run did not announce dry-run mode');
137
- }
138
-
139
- step('verify shipped assets are present');
140
- const required = [
141
- 'scripts/init.mjs',
142
- 'scripts/upgrade.mjs',
143
- 'hooks',
144
- 'commands',
145
- 'templates',
146
- 'README.md',
147
- ];
148
- const pkgRoot = join(installRoot, 'node_modules', PKG_NAME);
149
- for (const rel of required) {
150
- if (!existsSync(join(pkgRoot, rel))) {
151
- throw new Error(`shipped tarball missing: ${rel}`);
152
- }
153
- }
154
-
155
- step('hypomnema feedback-sync (installed) — check/write idempotency + bootstrap');
156
- // Seed a fixture wiki + claude-home and exercise the feedback-sync surface
157
- // through the installed CLI (proves the subcommand is routed + shipped and the
158
- // Phase D bootstrap helper runs end-to-end). --check exits 1 on a fresh
159
- // (un-written) projection, so use spawnSync (run() throws on non-zero).
160
- const fbWiki = join(work, 'fb-wiki');
161
- const fbHome = join(work, 'fb-home');
162
- const fbProject = 'smoke';
163
- run('mkdir', ['-p', join(fbWiki, 'pages', 'feedback')]);
164
- run('mkdir', ['-p', join(fbHome, 'projects', fbProject, 'memory')]);
165
- writeFileSync(
166
- join(fbWiki, 'hypo-config.md'),
167
- '---\ntitle: config\ntype: reference\n---\n# config\n',
168
- );
169
- writeFileSync(
170
- join(fbWiki, 'pages', 'feedback', 'smoke-rule.md'),
171
- [
172
- '---',
173
- 'title: Smoke Rule',
174
- 'type: feedback',
175
- 'status: active',
176
- 'scope: global',
177
- 'tier: L1',
178
- 'targets: [project-memory, claude-learned]',
179
- 'sensitivity: public',
180
- 'priority: 4',
181
- 'memory_summary: smoke rule for the packed CLI',
182
- 'global_summary: always smoke-test the packed CLI',
183
- 'promote_to_global: true',
184
- 'reason: catch packaging regressions',
185
- 'source: session:2026-05-21',
186
- 'updated: 2026-05-21',
187
- '---',
188
- 'body',
189
- '',
190
- ].join('\n'),
191
- );
192
- writeFileSync(
193
- join(fbHome, 'CLAUDE.md'),
194
- '# Global\n<learned_behaviors>\n- [2026-05-20] legacy hand rule — 이유: bootstrap source\n</learned_behaviors>\n',
195
- );
196
- writeFileSync(join(fbHome, 'projects', fbProject, 'memory', 'MEMORY.md'), '# Memory Index\n');
197
-
198
- const fbArgs = [`--hypo-dir=${fbWiki}`, `--claude-home=${fbHome}`, `--project-id=${fbProject}`];
199
- const fbRun = (extra) =>
200
- spawnSync(cliBin, ['feedback-sync', ...extra, ...fbArgs], { encoding: 'utf-8' });
201
-
202
- const fbCheck1 = fbRun(['--check']);
203
- if (fbCheck1.status !== 1) {
204
- throw new Error(
205
- `feedback-sync --check on fresh projection expected exit 1, got ${fbCheck1.status}`,
206
- );
207
- }
208
- const fbWrite = fbRun(['--write']);
209
- if (fbWrite.status !== 0) {
210
- throw new Error(
211
- `feedback-sync --write expected exit 0, got ${fbWrite.status}: ${fbWrite.stderr}`,
212
- );
213
- }
214
- if (
215
- !readFileSync(join(fbHome, 'CLAUDE.md'), 'utf-8').includes(
216
- 'HYPO:FEEDBACK-SYNC:START source=smoke-rule',
217
- )
218
- ) {
219
- throw new Error('feedback-sync --write did not project the managed block into CLAUDE.md');
220
- }
221
- const fbCheck2 = fbRun(['--check']);
222
- if (fbCheck2.status !== 0) {
223
- throw new Error(
224
- `feedback-sync --check after --write expected clean exit 0, got ${fbCheck2.status}`,
225
- );
226
- }
227
- const fbBootstrap = fbRun(['--bootstrap', '--dry-run']);
228
- if (fbBootstrap.status !== 0 || !/would create draft/.test(fbBootstrap.stderr)) {
229
- throw new Error(
230
- `feedback-sync --bootstrap --dry-run did not announce drafts (exit ${fbBootstrap.status})`,
231
- );
232
- }
233
-
234
- step('verify pre-commit hook was not mutated by npm pack');
235
- const preCommitAfter = existsSync(preCommitPath) ? readFileSync(preCommitPath, 'utf-8') : null;
236
- if (preCommitBefore !== preCommitAfter) {
237
- throw new Error(
238
- 'npm pack mutated .git/hooks/pre-commit — the prepare lifecycle ' +
239
- 'guard (npm_command=pack) is not firing.',
240
- );
241
- }
242
-
243
- console.log('\n✓ smoke-pack passed');
244
- cleanupOk = true;
245
- } catch (err) {
246
- console.error(`\n✗ smoke-pack failed: ${err.message}`);
247
- console.error(` work dir preserved: ${work}`);
248
- process.exitCode = 1;
249
- } finally {
250
- if (cleanupOk && !KEEP) {
251
- rmSync(work, { recursive: true, force: true });
252
- } else if (KEEP) {
253
- console.log(`\nwork dir kept: ${work}`);
254
- }
255
- // Sweep any stray tarballs left in the repo root from a crashed run.
256
- for (const f of readdirSync(REPO)) {
257
- if (f.startsWith(`${PKG_NAME}-`) && f.endsWith('.tgz')) {
258
- rmSync(join(REPO, f), { force: true });
259
- }
260
- }
261
- }