hypomnema 1.3.1 → 1.3.3
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 +2 -2
- package/.claude-plugin/plugin.json +2 -2
- package/README.ko.md +11 -10
- package/README.md +11 -10
- package/commands/audit.md +1 -1
- package/commands/crystallize.md +11 -11
- package/commands/doctor.md +1 -1
- package/commands/feedback.md +2 -2
- package/commands/graph.md +1 -1
- package/commands/ingest.md +1 -1
- package/commands/init.md +1 -1
- package/commands/lint.md +1 -1
- package/commands/query.md +1 -1
- package/commands/rename.md +79 -0
- package/commands/resume.md +2 -2
- package/commands/stats.md +1 -1
- package/commands/uninstall.md +1 -1
- package/commands/upgrade.md +1 -1
- package/commands/verify.md +1 -1
- package/docs/ARCHITECTURE.md +3 -3
- package/docs/CONTRIBUTING.md +2 -2
- package/hooks/hypo-auto-commit.mjs +8 -24
- package/hooks/hypo-auto-minimal-crystallize.mjs +57 -11
- package/hooks/hypo-compact-guard.mjs +6 -3
- package/hooks/hypo-cwd-change.mjs +1 -1
- package/hooks/hypo-first-prompt.mjs +2 -2
- package/hooks/hypo-hot-rebuild.mjs +14 -1
- package/hooks/hypo-personal-check.mjs +91 -179
- package/hooks/hypo-pre-commit.mjs +1 -1
- package/hooks/hypo-session-end.mjs +1 -1
- package/hooks/hypo-session-start.mjs +7 -7
- package/hooks/hypo-shared.mjs +1082 -89
- package/hooks/hypo-web-fetch-ingest.mjs +2 -2
- package/hooks/version-check-fetch.mjs +2 -8
- package/hooks/version-check.mjs +18 -0
- package/package.json +3 -2
- package/scripts/check-tracker-ids.mjs +329 -0
- package/scripts/crystallize.mjs +322 -110
- package/scripts/doctor.mjs +6 -9
- package/scripts/feedback-sync.mjs +1 -1
- package/scripts/init.mjs +1 -1
- package/scripts/install-git-hooks.mjs +75 -40
- package/scripts/lib/check-tracker-ids.mjs +140 -0
- package/scripts/lib/design-history-stale.mjs +59 -14
- package/scripts/lib/extensions.mjs +4 -4
- package/scripts/lib/fix-manifest.mjs +2 -2
- package/scripts/lib/fix-status-verify.mjs +5 -4
- package/scripts/lib/plugin-detect.mjs +15 -6
- package/scripts/lib/project-create.mjs +1 -1
- package/scripts/lint.mjs +63 -8
- package/scripts/rename.mjs +836 -0
- package/scripts/resume.mjs +75 -19
- package/scripts/uninstall.mjs +1 -1
- package/scripts/upgrade.mjs +26 -25
- package/skills/crystallize/SKILL.md +12 -9
- package/skills/graph/SKILL.md +1 -1
- package/skills/ingest/SKILL.md +1 -1
- package/skills/lint/SKILL.md +1 -1
- package/skills/query/SKILL.md +1 -1
- package/skills/verify/SKILL.md +1 -1
- package/templates/SCHEMA.md +2 -2
- package/templates/hypo-config.md +2 -2
- package/templates/hypo-guide.md +22 -1
- package/templates/hypo-help.md +1 -0
- package/templates/projects/_template/index.md +1 -1
package/scripts/resume.mjs
CHANGED
|
@@ -18,7 +18,11 @@
|
|
|
18
18
|
* ~/Documents/{hypomnema,wiki,notes}) that contains
|
|
19
19
|
* a hypo-config.md marker.
|
|
20
20
|
* 3. else the default ~/hypomnema.
|
|
21
|
-
* --project=<name> Project name
|
|
21
|
+
* --project=<name> Project name. When omitted, resolveActiveProject()
|
|
22
|
+
* prefers the project whose working_dir contains the
|
|
23
|
+
* current directory (cwd-first), and only falls back to
|
|
24
|
+
* the most recently active hot.md row when nothing under
|
|
25
|
+
* cwd matches.
|
|
22
26
|
* --json Output as JSON
|
|
23
27
|
*/
|
|
24
28
|
|
|
@@ -58,7 +62,8 @@ function parseFrontmatterField(content, key) {
|
|
|
58
62
|
|
|
59
63
|
// Among `slugs`, return the one whose projects/<slug>/index.md `working_dir`
|
|
60
64
|
// is the LONGEST prefix of cwd (so /repo/sub wins over /repo). Returns null
|
|
61
|
-
// when cwd is falsy or matches none.
|
|
65
|
+
// when cwd is falsy or matches none. resume gives this authority OVER recency
|
|
66
|
+
// (ADR 0044): a cwd↔working_dir match wins regardless of which row is newest.
|
|
62
67
|
function pickByCwd(hypoDir, slugs, cwd) {
|
|
63
68
|
if (!cwd) return null;
|
|
64
69
|
let best = null;
|
|
@@ -78,6 +83,33 @@ function pickByCwd(hypoDir, slugs, cwd) {
|
|
|
78
83
|
return best;
|
|
79
84
|
}
|
|
80
85
|
|
|
86
|
+
// When cwd is set but no project's working_dir matches it, resume falls back to
|
|
87
|
+
// recency silently — the user lands in an unrelated project with no clue why.
|
|
88
|
+
// Emit a one-line stderr diagnostic (stdout `Project:`/`--json` contract is untouched)
|
|
89
|
+
// naming why each candidate failed: missing index.md, missing working_dir, or a
|
|
90
|
+
// working_dir that simply doesn't contain cwd. Logic mirrors pickByCwd's lookup.
|
|
91
|
+
function warnCwdFallback(hypoDir, slugs, cwd) {
|
|
92
|
+
// No cwd, or no candidate rows at all (fresh-init / no real project): there is
|
|
93
|
+
// nothing to "fall back to most-recent" toward, so stay silent — the caller
|
|
94
|
+
// surfaces the real "no active project found" error instead.
|
|
95
|
+
if (!cwd || slugs.length === 0) return;
|
|
96
|
+
const reasons = [];
|
|
97
|
+
for (const slug of slugs) {
|
|
98
|
+
const indexPath = join(hypoDir, 'projects', slug, 'index.md');
|
|
99
|
+
if (!existsSync(indexPath)) {
|
|
100
|
+
reasons.push(`${slug} (no index.md)`);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const wd = parseFrontmatterField(readFileSync(indexPath, 'utf-8'), 'working_dir');
|
|
104
|
+
if (!wd) reasons.push(`${slug} (no working_dir)`);
|
|
105
|
+
// else: has working_dir but didn't contain cwd — expected, not flagged.
|
|
106
|
+
}
|
|
107
|
+
const detail = reasons.length ? ` Candidates missing cwd metadata: ${reasons.join(', ')}.` : '';
|
|
108
|
+
process.stderr.write(
|
|
109
|
+
`note: cwd "${cwd}" matched no project working_dir; falling back to most-recent.${detail}\n`,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
81
113
|
function resolveActiveProject(hypoDir, cwd = null) {
|
|
82
114
|
const hotPath = join(hypoDir, 'hot.md');
|
|
83
115
|
if (!existsSync(hotPath)) return null;
|
|
@@ -93,38 +125,62 @@ function resolveActiveProject(hypoDir, cwd = null) {
|
|
|
93
125
|
),
|
|
94
126
|
].map((m) => ({ name: m[1].trim(), date: m[2] || '', slug: m[3] }));
|
|
95
127
|
if (wikiRows.length > 0) {
|
|
96
|
-
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
128
|
+
// cwd-first (ADR 0044): a cwd↔working_dir match wins over recency, across
|
|
129
|
+
// ALL rows (not just a same-date tie). The user is physically in that
|
|
130
|
+
// project, so cwd is a stronger intent signal than "some other project was
|
|
131
|
+
// touched more recently". This reverses the earlier tie-breaker-only
|
|
132
|
+
// semantics now that resume=cwd-positive (ADR 0043). Tradeoff: a stale cwd
|
|
133
|
+
// match can mask a genuinely newer project; `--project` overrides. close
|
|
134
|
+
// callers pass null → recency path below (resume=cwd-positive / close=no-pick).
|
|
135
|
+
if (cwd) {
|
|
103
136
|
const picked = pickByCwd(
|
|
104
137
|
hypoDir,
|
|
105
|
-
|
|
138
|
+
wikiRows.map((r) => r.slug),
|
|
106
139
|
cwd,
|
|
107
140
|
);
|
|
108
141
|
if (picked) return picked;
|
|
109
142
|
}
|
|
143
|
+
// No cwd match → most recent by date (stable-sort keeps the first table row
|
|
144
|
+
// on a tie, the legacy behavior).
|
|
145
|
+
if (cwd)
|
|
146
|
+
warnCwdFallback(
|
|
147
|
+
hypoDir,
|
|
148
|
+
wikiRows.map((r) => r.slug),
|
|
149
|
+
cwd,
|
|
150
|
+
);
|
|
151
|
+
wikiRows.sort((a, b) => b.date.localeCompare(a.date));
|
|
110
152
|
return wikiRows[0].slug;
|
|
111
153
|
}
|
|
112
154
|
// Legacy markdown-link rows: | [name](projects/name/...) | ...
|
|
113
|
-
const
|
|
114
|
-
if (
|
|
155
|
+
const mdSlugs = [...content.matchAll(/\|\s*\[([^\]]+)\]\(projects\/([^/)]+)/g)].map((m) => m[2]);
|
|
156
|
+
if (mdSlugs.length > 0) {
|
|
157
|
+
if (cwd) {
|
|
158
|
+
const picked = pickByCwd(hypoDir, mdSlugs, cwd);
|
|
159
|
+
if (picked) return picked;
|
|
160
|
+
warnCwdFallback(hypoDir, mdSlugs, cwd);
|
|
161
|
+
}
|
|
162
|
+
return mdSlugs[0]; // legacy: first table row
|
|
163
|
+
}
|
|
115
164
|
|
|
116
|
-
// fallback: most recently modified
|
|
165
|
+
// fallback: a cwd-matched project, else the most recently modified one with a
|
|
166
|
+
// session-state.md. (mtime is only a heuristic once hot.md can't name a
|
|
167
|
+
// project — an explicit working_dir match is safer, so cwd-first here too.)
|
|
117
168
|
const projectsDir = join(hypoDir, 'projects');
|
|
118
169
|
if (!existsSync(projectsDir)) return null;
|
|
119
170
|
|
|
171
|
+
// Skip the scaffold project init.mjs writes — it isn't a real active project.
|
|
172
|
+
const candidates = readdirSync(projectsDir).filter(
|
|
173
|
+
(p) => p !== '_template' && existsSync(join(projectsDir, p, 'session-state.md')),
|
|
174
|
+
);
|
|
175
|
+
if (cwd) {
|
|
176
|
+
const picked = pickByCwd(hypoDir, candidates, cwd);
|
|
177
|
+
if (picked) return picked;
|
|
178
|
+
warnCwdFallback(hypoDir, candidates, cwd);
|
|
179
|
+
}
|
|
120
180
|
let latest = null;
|
|
121
181
|
let latestMtime = 0;
|
|
122
|
-
for (const p of
|
|
123
|
-
|
|
124
|
-
if (p === '_template') continue;
|
|
125
|
-
const ssPath = join(projectsDir, p, 'session-state.md');
|
|
126
|
-
if (!existsSync(ssPath)) continue;
|
|
127
|
-
const mtime = statSync(ssPath).mtimeMs;
|
|
182
|
+
for (const p of candidates) {
|
|
183
|
+
const mtime = statSync(join(projectsDir, p, 'session-state.md')).mtimeMs;
|
|
128
184
|
if (mtime > latestMtime) {
|
|
129
185
|
latestMtime = mtime;
|
|
130
186
|
latest = p;
|
package/scripts/uninstall.mjs
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* --force-extensions Remove user-modified extension files (hypo-ext-*) instead of preserving them
|
|
16
16
|
* --hooks-dir=<path> Override Claude hooks directory (default: ~/.claude/hooks)
|
|
17
17
|
*
|
|
18
|
-
* Extensions (ADR 0024
|
|
18
|
+
* Extensions (ADR 0024): hypo-ext-* hard-copies under
|
|
19
19
|
* ~/.claude/{hooks,commands,skills,agents}/ and ~/.codex/{hooks,commands}/ (with
|
|
20
20
|
* --codex) are removed when their on-disk SHA matches the recorded one in
|
|
21
21
|
* ~/.claude/hypo-pkg.json#extensions.<target>. User-modified copies are preserved
|
package/scripts/upgrade.mjs
CHANGED
|
@@ -16,12 +16,12 @@
|
|
|
16
16
|
* --force-extensions Overwrite user-modified / conflicting extension copies (creates .bak)
|
|
17
17
|
* --codex Mirror to ~/.codex/{hooks,commands,settings.json} — core
|
|
18
18
|
* hook drift/apply, settings.json registration, the
|
|
19
|
-
* wiki-*.mjs → hypo-*.mjs rename migration
|
|
20
|
-
* the user-extensions companion sync (E4
|
|
19
|
+
* wiki-*.mjs → hypo-*.mjs rename migration, and
|
|
20
|
+
* the user-extensions companion sync (E4).
|
|
21
21
|
* --json Output results as JSON
|
|
22
22
|
* --allow-downgrade Override the guard that refuses to overwrite a NEWER
|
|
23
23
|
* active install with an older package (ADR 0038)
|
|
24
|
-
* --allow-dual-install Override the
|
|
24
|
+
* --allow-dual-install Override the dual-install guard: register the Claude core
|
|
25
25
|
* surface even though the Hypomnema plugin is also enabled
|
|
26
26
|
* (knowingly accept the double-registration risk)
|
|
27
27
|
*/
|
|
@@ -412,7 +412,7 @@ function applyHookNameMigration(oldRefs, settingsPath, hooksDir) {
|
|
|
412
412
|
if (applied.length > 0) {
|
|
413
413
|
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
414
414
|
// Copy renamed hook files to the target hooks dir (~/.claude/hooks or
|
|
415
|
-
// ~/.codex/hooks per the caller —
|
|
415
|
+
// ~/.codex/hooks per the caller — codex mirror).
|
|
416
416
|
for (const [oldName, newName] of Object.entries(HOOK_RENAMES)) {
|
|
417
417
|
const oldPath = join(hooksDir, oldName);
|
|
418
418
|
const newPath = join(hooksDir, newName);
|
|
@@ -761,7 +761,7 @@ function applyCommands(commandResults, force) {
|
|
|
761
761
|
return applied;
|
|
762
762
|
}
|
|
763
763
|
|
|
764
|
-
//
|
|
764
|
+
// In plugin mode `applyCommands` is skipped (no command copy), but the
|
|
765
765
|
// runtime still needs hypo-pkg.json to resolve PKG_ROOT for lint/feedback scripts
|
|
766
766
|
// (hooks/hypo-shared.mjs → hypo-personal-check). Write minimal metadata pointing
|
|
767
767
|
// at the plugin's package root, preserving any existing fields (e.g. `extensions`)
|
|
@@ -797,7 +797,7 @@ const claudeSettingsPath = join(HOME, '.claude', 'settings.json');
|
|
|
797
797
|
const codexHooksDir = join(HOME, '.codex', 'hooks');
|
|
798
798
|
const codexSettingsPath = join(HOME, '.codex', 'settings.json');
|
|
799
799
|
|
|
800
|
-
//
|
|
800
|
+
// When `/hypo:upgrade` runs as the Claude Code PLUGIN, the 15 core hooks
|
|
801
801
|
// and 14 slash commands are provided by the plugin's hooks.json + commands/
|
|
802
802
|
// (auto-wired by Claude Code), NOT copied into ~/.claude/. The manual/npm health
|
|
803
803
|
// check below would then report all of them "missing" and recommend `--apply`,
|
|
@@ -806,20 +806,21 @@ const codexSettingsPath = join(HOME, '.codex', 'settings.json');
|
|
|
806
806
|
// every hook fires TWICE. The decisive signal: the plugin command runs the
|
|
807
807
|
// PLUGIN's upgrade.mjs, so PKG_ROOT lives under ~/.claude/plugins/. (A manual/npm
|
|
808
808
|
// upgrade.mjs run while the plugin is ALSO enabled is a different failure mode —
|
|
809
|
-
// dual install —
|
|
809
|
+
// dual install — handled by the guard below.)
|
|
810
810
|
// Match the Claude plugin cache shape specifically (`~/.claude/plugins/…`), NOT a
|
|
811
811
|
// generic `/plugins/` substring — this flag now GATES install behavior, so a
|
|
812
812
|
// legitimate npm/dev checkout under some unrelated `…/plugins/…` path must not be
|
|
813
813
|
// misclassified and silently stop managing its hooks. (detectChannel's broad
|
|
814
814
|
// `/plugins/` test is fine for the notifier's display-only use, but too loose here.)
|
|
815
815
|
const pluginMode = PKG_ROOT.replace(/\\/g, '/').includes('/.claude/plugins/');
|
|
816
|
-
//
|
|
816
|
+
// Dual install: the OTHER way the same double-registration can happen.
|
|
817
817
|
// Here the MANUAL/npm upgrade.mjs is running (pluginMode=false), but the Hypomnema
|
|
818
818
|
// plugin is ALSO enabled in ~/.claude/settings.json — so the plugin loader already
|
|
819
819
|
// provides the core hooks/commands/settings. A manual/npm `--apply` would copy and
|
|
820
820
|
// register them on top, and every core hook fires twice. The detector is fail-open
|
|
821
821
|
// (see lib/plugin-detect.mjs): a false positive would wrongly alter a legitimate
|
|
822
|
-
// npm-only user's upgrade, so it only fires on an exact `
|
|
822
|
+
// npm-only user's upgrade, so it only fires on an exact `hypo@<mp>: true` (or the
|
|
823
|
+
// legacy `hypomnema@<mp>: true`, matched across the plugin-rename migration window).
|
|
823
824
|
const hypomnemaPluginEnabled = !pluginMode && isHypomnemaPluginEnabled(claudeSettingsPath);
|
|
824
825
|
const dualInstallCoreConflict = hypomnemaPluginEnabled;
|
|
825
826
|
// Surface policy: the Claude core surface (hooks/settings/commands/hook-name
|
|
@@ -864,7 +865,7 @@ const extCheck = syncExtensions({
|
|
|
864
865
|
force: args.forceExtensions,
|
|
865
866
|
});
|
|
866
867
|
|
|
867
|
-
// E4
|
|
868
|
+
// E4: --codex mirrors the extensions sync into ~/.codex (hooks + commands
|
|
868
869
|
// only; skills/agents skipped with a notice). The per-target SHA map lives in the
|
|
869
870
|
// same ~/.claude/hypo-pkg.json under extensions.codex, so pkgPath is unchanged.
|
|
870
871
|
const extCodexSettingsPath = codexSettingsPath;
|
|
@@ -897,7 +898,7 @@ const invalidSettingsCodex = settingsCodex
|
|
|
897
898
|
? settingsCodex.some((s) => s.status === 'invalid-json')
|
|
898
899
|
: false;
|
|
899
900
|
const schemaDrift = schema.bump !== 'none' && schema.bump !== 'unknown' && schema.bump !== 'ahead';
|
|
900
|
-
//
|
|
901
|
+
// Dual-install: when core is skipped, hypo-pkg.json is deliberately left
|
|
901
902
|
// pointing at the PLUGIN's package root (preserved identity), so checkPkgJson()
|
|
902
903
|
// reports it 'stale' relative to this npm/manual PKG_ROOT. That mismatch is
|
|
903
904
|
// INTENTIONAL — `--apply` will not (and must not) rewrite it — so it must not
|
|
@@ -958,7 +959,7 @@ if (args.apply) {
|
|
|
958
959
|
migrationPath = writeMigrationReport(args.hypoDir, schema.installed, schema.current, {
|
|
959
960
|
// Use the core-skipped predicate, not raw pluginMode: in a dual-install skip
|
|
960
961
|
// the core surface is plugin-owned too, so the report must not claim the
|
|
961
|
-
// core hooks/settings were applied
|
|
962
|
+
// core hooks/settings were applied.
|
|
962
963
|
pluginMode: !managesClaudeCore,
|
|
963
964
|
});
|
|
964
965
|
}
|
|
@@ -976,7 +977,7 @@ if (args.apply) {
|
|
|
976
977
|
appliedCommands = applyCommands(commands, args.forceCommands);
|
|
977
978
|
appliedPkgJson = true;
|
|
978
979
|
} else if (pluginMode) {
|
|
979
|
-
//
|
|
980
|
+
// Plugin mode: the plugin loader owns the core hooks/commands and
|
|
980
981
|
// settings.json wiring — copying them here would double-register. Skip those,
|
|
981
982
|
// but STILL write minimal package metadata so the runtime can resolve PKG_ROOT
|
|
982
983
|
// for lint/feedback scripts (hooks/hypo-shared.mjs → hypo-personal-check). The
|
|
@@ -984,7 +985,7 @@ if (args.apply) {
|
|
|
984
985
|
// is the plugin's own path here, so this metadata is authoritative.
|
|
985
986
|
appliedPkgJson = writePluginModeMetadata();
|
|
986
987
|
} else {
|
|
987
|
-
//
|
|
988
|
+
// Dual-install skip: a manual/npm run while the plugin is enabled. We
|
|
988
989
|
// skip the core surface (the plugin owns it), but — unlike true plugin mode —
|
|
989
990
|
// PKG_ROOT here is the npm/manual path while the ACTIVE runtime hooks are the
|
|
990
991
|
// PLUGIN's. Rewriting a VALID hypo-pkg.json.pkgRoot to this npm path would
|
|
@@ -1029,7 +1030,7 @@ if (args.apply) {
|
|
|
1029
1030
|
apply: true,
|
|
1030
1031
|
force: args.forceExtensions,
|
|
1031
1032
|
});
|
|
1032
|
-
// E4
|
|
1033
|
+
// E4: codex apply runs AFTER the claude apply so it reads the freshly
|
|
1033
1034
|
// written hypo-pkg.json and merges extensions.codex alongside extensions.claude
|
|
1034
1035
|
// (the per-target spread in syncExtensions preserves the other target's map).
|
|
1035
1036
|
if (args.codex) {
|
|
@@ -1089,7 +1090,7 @@ if (args.json) {
|
|
|
1089
1090
|
JSON.stringify(
|
|
1090
1091
|
{
|
|
1091
1092
|
pluginMode,
|
|
1092
|
-
//
|
|
1093
|
+
// Dual-install signals.
|
|
1093
1094
|
hypomnemaPluginEnabled,
|
|
1094
1095
|
dualInstallCoreConflict,
|
|
1095
1096
|
coreManagedBy: managesClaudeCore ? 'self' : pluginMode ? 'plugin' : 'plugin-enabled',
|
|
@@ -1138,7 +1139,7 @@ if (args.json) {
|
|
|
1138
1139
|
// Human-readable report
|
|
1139
1140
|
const lines = [];
|
|
1140
1141
|
|
|
1141
|
-
//
|
|
1142
|
+
// Lead with the plugin-mode banner so the user understands why the core
|
|
1142
1143
|
// hook/command/settings sections read "managed by plugin" and that `--apply` will
|
|
1143
1144
|
// NOT touch them (only vault-side migrations + package metadata).
|
|
1144
1145
|
if (pluginMode) {
|
|
@@ -1155,7 +1156,7 @@ if (pluginMode) {
|
|
|
1155
1156
|
);
|
|
1156
1157
|
}
|
|
1157
1158
|
|
|
1158
|
-
//
|
|
1159
|
+
// Dual install: a manual/npm upgrade.mjs is running while the Hypomnema plugin is ALSO
|
|
1159
1160
|
// enabled — a dual install. Lead with a loud banner: the core surface is owned by
|
|
1160
1161
|
// the plugin and is intentionally skipped, so `--apply` will not double-register.
|
|
1161
1162
|
if (dualSkip) {
|
|
@@ -1202,7 +1203,7 @@ if (schema.bump === 'none') {
|
|
|
1202
1203
|
);
|
|
1203
1204
|
}
|
|
1204
1205
|
|
|
1205
|
-
// Hook files (target-aware so --codex can mirror the same block
|
|
1206
|
+
// Hook files (target-aware so --codex can mirror the same block).
|
|
1206
1207
|
function pushHookSummary(hookList, label, targetPath) {
|
|
1207
1208
|
const colHook = `Hook files${label}`.padEnd(20);
|
|
1208
1209
|
const up = hookList.filter((h) => h.status === 'up-to-date').length;
|
|
@@ -1235,7 +1236,7 @@ if (managesClaudeCore) {
|
|
|
1235
1236
|
}
|
|
1236
1237
|
if (hooksCodex) pushHookSummary(hooksCodex, ' (codex)', '~/.codex/hooks/');
|
|
1237
1238
|
|
|
1238
|
-
// settings.json registrations (target-aware mirror
|
|
1239
|
+
// settings.json registrations (target-aware mirror).
|
|
1239
1240
|
function pushSettingsSummary(sList, label, invalidFlag) {
|
|
1240
1241
|
const colS = `settings.json${label}`.padEnd(20);
|
|
1241
1242
|
const reg = sList.filter((s) => s.status === 'registered').length;
|
|
@@ -1262,7 +1263,7 @@ if (settingsCodex) pushSettingsSummary(settingsCodex, ' (codex)', invalidSetting
|
|
|
1262
1263
|
|
|
1263
1264
|
// Package metadata
|
|
1264
1265
|
if (dualSkip && pkgJson.status === 'stale') {
|
|
1265
|
-
//
|
|
1266
|
+
// Dual-install: the 'stale' here is the preserved plugin identity (pkgRoot points at
|
|
1266
1267
|
// the plugin, not this npm/manual copy). That is intentional — not actionable.
|
|
1267
1268
|
lines.push(
|
|
1268
1269
|
`✓ Package metadata hypo-pkg.json plugin-owned (preserved — not rewritten in a dual install)`,
|
|
@@ -1386,7 +1387,7 @@ function pushExtSummary(check, label) {
|
|
|
1386
1387
|
for (const c of check.conflicts) lines.push(` ✗ ${c.file} [${c.action} — left untouched]`);
|
|
1387
1388
|
for (const d of check.drifts) lines.push(` ⚠ ${d.file} [drift — left untouched]`);
|
|
1388
1389
|
}
|
|
1389
|
-
// E3
|
|
1390
|
+
// E3: a hard conflict blocks install (exit 1, even under --apply); drift is
|
|
1390
1391
|
// resolvable advisory. Emit the spec'd WIKI messages so the user knows the recovery.
|
|
1391
1392
|
if (nConflicts > 0) {
|
|
1392
1393
|
lines.push(' [WIKI: existing file conflicts. Backup and retry, or use --force-extensions]');
|
|
@@ -1502,11 +1503,11 @@ const totalDrift =
|
|
|
1502
1503
|
extCheck.actions.filter(
|
|
1503
1504
|
(a) => a.action === 'create' || a.action === 'update' || a.action === 'force-update',
|
|
1504
1505
|
).length +
|
|
1505
|
-
// E3
|
|
1506
|
+
// E3: unresolved drift/conflict is pending work too — without these the
|
|
1506
1507
|
// summary printed "up to date" while the exit code was 1.
|
|
1507
1508
|
extCheck.conflicts.length +
|
|
1508
1509
|
extCheck.drifts.length +
|
|
1509
|
-
// E4
|
|
1510
|
+
// E4: codex-target pending work counts identically (same message/exit
|
|
1510
1511
|
// consistency the E3 review caught — a codex conflict must not read "up to date").
|
|
1511
1512
|
(extCheckCodex
|
|
1512
1513
|
? extCheckCodex.actions.filter(
|
|
@@ -1547,7 +1548,7 @@ if (totalDrift === 0) {
|
|
|
1547
1548
|
|
|
1548
1549
|
console.log(lines.join('\n'));
|
|
1549
1550
|
|
|
1550
|
-
// E3
|
|
1551
|
+
// E3: a hard extension conflict blocks even under --apply (unlike ordinary
|
|
1551
1552
|
// drift, which only fails check mode). --force-extensions clears the resolvable
|
|
1552
1553
|
// cases; an unfollowable symlink/non-regular dest still counts and stays exit 1.
|
|
1553
1554
|
const extBlocked = extCheck.conflicts.length > 0 || (extCheckCodex?.conflicts.length ?? 0) > 0;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Close a session (steps 1
|
|
2
|
+
description: Close a session by capturing what happened into the wiki (steps 1-6), and on request consolidate scattered notes into stable pages (steps 7-11). Use when the user explicitly signals session end, asks to save or crystallize the session, or before a /compact. Task completion alone is not a close signal.
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
You are running `/hypo:crystallize`. The command serves two modes (spec §5.2.7 / §8.3):
|
|
@@ -7,11 +7,11 @@ You are running `/hypo:crystallize`. The command serves two modes (spec §5.2.7
|
|
|
7
7
|
1. **Session close (steps 1~6)** — gate the 5 mandatory memory files plus open-questions (conditional) so `/compact` can pass.
|
|
8
8
|
2. **Synthesis (steps 7~11)** — surface tag clusters, orphan pages, and drafts that are ready to consolidate.
|
|
9
9
|
|
|
10
|
-
When invoked
|
|
10
|
+
When invoked to close a session — via an explicit close signal ("세션 종료", "wrap up"), an accepted proactive-offer [세션 마무리], or `/compact` — run the session-close checklist first. Task completion alone does not put you in close mode. The synthesis scan only runs after close is confirmed and the user agrees.
|
|
11
11
|
|
|
12
12
|
## What this does
|
|
13
13
|
|
|
14
|
-
- **Close mode**: walks the checklist (session-state, project hot.md, root hot.md, session-log, open-questions(변경 시), log.md) plus a lint step, then writes via `crystallize.mjs --apply-session-close --payload=<path>` — which runs the lint gate automatically, **scoped to the files it writes** (debt elsewhere is a non-blocking notice). `--check-session-close`
|
|
14
|
+
- **Close mode**: walks the checklist (session-state, project hot.md, root hot.md, session-log, open-questions(변경 시), log.md) plus a lint step, then writes via `crystallize.mjs --apply-session-close --payload=<path>` — which runs the lint gate automatically, **scoped to the files it writes** (debt elsewhere is a non-blocking notice). `--check-session-close` is a read-only dry-run of the **full** PreCompact gate (ADR 0046) — close files + scoped lint + design-history + feedback projection — sharing one function (`precompactGateStatus`) with the gate. A green check means no gate blocker needs a human fix, so it is the signal to declare the session closed (pass `--transcript-path` to widen the lint scope to this session's edited files exactly as the interactive hook does). It is not a hard guarantee: the live `/compact` can still differ on a context-≥70% prompt, `HYPO_SKIP_GATE`, or a transcript-scoped lint error the check did not see.
|
|
15
15
|
- **Synthesis mode**: finds tag clusters (≥ N pages), orphan pages (no outbound `[[wikilinks]]`), and draft / stub pages, then guides consolidation into `pages/syntheses/<topic>.md` with back-links and `index.md` updates.
|
|
16
16
|
|
|
17
17
|
---
|
|
@@ -50,8 +50,8 @@ If `/hypo:crystallize` was invoked as a session-close action, run through this c
|
|
|
50
50
|
Surface each of these four to the user first. Every one is **advisory** (ADR 0029 identity guard): the user confirms or declines, and none performs an automatic action, writes a file on its own, or bypasses the mandatory gate.
|
|
51
51
|
|
|
52
52
|
- **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를 건너뛸까요?"* 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.
|
|
53
|
-
- **ADR-candidate check (#41)** — Did this session make an architectural or design decision (a new pattern, a tradeoff, a convention)? If yes, ask whether it warrants an ADR and capture that intent in the session-log entry. If nothing rose to ADR level, record `ADR 없음 — <one-line reason>` in that same session-log entry. **Never auto-write an ADR file** — the session-log note is the only action here.
|
|
54
|
-
- **design-history staleness check (#42)** —
|
|
53
|
+
- **ADR-candidate check (#41)** — Did this session make an architectural or design decision (a new pattern, a tradeoff, a convention)? If yes, ask whether it warrants an ADR and capture that intent in the session-log entry. If nothing rose to ADR level, you may record the literal marker `ADR 없음 — <one-line reason>` in that same session-log entry — but gate it on #42's bar, not this one: the marker is machine-read and W8 treats a session-log entry carrying `ADR 없음` (and no ADR reference) as a *no-design* session, excluding it from the design-history staleness check. So write `ADR 없음` only when the session had no design change at all. If it had a sub-ADR design shift (background / tradeoff / differentiation), append to design-history (#42a) instead — writing the marker there would suppress the W8 nudge that shift needs. **Never auto-write an ADR file** — the session-log note is the only action here.
|
|
54
|
+
- **design-history staleness check (#42)** — Two branches, so a stale W8 never blocks a clean close: (a) if this session changed design decisions that `projects/<name>/design-history.md` does not yet reflect — including background / tradeoff / differentiation shifts that are below ADR level but still belong in the ledger — recommend appending to it now (W8 flags this mechanically; an active-project W8 hard-blocks at PreCompact — append before you commit, not after the gate fires). (b) only if this session made **no** design change at all does the `ADR 없음` marker from #41 exempt the entry from W8 — do **not** touch design-history. Caution: `ADR 없음` means "no design change," which is a stricter bar than "no ADR-level decision." A session with a sub-ADR design shift should take branch (a) and append; writing `ADR 없음` there would suppress the W8 nudge it actually needs. If the file does not exist, skip silently — do **not** create it just for this check. Never auto-update it.
|
|
55
55
|
- **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.
|
|
56
56
|
|
|
57
57
|
When uncertain, surface the question rather than skip it. None of the four blocks the close or writes on its own.
|
|
@@ -59,7 +59,7 @@ When uncertain, surface the question rather than skip it. None of the four block
|
|
|
59
59
|
1. **session-state.md** — update `projects/<name>/session-state.md` with the next tasks list (what to tackle first next time).
|
|
60
60
|
2. **hot.md (project)** — update `projects/<name>/hot.md` with a session snapshot: what changed and decisions made. Keep under 500 words. Do not put next-step tasks here; those belong in session-state.md.
|
|
61
61
|
3. **hot.md (root)** — update `<wiki-root>/hot.md` active-projects pointer table: set the `Last Session` date for this project to today.
|
|
62
|
-
4. **session-log** — append a session entry to `projects/<name>/session-log/YYYY-MM.md` (
|
|
62
|
+
4. **session-log** — append a session entry to `projects/<name>/session-log/YYYY-MM-DD.md` (daily shard; the apply path creates today's file with seeded frontmatter if it does not exist yet).
|
|
63
63
|
5. **open-questions** — only if `pages/open-questions.md` exists and questions were raised or resolved this session: move resolved ones out; add newly raised ones. Skip if unchanged.
|
|
64
64
|
6. **log.md** — append a `session` entry to `<wiki-root>/log.md`.
|
|
65
65
|
|
|
@@ -69,9 +69,12 @@ After completing the checklist, verify it before reporting:
|
|
|
69
69
|
node <package-root>/scripts/crystallize.mjs --check-session-close [--hypo-dir="<path>"]
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
-
This runs the
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
This runs the **full** PreCompact gate via the shared `precompactGateStatus`
|
|
73
|
+
(ADR 0046): close files (`missing` / `stale`) plus lint blockers, stale
|
|
74
|
+
design-history, and feedback projection over-cap/conflict. Fix every `✗` it
|
|
75
|
+
reports and re-run until it prints **"Compact-ready"** — that is the signal the
|
|
76
|
+
session is closed. A close-files-only pass is not enough; the real `/compact`
|
|
77
|
+
also blocks on those other checks.
|
|
75
78
|
|
|
76
79
|
Once it passes, report each item with ✓ and ask: "Session closed. Would you like to also run knowledge synthesis now, or stop here?"
|
|
77
80
|
|
package/skills/graph/SKILL.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Generate a wikilink dependency graph from wiki pages
|
|
2
|
+
description: Generate a wikilink dependency graph from wiki pages (json, mermaid, or dot). Use when the user asks to visualize wiki structure, find orphan or hub pages, or map how notes link together.
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
You are running `/hypo:graph`. Build a wikilink dependency graph from all pages under `pages/` and `projects/` and output it in the requested format.
|
package/skills/ingest/SKILL.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Add
|
|
2
|
+
description: Add an external source (URL, doc, article, command output) to the wiki and synthesize a citable summary page. Use when the user shares a source to capture, asks to ingest something, or wants reliable external knowledge saved for reuse.
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
You are running `/hypo:ingest`. Add a new source document to `sources/` and create (or update) its corresponding `source-summary` page under `pages/`.
|
package/skills/lint/SKILL.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Lint wiki pages for frontmatter and broken wikilinks
|
|
2
|
+
description: Lint wiki pages for frontmatter errors and broken wikilinks. Use when the user asks to check or validate the wiki health, before a commit, or after bulk edits or renames.
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
You are running `/hypo:lint`. Validate all wiki pages for frontmatter correctness and broken `[[wikilink]]` references.
|
package/skills/query/SKILL.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Search wiki
|
|
2
|
+
description: Search the wiki by keyword and synthesize an answer from the relevant pages. Use when the user asks what the wiki knows about a topic, wants to recall a past decision, or needs prior context before starting work.
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
You are running `/hypo:query`. Full-text search across all wiki pages and projects, then synthesize an answer from the matching pages.
|
package/skills/verify/SKILL.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description:
|
|
2
|
+
description: Surface wiki knowledge that is stale or past its verify-by date and prompt review. Use when the user asks what needs re-checking, wants to audit knowledge freshness, or is reviewing the wiki for rot.
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
You are running `/hypo:verify`. Check all wiki pages for `verify_by` and `verify_by_date` fields, surface overdue pages, and guide a review pass.
|
package/templates/SCHEMA.md
CHANGED
|
@@ -30,7 +30,7 @@ Read this before any wiki operation (ingest / query / lint).
|
|
|
30
30
|
| `weekly-journal` | `journal/weekly/YYYY-Www.md` | Weekly session/ingest summary | append-only |
|
|
31
31
|
| `prd` | `projects/*/prd.md` | Project purpose, success criteria, constraints | mutable |
|
|
32
32
|
| `adr` | `projects/*/decisions/NNNN-*.md` | Architecture decision records | immutable |
|
|
33
|
-
| `session-log` | `projects/*/session-log/YYYY-MM.md` | Chronological session narrative (
|
|
33
|
+
| `session-log` | `projects/*/session-log/YYYY-MM-DD.md` | Chronological session narrative (daily shard) | append-only |
|
|
34
34
|
| `session-state` | `projects/*/session-state.md` | Next-session handoff — overwritten each close | overwrite |
|
|
35
35
|
| `project-index` | `projects/*/index.md` | Project overview + progress checklist | mutable |
|
|
36
36
|
| `open-questions` | `pages/open-questions.md` | Unresolved question queue | append+resolve |
|
|
@@ -54,7 +54,7 @@ Files responsible for session continuity — separate from the type taxonomy abo
|
|
|
54
54
|
| `hot.md` (root) | Active project pointer table + last session date | Pointers only. No session content. |
|
|
55
55
|
| `projects/<name>/hot.md` | Last session snapshot — "what was done" | 500-word cap. No next-tasks. Overwrite each close. |
|
|
56
56
|
| `projects/<name>/session-state.md` | Next-session handoff — "what to do next" | Overwrite each close. Read at session start. |
|
|
57
|
-
| `projects/<name>/session-log/YYYY-MM.md` | Append-only narrative timeline (
|
|
57
|
+
| `projects/<name>/session-log/YYYY-MM-DD.md` | Append-only narrative timeline (daily shard; legacy `YYYY-MM.md` still read) | No edits to existing entries. |
|
|
58
58
|
| `pages/open-questions.md` | Cross-project unresolved question queue | Append + mark resolved. |
|
|
59
59
|
|
|
60
60
|
**Operations**:
|
package/templates/hypo-config.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Hypomnema Config
|
|
3
3
|
type: config
|
|
4
|
-
version: "1.3.
|
|
4
|
+
version: "1.3.3"
|
|
5
5
|
created: YYYY-MM-DD
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -36,6 +36,6 @@ HYPO_SKIP_GATE=1
|
|
|
36
36
|
│ └── <name>/
|
|
37
37
|
│ ├── index.md ← project overview (working_dir: field)
|
|
38
38
|
│ ├── hot.md ← project-scoped session snapshot
|
|
39
|
-
│ └── session-log/ ←
|
|
39
|
+
│ └── session-log/ ← daily shards (YYYY-MM-DD.md)
|
|
40
40
|
└── sources/ ← raw ingested sources (read-only)
|
|
41
41
|
```
|
package/templates/hypo-guide.md
CHANGED
|
@@ -74,16 +74,37 @@ Trigger: explicit close mention, `/compact` request, or context limit approachin
|
|
|
74
74
|
|
|
75
75
|
Do **not** treat these as completion: plan approvals, clarification answers, permission grants, progress updates, partial findings, or mid-task checkpoints. A "좋아요" / "감사합니다" / "ok" following any of those is *not* a close signal — only offer after a genuine final completion/verification report.
|
|
76
76
|
|
|
77
|
+
**Proactive offer means offer, not close.** In this path (task done, no close signal) do not run the close checklist, write the `session-closed` marker, or declare the session ended on your own. Fire `AskUserQuestion` and proceed only after the user picks [세션 마무리]. Task completion is not a close trigger; closing without asking violates this procedure. This scopes the proactive path only: a real close signal, `/compact`, or context limit still closes via the triggers above.
|
|
78
|
+
|
|
77
79
|
Ask: *"이 작업이 마무리되었나요? 세션을 정리(crystallize)할까요?"* with options **[세션 마무리 / 계속 작업]**. On **세션 마무리** → run the session close checklist (or invoke `/hypo:crystallize`). On **계속 작업** → continue and do not re-ask until the next task completes. Ask at most once per completed task; never loop. (Decline simply ends the turn — Layer 3's Stop-chain only blocks on an explicit close signal, so no repeated prompts occur.)
|
|
78
80
|
|
|
79
81
|
1. Update `projects/<name>/session-state.md` (next tasks, overwrite)
|
|
80
82
|
2. Update `projects/<name>/hot.md` (what was done, ≤500 words, overwrite)
|
|
81
|
-
3. Append to `projects/<name>/session-log/YYYY-MM.md` (narrative entry, append-only)
|
|
83
|
+
3. Append to `projects/<name>/session-log/YYYY-MM-DD.md` (daily shard, narrative entry, append-only)
|
|
82
84
|
4. Update root `hot.md` pointer table + date
|
|
83
85
|
5. Run `scripts/lint.mjs` and fix errors in files **you** touched — debt in other
|
|
84
86
|
projects / shared pages you did not author is reported as a non-blocking
|
|
85
87
|
notice, not a gate. (The documented `crystallize.mjs --apply-session-close`
|
|
86
88
|
path runs this lint automatically, scoped to the files it writes.)
|
|
89
|
+
6. Verify with `scripts/crystallize.mjs --check-session-close`: a dry-run of the
|
|
90
|
+
**full** PreCompact gate (close files + lint + design-history + feedback
|
|
91
|
+
projection, ADR 0046), sharing one function with the gate. Only declare the
|
|
92
|
+
session closed once it prints **"Compact-ready"**. A "close files updated"
|
|
93
|
+
check alone is not enough — the real `/compact` gate also blocks on a lint
|
|
94
|
+
error in a close file or a feedback projection over-cap. (Not a hard
|
|
95
|
+
guarantee: the live gate can still differ on a context-≥70% prompt,
|
|
96
|
+
`HYPO_SKIP_GATE`, or a transcript-scoped lint error — pass `--transcript-path`
|
|
97
|
+
to include the last.) Pass `--session-id=<id>` to also see `marker_present`
|
|
98
|
+
(step 7).
|
|
99
|
+
7. Record the session-closed marker (ADR 0047). The Stop hook blocks until this
|
|
100
|
+
session's per-session marker exists, and a hand-edit close (writing the files
|
|
101
|
+
directly + committing) never writes it; the marker is written only by the
|
|
102
|
+
crystallize writer, never by the hook (ADR 0022 bypass guard). Normal path:
|
|
103
|
+
close via `crystallize.mjs --apply-session-close --session-id=<id> --transcript-path=<path>`,
|
|
104
|
+
which writes the marker once the gate is green. Hand-edit recovery: after
|
|
105
|
+
committing the files, run `crystallize.mjs --mark-session-closed --session-id=<id> --transcript-path=<path>`.
|
|
106
|
+
Both writers gate the marker on the SAME `precompactGateStatus` as `/compact`,
|
|
107
|
+
so the marker only lands when step 6 would print **"Compact-ready"**.
|
|
87
108
|
|
|
88
109
|
Skip session close for: single bug fix, single-file edit, Q&A only.
|
|
89
110
|
|
package/templates/hypo-help.md
CHANGED
|
@@ -42,6 +42,7 @@ Quick reference for all `/hypo:*` commands.
|
|
|
42
42
|
| `/hypo:verify` | Review overdue verify_by deadlines |
|
|
43
43
|
| `/hypo:lint` | Validate frontmatter and `[[wikilinks]]` |
|
|
44
44
|
| `/hypo:graph` | Generate link graph (json / mermaid / dot) |
|
|
45
|
+
| `/hypo:rename` | Rename a page or directory, rewriting inbound `[[wikilinks]]` |
|
|
45
46
|
|
|
46
47
|
---
|
|
47
48
|
|
|
@@ -28,7 +28,7 @@ tags: [project]
|
|
|
28
28
|
| `prd.md` | Purpose and success criteria |
|
|
29
29
|
| `hot.md` | Session snapshot and background |
|
|
30
30
|
| `session-state.md` | Next-session handoff |
|
|
31
|
-
| `session-log/` |
|
|
31
|
+
| `session-log/` | Daily-sharded work narrative (YYYY-MM-DD.md) |
|
|
32
32
|
| `decisions/` | Architecture decision records |
|
|
33
33
|
|
|
34
34
|
---
|