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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.ko.md +39 -14
- package/README.md +39 -14
- package/commands/capture.md +8 -6
- package/commands/crystallize.md +39 -20
- package/docs/ARCHITECTURE.md +49 -14
- package/docs/CONTRIBUTING.md +31 -29
- package/hooks/base-store.mjs +265 -0
- package/hooks/hooks.json +7 -1
- package/hooks/hypo-auto-minimal-crystallize.mjs +63 -20
- package/hooks/hypo-auto-stage.mjs +30 -0
- package/hooks/hypo-cwd-change.mjs +31 -2
- package/hooks/hypo-file-watch.mjs +21 -2
- package/hooks/hypo-first-prompt.mjs +19 -3
- package/hooks/hypo-lookup.mjs +86 -29
- package/hooks/hypo-personal-check.mjs +24 -3
- package/hooks/hypo-session-record.mjs +2 -3
- package/hooks/hypo-session-start.mjs +89 -7
- package/hooks/hypo-shared.mjs +904 -128
- package/hooks/proposal-store.mjs +513 -0
- package/package.json +40 -14
- package/scripts/capture.mjs +556 -37
- package/scripts/crystallize.mjs +639 -108
- package/scripts/doctor.mjs +304 -9
- package/scripts/feedback-sync.mjs +515 -44
- package/scripts/graph.mjs +9 -2
- package/scripts/init.mjs +230 -34
- package/scripts/lib/extensions.mjs +656 -1
- package/scripts/lib/hypo-ignore.mjs +54 -6
- package/scripts/lib/hypo-root.mjs +56 -6
- package/scripts/lib/page-usage.mjs +15 -2
- package/scripts/lib/pkg-json.mjs +40 -0
- package/scripts/lib/plugin-detect.mjs +96 -6
- package/scripts/lib/wd-match.mjs +23 -5
- package/scripts/lib/wikilink.mjs +32 -6
- package/scripts/lint.mjs +20 -4
- package/scripts/proposal.mjs +1032 -0
- package/scripts/query.mjs +25 -4
- package/scripts/resume.mjs +34 -12
- package/scripts/stats.mjs +28 -8
- package/scripts/uninstall.mjs +141 -6
- package/scripts/upgrade.mjs +197 -15
- package/skills/crystallize/SKILL.md +44 -7
- package/skills/debate/SKILL.md +88 -0
- package/skills/debate/references/orchestration-patterns.md +83 -0
- package/templates/.hyposcanignore +10 -0
- package/templates/SCHEMA.md +12 -0
- package/templates/gitignore +5 -0
- package/templates/hypo-config.md +1 -1
- package/templates/hypo-guide.md +6 -0
- package/scripts/.gitkeep +0 -0
- package/scripts/check-bilingual.mjs +0 -153
- package/scripts/check-readme-version.mjs +0 -126
- package/scripts/check-tracker-ids.mjs +0 -426
- package/scripts/check-versions.mjs +0 -171
- package/scripts/install-git-hooks.mjs +0 -293
- package/scripts/lib/changelog-classify.mjs +0 -216
- package/scripts/lib/check-bilingual.mjs +0 -244
- package/scripts/lib/check-tracker-ids.mjs +0 -217
- package/scripts/lib/pre-commit-format.mjs +0 -251
- package/scripts/pre-commit-format.mjs +0 -198
package/templates/hypo-guide.md
CHANGED
|
@@ -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();
|