hypomnema 1.3.0 → 1.3.2
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 +12 -10
- package/README.md +12 -10
- package/commands/crystallize.md +8 -8
- package/commands/feedback.md +1 -1
- package/commands/resume.md +1 -1
- package/commands/upgrade.md +2 -0
- package/docs/ARCHITECTURE.md +3 -3
- package/docs/CONTRIBUTING.md +2 -2
- package/hooks/hypo-auto-minimal-crystallize.mjs +57 -11
- package/hooks/hypo-compact-guard.mjs +1 -1
- 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 +26 -19
- package/hooks/hypo-shared.mjs +839 -58
- 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 +249 -109
- 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 +60 -0
- package/scripts/lib/project-create.mjs +1 -1
- package/scripts/lint.mjs +63 -8
- package/scripts/rename.mjs +373 -0
- package/scripts/resume.mjs +127 -13
- package/scripts/smoke-pack.mjs +23 -2
- package/scripts/uninstall.mjs +1 -1
- package/scripts/upgrade.mjs +266 -47
- package/skills/crystallize/SKILL.md +10 -7
- package/templates/SCHEMA.md +2 -2
- package/templates/hypo-config.md +2 -2
- package/templates/hypo-guide.md +20 -1
- package/templates/projects/_template/index.md +1 -1
package/scripts/upgrade.mjs
CHANGED
|
@@ -16,11 +16,14 @@
|
|
|
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 dual-install guard: register the Claude core
|
|
25
|
+
* surface even though the Hypomnema plugin is also enabled
|
|
26
|
+
* (knowingly accept the double-registration risk)
|
|
24
27
|
*/
|
|
25
28
|
|
|
26
29
|
import {
|
|
@@ -47,6 +50,7 @@ import {
|
|
|
47
50
|
readFileIfRegular,
|
|
48
51
|
} from './lib/pkg-json.mjs';
|
|
49
52
|
import { syncExtensions } from './lib/extensions.mjs';
|
|
53
|
+
import { isHypomnemaPluginEnabled } from './lib/plugin-detect.mjs';
|
|
50
54
|
import { classifyInstall, downgradeGuardMessage } from '../hooks/version-check.mjs';
|
|
51
55
|
|
|
52
56
|
const HOME = homedir();
|
|
@@ -80,6 +84,7 @@ function parseArgs(argv) {
|
|
|
80
84
|
forceExtensions: false,
|
|
81
85
|
codex: false,
|
|
82
86
|
allowDowngrade: false,
|
|
87
|
+
allowDualInstall: false,
|
|
83
88
|
};
|
|
84
89
|
for (const arg of argv.slice(2)) {
|
|
85
90
|
if (arg.startsWith('--hypo-dir=')) args.hypoDir = expandHome(arg.slice(11));
|
|
@@ -89,6 +94,7 @@ function parseArgs(argv) {
|
|
|
89
94
|
else if (arg === '--codex') args.codex = true;
|
|
90
95
|
else if (arg === '--json') args.json = true;
|
|
91
96
|
else if (arg === '--allow-downgrade') args.allowDowngrade = true;
|
|
97
|
+
else if (arg === '--allow-dual-install') args.allowDualInstall = true;
|
|
92
98
|
}
|
|
93
99
|
if (!args.hypoDir) args.hypoDir = resolveHypoRoot();
|
|
94
100
|
return args;
|
|
@@ -406,7 +412,7 @@ function applyHookNameMigration(oldRefs, settingsPath, hooksDir) {
|
|
|
406
412
|
if (applied.length > 0) {
|
|
407
413
|
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
408
414
|
// Copy renamed hook files to the target hooks dir (~/.claude/hooks or
|
|
409
|
-
// ~/.codex/hooks per the caller —
|
|
415
|
+
// ~/.codex/hooks per the caller — codex mirror).
|
|
410
416
|
for (const [oldName, newName] of Object.entries(HOOK_RENAMES)) {
|
|
411
417
|
const oldPath = join(hooksDir, oldName);
|
|
412
418
|
const newPath = join(hooksDir, newName);
|
|
@@ -458,7 +464,7 @@ function applyHypoignoreMigration(result) {
|
|
|
458
464
|
return appended;
|
|
459
465
|
}
|
|
460
466
|
|
|
461
|
-
function writeMigrationReport(hypoDir, fromVersion, toVersion) {
|
|
467
|
+
function writeMigrationReport(hypoDir, fromVersion, toVersion, { pluginMode = false } = {}) {
|
|
462
468
|
const today = new Date().toISOString().slice(0, 10);
|
|
463
469
|
const filename = `MIGRATION-v${toVersion}.md`;
|
|
464
470
|
const dest = join(hypoDir, filename);
|
|
@@ -544,9 +550,15 @@ Review the SCHEMA diff and update your wiki pages accordingly.
|
|
|
544
550
|
|
|
545
551
|
## Action items
|
|
546
552
|
|
|
547
|
-
This report was generated during \`/hypo:upgrade --apply\`.
|
|
548
|
-
|
|
549
|
-
|
|
553
|
+
This report was generated during \`/hypo:upgrade --apply\`. ${
|
|
554
|
+
pluginMode
|
|
555
|
+
? 'You are on a **plugin install**, so the core hook files and settings.json hook ' +
|
|
556
|
+
'registrations were NOT touched — the Claude Code plugin loader owns them (upgrade the ' +
|
|
557
|
+
'plugin via `/plugin marketplace update hypomnema` then `/reload-plugins`). Vault ' +
|
|
558
|
+
'extensions, if any, were still synced.'
|
|
559
|
+
: 'Hook files and settings.json entries were applied by that run (or skipped with a ' +
|
|
560
|
+
'warning if the target was malformed — see the upgrade output).'
|
|
561
|
+
} \`SCHEMA.md\` is intentionally **not** overwritten by upgrade — the
|
|
550
562
|
remaining steps are manual:
|
|
551
563
|
|
|
552
564
|
- [ ] Compare your \`SCHEMA.md\` (v${fromVersion}) with the package template (v${toVersion}) and merge changes manually
|
|
@@ -749,6 +761,31 @@ function applyCommands(commandResults, force) {
|
|
|
749
761
|
return applied;
|
|
750
762
|
}
|
|
751
763
|
|
|
764
|
+
// In plugin mode `applyCommands` is skipped (no command copy), but the
|
|
765
|
+
// runtime still needs hypo-pkg.json to resolve PKG_ROOT for lint/feedback scripts
|
|
766
|
+
// (hooks/hypo-shared.mjs → hypo-personal-check). Write minimal metadata pointing
|
|
767
|
+
// at the plugin's package root, preserving any existing fields (e.g. `extensions`)
|
|
768
|
+
// but DROPPING any prior `commands` map (no commands were copied, so a stale map
|
|
769
|
+
// would falsely assert ownership of ~/.claude/commands/hypo).
|
|
770
|
+
function writePluginModeMetadata() {
|
|
771
|
+
const path = pkgJsonPath();
|
|
772
|
+
// Drop any prior top-level `commands` SHA map: no commands were copied in plugin
|
|
773
|
+
// mode, so keeping a manual install's map would falsely assert ownership of
|
|
774
|
+
// ~/.claude/commands/hypo. Preserve every other field (e.g. `extensions`).
|
|
775
|
+
const { commands: _droppedCommands, ...existing } = readPkgJsonSafe(path) || {};
|
|
776
|
+
let pkgVersion = null;
|
|
777
|
+
try {
|
|
778
|
+
pkgVersion = JSON.parse(readFileSync(join(PKG_ROOT, 'package.json'), 'utf-8')).version;
|
|
779
|
+
} catch {}
|
|
780
|
+
writePkgJsonAtomic(path, {
|
|
781
|
+
...existing,
|
|
782
|
+
pkgRoot: PKG_ROOT,
|
|
783
|
+
pkgVersion,
|
|
784
|
+
schemaVersion: '2.0',
|
|
785
|
+
});
|
|
786
|
+
return true;
|
|
787
|
+
}
|
|
788
|
+
|
|
752
789
|
// ── main ─────────────────────────────────────────────────────────────────────
|
|
753
790
|
|
|
754
791
|
const args = parseArgs(process.argv);
|
|
@@ -760,6 +797,44 @@ const claudeSettingsPath = join(HOME, '.claude', 'settings.json');
|
|
|
760
797
|
const codexHooksDir = join(HOME, '.codex', 'hooks');
|
|
761
798
|
const codexSettingsPath = join(HOME, '.codex', 'settings.json');
|
|
762
799
|
|
|
800
|
+
// When `/hypo:upgrade` runs as the Claude Code PLUGIN, the 15 core hooks
|
|
801
|
+
// and 14 slash commands are provided by the plugin's hooks.json + commands/
|
|
802
|
+
// (auto-wired by Claude Code), NOT copied into ~/.claude/. The manual/npm health
|
|
803
|
+
// check below would then report all of them "missing" and recommend `--apply`,
|
|
804
|
+
// which copies the hooks into ~/.claude/hooks/ and registers 14 settings.json
|
|
805
|
+
// events → Claude Code runs BOTH the plugin hooks.json AND user settings.json, so
|
|
806
|
+
// every hook fires TWICE. The decisive signal: the plugin command runs the
|
|
807
|
+
// PLUGIN's upgrade.mjs, so PKG_ROOT lives under ~/.claude/plugins/. (A manual/npm
|
|
808
|
+
// upgrade.mjs run while the plugin is ALSO enabled is a different failure mode —
|
|
809
|
+
// dual install — handled by the guard below.)
|
|
810
|
+
// Match the Claude plugin cache shape specifically (`~/.claude/plugins/…`), NOT a
|
|
811
|
+
// generic `/plugins/` substring — this flag now GATES install behavior, so a
|
|
812
|
+
// legitimate npm/dev checkout under some unrelated `…/plugins/…` path must not be
|
|
813
|
+
// misclassified and silently stop managing its hooks. (detectChannel's broad
|
|
814
|
+
// `/plugins/` test is fine for the notifier's display-only use, but too loose here.)
|
|
815
|
+
const pluginMode = PKG_ROOT.replace(/\\/g, '/').includes('/.claude/plugins/');
|
|
816
|
+
// Dual install: the OTHER way the same double-registration can happen.
|
|
817
|
+
// Here the MANUAL/npm upgrade.mjs is running (pluginMode=false), but the Hypomnema
|
|
818
|
+
// plugin is ALSO enabled in ~/.claude/settings.json — so the plugin loader already
|
|
819
|
+
// provides the core hooks/commands/settings. A manual/npm `--apply` would copy and
|
|
820
|
+
// register them on top, and every core hook fires twice. The detector is fail-open
|
|
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 `hypo@<mp>: true` (or the
|
|
823
|
+
// legacy `hypomnema@<mp>: true`, matched across the plugin-rename migration window).
|
|
824
|
+
const hypomnemaPluginEnabled = !pluginMode && isHypomnemaPluginEnabled(claudeSettingsPath);
|
|
825
|
+
const dualInstallCoreConflict = hypomnemaPluginEnabled;
|
|
826
|
+
// Surface policy: the Claude core surface (hooks/settings/commands/hook-name
|
|
827
|
+
// migration) is skipped when EITHER the plugin runs this script (pluginMode) OR a
|
|
828
|
+
// manual/npm run detects the plugin is enabled (dualInstallCoreConflict) — unless
|
|
829
|
+
// the user knowingly overrides with --allow-dual-install. The codex core surface
|
|
830
|
+
// (--codex) and vault-defined extensions are NOT plugin-provided, so they stay
|
|
831
|
+
// managed in every case.
|
|
832
|
+
const managesClaudeCore = !pluginMode && (!dualInstallCoreConflict || args.allowDualInstall);
|
|
833
|
+
// dualSkip = core was skipped specifically because of the dual install (not a true
|
|
834
|
+
// plugin-mode run, and not overridden). Drives the warning banner and the metadata
|
|
835
|
+
// preservation below.
|
|
836
|
+
const dualSkip = dualInstallCoreConflict && !args.allowDualInstall;
|
|
837
|
+
|
|
763
838
|
const schema = checkSchemaVersion(args.hypoDir);
|
|
764
839
|
const hooks = checkHookFiles(claudeHooksDir);
|
|
765
840
|
const settings = checkSettingsJson(claudeSettingsPath, claudeHooksDir);
|
|
@@ -790,7 +865,7 @@ const extCheck = syncExtensions({
|
|
|
790
865
|
force: args.forceExtensions,
|
|
791
866
|
});
|
|
792
867
|
|
|
793
|
-
// E4
|
|
868
|
+
// E4: --codex mirrors the extensions sync into ~/.codex (hooks + commands
|
|
794
869
|
// only; skills/agents skipped with a notice). The per-target SHA map lives in the
|
|
795
870
|
// same ~/.claude/hypo-pkg.json under extensions.codex, so pkgPath is unchanged.
|
|
796
871
|
const extCodexSettingsPath = codexSettingsPath;
|
|
@@ -823,7 +898,14 @@ const invalidSettingsCodex = settingsCodex
|
|
|
823
898
|
? settingsCodex.some((s) => s.status === 'invalid-json')
|
|
824
899
|
: false;
|
|
825
900
|
const schemaDrift = schema.bump !== 'none' && schema.bump !== 'unknown' && schema.bump !== 'ahead';
|
|
826
|
-
|
|
901
|
+
// Dual-install: when core is skipped, hypo-pkg.json is deliberately left
|
|
902
|
+
// pointing at the PLUGIN's package root (preserved identity), so checkPkgJson()
|
|
903
|
+
// reports it 'stale' relative to this npm/manual PKG_ROOT. That mismatch is
|
|
904
|
+
// INTENTIONAL — `--apply` will not (and must not) rewrite it — so it must not
|
|
905
|
+
// count as actionable drift, or the user would be nagged to run --apply forever.
|
|
906
|
+
// A genuinely missing/corrupt file (status 'missing') is still surfaced (warning
|
|
907
|
+
// below), because the runtime then cannot resolve its package root at all.
|
|
908
|
+
const pkgJsonDrift = pkgJson.status !== 'up-to-date' && !(dualSkip && pkgJson.status === 'stale');
|
|
827
909
|
const staleCommands = commands.filter((c) => c.status === 'stale' || c.status === 'missing');
|
|
828
910
|
const userModifiedCommands = commands.filter((c) => c.status === 'user-modified');
|
|
829
911
|
const orphanedCommands = commands.filter((c) => c.status === 'orphaned');
|
|
@@ -871,21 +953,59 @@ if (args.apply) {
|
|
|
871
953
|
process.exit(2);
|
|
872
954
|
}
|
|
873
955
|
}
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
oldHookRefs,
|
|
877
|
-
claudeSettingsPath,
|
|
878
|
-
claudeHooksDir,
|
|
879
|
-
);
|
|
880
|
-
}
|
|
956
|
+
// Migration report is vault-side (writes into the Hypomnema root) and applies
|
|
957
|
+
// in both install models.
|
|
881
958
|
if (schema.bump === 'major' && schema.installed && schema.current && existsSync(args.hypoDir)) {
|
|
882
|
-
migrationPath = writeMigrationReport(args.hypoDir, schema.installed, schema.current
|
|
959
|
+
migrationPath = writeMigrationReport(args.hypoDir, schema.installed, schema.current, {
|
|
960
|
+
// Use the core-skipped predicate, not raw pluginMode: in a dual-install skip
|
|
961
|
+
// the core surface is plugin-owned too, so the report must not claim the
|
|
962
|
+
// core hooks/settings were applied.
|
|
963
|
+
pluginMode: !managesClaudeCore,
|
|
964
|
+
});
|
|
965
|
+
}
|
|
966
|
+
if (managesClaudeCore) {
|
|
967
|
+
if (oldHookRefs.length > 0) {
|
|
968
|
+
appliedHookNameRenames = applyHookNameMigration(
|
|
969
|
+
oldHookRefs,
|
|
970
|
+
claudeSettingsPath,
|
|
971
|
+
claudeHooksDir,
|
|
972
|
+
);
|
|
973
|
+
}
|
|
974
|
+
appliedHooks = applyHookFiles(hooks, claudeHooksDir);
|
|
975
|
+
appliedSettings = applySettingsJson(settings, claudeSettingsPath);
|
|
976
|
+
// applyCommands handles the single atomic hypo-pkg.json write (pkgRoot, version, schema, commands map)
|
|
977
|
+
appliedCommands = applyCommands(commands, args.forceCommands);
|
|
978
|
+
appliedPkgJson = true;
|
|
979
|
+
} else if (pluginMode) {
|
|
980
|
+
// Plugin mode: the plugin loader owns the core hooks/commands and
|
|
981
|
+
// settings.json wiring — copying them here would double-register. Skip those,
|
|
982
|
+
// but STILL write minimal package metadata so the runtime can resolve PKG_ROOT
|
|
983
|
+
// for lint/feedback scripts (hooks/hypo-shared.mjs → hypo-personal-check). The
|
|
984
|
+
// commands SHA map is intentionally omitted (no command copy happened). PKG_ROOT
|
|
985
|
+
// is the plugin's own path here, so this metadata is authoritative.
|
|
986
|
+
appliedPkgJson = writePluginModeMetadata();
|
|
987
|
+
} else {
|
|
988
|
+
// Dual-install skip: a manual/npm run while the plugin is enabled. We
|
|
989
|
+
// skip the core surface (the plugin owns it), but — unlike true plugin mode —
|
|
990
|
+
// PKG_ROOT here is the npm/manual path while the ACTIVE runtime hooks are the
|
|
991
|
+
// PLUGIN's. Rewriting a VALID hypo-pkg.json.pkgRoot to this npm path would
|
|
992
|
+
// mis-point the plugin runtime's lint/feedback resolution, so we PRESERVE an
|
|
993
|
+
// existing plugin-written identity (pkgJson.status 'stale'/'up-to-date' both
|
|
994
|
+
// mean a usable pkgRoot is already on disk) and do not touch it.
|
|
995
|
+
//
|
|
996
|
+
// If the metadata is MISSING or corrupt (status 'missing'; corrupt files are
|
|
997
|
+
// renamed to *.corrupt-*.json by readPkgJson and then read as absent), there is
|
|
998
|
+
// no plugin identity to preserve. Write minimal fallback metadata pointing at
|
|
999
|
+
// this (same-version) npm copy so the plugin runtime can resolve a package root
|
|
1000
|
+
// at all — strictly better than the pkgRoot-less file extension sync would
|
|
1001
|
+
// otherwise create, or no file at all. The dual-install banner still tells the
|
|
1002
|
+
// user to resolve the dual install.
|
|
1003
|
+
if (pkgJson.status === 'missing') {
|
|
1004
|
+
appliedPkgJson = writePluginModeMetadata();
|
|
1005
|
+
} else {
|
|
1006
|
+
appliedPkgJson = false;
|
|
1007
|
+
}
|
|
883
1008
|
}
|
|
884
|
-
appliedHooks = applyHookFiles(hooks, claudeHooksDir);
|
|
885
|
-
appliedSettings = applySettingsJson(settings, claudeSettingsPath);
|
|
886
|
-
// applyCommands handles the single atomic hypo-pkg.json write (pkgRoot, version, schema, commands map)
|
|
887
|
-
appliedCommands = applyCommands(commands, args.forceCommands);
|
|
888
|
-
appliedPkgJson = true;
|
|
889
1009
|
appliedHypoignore = applyHypoignoreMigration(hypoignore);
|
|
890
1010
|
// codex core hooks + settings + wiki-*→hypo-* rename mirror. Same order
|
|
891
1011
|
// as the claude side (rename first so subsequent hook copy can find renamed targets).
|
|
@@ -910,7 +1030,7 @@ if (args.apply) {
|
|
|
910
1030
|
apply: true,
|
|
911
1031
|
force: args.forceExtensions,
|
|
912
1032
|
});
|
|
913
|
-
// E4
|
|
1033
|
+
// E4: codex apply runs AFTER the claude apply so it reads the freshly
|
|
914
1034
|
// written hypo-pkg.json and merges extensions.codex alongside extensions.claude
|
|
915
1035
|
// (the per-target spread in syncExtensions preserves the other target's map).
|
|
916
1036
|
if (args.codex) {
|
|
@@ -939,17 +1059,28 @@ const codexCoreDrift =
|
|
|
939
1059
|
invalidSettingsCodex ||
|
|
940
1060
|
(oldHookRefsCodex?.length ?? 0) > 0);
|
|
941
1061
|
|
|
942
|
-
|
|
1062
|
+
// Claude core-surface drift (hooks/settings/commands/rename/metadata). In plugin
|
|
1063
|
+
// mode these are plugin-managed, so they must NOT count as drift — otherwise the
|
|
1064
|
+
// report nags "N items need updating" and recommends a double-registering --apply.
|
|
1065
|
+
// Plugin-provided surface (hooks/settings/commands/rename) — excluded from drift
|
|
1066
|
+
// in plugin mode. pkgJsonDrift is intentionally NOT here: hypo-pkg.json is written
|
|
1067
|
+
// in BOTH install models (plugin mode writes minimal metadata so the runtime can
|
|
1068
|
+
// resolve PKG_ROOT for lint/feedback), so a missing/stale metadata file should
|
|
1069
|
+
// still prompt a (safe, metadata-only) --apply.
|
|
1070
|
+
const claudeCoreDrift =
|
|
943
1071
|
staleHooks.length > 0 ||
|
|
944
1072
|
missingSettings.length > 0 ||
|
|
945
|
-
schemaDrift ||
|
|
946
1073
|
invalidSettings ||
|
|
947
|
-
pkgJsonDrift ||
|
|
948
1074
|
oldHookRefs.length > 0 ||
|
|
949
1075
|
staleCommands.length > 0 ||
|
|
950
1076
|
userModifiedCommands.length > 0 ||
|
|
951
1077
|
orphanedCommands.length > 0 ||
|
|
952
|
-
nonRegularCommands.length > 0
|
|
1078
|
+
nonRegularCommands.length > 0;
|
|
1079
|
+
|
|
1080
|
+
const hasDrift =
|
|
1081
|
+
(managesClaudeCore && claudeCoreDrift) ||
|
|
1082
|
+
pkgJsonDrift ||
|
|
1083
|
+
schemaDrift ||
|
|
953
1084
|
hypoignore.status === 'needs-migration' ||
|
|
954
1085
|
extDrift ||
|
|
955
1086
|
codexCoreDrift;
|
|
@@ -958,6 +1089,12 @@ if (args.json) {
|
|
|
958
1089
|
console.log(
|
|
959
1090
|
JSON.stringify(
|
|
960
1091
|
{
|
|
1092
|
+
pluginMode,
|
|
1093
|
+
// Dual-install signals.
|
|
1094
|
+
hypomnemaPluginEnabled,
|
|
1095
|
+
dualInstallCoreConflict,
|
|
1096
|
+
coreManagedBy: managesClaudeCore ? 'self' : pluginMode ? 'plugin' : 'plugin-enabled',
|
|
1097
|
+
dualInstallOverride: args.allowDualInstall,
|
|
961
1098
|
schema,
|
|
962
1099
|
hooks,
|
|
963
1100
|
settings,
|
|
@@ -1002,6 +1139,49 @@ if (args.json) {
|
|
|
1002
1139
|
// Human-readable report
|
|
1003
1140
|
const lines = [];
|
|
1004
1141
|
|
|
1142
|
+
// Lead with the plugin-mode banner so the user understands why the core
|
|
1143
|
+
// hook/command/settings sections read "managed by plugin" and that `--apply` will
|
|
1144
|
+
// NOT touch them (only vault-side migrations + package metadata).
|
|
1145
|
+
if (pluginMode) {
|
|
1146
|
+
lines.push(
|
|
1147
|
+
'ℹ Plugin install detected — Hypomnema is loaded via the Claude Code plugin.',
|
|
1148
|
+
' Core hooks, slash commands, and settings.json wiring are provided by the',
|
|
1149
|
+
' plugin loader, so `/hypo:upgrade` does NOT manage them (and `--apply` will',
|
|
1150
|
+
' not copy/register them — that would double-register every hook).',
|
|
1151
|
+
' → To upgrade the plugin: `/plugin marketplace update hypomnema` then `/reload-plugins`.',
|
|
1152
|
+
' → `/hypo:upgrade --apply` here applies vault-side migrations (SCHEMA,',
|
|
1153
|
+
' .hypoignore), refreshes package metadata, and still syncs any vault',
|
|
1154
|
+
' extensions — but does NOT install the core hooks/commands/settings.',
|
|
1155
|
+
'',
|
|
1156
|
+
);
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
// Dual install: a manual/npm upgrade.mjs is running while the Hypomnema plugin is ALSO
|
|
1160
|
+
// enabled — a dual install. Lead with a loud banner: the core surface is owned by
|
|
1161
|
+
// the plugin and is intentionally skipped, so `--apply` will not double-register.
|
|
1162
|
+
if (dualSkip) {
|
|
1163
|
+
lines.push(
|
|
1164
|
+
'⚠ Dual install detected — you are running the MANUAL/npm `upgrade.mjs`, but the',
|
|
1165
|
+
' Hypomnema plugin is ALSO enabled in ~/.claude/settings.json. The plugin loader',
|
|
1166
|
+
' already provides the core hooks, slash commands, and settings.json wiring, so',
|
|
1167
|
+
' this run does NOT copy/register them (doing so would double-register every hook).',
|
|
1168
|
+
' → Recommended: pick ONE install. To keep the plugin, remove the npm/manual copy',
|
|
1169
|
+
' (`npm uninstall -g hypomnema`) and upgrade via `/plugin marketplace update',
|
|
1170
|
+
' hypomnema` + `/reload-plugins`. Vault extensions + codex (if any) are still synced.',
|
|
1171
|
+
' → To register the core surface here anyway (knowingly accept the double-register',
|
|
1172
|
+
' risk), re-run with `--allow-dual-install`.',
|
|
1173
|
+
'',
|
|
1174
|
+
);
|
|
1175
|
+
} else if (dualInstallCoreConflict && args.allowDualInstall) {
|
|
1176
|
+
// Override path: the user forced core registration despite the enabled plugin.
|
|
1177
|
+
lines.push(
|
|
1178
|
+
'⚠ Dual install — `--allow-dual-install` set: registering the Claude core surface',
|
|
1179
|
+
' even though the Hypomnema plugin is enabled. Every core hook may now fire TWICE',
|
|
1180
|
+
' (plugin loader + ~/.claude registration) until one install is removed.',
|
|
1181
|
+
'',
|
|
1182
|
+
);
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1005
1185
|
// Schema version
|
|
1006
1186
|
if (schema.bump === 'none') {
|
|
1007
1187
|
lines.push(`✓ SCHEMA version ${schema.installed} (up to date)`);
|
|
@@ -1023,7 +1203,7 @@ if (schema.bump === 'none') {
|
|
|
1023
1203
|
);
|
|
1024
1204
|
}
|
|
1025
1205
|
|
|
1026
|
-
// Hook files (target-aware so --codex can mirror the same block
|
|
1206
|
+
// Hook files (target-aware so --codex can mirror the same block).
|
|
1027
1207
|
function pushHookSummary(hookList, label, targetPath) {
|
|
1028
1208
|
const colHook = `Hook files${label}`.padEnd(20);
|
|
1029
1209
|
const up = hookList.filter((h) => h.status === 'up-to-date').length;
|
|
@@ -1047,10 +1227,16 @@ function pushHookSummary(hookList, label, targetPath) {
|
|
|
1047
1227
|
}
|
|
1048
1228
|
}
|
|
1049
1229
|
}
|
|
1050
|
-
|
|
1230
|
+
if (managesClaudeCore) {
|
|
1231
|
+
pushHookSummary(hooks, '', '~/.claude/hooks/');
|
|
1232
|
+
} else {
|
|
1233
|
+
lines.push(
|
|
1234
|
+
'✓ Hook files provided by the plugin loader (not managed in ~/.claude/hooks/)',
|
|
1235
|
+
);
|
|
1236
|
+
}
|
|
1051
1237
|
if (hooksCodex) pushHookSummary(hooksCodex, ' (codex)', '~/.codex/hooks/');
|
|
1052
1238
|
|
|
1053
|
-
// settings.json registrations (target-aware mirror
|
|
1239
|
+
// settings.json registrations (target-aware mirror).
|
|
1054
1240
|
function pushSettingsSummary(sList, label, invalidFlag) {
|
|
1055
1241
|
const colS = `settings.json${label}`.padEnd(20);
|
|
1056
1242
|
const reg = sList.filter((s) => s.status === 'registered').length;
|
|
@@ -1066,11 +1252,33 @@ function pushSettingsSummary(sList, label, invalidFlag) {
|
|
|
1066
1252
|
}
|
|
1067
1253
|
}
|
|
1068
1254
|
}
|
|
1069
|
-
|
|
1255
|
+
if (managesClaudeCore) {
|
|
1256
|
+
pushSettingsSummary(settings, '', invalidSettings);
|
|
1257
|
+
} else {
|
|
1258
|
+
lines.push(
|
|
1259
|
+
'✓ settings.json hook wiring provided by the plugin (no ~/.claude registration)',
|
|
1260
|
+
);
|
|
1261
|
+
}
|
|
1070
1262
|
if (settingsCodex) pushSettingsSummary(settingsCodex, ' (codex)', invalidSettingsCodex);
|
|
1071
1263
|
|
|
1072
1264
|
// Package metadata
|
|
1073
|
-
if (pkgJson.status === '
|
|
1265
|
+
if (dualSkip && pkgJson.status === 'stale') {
|
|
1266
|
+
// Dual-install: the 'stale' here is the preserved plugin identity (pkgRoot points at
|
|
1267
|
+
// the plugin, not this npm/manual copy). That is intentional — not actionable.
|
|
1268
|
+
lines.push(
|
|
1269
|
+
`✓ Package metadata hypo-pkg.json plugin-owned (preserved — not rewritten in a dual install)`,
|
|
1270
|
+
);
|
|
1271
|
+
} else if (dualSkip && pkgJson.status === 'missing') {
|
|
1272
|
+
// Missing/corrupt in a dual install: there is no plugin identity to preserve, so
|
|
1273
|
+
// `--apply` writes minimal fallback metadata (pointing at this same-version npm
|
|
1274
|
+
// copy) — enough for the plugin runtime to resolve its scripts. The real fix is
|
|
1275
|
+
// still to resolve the dual install.
|
|
1276
|
+
lines.push(
|
|
1277
|
+
`⚠ Package metadata hypo-pkg.json missing/unreadable — \`--apply\` writes fallback metadata`,
|
|
1278
|
+
` for this npm copy so the plugin runtime can resolve its scripts.`,
|
|
1279
|
+
` Better: resolve the dual install (remove the npm/manual copy).`,
|
|
1280
|
+
);
|
|
1281
|
+
} else if (pkgJson.status === 'up-to-date') {
|
|
1074
1282
|
lines.push(`✓ Package metadata hypo-pkg.json up to date`);
|
|
1075
1283
|
} else if (pkgJson.status === 'stale') {
|
|
1076
1284
|
lines.push(
|
|
@@ -1087,7 +1295,9 @@ const cmdMissCount = commands.filter((c) => c.status === 'missing').length;
|
|
|
1087
1295
|
const cmdUserCount = userModifiedCommands.length;
|
|
1088
1296
|
const cmdOrphanCount = orphanedCommands.length;
|
|
1089
1297
|
const cmdNonRegCount = nonRegularCommands.length;
|
|
1090
|
-
if (
|
|
1298
|
+
if (!managesClaudeCore) {
|
|
1299
|
+
lines.push('✓ Slash commands provided by the plugin loader (not ~/.claude/commands/hypo/)');
|
|
1300
|
+
} else if (commands.length === 0) {
|
|
1091
1301
|
lines.push(`⚠ Slash commands package commands/ is empty`);
|
|
1092
1302
|
} else if (
|
|
1093
1303
|
cmdStaleCount === 0 &&
|
|
@@ -1134,7 +1344,10 @@ function pushHookNameSummary(refs, label) {
|
|
|
1134
1344
|
lines.push(`✓ ${colN}All hook references use current hypo-*.mjs names`);
|
|
1135
1345
|
}
|
|
1136
1346
|
}
|
|
1137
|
-
|
|
1347
|
+
// In plugin mode the Claude settings.json is plugin-owned and --apply skips the
|
|
1348
|
+
// rename migration, so do not print a "run --apply to rename" instruction it will
|
|
1349
|
+
// not honor. (codex hook-name migration is unaffected by pluginMode.)
|
|
1350
|
+
if (managesClaudeCore) pushHookNameSummary(oldHookRefs, '');
|
|
1138
1351
|
if (oldHookRefsCodex) pushHookNameSummary(oldHookRefsCodex, ' (codex)');
|
|
1139
1352
|
|
|
1140
1353
|
// .hypoignore migration (ensure required runtime patterns are present)
|
|
@@ -1174,7 +1387,7 @@ function pushExtSummary(check, label) {
|
|
|
1174
1387
|
for (const c of check.conflicts) lines.push(` ✗ ${c.file} [${c.action} — left untouched]`);
|
|
1175
1388
|
for (const d of check.drifts) lines.push(` ⚠ ${d.file} [drift — left untouched]`);
|
|
1176
1389
|
}
|
|
1177
|
-
// E3
|
|
1390
|
+
// E3: a hard conflict blocks install (exit 1, even under --apply); drift is
|
|
1178
1391
|
// resolvable advisory. Emit the spec'd WIKI messages so the user knows the recovery.
|
|
1179
1392
|
if (nConflicts > 0) {
|
|
1180
1393
|
lines.push(' [WIKI: existing file conflicts. Backup and retry, or use --force-extensions]');
|
|
@@ -1269,26 +1482,32 @@ pushAppliedExt(appliedExtensionsCodex, ' (codex)');
|
|
|
1269
1482
|
|
|
1270
1483
|
// Summary
|
|
1271
1484
|
lines.push('');
|
|
1485
|
+
// Claude core-surface item count — zeroed in plugin mode (plugin-managed), so the
|
|
1486
|
+
// summary never reads "N items need updating" for hooks/settings/commands.
|
|
1487
|
+
const claudeCoreCount = managesClaudeCore
|
|
1488
|
+
? staleHooks.length +
|
|
1489
|
+
missingSettings.length +
|
|
1490
|
+
(invalidSettings ? 1 : 0) +
|
|
1491
|
+
oldHookRefs.length +
|
|
1492
|
+
staleCommands.length +
|
|
1493
|
+
userModifiedCommands.length +
|
|
1494
|
+
orphanedCommands.length +
|
|
1495
|
+
nonRegularCommands.length
|
|
1496
|
+
: 0;
|
|
1497
|
+
|
|
1272
1498
|
const totalDrift =
|
|
1273
|
-
|
|
1274
|
-
missingSettings.length +
|
|
1275
|
-
(schemaDrift ? 1 : 0) +
|
|
1276
|
-
(invalidSettings ? 1 : 0) +
|
|
1499
|
+
claudeCoreCount +
|
|
1277
1500
|
(pkgJsonDrift ? 1 : 0) +
|
|
1278
|
-
|
|
1279
|
-
staleCommands.length +
|
|
1280
|
-
userModifiedCommands.length +
|
|
1281
|
-
orphanedCommands.length +
|
|
1282
|
-
nonRegularCommands.length +
|
|
1501
|
+
(schemaDrift ? 1 : 0) +
|
|
1283
1502
|
(hypoignore.status === 'needs-migration' ? hypoignore.missing.length : 0) +
|
|
1284
1503
|
extCheck.actions.filter(
|
|
1285
1504
|
(a) => a.action === 'create' || a.action === 'update' || a.action === 'force-update',
|
|
1286
1505
|
).length +
|
|
1287
|
-
// E3
|
|
1506
|
+
// E3: unresolved drift/conflict is pending work too — without these the
|
|
1288
1507
|
// summary printed "up to date" while the exit code was 1.
|
|
1289
1508
|
extCheck.conflicts.length +
|
|
1290
1509
|
extCheck.drifts.length +
|
|
1291
|
-
// E4
|
|
1510
|
+
// E4: codex-target pending work counts identically (same message/exit
|
|
1292
1511
|
// consistency the E3 review caught — a codex conflict must not read "up to date").
|
|
1293
1512
|
(extCheckCodex
|
|
1294
1513
|
? extCheckCodex.actions.filter(
|
|
@@ -1329,7 +1548,7 @@ if (totalDrift === 0) {
|
|
|
1329
1548
|
|
|
1330
1549
|
console.log(lines.join('\n'));
|
|
1331
1550
|
|
|
1332
|
-
// E3
|
|
1551
|
+
// E3: a hard extension conflict blocks even under --apply (unlike ordinary
|
|
1333
1552
|
// drift, which only fails check mode). --force-extensions clears the resolvable
|
|
1334
1553
|
// cases; an unfollowable symlink/non-regular dest still counts and stays exit 1.
|
|
1335
1554
|
const extBlocked = extCheck.conflicts.length > 0 || (extCheckCodex?.conflicts.length ?? 0) > 0;
|
|
@@ -11,7 +11,7 @@ When invoked at the end of a session (or with phrases like "세션 종료", "wra
|
|
|
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/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.2"
|
|
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
|
@@ -78,12 +78,31 @@ Ask: *"이 작업이 마무리되었나요? 세션을 정리(crystallize)할까
|
|
|
78
78
|
|
|
79
79
|
1. Update `projects/<name>/session-state.md` (next tasks, overwrite)
|
|
80
80
|
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)
|
|
81
|
+
3. Append to `projects/<name>/session-log/YYYY-MM-DD.md` (daily shard, narrative entry, append-only)
|
|
82
82
|
4. Update root `hot.md` pointer table + date
|
|
83
83
|
5. Run `scripts/lint.mjs` and fix errors in files **you** touched — debt in other
|
|
84
84
|
projects / shared pages you did not author is reported as a non-blocking
|
|
85
85
|
notice, not a gate. (The documented `crystallize.mjs --apply-session-close`
|
|
86
86
|
path runs this lint automatically, scoped to the files it writes.)
|
|
87
|
+
6. Verify with `scripts/crystallize.mjs --check-session-close`: a dry-run of the
|
|
88
|
+
**full** PreCompact gate (close files + lint + design-history + feedback
|
|
89
|
+
projection, ADR 0046), sharing one function with the gate. Only declare the
|
|
90
|
+
session closed once it prints **"Compact-ready"**. A "close files updated"
|
|
91
|
+
check alone is not enough — the real `/compact` gate also blocks on a lint
|
|
92
|
+
error in a close file or a feedback projection over-cap. (Not a hard
|
|
93
|
+
guarantee: the live gate can still differ on a context-≥70% prompt,
|
|
94
|
+
`HYPO_SKIP_GATE`, or a transcript-scoped lint error — pass `--transcript-path`
|
|
95
|
+
to include the last.) Pass `--session-id=<id>` to also see `marker_present`
|
|
96
|
+
(step 7).
|
|
97
|
+
7. Record the session-closed marker (ADR 0047). The Stop hook blocks until this
|
|
98
|
+
session's per-session marker exists, and a hand-edit close (writing the files
|
|
99
|
+
directly + committing) never writes it; the marker is written only by the
|
|
100
|
+
crystallize writer, never by the hook (ADR 0022 bypass guard). Normal path:
|
|
101
|
+
close via `crystallize.mjs --apply-session-close --session-id=<id> --transcript-path=<path>`,
|
|
102
|
+
which writes the marker once the gate is green. Hand-edit recovery: after
|
|
103
|
+
committing the files, run `crystallize.mjs --mark-session-closed --session-id=<id> --transcript-path=<path>`.
|
|
104
|
+
Both writers gate the marker on the SAME `precompactGateStatus` as `/compact`,
|
|
105
|
+
so the marker only lands when step 6 would print **"Compact-ready"**.
|
|
87
106
|
|
|
88
107
|
Skip session close for: single bug fix, single-file edit, Q&A only.
|
|
89
108
|
|
|
@@ -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
|
---
|