stackmem 1.0.10 → 1.0.11

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.
Files changed (3) hide show
  1. package/dist/cli.js +98 -1
  2. package/package.json +1 -1
  3. package/src/cli.ts +118 -0
package/dist/cli.js CHANGED
@@ -6,7 +6,7 @@ import { fileURLToPath } from "node:url";
6
6
  import { createInterface } from "node:readline/promises";
7
7
  import { analyzeCodebase } from "./ast-index.js";
8
8
  import { getDeviceId } from "./device.js";
9
- import { applyContextBudget, buildSummaryContent, clusterMemories, computeDecayScore, computeMemoryLinks, decideSaveAction, estimateTokens, indicatesFailedApproach, isAuthRelatedFile, isCodeFile, isQualityMessage, scoreMemoryRelevance, } from "./scoring.js";
9
+ import { applyContextBudget, buildSummaryContent, clusterMemories, computeDecayScore, computeMemoryLinks, decideSaveAction, estimateTokens, extractEntities, indicatesFailedApproach, isAuthRelatedFile, isCodeFile, isQualityMessage, scoreMemoryRelevance, } from "./scoring.js";
10
10
  import { supabase } from "./storage.js";
11
11
  const CONTEXT_BUDGET_TOKENS = 2000;
12
12
  const COMPRESSION_THRESHOLD = 20;
