@jhizzard/termdeck 1.0.0 → 1.0.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jhizzard/termdeck",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Browser-based terminal multiplexer with metadata overlays, panel flashback memory recall, and AI-aware session management",
5
5
  "bin": {
6
6
  "termdeck": "./packages/cli/src/index.js"
@@ -11,6 +11,7 @@
11
11
  "packages/cli/templates/**",
12
12
  "packages/server/src/**",
13
13
  "packages/client/public/**",
14
+ "packages/stack-installer/assets/hooks/**",
14
15
  "config/config.example.yaml",
15
16
  "config/secrets.env.example",
16
17
  "config/transcript-migration.sql",
@@ -45,7 +45,8 @@ const {
45
45
  supabaseUrl: urlHelper,
46
46
  migrations,
47
47
  pgRunner,
48
- preconditions
48
+ preconditions,
49
+ auditUpgrade: auditUpgradeMod
49
50
  } = require(SETUP_DIR);
50
51
 
51
52
  const HELP = [
@@ -339,6 +340,43 @@ async function applyMigrations(client, dryRun) {
339
340
  }
340
341
  }
341
342
 
343
+ // Sprint 51.5 T1 — schema-introspection audit-upgrade. Runs AFTER the
344
+ // fresh-install applyMigrations() loop completes, but reports separately.
345
+ // On a Sprint-37-era project that pre-dated several mnestra migrations,
346
+ // applyMigrations now has the full bundled set (Sprint 51.5 synced 013-015
347
+ // from canonical engram), so this audit is mostly a belt-and-suspenders
348
+ // confirmation. It still surfaces drift if, e.g., the user manually
349
+ // dropped a column post-install. Mnestra-kind only — rumen cron probes
350
+ // reference vault secrets the user hasn't set up yet at this point. Run
351
+ // init-rumen for the rumen-side audit.
352
+ async function runMnestraAudit(client, projectRef, dryRun) {
353
+ step('Audit-upgrade: probing for missing mnestra schema artifacts...');
354
+ if (dryRun) { ok('(dry-run)'); return; }
355
+ const probes = auditUpgradeMod.PROBES.filter((p) => p.kind === 'mnestra');
356
+ let result;
357
+ try {
358
+ result = await auditUpgradeMod.auditUpgrade({
359
+ pgClient: client,
360
+ projectRef,
361
+ probes
362
+ });
363
+ } catch (err) {
364
+ fail(err.message);
365
+ return;
366
+ }
367
+ if (result.applied.length === 0 && result.errors.length === 0) {
368
+ ok(`(install up to date — ${result.probed.length} probes all present)`);
369
+ return;
370
+ }
371
+ ok(`(probed ${result.probed.length}, applied ${result.applied.length})`);
372
+ for (const name of result.applied) {
373
+ process.stdout.write(` ✓ applied ${name}\n`);
374
+ }
375
+ for (const e of result.errors) {
376
+ process.stdout.write(` ! ${e.name}: ${e.error}\n`);
377
+ }
378
+ }
379
+
342
380
  async function checkExistingStore(client) {
343
381
  step('Checking for existing memory_items table...');
344
382
  try {
@@ -443,6 +481,92 @@ function writeYamlConfig(dryRun) {
443
481
  else ok();
444
482
  }
445
483
 
484
+ // Sprint 51.6 T3 — hook upgrade gap fix.
485
+ //
486
+ // Codex's Sprint 51.6 GAP at 20:11 ET surfaced this: the bundled session-end
487
+ // hook ships in `packages/stack-installer/assets/hooks/memory-session-end.js`,
488
+ // and `npm install -g @jhizzard/termdeck@latest` lands the new bundled file
489
+ // in node_modules — but `termdeck init --mnestra` never touched
490
+ // ~/.claude/hooks/memory-session-end.js. The user's daily-driver kept
491
+ // running the OLD installed copy forever. v1.0.2 closes that gap by adding
492
+ // this refresh step to init --mnestra. The version stamp in the bundled
493
+ // hook (// @termdeck/stack-installer-hook v<N>) gates the overwrite — only
494
+ // strictly-newer bundled stamps trigger a refresh, so a hand-edited
495
+ // installed file with v=current stays put.
496
+ //
497
+ // Backup is best-effort timestamped: `<dest>.bak.<YYYYMMDDhhmmss>`. Matches
498
+ // the pattern Joshua already had on disk from earlier stack-installer runs.
499
+ function refreshBundledHookIfNewer(opts = {}) {
500
+ const dryRun = !!opts.dryRun;
501
+ const HOME = require('os').homedir();
502
+ const HOOK_DEST = opts.destPath || path.join(HOME, '.claude', 'hooks', 'memory-session-end.js');
503
+ // Sprint 51.6 T4-CODEX audit 20:28 ET fix: bundled hook source must be on
504
+ // a path that ships in @jhizzard/termdeck's npm tarball. Root package.json
505
+ // includes `packages/stack-installer/assets/hooks/**` (added 51.6 T3) so
506
+ // this path resolves both in the monorepo and in the published tarball.
507
+ const HOOK_SOURCE = opts.sourcePath
508
+ || path.join(__dirname, '..', '..', 'stack-installer', 'assets', 'hooks', 'memory-session-end.js');
509
+ const SIG_RE = /@termdeck\/stack-installer-hook\s+v(\d+)/;
510
+ const TERMDECK_MARKERS = [
511
+ /TermDeck session-end memory hook/,
512
+ /@jhizzard\/termdeck-stack/,
513
+ /Vendored into ~\/\.claude\/hooks\/memory-session-end\.js by @jhizzard/i,
514
+ ];
515
+
516
+ function readHead(p) {
517
+ try { return fs.readFileSync(p, 'utf8').slice(0, 4096); }
518
+ catch (_) { return null; }
519
+ }
520
+ function readVersion(p) {
521
+ const head = readHead(p);
522
+ if (!head) return null;
523
+ const m = head.match(SIG_RE);
524
+ return m ? parseInt(m[1], 10) : null;
525
+ }
526
+ function looksTermdeckManaged(p) {
527
+ const head = readHead(p);
528
+ if (!head) return false;
529
+ return TERMDECK_MARKERS.some((m) => m.test(head));
530
+ }
531
+
532
+ if (!fs.existsSync(HOOK_SOURCE)) {
533
+ return { status: 'no-bundled', message: 'bundled hook source not found' };
534
+ }
535
+ const bundled = readVersion(HOOK_SOURCE);
536
+ if (bundled === null) {
537
+ return { status: 'bundled-unsigned', message: 'bundled hook missing version stamp; skipping refresh' };
538
+ }
539
+ if (!fs.existsSync(HOOK_DEST)) {
540
+ if (dryRun) return { status: 'would-install', bundled };
541
+ fs.mkdirSync(path.dirname(HOOK_DEST), { recursive: true });
542
+ fs.copyFileSync(HOOK_SOURCE, HOOK_DEST);
543
+ fs.chmodSync(HOOK_DEST, 0o644);
544
+ return { status: 'installed', bundled };
545
+ }
546
+ const installed = readVersion(HOOK_DEST);
547
+ if (installed !== null && installed >= bundled) {
548
+ return { status: 'up-to-date', installed, bundled };
549
+ }
550
+ // Sprint 51.6 T4-CODEX audit 20:23 ET safety gate: an unsigned installed
551
+ // hook gets refreshed ONLY if it looks TermDeck-managed (carries one of
552
+ // the docstring markers from a prior bundled cut). A genuinely custom
553
+ // user hook with no TermDeck fingerprint stays put.
554
+ if (installed === null && !looksTermdeckManaged(HOOK_DEST)) {
555
+ return {
556
+ status: 'custom-hook-preserved',
557
+ message: 'installed hook lacks TermDeck-managed markers; keeping as-is. Re-run with --force-overwrite to bypass.',
558
+ bundled,
559
+ };
560
+ }
561
+ if (dryRun) return { status: 'would-refresh', from: installed, to: bundled };
562
+ const stamp = new Date().toISOString().replace(/[-:T.Z]/g, '').slice(0, 14);
563
+ const backup = `${HOOK_DEST}.bak.${stamp}`;
564
+ try { fs.copyFileSync(HOOK_DEST, backup); } catch (_) { /* best-effort */ }
565
+ fs.copyFileSync(HOOK_SOURCE, HOOK_DEST);
566
+ fs.chmodSync(HOOK_DEST, 0o644);
567
+ return { status: 'refreshed', from: installed, to: bundled, backup };
568
+ }
569
+
446
570
  function printNextSteps() {
447
571
  process.stdout.write(`
448
572
  Mnestra is configured.
@@ -538,7 +662,29 @@ async function main(argv) {
538
662
  try {
539
663
  await checkExistingStore(client);
540
664
  await applyMigrations(client, false);
665
+ await runMnestraAudit(client, inputs.projectUrl.projectRef, false);
541
666
  writeYamlConfig(false);
667
+ // Sprint 51.6 T3: refresh ~/.claude/hooks/memory-session-end.js when the
668
+ // bundled hook's version stamp is newer than the installed copy. Closes
669
+ // the upgrade gap where bundled fixes never reached users' machines via
670
+ // the standard `npm install -g @jhizzard/termdeck@latest && termdeck
671
+ // init --mnestra` path. Best-effort, timestamped backup, fail-soft.
672
+ step('Refreshing ~/.claude/hooks/memory-session-end.js (if bundled is newer)...');
673
+ try {
674
+ const r = refreshBundledHookIfNewer({ dryRun: false });
675
+ if (r.status === 'refreshed') {
676
+ ok(`refreshed v${r.from ?? 0} → v${r.to} (backup: ${path.basename(r.backup)})`);
677
+ } else if (r.status === 'installed') {
678
+ ok(`installed v${r.bundled} (no prior copy)`);
679
+ } else if (r.status === 'up-to-date') {
680
+ ok(`up-to-date (v${r.installed})`);
681
+ } else {
682
+ ok(`(${r.status}${r.message ? ': ' + r.message : ''})`);
683
+ }
684
+ } catch (err) {
685
+ // Don't abort init for a hook-refresh failure — log + continue.
686
+ process.stdout.write(` ! hook refresh failed: ${err.message} (continuing)\n`);
687
+ }
542
688
  // v0.6.9: post-write outcome verification. Confirms each migration's
543
689
  // expected schema bits actually landed — including memory_items.
544
690
  // source_session_id (the v0.6.5 column whose absence cascaded into
@@ -584,3 +730,5 @@ if (require.main === module) {
584
730
  }
585
731
 
586
732
  module.exports = main;
733
+ // Sprint 51.6 T3 — exported for tests/init-mnestra-hook-refresh.test.js.
734
+ module.exports.refreshBundledHookIfNewer = refreshBundledHookIfNewer;