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.
- package/dist/cli.js +98 -1
- package/package.json +1 -1
- 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
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();
|