@jhizzard/termdeck-stack 1.8.2 → 1.8.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/package.json +1 -1
- package/src/index.js +84 -6
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -51,7 +51,28 @@ const SETTINGS_JSON = path.join(HOME, '.claude', 'settings.json');
|
|
|
51
51
|
const HOOK_DEST_DIR = path.join(HOME, '.claude', 'hooks');
|
|
52
52
|
const HOOK_DEST = path.join(HOOK_DEST_DIR, 'memory-session-end.js');
|
|
53
53
|
const HOOK_SOURCE = path.join(__dirname, '..', 'assets', 'hooks', 'memory-session-end.js');
|
|
54
|
-
|
|
54
|
+
|
|
55
|
+
// Sprint 75 T2 — hook commands are written into ~/.claude/settings.json with
|
|
56
|
+
// ABSOLUTE paths. The pre-1.10 literal `node ~/.claude/hooks/...` shape relied
|
|
57
|
+
// on shell tilde expansion — it worked on macOS/Linux only by luck of how the
|
|
58
|
+
// harness invokes hook commands, and is a hard break on Windows (audit item 4).
|
|
59
|
+
// Computed at CALL time (not require time) from os.homedir() so a process that
|
|
60
|
+
// re-points HOME (tests, sandboxed installs) gets the right path. The path is
|
|
61
|
+
// double-quoted so a home dir containing spaces (`/Users/First Last/`) still
|
|
62
|
+
// produces a command the harness shell can execute. Lockstep twin lives in
|
|
63
|
+
// packages/cli/src/init-mnestra.js (`_hookCommandFor`) — INSTALLER-PITFALLS
|
|
64
|
+
// Class N: change both or neither.
|
|
65
|
+
function _hookCommandFor(filename) {
|
|
66
|
+
return `node "${path.join(os.homedir(), '.claude', 'hooks', filename)}"`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// True when an entry's command still carries the legacy tilde shape and
|
|
70
|
+
// should be rewritten to the absolute form.
|
|
71
|
+
function _isTildeHookCommand(command) {
|
|
72
|
+
return typeof command === 'string' && command.includes('~/');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const HOOK_COMMAND = _hookCommandFor('memory-session-end.js');
|
|
55
76
|
const HOOK_TIMEOUT_SECONDS = 30;
|
|
56
77
|
|
|
57
78
|
// Sprint 64 T3 — PreCompact hook (Investigation 2 of
|
|
@@ -62,7 +83,7 @@ const HOOK_TIMEOUT_SECONDS = 30;
|
|
|
62
83
|
// module-export contract.
|
|
63
84
|
const PRECOMPACT_HOOK_DEST = path.join(HOOK_DEST_DIR, 'memory-pre-compact.js');
|
|
64
85
|
const PRECOMPACT_HOOK_SOURCE = path.join(__dirname, '..', 'assets', 'hooks', 'memory-pre-compact.js');
|
|
65
|
-
const PRECOMPACT_HOOK_COMMAND = '
|
|
86
|
+
const PRECOMPACT_HOOK_COMMAND = _hookCommandFor('memory-pre-compact.js');
|
|
66
87
|
const PRECOMPACT_HOOK_TIMEOUT_SECONDS = 30;
|
|
67
88
|
const SECRETS_PATH = path.join(HOME, '.termdeck', 'secrets.env');
|
|
68
89
|
|
|
@@ -461,7 +482,8 @@ function _isSessionEndHookEntry(entry) {
|
|
|
461
482
|
// the registration; the migration branch below heals existing installs from
|
|
462
483
|
// `@jhizzard/termdeck-stack@<=0.5.0` that wired the hook under `Stop`.
|
|
463
484
|
function _mergeSessionEndHookEntry(settings, opts = {}) {
|
|
464
|
-
|
|
485
|
+
// Command computed at call time (Sprint 75 T2) — see _hookCommandFor.
|
|
486
|
+
const command = opts.command || _hookCommandFor('memory-session-end.js');
|
|
465
487
|
const timeout = opts.timeout != null ? opts.timeout : HOOK_TIMEOUT_SECONDS;
|
|
466
488
|
const entry = { type: 'command', command, timeout };
|
|
467
489
|
|
|
@@ -486,10 +508,29 @@ function _mergeSessionEndHookEntry(settings, opts = {}) {
|
|
|
486
508
|
|
|
487
509
|
if (!Array.isArray(settings.hooks.SessionEnd)) settings.hooks.SessionEnd = [];
|
|
488
510
|
|
|
511
|
+
// Sprint 75 T2 — rewrite a stale literal-`~` command (written by installers
|
|
512
|
+
// ≤ v1.9.x) to the absolute form. The "already wired?" predicate matches by
|
|
513
|
+
// hook FILENAME substring, so without this rewrite a legacy entry would be
|
|
514
|
+
// reported already-installed and keep its `~` forever. Idempotent: absolute
|
|
515
|
+
// commands (and user-custom commands without `~/`) are never touched.
|
|
516
|
+
let tildeMigrated = false;
|
|
517
|
+
for (const group of settings.hooks.SessionEnd) {
|
|
518
|
+
if (!group || !Array.isArray(group.hooks)) continue;
|
|
519
|
+
for (const e of group.hooks) {
|
|
520
|
+
if (_isSessionEndHookEntry(e) && _isTildeHookCommand(e.command)) {
|
|
521
|
+
e.command = command;
|
|
522
|
+
tildeMigrated = true;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
489
527
|
for (const group of settings.hooks.SessionEnd) {
|
|
490
528
|
if (!group || !Array.isArray(group.hooks)) continue;
|
|
491
529
|
if (group.hooks.some(_isSessionEndHookEntry)) {
|
|
492
|
-
|
|
530
|
+
const status = tildeMigrated ? 'migrated-tilde-path'
|
|
531
|
+
: migrated ? 'migrated-from-stop'
|
|
532
|
+
: 'already-installed';
|
|
533
|
+
return { settings, status };
|
|
493
534
|
}
|
|
494
535
|
}
|
|
495
536
|
|
|
@@ -517,17 +558,30 @@ function _isPreCompactHookEntry(entry) {
|
|
|
517
558
|
// Mutates the input. matcher='*' is the documented wildcard for PreCompact —
|
|
518
559
|
// fires on both auto-compact (token-limit-driven) AND manual /compact triggers.
|
|
519
560
|
function _mergePreCompactHookEntry(settings, opts = {}) {
|
|
520
|
-
|
|
561
|
+
// Command computed at call time (Sprint 75 T2) — see _hookCommandFor.
|
|
562
|
+
const command = opts.command || _hookCommandFor('memory-pre-compact.js');
|
|
521
563
|
const timeout = opts.timeout != null ? opts.timeout : PRECOMPACT_HOOK_TIMEOUT_SECONDS;
|
|
522
564
|
const entry = { type: 'command', command, timeout };
|
|
523
565
|
|
|
524
566
|
if (!settings.hooks || typeof settings.hooks !== 'object') settings.hooks = {};
|
|
525
567
|
if (!Array.isArray(settings.hooks.PreCompact)) settings.hooks.PreCompact = [];
|
|
526
568
|
|
|
569
|
+
// Sprint 75 T2 — same stale literal-`~` rewrite as the SessionEnd merge.
|
|
570
|
+
let tildeMigrated = false;
|
|
571
|
+
for (const group of settings.hooks.PreCompact) {
|
|
572
|
+
if (!group || !Array.isArray(group.hooks)) continue;
|
|
573
|
+
for (const e of group.hooks) {
|
|
574
|
+
if (_isPreCompactHookEntry(e) && _isTildeHookCommand(e.command)) {
|
|
575
|
+
e.command = command;
|
|
576
|
+
tildeMigrated = true;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
527
581
|
for (const group of settings.hooks.PreCompact) {
|
|
528
582
|
if (!group || !Array.isArray(group.hooks)) continue;
|
|
529
583
|
if (group.hooks.some(_isPreCompactHookEntry)) {
|
|
530
|
-
return { settings, status: 'already-installed' };
|
|
584
|
+
return { settings, status: tildeMigrated ? 'migrated-tilde-path' : 'already-installed' };
|
|
531
585
|
}
|
|
532
586
|
}
|
|
533
587
|
|
|
@@ -731,6 +785,16 @@ async function installSessionEndHook(opts = {}) {
|
|
|
731
785
|
if (merged.status === 'already-installed') {
|
|
732
786
|
statusLine(`${ANSI.dim}=${ANSI.reset}`, 'settings.json SessionEnd hook', 'already installed');
|
|
733
787
|
settingsStatus = 'already-installed';
|
|
788
|
+
} else if (merged.status === 'migrated-tilde-path') {
|
|
789
|
+
// Sprint 75 T2 — legacy `node ~/.claude/...` command rewritten absolute.
|
|
790
|
+
if (dryRun) {
|
|
791
|
+
statusLine(`${ANSI.yellow}↩${ANSI.reset}`, '(dry-run)', `would rewrite legacy ~ hook command to absolute path in ${settingsPath}`);
|
|
792
|
+
settingsStatus = 'would-migrate-tilde';
|
|
793
|
+
} else {
|
|
794
|
+
_writeSettingsJson(settingsPath, merged.settings);
|
|
795
|
+
statusLine(`${ANSI.green}↻${ANSI.reset}`, 'settings.json SessionEnd hook', 'rewrote legacy ~ command to absolute path');
|
|
796
|
+
settingsStatus = 'migrated-tilde';
|
|
797
|
+
}
|
|
734
798
|
} else if (merged.status === 'migrated-from-stop') {
|
|
735
799
|
if (dryRun) {
|
|
736
800
|
statusLine(`${ANSI.yellow}↩${ANSI.reset}`, '(dry-run)', `would migrate Stop hook → SessionEnd in ${settingsPath}`);
|
|
@@ -847,6 +911,16 @@ async function installPreCompactHook(opts = {}) {
|
|
|
847
911
|
if (merged.status === 'already-installed') {
|
|
848
912
|
statusLine(`${ANSI.dim}=${ANSI.reset}`, 'settings.json PreCompact hook', 'already installed');
|
|
849
913
|
settingsStatus = 'already-installed';
|
|
914
|
+
} else if (merged.status === 'migrated-tilde-path') {
|
|
915
|
+
// Sprint 75 T2 — legacy `node ~/.claude/...` command rewritten absolute.
|
|
916
|
+
if (dryRun) {
|
|
917
|
+
statusLine(`${ANSI.yellow}↩${ANSI.reset}`, '(dry-run)', `would rewrite legacy ~ pre-compact command to absolute path in ${settingsPath}`);
|
|
918
|
+
settingsStatus = 'would-migrate-tilde';
|
|
919
|
+
} else {
|
|
920
|
+
_writeSettingsJson(settingsPath, merged.settings);
|
|
921
|
+
statusLine(`${ANSI.green}↻${ANSI.reset}`, 'settings.json PreCompact hook', 'rewrote legacy ~ command to absolute path');
|
|
922
|
+
settingsStatus = 'migrated-tilde';
|
|
923
|
+
}
|
|
850
924
|
} else if (dryRun) {
|
|
851
925
|
statusLine(`${ANSI.yellow}↩${ANSI.reset}`, '(dry-run)', `would merge PreCompact hook into ${settingsPath}`);
|
|
852
926
|
settingsStatus = 'would-install';
|
|
@@ -1037,6 +1111,10 @@ module.exports._hookSignatureUpgradeAvailable = _hookSignatureUpgradeAvailable;
|
|
|
1037
1111
|
module.exports.HOOK_SIGNATURE_REGEX = HOOK_SIGNATURE_REGEX;
|
|
1038
1112
|
module.exports.installSessionEndHook = installSessionEndHook;
|
|
1039
1113
|
module.exports.HOOK_COMMAND = HOOK_COMMAND;
|
|
1114
|
+
// Sprint 75 T2 — absolute-path hook-command builders (lockstep twin in
|
|
1115
|
+
// packages/cli/src/init-mnestra.js; exported for tests).
|
|
1116
|
+
module.exports._hookCommandFor = _hookCommandFor;
|
|
1117
|
+
module.exports._isTildeHookCommand = _isTildeHookCommand;
|
|
1040
1118
|
module.exports.HOOK_SOURCE = HOOK_SOURCE;
|
|
1041
1119
|
module.exports.HOOK_DEST = HOOK_DEST;
|
|
1042
1120
|
module.exports.HOOK_TIMEOUT_SECONDS = HOOK_TIMEOUT_SECONDS;
|