Skip to content

Commit 7739086

Browse files
committed
feat: list_modules MCP tool, cache invalidation, table search, demo GIF
- Add list_modules MCP tool: returns all modules sorted by name - MCP cache invalidation: check stacklit.json mtime on each call - HTML table view: search/filter input for modules - Demo GIF recorded with vhs for README
1 parent bc32c27 commit 7739086

7 files changed

Lines changed: 179 additions & 53 deletions

File tree

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,7 @@ $ stacklit init
2727
Opening visual map...
2828
```
2929

30-
<!-- TODO: replace with terminal recording GIF -->
31-
<!-- ![demo](https://github.com/glincker/stacklit/assets/demo.gif) -->
30+
![Stacklit demo](demo.gif)
3231

3332
### Without stacklit
3433

assets/template.html

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,28 @@
304304
padding: 16px;
305305
}
306306

307+
#table-search {
308+
background: transparent;
309+
border: none;
310+
border-bottom: 1px solid var(--border);
311+
color: var(--text);
312+
font-family: var(--font-mono, monospace);
313+
font-size: 11px;
314+
outline: none;
315+
padding: 4px 2px;
316+
width: 100%;
317+
margin-bottom: 12px;
318+
display: block;
319+
}
320+
321+
#table-search::placeholder {
322+
color: var(--text-muted);
323+
}
324+
325+
#table-search:focus {
326+
border-bottom-color: var(--accent);
327+
}
328+
307329
#view-table::-webkit-scrollbar { width: 4px; }
308330
#view-table::-webkit-scrollbar-track { background: transparent; }
309331
#view-table::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
@@ -761,6 +783,7 @@ <h1>stacklit <span id="proj-name">—</span></h1>
761783

762784
<!-- View 3: Table -->
763785
<div id="view-table" class="view">
786+
<input id="table-search" type="search" placeholder="Search modules..." autocomplete="off" spellcheck="false">
764787
<table id="mod-table">
765788
<thead>
766789
<tr>
@@ -1798,6 +1821,7 @@ <h2>Languages</h2>
17981821
let tableSortCol = 'lines';
17991822
let tableSortDir = -1; // -1 = desc
18001823
let selectedTableRow = null;
1824+
let tableSearchQuery = '';
18011825

18021826
function buildTableView() {
18031827
renderTable();
@@ -1814,6 +1838,14 @@ <h2>Languages</h2>
18141838
renderTable();
18151839
});
18161840
});
1841+
1842+
const searchInput = document.getElementById('table-search');
1843+
if (searchInput) {
1844+
searchInput.addEventListener('input', function () {
1845+
tableSearchQuery = searchInput.value;
1846+
renderTable();
1847+
});
1848+
}
18171849
}
18181850

18191851
function renderTable() {
@@ -1825,8 +1857,18 @@ <h2>Languages</h2>
18251857
}
18261858
});
18271859

1860+
// Filter nodes by search query
1861+
const needle = tableSearchQuery.trim().toLowerCase();
1862+
const filtered = needle
1863+
? nodeArr.filter(function (n) {
1864+
const name = (n.id || '').toLowerCase();
1865+
const purpose = ((n.mod && n.mod.purpose) || '').toLowerCase();
1866+
return name.indexOf(needle) !== -1 || purpose.indexOf(needle) !== -1;
1867+
})
1868+
: nodeArr;
1869+
18281870
// Sort nodes
1829-
const sorted = nodeArr.slice().sort(function (a, b) {
1871+
const sorted = filtered.slice().sort(function (a, b) {
18301872
let av, bv;
18311873
const col = tableSortCol;
18321874
if (col === 'id') { av = a.id; bv = b.id; }

demo.gif

228 KB
Loading

demo.tape

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
Output demo.gif
2+
3+
Set Shell "bash"
4+
Set FontSize 14
5+
Set Width 900
6+
Set Height 500
7+
Set Theme { "name": "stacklit", "black": "#0d1117", "red": "#f85149", "green": "#56d364", "yellow": "#e3b341", "blue": "#58a6ff", "magenta": "#bc8cff", "cyan": "#39d2c0", "white": "#c9d1d9", "brightBlack": "#484f58", "brightRed": "#f85149", "brightGreen": "#56d364", "brightYellow": "#e3b341", "brightBlue": "#58a6ff", "brightMagenta": "#bc8cff", "brightCyan": "#39d2c0", "brightWhite": "#e6edf3", "background": "#0d1117", "foreground": "#c9d1d9", "selectionBackground": "#264f78", "cursorColor": "#c9d1d9" }
8+
Set Padding 20
9+
10+
Type "# Stacklit: your codebase, in 1,500 tokens"
11+
Enter
12+
Sleep 1s
13+
14+
Type "stacklit init"
15+
Enter
16+
Sleep 3s
17+
18+
Type ""
19+
Sleep 1s
20+
21+
Type "# stacklit.json is now committed to git"
22+
Enter
23+
Sleep 500ms
24+
25+
Type "cat stacklit.json | head -25"
26+
Enter
27+
Sleep 3s
28+
29+
Type ""
30+
Sleep 1s
31+
32+
Type "# Start MCP server for AI agents"
33+
Enter
34+
Sleep 500ms
35+
36+
Type "# stacklit serve"
37+
Enter
38+
Sleep 2s

internal/mcp/server.go

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,31 @@ import (
55
"encoding/json"
66
"fmt"
77
"os"
8+
"sort"
89
"strings"
10+
"time"
911

1012
sdkmcp "github.com/modelcontextprotocol/go-sdk/mcp"
1113

1214
"github.com/glincker/stacklit/internal/schema"
1315
)
1416

1517
var cachedIndex *schema.Index
18+
var cachedIndexModTime time.Time
1619

1720
func loadIndex() (*schema.Index, error) {
18-
if cachedIndex != nil {
21+
const indexFile = "stacklit.json"
22+
23+
info, err := os.Stat(indexFile)
24+
if err != nil {
25+
return nil, fmt.Errorf("stacklit.json not found — run 'stacklit init' first")
26+
}
27+
28+
if cachedIndex != nil && !info.ModTime().After(cachedIndexModTime) {
1929
return cachedIndex, nil
2030
}
21-
data, err := os.ReadFile("stacklit.json")
31+
32+
data, err := os.ReadFile(indexFile)
2233
if err != nil {
2334
return nil, fmt.Errorf("stacklit.json not found — run 'stacklit init' first")
2435
}
@@ -27,6 +38,7 @@ func loadIndex() (*schema.Index, error) {
2738
return nil, fmt.Errorf("failed to parse stacklit.json: %w", err)
2839
}
2940
cachedIndex = &idx
41+
cachedIndexModTime = info.ModTime()
3042
return cachedIndex, nil
3143
}
3244

@@ -154,6 +166,41 @@ func StartServer() error {
154166
})
155167
})
156168

169+
// list_modules — all modules with summary info, sorted by name
170+
sdkmcp.AddTool(s, &sdkmcp.Tool{
171+
Name: "list_modules",
172+
Description: "List all modules with their purpose, file/line counts, and activity — sorted by name",
173+
}, func(ctx context.Context, req *sdkmcp.CallToolRequest, _ emptyArgs) (*sdkmcp.CallToolResult, any, error) {
174+
idx, err := loadIndex()
175+
if err != nil {
176+
return errResult(err)
177+
}
178+
type moduleSummary struct {
179+
Name string `json:"name"`
180+
Purpose string `json:"purpose"`
181+
Files int `json:"files"`
182+
Lines int `json:"lines"`
183+
Activity string `json:"activity,omitempty"`
184+
}
185+
names := make([]string, 0, len(idx.Modules))
186+
for name := range idx.Modules {
187+
names = append(names, name)
188+
}
189+
sort.Strings(names)
190+
summaries := make([]moduleSummary, 0, len(names))
191+
for _, name := range names {
192+
mod := idx.Modules[name]
193+
summaries = append(summaries, moduleSummary{
194+
Name: name,
195+
Purpose: mod.Purpose,
196+
Files: mod.Files,
197+
Lines: mod.Lines,
198+
Activity: mod.Activity,
199+
})
200+
}
201+
return textResult(summaries)
202+
})
203+
157204
// get_hot_files — git churn list
158205
sdkmcp.AddTool(s, &sdkmcp.Tool{
159206
Name: "get_hot_files",

stacklit.json

Lines changed: 36 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
{
22
"$schema": "https://stacklit.dev/schema/v1.json",
33
"version": "1",
4-
"generated_at": "2026-04-09T22:21:13Z",
4+
"generated_at": "2026-04-09T23:56:36Z",
55
"stacklit_version": "dev",
6-
"merkle_hash": "dbf1968b555b90f2eb497376531e8b6e6da14588b79e47ff5a836f9e4432287d",
6+
"merkle_hash": "fdac4b84e691421e649f3523cb688eb24554439ade7ccea2f27a3d3cb3466ae9",
77
"project": {
88
"name": "stacklit",
99
"root": ".",
@@ -14,15 +14,15 @@
1414
"languages": {
1515
"go": {
1616
"files": 48,
17-
"lines": 5650
17+
"lines": 5697
1818
},
1919
"java": {
2020
"files": 1,
2121
"lines": 16
2222
},
2323
"javascript": {
2424
"files": 3,
25-
"lines": 137
25+
"lines": 133
2626
},
2727
"python": {
2828
"files": 1,
@@ -47,7 +47,7 @@
4747
"cmd/stacklit/main.go"
4848
],
4949
"total_files": 59,
50-
"total_lines": 5866
50+
"total_lines": 5909
5151
},
5252
"modules": {
5353
"assets": {
@@ -259,7 +259,7 @@
259259
"purpose": "MCP server for AI agents",
260260
"language": "go",
261261
"files": 1,
262-
"lines": 187,
262+
"lines": 234,
263263
"file_list": [
264264
"server.go"
265265
],
@@ -386,15 +386,15 @@
386386
],
387387
"type_defs": {
388388
"Architecture": "Pattern string, Summary string",
389-
"Dependencies": "Edges [][]string, Entrypoints []string, MostDepended []string, Isolated []string",
390389
"GitInfo": "HotFiles []HotFile, Recent []string, Stable []string",
391390
"Hints": "AddFeature string, TestCmd string, EnvVars []string, DoNotTouch []string",
392-
"HotFile": "Path string, Commits90d int",
393391
"Index": "Schema string, Version string, GeneratedAt string, StacklitVersion string, MerkleHash string, Project Project, Tech Tech, Structure Structure, Modules map[string]ModuleInfo, Dependencies Dependenci...",
392+
"LangStats": "Files int, Lines int",
394393
"MultiIndex": "Schema string, Version string, Type string, GeneratedAt string, TotalFiles int, TotalLines int, TotalModules int, Repos []RepoSummary",
395394
"Project": "Name string, Root string, Type string, Workspaces []string",
396395
"RepoSummary": "Name string, Path string, PrimaryLanguage string, TotalFiles int, TotalLines int, Modules map[string]ModuleInfo, Frameworks []string, Entrypoints []string",
397-
"Structure": "Entrypoints []string, TotalFiles int, TotalLines int, KeyDirectories map[string]string"
396+
"Structure": "Entrypoints []string, TotalFiles int, TotalLines int, KeyDirectories map[string]string",
397+
"Tech": "PrimaryLanguage string, Languages map[string]LangStats, Frameworks []string"
398398
},
399399
"depended_by": [
400400
"internal/cli",
@@ -456,7 +456,7 @@
456456
"purpose": "Bin",
457457
"language": "javascript",
458458
"files": 1,
459-
"lines": 23,
459+
"lines": 19,
460460
"file_list": [
461461
"stacklit.js"
462462
],
@@ -578,38 +578,42 @@
578578
"hot_files": [
579579
{
580580
"path": "internal/engine/engine.go",
581-
"commits_90d": 11
581+
"commits_90d": 12
582582
},
583583
{
584584
"path": "stacklit.json",
585-
"commits_90d": 9
585+
"commits_90d": 10
586586
},
587587
{
588588
"path": "stacklit.mmd",
589-
"commits_90d": 9
589+
"commits_90d": 10
590590
},
591591
{
592592
"path": "assets/template.html",
593-
"commits_90d": 7
593+
"commits_90d": 9
594594
},
595595
{
596596
"path": "internal/graph/graph.go",
597597
"commits_90d": 7
598598
},
599599
{
600600
"path": "internal/schema/schema.go",
601-
"commits_90d": 6
601+
"commits_90d": 7
602+
},
603+
{
604+
"path": "go.mod",
605+
"commits_90d": 5
602606
},
603607
{
604608
"path": "internal/cli/init.go",
605609
"commits_90d": 5
606610
},
607611
{
608-
"path": "go.mod",
612+
"path": "go.sum",
609613
"commits_90d": 4
610614
},
611615
{
612-
"path": "go.sum",
616+
"path": "README.md",
613617
"commits_90d": 3
614618
},
615619
{
@@ -651,35 +655,31 @@
651655
{
652656
"path": "internal/walker/walker_test.go",
653657
"commits_90d": 3
654-
},
655-
{
656-
"path": ".gitignore",
657-
"commits_90d": 2
658658
}
659659
],
660660
"recent": [
661-
"stacklit.json",
662-
"stacklit.mmd",
663-
"internal/graph/graph_test.go",
664-
"internal/cli/diff.go",
665-
"internal/cli/init.go",
666-
"internal/config/config.go",
667-
"internal/config/config_test.go",
668-
"internal/engine/engine.go",
669-
"internal/graph/graph.go",
670-
"internal/schema/schema.go"
671-
],
672-
"stable": [
661+
"npm/README.md",
662+
"npm/bin/stacklit.js",
663+
"npm/package.json",
664+
"README.md",
665+
"SKILL.md",
666+
"assets/template.html",
673667
".github/workflows/ci.yml",
674668
".github/workflows/release.yml",
675669
".goreleaser.yml",
670+
"go.mod"
671+
],
672+
"stable": [
676673
"LICENSE",
677-
"README.md",
678-
"SKILL.md",
679674
"action/action.yml",
680675
"assets/lang-icons.js",
681676
"internal/cli/serve.go",
682-
"internal/config/config.go"
677+
"internal/config/config.go",
678+
"internal/config/config_test.go",
679+
"internal/detect/envvars.go",
680+
"internal/detect/frameworks.go",
681+
"internal/detect/frameworks_test.go",
682+
"internal/git/activity.go"
683683
]
684684
},
685685
"hints": {

0 commit comments

Comments
 (0)