Skip to content

Commit 734becf

Browse files
authored
fix(mcp,cli): reduce redundancy on memory tree/fetch output formats (#9)
## Summary Reduce redundancy on `MemoryFetch`, `MemoryTree` and `remindb inspect --tree` output formats. ## Self-review ### Verified - [x] Tested manually via CLI or local MCP plugin install - [x] Ran the test suite with fuzzing (`make test-all`, `make fuzz`) ### Touched - [x] **MCP tools or public skills** — code and skill stay in sync (`skills/remind/`, `skills/memoize/`) - [ ] **Temperature config** — both public skills reflect new values - [ ] **Parser** — fuzz target covers the change - [ ] **Schema or migrations** — FTS5 triggers in sync - [ ] **Write path** — each write tool call still produces exactly one snapshot - [ ] **Client output format** — unchanged, or breaking change noted in summary - [x] **CLI** — user-facing changes documented in summary
1 parent 03b15d5 commit 734becf

6 files changed

Lines changed: 107 additions & 20 deletions

File tree

README.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,15 @@ A real slice (from `remindb inspect --tree`):
4242
```
4343
[preamble] Preamble: framework, language, project (id=3kGXxidmWBp file=CLAUDE.md temp=0.50 tok=14)
4444
[heading] Project Instructions (id=6EuIVj5zt5j file=CLAUDE.md temp=0.75 tok=5)
45-
[heading] Architecture (id=603qfsg4qd2 file=CLAUDE.md temp=0.88 tok=3)
46-
[text] Next.js 15 conventions with a clear separation of data… (id=3GGuLAq3yNP file=CLAUDE.md temp=0.82 tok=111)
47-
[list] 7-item list: app/, components/, lib/, db/, hooks/, types… (id=ITAKw5NVNPt file=CLAUDE.md temp=0.71 tok=228)
48-
[heading] Data Model (id=FQwpXL4bm6Y file=CLAUDE.md temp=0.62 tok=3)
49-
[list] 7-item list: products, variants, orders, carts, users, s… (id=Il8jcgTJOGt file=CLAUDE.md temp=0.55 tok=155)
50-
[heading] Payment Integration (id=LTQZLSkPsDW file=CLAUDE.md temp=0.30 tok=5)
51-
[text] Stripe Payment Intents; not legacy Checkout Sessions… (id=GLbXrUYs32G file=CLAUDE.md temp=0.24 tok=35)
52-
[heading] Observability (id=2wkOdf47OjR file=CLAUDE.md temp=0.08 tok=4)
53-
[list] 4-item list: Sentry · Vercel logs · OTel tracing · Prom… (id=C1HCYSAOkpu file=CLAUDE.md temp=0.08 tok=90)
45+
[heading] Architecture (id=603qfsg4qd2 temp=0.88 tok=3)
46+
[text] Next.js 15 conventions with a clear separation of data… (id=3GGuLAq3yNP temp=0.82 tok=111)
47+
[list] 7-item list: app/, components/, lib/, db/, hooks/, types… (id=ITAKw5NVNPt temp=0.71 tok=228)
48+
[heading] Data Model (id=FQwpXL4bm6Y temp=0.62 tok=3)
49+
[list] 7-item list: products, variants, orders, carts, users, s… (id=Il8jcgTJOGt temp=0.55 tok=155)
50+
[heading] Payment Integration (id=LTQZLSkPsDW temp=0.30 tok=5)
51+
[text] Stripe Payment Intents; not legacy Checkout Sessions… (id=GLbXrUYs32G temp=0.24 tok=35)
52+
[heading] Observability (id=2wkOdf47OjR temp=0.08 tok=4)
53+
[list] 4-item list: Sentry · Vercel logs · OTel tracing · Prom… (id=C1HCYSAOkpu temp=0.08 tok=90)
5454
```
5555

5656
A fresh compile starts every node at `temp=0.50`. The spread above is what an agent sees after a few sessions of reading. *Architecture* is hot because the agent keeps coming back to it. *Observability* has gone cold and will get flagged for summarization on the next nudge.

cmd/remindb/inspect.go

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"io"
77
"os"
8+
"path/filepath"
89
"strings"
910
"unicode/utf8"
1011

@@ -89,8 +90,10 @@ func runInspect(cmd *cobra.Command, _ []string) error {
8990
_, _ = fmt.Fprintln(w, paint(ansiBold+ansiCyan, "=== Node Tree ==="))
9091
roots, childMap := store.BuildTree(all)
9192

93+
compileRoot, _ := st.GetLatestCompileRoot(ctx)
94+
9295
for _, root := range roots {
93-
printTree(w, childMap, root, 0, inspectTreeDepth)
96+
printTree(w, childMap, root, "", compileRoot, 0, inspectTreeDepth)
9497
}
9598
return nil
9699
}
@@ -121,15 +124,21 @@ func printStats(w io.Writer, s *store.Stats) {
121124
_, _ = fmt.Fprintln(w)
122125
}
123126

124-
func printTree(w io.Writer, children map[string][]*store.Node, n *store.Node, depth, maxDepth int) {
127+
func printTree(w io.Writer, children map[string][]*store.Node, n *store.Node, parentSource, compileRoot string, depth, maxDepth int) {
125128
indent := strings.Repeat(" ", depth)
126129

127-
_, _ = fmt.Fprintf(w, "%s%s %s (%s %s %s %s)\n",
130+
_, _ = fmt.Fprintf(w, "%s%s %s (%s",
128131
indent,
129132
paint(ansiYellow, "["+n.NodeType+"]"),
130133
paint(ansiBrightW, n.Label),
131134
paint(ansiDim, "id="+n.ID),
132-
paint(ansiDim, "file="+n.SourceFile),
135+
)
136+
137+
if n.SourceFile != parentSource {
138+
_, _ = fmt.Fprintf(w, " %s", paint(ansiDim, "file="+relSourcePath(n.SourceFile, compileRoot)))
139+
}
140+
141+
_, _ = fmt.Fprintf(w, " %s %s)\n",
133142
"temp="+tempPaint(n.Temperature),
134143
paint(ansiDim, fmt.Sprintf("tok=%d", n.TokenCount)),
135144
)
@@ -139,10 +148,23 @@ func printTree(w io.Writer, children map[string][]*store.Node, n *store.Node, de
139148
}
140149

141150
for _, child := range children[n.ID] {
142-
printTree(w, children, child, depth+1, maxDepth)
151+
printTree(w, children, child, n.SourceFile, compileRoot, depth+1, maxDepth)
143152
}
144153
}
145154

155+
func relSourcePath(source, compileRoot string) string {
156+
if compileRoot == "" || !filepath.IsAbs(source) {
157+
return source
158+
}
159+
160+
rel, err := filepath.Rel(compileRoot, source)
161+
if err != nil || strings.HasPrefix(rel, "..") {
162+
return source
163+
}
164+
165+
return rel
166+
}
167+
146168
func paint(code, s string) string {
147169
if !inspectColorOn {
148170
return s

pkg/mcp/tools/tools_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"os"
66
"path/filepath"
7+
"strings"
78
"testing"
89
"time"
910

@@ -369,6 +370,49 @@ func TestHandleTree(t *testing.T) {
369370
}
370371
}
371372

373+
func TestHandleTree_FileFieldRelativeAndOnRootOnly(t *testing.T) {
374+
d, st := setup(t)
375+
ctx := context.Background()
376+
dir := t.TempDir()
377+
378+
aPath := filepath.Join(dir, "a.md")
379+
bPath := filepath.Join(dir, "b.md")
380+
if err := os.WriteFile(aPath, []byte("# A heading\n\nA paragraph.\n"), 0o644); err != nil {
381+
t.Fatal(err)
382+
}
383+
if err := os.WriteFile(bPath, []byte("# B heading\n\nB paragraph.\n"), 0o644); err != nil {
384+
t.Fatal(err)
385+
}
386+
387+
if _, err := compiler.CompileDir(ctx, st, dir, "initial"); err != nil {
388+
t.Fatalf("CompileDir: %v", err)
389+
}
390+
391+
result, _, err := d.HandleTree(ctx, &gomcp.CallToolRequest{}, TreeInput{})
392+
if err != nil {
393+
t.Fatalf("HandleTree: %v", err)
394+
}
395+
if len(result.Content) == 0 {
396+
t.Fatal("empty content")
397+
}
398+
399+
text := result.Content[0].(*gomcp.TextContent).Text
400+
401+
if !strings.Contains(text, "file=a.md") {
402+
t.Errorf("expected file=a.md (relative), got:\n%s", text)
403+
}
404+
if !strings.Contains(text, "file=b.md") {
405+
t.Errorf("expected file=b.md (relative), got:\n%s", text)
406+
}
407+
if strings.Contains(text, "file="+dir) {
408+
t.Errorf("absolute path leaked into output:\n%s", text)
409+
}
410+
411+
if got := strings.Count(text, "file="); got != 2 {
412+
t.Errorf("expected 2 file= entries (one per file root, none on descendants), got %d. Tree:\n%s", got, text)
413+
}
414+
}
415+
372416
func TestHandleWrite_BlocksOnOpMu(t *testing.T) {
373417
d, st := setup(t)
374418
ctx := context.Background()

pkg/mcp/tools/tree.go

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package tools
33
import (
44
"context"
55
"fmt"
6+
"path/filepath"
67
"strings"
78
"time"
89

@@ -44,9 +45,11 @@ func (d *Deps) HandleTree(ctx context.Context, _ *gomcp.CallToolRequest, input T
4445
roots = []*store.Node{root}
4546
}
4647

48+
compileRoot, _ := d.Store.GetLatestCompileRoot(ctx)
49+
4750
var b strings.Builder
4851
for _, root := range roots {
49-
writeTreeNode(&b, childMap, root, 0, maxDepth)
52+
writeTreeNode(&b, childMap, root, "", compileRoot, 0, maxDepth)
5053
}
5154

5255
if b.Len() == 0 {
@@ -59,15 +62,33 @@ func (d *Deps) HandleTree(ctx context.Context, _ *gomcp.CallToolRequest, input T
5962
}, nil, nil
6063
}
6164

62-
func writeTreeNode(b *strings.Builder, children map[string][]*store.Node, n *store.Node, depth, maxDepth int) {
65+
func writeTreeNode(b *strings.Builder, children map[string][]*store.Node, n *store.Node, parentSource, compileRoot string, depth, maxDepth int) {
6366
indent := strings.Repeat(" ", depth)
64-
fmt.Fprintf(b, "%s[%s] %s (id=%s file=%s temp=%.2f tok=%d)\n", indent, n.NodeType, n.Label, n.ID, n.SourceFile, n.Temperature, n.TokenCount)
67+
fmt.Fprintf(b, "%s[%s] %s (id=%s", indent, n.NodeType, n.Label, n.ID)
68+
69+
if n.SourceFile != parentSource {
70+
fmt.Fprintf(b, " file=%s", relSourcePath(n.SourceFile, compileRoot))
71+
}
72+
fmt.Fprintf(b, " temp=%.2f tok=%d)\n", n.Temperature, n.TokenCount)
6573

6674
if depth >= maxDepth {
6775
return
6876
}
6977

7078
for _, child := range children[n.ID] {
71-
writeTreeNode(b, children, child, depth+1, maxDepth)
79+
writeTreeNode(b, children, child, n.SourceFile, compileRoot, depth+1, maxDepth)
7280
}
7381
}
82+
83+
func relSourcePath(source, compileRoot string) string {
84+
if compileRoot == "" || !filepath.IsAbs(source) {
85+
return source
86+
}
87+
88+
rel, err := filepath.Rel(compileRoot, source)
89+
if err != nil || strings.HasPrefix(rel, "..") {
90+
return source
91+
}
92+
93+
return rel
94+
}

pkg/query/engine_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ func TestFormat(t *testing.T) {
293293
t.Fatal("empty output")
294294
}
295295

296-
expected := "[heading] Title (score=1.25)\nhello world\n\n---\n\n[code] Example (score=2.50)\nx := 1\n"
296+
expected := "[heading] (score=1.25)\nhello world\n\n---\n\n[code] (score=2.50)\nx := 1\n"
297297
if out != expected {
298298
t.Errorf("Format =\n%q\nwant\n%q", out, expected)
299299
}

pkg/query/format.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ func Format(result *Result) string {
1313
b.WriteString("\n---\n\n")
1414
}
1515
n := sn.Node
16-
fmt.Fprintf(&b, "[%s] %s (score=%.2f)\n", n.NodeType, n.Label, sn.Score)
16+
fmt.Fprintf(&b, "[%s] (score=%.2f)\n", n.NodeType, sn.Score)
1717

1818
b.WriteString(n.Content)
1919
b.WriteByte('\n')

0 commit comments

Comments
 (0)