hypomnema 1.6.2 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.ko.md +39 -14
  4. package/README.md +39 -14
  5. package/commands/capture.md +8 -6
  6. package/commands/crystallize.md +39 -20
  7. package/docs/ARCHITECTURE.md +49 -14
  8. package/docs/CONTRIBUTING.md +31 -29
  9. package/hooks/base-store.mjs +265 -0
  10. package/hooks/hooks.json +7 -1
  11. package/hooks/hypo-auto-minimal-crystallize.mjs +63 -20
  12. package/hooks/hypo-auto-stage.mjs +30 -0
  13. package/hooks/hypo-cwd-change.mjs +31 -2
  14. package/hooks/hypo-file-watch.mjs +21 -2
  15. package/hooks/hypo-first-prompt.mjs +19 -3
  16. package/hooks/hypo-lookup.mjs +86 -29
  17. package/hooks/hypo-personal-check.mjs +24 -3
  18. package/hooks/hypo-session-record.mjs +2 -3
  19. package/hooks/hypo-session-start.mjs +89 -7
  20. package/hooks/hypo-shared.mjs +904 -128
  21. package/hooks/proposal-store.mjs +513 -0
  22. package/package.json +40 -14
  23. package/scripts/capture.mjs +556 -37
  24. package/scripts/crystallize.mjs +639 -108
  25. package/scripts/doctor.mjs +304 -9
  26. package/scripts/feedback-sync.mjs +515 -44
  27. package/scripts/graph.mjs +9 -2
  28. package/scripts/init.mjs +230 -34
  29. package/scripts/lib/extensions.mjs +656 -1
  30. package/scripts/lib/hypo-ignore.mjs +54 -6
  31. package/scripts/lib/hypo-root.mjs +56 -6
  32. package/scripts/lib/page-usage.mjs +15 -2
  33. package/scripts/lib/pkg-json.mjs +40 -0
  34. package/scripts/lib/plugin-detect.mjs +96 -6
  35. package/scripts/lib/wd-match.mjs +23 -5
  36. package/scripts/lib/wikilink.mjs +32 -6
  37. package/scripts/lint.mjs +20 -4
  38. package/scripts/proposal.mjs +1032 -0
  39. package/scripts/query.mjs +25 -4
  40. package/scripts/resume.mjs +34 -12
  41. package/scripts/stats.mjs +28 -8
  42. package/scripts/uninstall.mjs +141 -6
  43. package/scripts/upgrade.mjs +197 -15
  44. package/skills/crystallize/SKILL.md +44 -7
  45. package/skills/debate/SKILL.md +88 -0
  46. package/skills/debate/references/orchestration-patterns.md +83 -0
  47. package/templates/.hyposcanignore +10 -0
  48. package/templates/SCHEMA.md +12 -0
  49. package/templates/gitignore +5 -0
  50. package/templates/hypo-config.md +1 -1
  51. package/templates/hypo-guide.md +6 -0
  52. package/scripts/.gitkeep +0 -0
  53. package/scripts/check-bilingual.mjs +0 -153
  54. package/scripts/check-readme-version.mjs +0 -126
  55. package/scripts/check-tracker-ids.mjs +0 -426
  56. package/scripts/check-versions.mjs +0 -171
  57. package/scripts/install-git-hooks.mjs +0 -293
  58. package/scripts/lib/changelog-classify.mjs +0 -216
  59. package/scripts/lib/check-bilingual.mjs +0 -244
  60. package/scripts/lib/check-tracker-ids.mjs +0 -217
  61. package/scripts/lib/pre-commit-format.mjs +0 -251
  62. package/scripts/pre-commit-format.mjs +0 -198
@@ -2,6 +2,7 @@
2
2
  title: Wiki Operations Guide
3
3
  type: reference
4
4
  updated: YYYY-MM-DD
5
+ version: 1
5
6
  tags: [wiki, guide, operations]
6
7
  ---
7
8
 
@@ -108,6 +109,11 @@ Ask: *"이 작업이 마무리되었나요? 세션을 정리(crystallize)할까
108
109
  committing the files, run `/hypo:crystallize` (`--mark-session-closed --session-id=<id> --transcript-path=<path>`).
109
110
  Both writers gate the marker on the SAME `precompactGateStatus` as `/compact`,
110
111
  so the marker only lands when step 6 would print **"Compact-ready"**.
112
+ `--session-id` is not optional on the apply path. Before it writes anything, the
113
+ apply reads that session's transcript for evidence the **user** asked to close, and
114
+ refuses the whole close (exit 1, nothing on disk, nothing committed) when the id is
115
+ missing, resolves to no transcript, or the user never asked. A refusal means the
116
+ close should not happen: ask, then re-run.
111
117
 