@@ -32,6 +32,7 @@ function usage() {
32
32
  cm search [--project <name>] <query>
33
33
  cm resolve [project] <memory-id>
34
34
  cm delete [project] <memory-id>
35
+ cm inspect [project] <memory-id>
35
36
  cm compress [project]
36
37
  cm init <project> <path>
37
38
  cm context [--project <name>] <task>
@@ -88,6 +89,13 @@ function cmdHelp() {
88
89
  cm delete [project] <memory-id>
89
90
  Delete a memory and any memory_links referencing it.
90
91
 
92
+ cm inspect [project] <memory-id>
93
+ Show a rich view of a single memory: type, content, created
94
+ date, access count, decay score, and resolved status, plus
95
+ every linked memory (in either link direction) with its link
96
+ score, and any execution_log fixes whose problem or solution
97
+ shares a key entity with the memory's content.
98
+
91
99
  cm compress [project]
92
100
  Cluster related unresolved memories (needs 20+) and collapse
93
101
  clusters of 3 or more into a single summary memory.
@@ -496,6 +504,86 @@ async function cmdDelete(project, memoryId) {
496
504
  fail(`Failed to delete memory: ${error.message}`);
497
505
  console.log(`Deleted memory [${memoryId}]`);
498
506
  }
507
+ // --- cm inspect ------------------------------------------------------------
508
+ function formatDate(dateString) {
509
+ return new Date(dateString).toLocaleDateString("en-US", {
510
+ month: "short",
511
+ day: "numeric",
512
+ year: "numeric",
513
+ });
514
+ }
515
+ async function cmdInspect(project, memoryId) {
516
+ const { data: memory, error } = await supabase
517
+ .from("memories")
518
+ .select("*")
519
+ .eq("project", project)
520
+ .eq("id", memoryId)
521
+ .single();
522
+ if (error || !memory)
523
+ fail(`Memory [${memoryId}] not found for "${project}".`);
524
+ const decayScore = computeDecayScore(new Date(memory.created_at), memory.access_count ?? 0);
525
+ const { data: links, error: linksError } = await supabase
526
+ .from("memory_links")
527
+ .select("source_id, target_id, score")
528
+ .or(`source_id.eq.${memoryId},target_id.eq.${memoryId}`);
529
+ if (linksError)
530
+ fail(`Failed to load memory links: ${linksError.message}`);
531
+ const linkedEntries = (links ?? []).map((link) => ({
532
+ linkedId: link.source_id === memoryId ? link.target_id : link.source_id,
533
+ score: link.score,
534
+ }));
535
+ let linkedMemories = [];
536
+ if (linkedEntries.length > 0) {
537
+ const linkedIds = linkedEntries.map((entry) => entry.linkedId);
538
+ const { data: linkedRows, error: linkedError } = await supabase
539
+ .from("memories")
540
+ .select("id, type, content, created_at")
541
+ .in("id", linkedIds);
542
+ if (linkedError)
543
+ fail(`Failed to load linked memories: ${linkedError.message}`);
544
+ const scoreById = new Map(linkedEntries.map((entry) => [entry.linkedId, entry.score]));
545
+ linkedMemories = (linkedRows ?? [])
546
+ .map((row) => ({ ...row, score: scoreById.get(row.id) ?? 0 }))
547
+ .sort((a, b) => b.score - a.score);
548
+ }
549
+ const entities = extractEntities(memory.content);
550
+ const { data: fixes, error: fixesError } = await supabase
551
+ .from("execution_log")
552
+ .select("problem, solution")
553
+ .eq("project", project);
554
+ if (fixesError)
555
+ fail(`Failed to load execution log: ${fixesError.message}`);
556
+ const relatedFixes = (fixes ?? []).filter((fix) => {
557
+ const fixEntities = extractEntities(`${fix.problem} ${fix.solution}`);
558
+ for (const entity of entities) {
559
+ if (fixEntities.has(entity))
560
+ return true;
561
+ }
562
+ return false;
563
+ });
564
+ const divider = "─".repeat(40);
565
+ console.log(divider);
566
+ console.log(`${memory.type.toUpperCase()} · accessed ${memory.access_count ?? 0}x · decay ${decayScore.toFixed(2)}`);
567
+ console.log(divider);
568
+ console.log(memory.content);
569
+ console.log("");
570
+ console.log(`Created: ${formatDate(memory.created_at)}`);
571
+ console.log(`Status: ${memory.resolved ? "resolved" : "unresolved"}`);
572
+ console.log("");
573
+ console.log(`LINKED MEMORIES (${linkedMemories.length})`);
574
+ for (const linked of linkedMemories) {
575
+ console.log(` └─ [score: ${linked.score.toFixed(2)}] ${linked.type.toUpperCase()}`);
576
+ console.log(` ${linked.content}`);
577
+ console.log(` ${formatDate(linked.created_at)}`);
578
+ console.log("");
579
+ }
580
+ if (relatedFixes.length > 0) {
581
+ console.log(`RELATED FIXES (${relatedFixes.length})`);
582
+ for (const fix of relatedFixes) {
583
+ console.log(` └─ FIX: ${fix.problem} → ${fix.solution}`);
584
+ }
585
+ }
586
+ }
499
587
  // --- cm init -------------------------------------------------------------
500
588
  // The hook logic lives in its own .mjs file (not directly in
501
589
  // .git/hooks/post-commit) so its module type is unambiguous — an
@@ -1418,6 +1506,15 @@ async function main() {
1418
1506
  await cmdDelete(project, memoryId);
1419
1507
  break;
1420
1508
  }
1509
+ case "inspect": {
1510
+ const split = splitOptionalProject(args, 1);
1511
+ if (!split)
1512
+ usage();
1513
+ const project = requireProject(split.project);
1514
+ const [memoryId] = split.rest;
1515
+ await cmdInspect(project, memoryId);
1516
+ break;
1517
+ }
1421
1518
  case "compress": {
1422
1519
  const split = splitOptionalProject(args, 0);
1423
1520
  if (!split)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stackmem",
3
- "version": "1.0.10",
3
+ "version": "1.0.11",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "type": "module",
package/src/cli.ts CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  computeMemoryLinks,
15
15
  decideSaveAction,
16
16
  estimateTokens,
17
+ extractEntities,
17
18
  indicatesFailedApproach,
18
19
  isAuthRelatedFile,
19
20
  isCodeFile,
@@ -50,6 +51,7 @@ function usage(): never {
50
51
  cm search [--project <name>] <query>
51
52
  cm resolve [project] <memory-id>
52
53
  cm delete [project] <memory-id>
54
+ cm inspect [project] <memory-id>
53
55
  cm compress [project]
54
56
  cm init <project> <path>
55
57
  cm context [--project <name>] <task>
@@ -107,6 +109,13 @@ function cmdHelp() {
107
109
  cm delete [project] <memory-id>
108
110
  Delete a memory and any memory_links referencing it.
109
111
 
112
+ cm inspect [project] <memory-id>
113
+ Show a rich view of a single memory: type, content, created
114
+ date, access count, decay score, and resolved status, plus
115
+ every linked memory (in either link direction) with its link
116
+ score, and any execution_log fixes whose problem or solution
117
+ shares a key entity with the memory's content.
118
+
110
119
  cm compress [project]
111
120
  Cluster related unresolved memories (needs 20+) and collapse
112
121
  clusters of 3 or more into a single summary memory.
@@ -593,6 +602,107 @@ async function cmdDelete(project: string, memoryId: string) {
593
602
  console.log(`Deleted memory [${memoryId}]`);
594
603
  }
595
604
 
605
+ // --- cm inspect ------------------------------------------------------------
606
+
607
+ function formatDate(dateString: string): string {
608
+ return new Date(dateString).toLocaleDateString("en-US", {
609
+ month: "short",
610
+ day: "numeric",
611
+ year: "numeric",
612
+ });
613
+ }
614
+
615
+ async function cmdInspect(project: string, memoryId: string) {
616
+ const { data: memory, error } = await supabase
617
+ .from("memories")
618
+ .select("*")
619
+ .eq("project", project)
620
+ .eq("id", memoryId)
621
+ .single();
622
+
623
+ if (error || !memory) fail(`Memory [${memoryId}] not found for "${project}".`);
624
+
625
+ const decayScore = computeDecayScore(new Date(memory.created_at), memory.access_count ?? 0);
626
+
627
+ const { data: links, error: linksError } = await supabase
628
+ .from("memory_links")
629
+ .select("source_id, target_id, score")
630
+ .or(`source_id.eq.${memoryId},target_id.eq.${memoryId}`);
631
+
632
+ if (linksError) fail(`Failed to load memory links: ${linksError.message}`);
633
+
634
+ const linkedEntries = (links ?? []).map((link) => ({
635
+ linkedId: link.source_id === memoryId ? link.target_id : link.source_id,
636
+ score: link.score,
637
+ }));
638
+
639
+ let linkedMemories: Array<{
640
+ id: string;
641
+ type: string;
642
+ content: string;
643
+ created_at: string;
644
+ score: number;
645
+ }> = [];
646
+
647
+ if (linkedEntries.length > 0) {
648
+ const linkedIds = linkedEntries.map((entry) => entry.linkedId);
649
+ const { data: linkedRows, error: linkedError } = await supabase
650
+ .from("memories")
651
+ .select("id, type, content, created_at")
652
+ .in("id", linkedIds);
653
+
654
+ if (linkedError) fail(`Failed to load linked memories: ${linkedError.message}`);
655
+
656
+ const scoreById = new Map(linkedEntries.map((entry) => [entry.linkedId, entry.score]));
657
+ linkedMemories = (linkedRows ?? [])
658
+ .map((row) => ({ ...row, score: scoreById.get(row.id) ?? 0 }))
659
+ .sort((a, b) => b.score - a.score);
660
+ }
661
+
662
+ const entities = extractEntities(memory.content);
663
+ const { data: fixes, error: fixesError } = await supabase
664
+ .from("execution_log")
665
+ .select("problem, solution")
666
+ .eq("project", project);
667
+
668
+ if (fixesError) fail(`Failed to load execution log: ${fixesError.message}`);
669
+
670
+ const relatedFixes = (fixes ?? []).filter((fix) => {
671
+ const fixEntities = extractEntities(`${fix.problem} ${fix.solution}`);
672
+ for (const entity of entities) {
673
+ if (fixEntities.has(entity)) return true;
674
+ }
675
+ return false;
676
+ });
677
+
678
+ const divider = "─".repeat(40);
679
+ console.log(divider);
680
+ console.log(
681
+ `${memory.type.toUpperCase()} · accessed ${memory.access_count ?? 0}x · decay ${decayScore.toFixed(2)}`
682
+ );
683
+ console.log(divider);
684
+ console.log(memory.content);
685
+ console.log("");
686
+ console.log(`Created: ${formatDate(memory.created_at)}`);
687
+ console.log(`Status: ${memory.resolved ? "resolved" : "unresolved"}`);
688
+ console.log("");
689
+
690
+ console.log(`LINKED MEMORIES (${linkedMemories.length})`);
691
+ for (const linked of linkedMemories) {
692
+ console.log(` └─ [score: ${linked.score.toFixed(2)}] ${linked.type.toUpperCase()}`);
693
+ console.log(` ${linked.content}`);
694
+ console.log(` ${formatDate(linked.created_at)}`);
695
+ console.log("");
696
+ }
697
+
698
+ if (relatedFixes.length > 0) {
699
+ console.log(`RELATED FIXES (${relatedFixes.length})`);
700
+ for (const fix of relatedFixes) {
701
+ console.log(` └─ FIX: ${fix.problem} → ${fix.solution}`);
702
+ }
703
+ }
704
+ }
705
+
596
706
  // --- cm init -------------------------------------------------------------
597
707
 
598
708
  // The hook logic lives in its own .mjs file (not directly in
@@ -1600,6 +1710,14 @@ async function main() {
1600
1710
  await cmdDelete(project, memoryId);
1601
1711
  break;
1602
1712
  }
1713
+ case "inspect": {
1714
+ const split = splitOptionalProject(args, 1);
1715
+ if (!split) usage();
1716
+ const project = requireProject(split.project);
1717
+ const [memoryId] = split.rest;
1718
+ await cmdInspect(project, memoryId);
1719
+ break;
1720
+ }
1603
1721
  case "compress": {
1604
1722
  const split = splitOptionalProject(args, 0);
1605
1723
  if (!split) usage();