112
118
  Skip session close for: single bug fix, single-file edit, Q&A only.
113
119
 
package/scripts/.gitkeep DELETED
File without changes
@@ -1,153 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * check-bilingual.mjs — CLI gate for the bilingual release-doc rule.
4
- *
5
- * Modes:
6
- * --changelog [version] Validate CHANGELOG.md section for given version.
7
- * Defaults to package.json's "version" field.
8
- * Wired into npm `prepublishOnly` so publishes fail
9
- * when the Korean summary is missing.
10
- *
11
- * --tag <ref> Validate annotated tag body for given ref.
12
- * Wired into .github/workflows/release.yml so a
13
- * lightweight tag or a missing Korean section
14
- * blocks the npm publish step.
15
- *
16
- * Exits 0 on pass, 1 on fail with a stderr diagnostic.
17
- */
18
-
19
- import { readFileSync } from 'fs';
20
- import { spawnSync } from 'child_process';
21
- import { fileURLToPath } from 'url';
22
- import { dirname, join } from 'path';
23
- import {
24
- validateChangelog,
25
- validateTagBody,
26
- listChangelogVersions,
27
- meetsKoreanCutoff,
28
- } from './lib/check-bilingual.mjs';
29
-
30
- const __dirname = dirname(fileURLToPath(import.meta.url));
31
- const REPO_ROOT = join(__dirname, '..');
32
-
33
- const RULE_REF =
34
- 'Rule source: CLAUDE.md learned_behaviors (release-doc-bilingual, 2026-05-24). ' +
35
- 'OSS Hypomnema ships must carry English body + Korean summary in both CHANGELOG section and git tag annotation.';
36
-
37
- function fail(msg) {
38
- process.stderr.write(`[check-bilingual] FAIL: ${msg}\n${RULE_REF}\n`);
39
- process.exit(1);
40
- }
41
-
42
- function ok(msg) {
43
- process.stdout.write(`[check-bilingual] OK: ${msg}\n`);
44
- process.exit(0);
45
- }
46
-
47
- function usage(exitCode) {
48
- process.stdout.write(
49
- `Usage:\n` +
50
- ` node scripts/check-bilingual.mjs --changelog [version]\n` +
51
- ` Validate one CHANGELOG.md "## [<version>]" section (section model).\n` +
52
- ` Default version: package.json.\n` +
53
- ` node scripts/check-bilingual.mjs --changelog --all\n` +
54
- ` Validate EVERY documented version (Korean enforced at >= 1.2.0,\n` +
55
- ` English-only versions below the cutoff pass).\n` +
56
- ` node scripts/check-bilingual.mjs --tag <ref>\n` +
57
- ` Validate annotated tag body (lightweight tags are rejected).\n`,
58
- );
59
- process.exit(exitCode);
60
- }
61
-
62
- const args = process.argv.slice(2);
63
- const mode = args[0];
64
- const wantAll = args.includes('--all');
65
-
66
- if (mode === '--help' || mode === '-h') usage(0);
67
- if (!mode) usage(1);
68
-
69
- if (mode === '--changelog') {
70
- let content;
71
- try {
72
- content = readFileSync(join(REPO_ROOT, 'CHANGELOG.md'), 'utf-8');
73
- } catch (err) {
74
- fail(`cannot read CHANGELOG.md: ${err.message}`);
75
- }
76
-
77
- if (wantAll) {
78
- // Validate every documented version. Korean is enforced only at/after the
79
- // cutoff; pre-cutoff versions pass on English presence (format.md §9). This
80
- // is the migration gate — it must not green-pass a half-migrated file.
81
- const versions = listChangelogVersions(content);
82
- if (versions.length === 0) fail('no "## [<version>]" sections found in CHANGELOG.md');
83
- const failures = [];
84
- let enforced = 0;
85
- for (const v of versions) {
86
- const r = validateChangelog(content, v);
87
- if (meetsKoreanCutoff(v)) enforced++;
88
- if (!r.ok) failures.push(` [${v}] ${r.reason}`);
89
- }
90
- if (failures.length) {
91
- fail(`${failures.length}/${versions.length} version(s) failed:\n${failures.join('\n')}`);
92
- }
93
- ok(
94
- `CHANGELOG.md --all: ${versions.length} versions conform ` +
95
- `(${enforced} Korean-enforced at >= 1.2.0, ${versions.length - enforced} English-only below cutoff).`,
96
- );
97
- }
98
-
99
- // single-version path (kept: prepublishOnly and release.yml call --changelog
100
- // with no version, defaulting to package.json's).
101
- let version = args[1] && !args[1].startsWith('--') ? args[1] : undefined;
102
- if (!version) {
103
- try {
104
- const pkg = JSON.parse(readFileSync(join(REPO_ROOT, 'package.json'), 'utf-8'));
105
- version = pkg.version;
106
- } catch (err) {
107
- fail(`cannot read package.json: ${err.message}`);
108
- }
109
- }
110
- if (!version) fail('no version (arg empty, package.json has no "version" field)');
111
-
112
- const result = validateChangelog(content, version);
113
- if (!result.ok) fail(result.reason);
114
- if (result.koreanExempt) {
115
- ok(`CHANGELOG.md [${version}] — pre-cutoff version, English-only (Korean exempt).`);
116
- } else {
117
- ok(`CHANGELOG.md [${version}] — ${result.hangulCount} Hangul chars across "#### 한국어" sub-blocks.`);
118
- }
119
- } else if (mode === '--tag') {
120
- const ref = args[1];
121
- if (!ref) fail('--tag requires a ref argument (e.g. v1.2.1)');
122
-
123
- // Reject lightweight tags. `git rev-parse <ref>^{tag}` succeeds ONLY for
124
- // annotated tags. For lightweight tags the ^{tag} peel fails because there
125
- // is no tag object — the ref points straight at a commit.
126
- const tagObj = spawnSync('git', ['rev-parse', '--verify', '--quiet', `${ref}^{tag}`], {
127
- encoding: 'utf-8',
128
- cwd: REPO_ROOT,
129
- });
130
- if (tagObj.status !== 0) {
131
- const exists = spawnSync('git', ['rev-parse', '--verify', '--quiet', ref], {
132
- encoding: 'utf-8',
133
- cwd: REPO_ROOT,
134
- });
135
- if (exists.status !== 0) fail(`tag ${ref} not found`);
136
- fail(
137
- `tag ${ref} is a lightweight tag, not annotated. ` +
138
- `Re-create with: git tag -a ${ref} -m "<English body>\n\n---\n\n<Korean summary>"`,
139
- );
140
- }
141
-
142
- const tagBody = spawnSync('git', ['tag', '-l', `--format=%(contents)`, ref], {
143
- encoding: 'utf-8',
144
- cwd: REPO_ROOT,
145
- });
146
- if (tagBody.status !== 0) fail(`failed to read tag ${ref}: ${tagBody.stderr}`);
147
-
148
- const result = validateTagBody(tagBody.stdout || '');
149
- if (!result.ok) fail(`tag ${ref} — ${result.reason}`);
150
- ok(`tag ${ref} annotation — ${result.hangulCount} Hangul chars after last "---" separator.`);
151
- } else {
152
- fail(`unknown mode: ${mode}. Use --changelog or --tag (see --help).`);
153
- }
@@ -1,126 +0,0 @@
1
- #!/usr/bin/env node
2
- // check-readme-version.mjs — assert the release version string is present in BOTH
3
- // README.md and README.ko.md. This is the machine FLOOR for the README-reconcile
4
- // step that was dropped three times (v1.2 / v1.3.0 / v1.3.1): a publish of vX.Y.Z
5
- // must not go out unless vX.Y.Z is written into both READMEs' version narrative.
6
- //
7
- // SCOPE (honest): this catches the GROSS drop — shipping a version whose sentence
8
- // was never added to the README at all. It does NOT verify the narrative is
9
- // accurate or that the first-viewport "current release" pointer was updated; that
10
- // stays a judgment step owned by the release checklist (docs/CONTRIBUTING.md
11
- // "Cutting a release"). README/CHANGELOG carry
12
- // prose version HISTORY (every past vX.Y.Z), so check-versions.mjs intentionally
13
- // excludes them — this script is their complement, not a duplicate.
14
- //
15
- // Usage:
16
- // node scripts/check-readme-version.mjs # version from package.json
17
- // node scripts/check-readme-version.mjs --version 1.4.0 # explicit version
18
- // node scripts/check-readme-version.mjs --root <dir> # point at a fixture (tests)
19
- // node scripts/check-readme-version.mjs --json
20
- //
21
- // Exit 0 = both READMEs mention the version. Exit 1 = missing in one/both, or unreadable.
22
-
23
- import { readFileSync } from 'fs';
24
- import { join, dirname } from 'path';
25
- import { fileURLToPath } from 'url';
26
-
27
- function parseArgs(argv) {
28
- const args = { root: null, version: null, json: false };
29
- for (let i = 0; i < argv.length; i++) {
30
- const a = argv[i];
31
- if (a.startsWith('--root=')) args.root = a.slice(7);
32
- else if (a === '--root') args.root = argv[++i];
33
- else if (a.startsWith('--version=')) args.version = a.slice(10);
34
- else if (a === '--version') args.version = argv[++i];
35
- else if (a === '--json') args.json = true;
36
- }
37
- return args;
38
- }
39
-
40
- const REPO_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
41
-
42
- const README_FILES = ['README.md', 'README.ko.md'];
43
-
44
- // Build a boundary-aware matcher so a version does not spuriously match inside a
45
- // LONGER version token yet still matches at a sentence boundary. The checked
46
- // version must be a standalone token, NOT a prefix of another version:
47
- // - "1.3.40", "v11.3.4" → reject (digit continuation)
48
- // - "1.3.4.5" → reject (dot-then-digit continuation)
49
- // - "1.3.4-rc.1", "1.3.4+b" → reject (a stable check must NOT pass on a
50
- // prerelease/build string — that defeats the floor)
51
- // - "1.3.0-rc.1a", "1.3.0-rc.10", "1.3.0-rc.1.alpha" → a prerelease check must
52
- // reject these continuation tokens too
53
- // - "shipped v1.3.4." / "v1.3.4**" / "(v1.3.4)" → match (sentence/markup boundary)
54
- // Leading: no digit/dot immediately before (an optional "v" is fine — it is
55
- // neither). Trailing: not a semver-continuation char ([0-9A-Za-z+-]) and not a
56
- // dot-then-alphanumeric (".5" / ".alpha" continuation); a sentence period
57
- // (dot followed by space / punctuation / end) is allowed.
58
- function versionPresent(text, version) {
59
- const escaped = version.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
60
- return new RegExp(`(?<![\\d.])${escaped}(?![\\dA-Za-z+-])(?!\\.[\\dA-Za-z])`).test(text);
61
- }
62
-
63
- function checkReadmeVersions(root, version) {
64
- const results = README_FILES.map((file) => {
65
- const abs = join(root, file);
66
- try {
67
- const text = readFileSync(abs, 'utf-8');
68
- return { file, present: versionPresent(text, version) };
69
- } catch (err) {
70
- return { file, present: false, error: err?.message ?? String(err) };
71
- }
72
- });
73
- const ok = results.every((r) => r.present);
74
- return { ok, version, results };
75
- }
76
-
77
- function main() {
78
- const args = parseArgs(process.argv.slice(2));
79
- const root = args.root || REPO_ROOT;
80
-
81
- let version = args.version;
82
- if (!version) {
83
- try {
84
- version = JSON.parse(readFileSync(join(root, 'package.json'), 'utf-8')).version;
85
- } catch (err) {
86
- const msg = `cannot read package.json for version: ${err?.message ?? err}`;
87
- if (args.json) console.log(JSON.stringify({ ok: false, error: msg }, null, 2));
88
- else console.error(`✗ ${msg}`);
89
- process.exit(1);
90
- }
91
- }
92
- if (typeof version !== 'string' || !version) {
93
- const msg = 'no version (—version empty and package.json has no "version" field)';
94
- if (args.json) console.log(JSON.stringify({ ok: false, error: msg }, null, 2));
95
- else console.error(`✗ ${msg}`);
96
- process.exit(1);
97
- }
98
-
99
- const report = checkReadmeVersions(root, version);
100
-
101
- if (args.json) {
102
- console.log(JSON.stringify(report, null, 2));
103
- } else {
104
- for (const r of report.results) {
105
- const mark = r.error ? `ERROR: ${r.error}` : r.present ? 'found' : 'MISSING';
106
- console.log(` ${r.file.padEnd(13)} ${mark}`);
107
- }
108
- if (report.ok) {
109
- console.log(`\n✓ both READMEs mention version ${version}`);
110
- } else {
111
- const missing = report.results
112
- .filter((r) => !r.present)
113
- .map((r) => (r.error ? `${r.file} (unreadable)` : r.file))
114
- .join(', ');
115
- console.error(
116
- `\n✗ version ${version} missing from: ${missing}\n` +
117
- ` Reconcile the version narrative in both READMEs before releasing ` +
118
- `(the README reconcile step — see docs/CONTRIBUTING.md "Cutting a release").`,
119
- );
120
- }
121
- }
122
-
123
- process.exit(report.ok ? 0 : 1);
124
- }
125
-
126
- main();