lore-memory 0.2.0 → 0.4.0
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/README.md +54 -564
- package/bin/lore.js +32 -2
- package/package.json +4 -2
- package/src/commands/init.js +21 -4
- package/src/commands/mine.js +4 -3
- package/src/commands/onboard.js +3 -2
- package/src/commands/prompt.js +63 -0
- package/src/commands/search.js +8 -1
- package/src/commands/stale.js +1 -1
- package/src/commands/ui.js +171 -0
- package/src/lib/format.js +45 -2
- package/src/lib/relevance.js +3 -1
- package/src/mcp/tools/search.js +1 -2
- package/src/ui/public/app.js +286 -0
- package/src/ui/public/index.html +118 -0
- package/src/ui/public/style.css +321 -0
- package/src/watcher/comments.js +16 -4
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
2
|
+
// --- Security ---
|
|
3
|
+
function escapeHtml(unsafe) {
|
|
4
|
+
if (!unsafe) return '';
|
|
5
|
+
return unsafe.toString()
|
|
6
|
+
.replace(/&/g, "&")
|
|
7
|
+
.replace(/</g, "<")
|
|
8
|
+
.replace(/>/g, ">")
|
|
9
|
+
.replace(/"/g, """)
|
|
10
|
+
.replace(/'/g, "'");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// --- Navigation ---
|
|
14
|
+
const navLinks = document.querySelectorAll('.nav-links a');
|
|
15
|
+
const views = document.querySelectorAll('.view');
|
|
16
|
+
|
|
17
|
+
function showView(targetId) {
|
|
18
|
+
navLinks.forEach(l => l.classList.remove('active'));
|
|
19
|
+
document.querySelector(`[data-target="${targetId}"]`).classList.add('active');
|
|
20
|
+
|
|
21
|
+
views.forEach(v => v.classList.remove('active'));
|
|
22
|
+
document.getElementById(`view-${targetId}`).classList.add('active');
|
|
23
|
+
|
|
24
|
+
if (targetId === 'graph') {
|
|
25
|
+
loadGraph();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
navLinks.forEach(link => {
|
|
30
|
+
link.addEventListener('click', (e) => {
|
|
31
|
+
e.preventDefault();
|
|
32
|
+
showView(e.target.dataset.target);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// --- Toast Notifications ---
|
|
37
|
+
function showToast(msg) {
|
|
38
|
+
const toast = document.getElementById('toast');
|
|
39
|
+
toast.textContent = msg;
|
|
40
|
+
toast.classList.remove('hidden');
|
|
41
|
+
setTimeout(() => toast.classList.add('hidden'), 3000);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// --- Data Loading ---
|
|
45
|
+
async function loadStats() {
|
|
46
|
+
try {
|
|
47
|
+
const res = await fetch('/api/stats');
|
|
48
|
+
const data = await res.json();
|
|
49
|
+
|
|
50
|
+
// Dashboard
|
|
51
|
+
document.getElementById('score-value').textContent = data.score.score;
|
|
52
|
+
document.getElementById('stat-coverage').textContent = `${data.score.coverage}%`;
|
|
53
|
+
document.getElementById('fill-coverage').style.width = `${data.score.coverage}%`;
|
|
54
|
+
document.getElementById('stat-freshness').textContent = `${data.score.freshness}%`;
|
|
55
|
+
document.getElementById('fill-freshness').style.width = `${data.score.freshness}%`;
|
|
56
|
+
document.getElementById('stat-depth').textContent = `${data.score.depth}%`;
|
|
57
|
+
document.getElementById('fill-depth').style.width = `${data.score.depth}%`;
|
|
58
|
+
|
|
59
|
+
// Tips
|
|
60
|
+
const tipsUl = document.getElementById('score-tips');
|
|
61
|
+
tipsUl.innerHTML = '';
|
|
62
|
+
if (data.score.topUnlogged && data.score.topUnlogged.length > 0) {
|
|
63
|
+
const top = data.score.topUnlogged[0];
|
|
64
|
+
const li = document.createElement('li');
|
|
65
|
+
li.textContent = `CRITICAL: Unlogged high-activity module [${top.module}] (${top.commits} commits)`;
|
|
66
|
+
tipsUl.appendChild(li);
|
|
67
|
+
} else {
|
|
68
|
+
tipsUl.innerHTML = '<li>System nominal. All core modules documented.</li>';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Nav Badge
|
|
72
|
+
document.getElementById('nav-draft-count').textContent = data.draftCount;
|
|
73
|
+
|
|
74
|
+
} catch (e) {
|
|
75
|
+
console.error('Failed to load stats', e);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let allEntries = [];
|
|
80
|
+
|
|
81
|
+
async function loadEntries() {
|
|
82
|
+
try {
|
|
83
|
+
const res = await fetch('/api/entries');
|
|
84
|
+
allEntries = await res.json();
|
|
85
|
+
renderEntries(allEntries);
|
|
86
|
+
} catch (e) {
|
|
87
|
+
console.error('Failed to load entries', e);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function renderEntries(entries) {
|
|
92
|
+
const container = document.getElementById('kb-list');
|
|
93
|
+
container.innerHTML = '';
|
|
94
|
+
|
|
95
|
+
if (entries.length === 0) {
|
|
96
|
+
container.innerHTML = '<p class="muted">No knowledge entries found in memory banks.</p>';
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
entries.forEach(entry => {
|
|
101
|
+
const card = document.createElement('div');
|
|
102
|
+
card.className = 'entry-card';
|
|
103
|
+
|
|
104
|
+
const badgeClass = `type-${entry.type.toLowerCase()}`;
|
|
105
|
+
|
|
106
|
+
let filesHtml = '';
|
|
107
|
+
if (entry.files && entry.files.length > 0) {
|
|
108
|
+
filesHtml = `<div class="entry-meta">Files: ${escapeHtml(entry.files.join(', '))}</div>`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
let tagsHtml = '';
|
|
112
|
+
if (entry.tags && entry.tags.length > 0) {
|
|
113
|
+
tagsHtml = `<div class="entry-meta">Tags: ${escapeHtml(entry.tags.join(', '))}</div>`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
card.innerHTML = `
|
|
117
|
+
<div class="entry-header">
|
|
118
|
+
<span class="type-badge ${escapeHtml(badgeClass)}">[${escapeHtml(entry.type).toUpperCase()}]</span>
|
|
119
|
+
<span class="entry-date muted">${escapeHtml(entry.date.split('T')[0])}</span>
|
|
120
|
+
</div>
|
|
121
|
+
<div class="entry-title">${escapeHtml(entry.title)}</div>
|
|
122
|
+
<div class="entry-context">${escapeHtml(entry.context)}</div>
|
|
123
|
+
${filesHtml}
|
|
124
|
+
${tagsHtml}
|
|
125
|
+
`;
|
|
126
|
+
container.appendChild(card);
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Knowledge Base Filter & Search
|
|
131
|
+
document.getElementById('kb-search').addEventListener('input', (e) => {
|
|
132
|
+
const query = e.target.value.toLowerCase();
|
|
133
|
+
const typeMode = document.getElementById('kb-filter').value;
|
|
134
|
+
const filtered = allEntries.filter(e => {
|
|
135
|
+
const matchQuery = e.title.toLowerCase().includes(query) || e.context.toLowerCase().includes(query);
|
|
136
|
+
const matchType = typeMode === 'all' || e.type === typeMode;
|
|
137
|
+
return matchQuery && matchType;
|
|
138
|
+
});
|
|
139
|
+
renderEntries(filtered);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
document.getElementById('kb-filter').addEventListener('change', (e) => {
|
|
143
|
+
const typeMode = e.target.value;
|
|
144
|
+
const query = document.getElementById('kb-search').value.toLowerCase();
|
|
145
|
+
const filtered = allEntries.filter(e => {
|
|
146
|
+
const matchQuery = e.title.toLowerCase().includes(query) || e.context.toLowerCase().includes(query);
|
|
147
|
+
const matchType = typeMode === 'all' || e.type === typeMode;
|
|
148
|
+
return matchQuery && matchType;
|
|
149
|
+
});
|
|
150
|
+
renderEntries(filtered);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Drafts
|
|
154
|
+
async function loadDrafts() {
|
|
155
|
+
try {
|
|
156
|
+
const res = await fetch('/api/drafts');
|
|
157
|
+
const drafts = await res.json();
|
|
158
|
+
|
|
159
|
+
const container = document.getElementById('drafts-list');
|
|
160
|
+
container.innerHTML = '';
|
|
161
|
+
|
|
162
|
+
if (drafts.length === 0) {
|
|
163
|
+
container.innerHTML = '<p class="muted">All drafts have been processed.</p>';
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
drafts.forEach(draft => {
|
|
168
|
+
const confPercent = Math.round((draft.confidence || 0) * 100);
|
|
169
|
+
const card = document.createElement('div');
|
|
170
|
+
card.className = 'entry-card';
|
|
171
|
+
card.id = `draft-${draft.draftId}`;
|
|
172
|
+
|
|
173
|
+
const badgeClass = `type-${draft.suggestedType.toLowerCase()}`;
|
|
174
|
+
|
|
175
|
+
let filesHtml = '';
|
|
176
|
+
if (draft.files && draft.files.length > 0) {
|
|
177
|
+
filesHtml = `<div class="entry-meta">Linked File: ${escapeHtml(draft.files[0])}</div>`;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
card.innerHTML = `
|
|
181
|
+
<div class="entry-header">
|
|
182
|
+
<span class="type-badge ${escapeHtml(badgeClass)}">SUGGESTED: [${escapeHtml(draft.suggestedType).toUpperCase()}]</span>
|
|
183
|
+
<span class="entry-date muted">Confidence: ${confPercent}%</span>
|
|
184
|
+
</div>
|
|
185
|
+
<div class="entry-title">${escapeHtml(draft.suggestedTitle)}</div>
|
|
186
|
+
<div class="entry-context">Evidence: ${escapeHtml(draft.evidence)}</div>
|
|
187
|
+
${filesHtml}
|
|
188
|
+
<div class="draft-actions">
|
|
189
|
+
<button class="btn btn-accept" onclick="acceptDraft('${escapeHtml(draft.draftId)}')">Accept</button>
|
|
190
|
+
<button class="btn btn-delete" onclick="deleteDraft('${escapeHtml(draft.draftId)}')">Delete</button>
|
|
191
|
+
</div>
|
|
192
|
+
`;
|
|
193
|
+
container.appendChild(card);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
} catch (e) {
|
|
197
|
+
console.error('Failed to load drafts', e);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Expose actions to global scope for inline handlers
|
|
202
|
+
window.acceptDraft = async (id) => {
|
|
203
|
+
try {
|
|
204
|
+
const res = await fetch(`/api/drafts/${id}/accept`, { method: 'POST' });
|
|
205
|
+
if (res.ok) {
|
|
206
|
+
document.getElementById(`draft-${id}`).remove();
|
|
207
|
+
showToast('Draft Accepted into Memory');
|
|
208
|
+
loadStats(); // update count
|
|
209
|
+
loadEntries(); // refresh KB
|
|
210
|
+
}
|
|
211
|
+
} catch (e) { }
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
window.deleteDraft = async (id) => {
|
|
215
|
+
try {
|
|
216
|
+
const res = await fetch(`/api/drafts/${id}`, { method: 'DELETE' });
|
|
217
|
+
if (res.ok) {
|
|
218
|
+
document.getElementById(`draft-${id}`).remove();
|
|
219
|
+
showToast('Draft Deleted');
|
|
220
|
+
loadStats();
|
|
221
|
+
}
|
|
222
|
+
} catch (e) { }
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
// Graph
|
|
226
|
+
let network = null;
|
|
227
|
+
async function loadGraph() {
|
|
228
|
+
if (network) return; // already loaded
|
|
229
|
+
|
|
230
|
+
try {
|
|
231
|
+
const res = await fetch('/api/graph');
|
|
232
|
+
const graphData = await res.json();
|
|
233
|
+
|
|
234
|
+
const container = document.getElementById('network-container');
|
|
235
|
+
|
|
236
|
+
const options = {
|
|
237
|
+
layout: {
|
|
238
|
+
hierarchical: {
|
|
239
|
+
enabled: true,
|
|
240
|
+
direction: 'UD', // Up-Down
|
|
241
|
+
sortMethod: 'directed', // Follows dependency arrows
|
|
242
|
+
levelSeparation: 150, // Space between tiers
|
|
243
|
+
nodeSpacing: 250,
|
|
244
|
+
treeSpacing: 400,
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
interaction: {
|
|
248
|
+
hover: true,
|
|
249
|
+
tooltipDelay: 200,
|
|
250
|
+
hideEdgesOnDrag: true
|
|
251
|
+
},
|
|
252
|
+
nodes: {
|
|
253
|
+
shape: 'dot',
|
|
254
|
+
size: 16,
|
|
255
|
+
font: { color: '#00FF41', face: 'monospace', size: 12 },
|
|
256
|
+
color: {
|
|
257
|
+
background: '#050505',
|
|
258
|
+
border: '#008F11',
|
|
259
|
+
highlight: { background: '#00FF41', border: '#FFFFFF' },
|
|
260
|
+
hover: { background: '#008F11', border: '#00FF41' }
|
|
261
|
+
},
|
|
262
|
+
shadow: { enabled: true, color: 'rgba(0, 255, 65, 0.4)', size: 10, x: 0, y: 0 }
|
|
263
|
+
},
|
|
264
|
+
edges: {
|
|
265
|
+
color: { color: '#004F09', highlight: '#00FF41', hover: '#008F11' },
|
|
266
|
+
arrows: { to: { enabled: true, scaleFactor: 0.5 } },
|
|
267
|
+
smooth: { type: 'cubicBezier', forceDirection: 'vertical', roundness: 0.4 }
|
|
268
|
+
},
|
|
269
|
+
physics: {
|
|
270
|
+
enabled: false // Physics usually conflicts with strict hierarchical layouts
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
network = new vis.Network(container, graphData, options);
|
|
275
|
+
|
|
276
|
+
} catch (e) {
|
|
277
|
+
console.error('Failed to load graph', e);
|
|
278
|
+
document.getElementById('network-container').innerHTML = '<p class="muted" style="padding: 20px;">Could not render graph data.</p>';
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Init
|
|
283
|
+
loadStats();
|
|
284
|
+
loadEntries();
|
|
285
|
+
loadDrafts();
|
|
286
|
+
});
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Lore Dashboard</title>
|
|
7
|
+
<!-- Retro Fonts -->
|
|
8
|
+
<link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&family=Fira+Code:wght@400;600&display=swap" rel="stylesheet">
|
|
9
|
+
<!-- Vis Network for Graphs -->
|
|
10
|
+
<script type="text/javascript" src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script>
|
|
11
|
+
<link rel="stylesheet" href="style.css">
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<div class="crt-overlay"></div>
|
|
15
|
+
|
|
16
|
+
<div class="layout">
|
|
17
|
+
<!-- Sidebar Navigation -->
|
|
18
|
+
<nav class="sidebar">
|
|
19
|
+
<div class="logo">
|
|
20
|
+
<pre>
|
|
21
|
+
██╗ ██████╗ ██████╗ ███████╗
|
|
22
|
+
██║ ██╔═══██╗██╔══██╗██╔════╝
|
|
23
|
+
██║ ██║ ██║██████╔╝█████╗
|
|
24
|
+
██║ ██║ ██║██╔══██╗██╔══╝
|
|
25
|
+
███████╗╚██████╔╝██║ ██║███████╗
|
|
26
|
+
╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝
|
|
27
|
+
</pre>
|
|
28
|
+
<div class="subtitle">PROJECT MEMORY</div>
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
<ul class="nav-links">
|
|
32
|
+
<li><a href="#" class="active" data-target="dashboard">Dashboard</a></li>
|
|
33
|
+
<li><a href="#" data-target="knowledge">Knowledge Base</a></li>
|
|
34
|
+
<li><a href="#" data-target="drafts">Pending Drafts (<span id="nav-draft-count">0</span>)</a></li>
|
|
35
|
+
<li><a href="#" data-target="graph">Dependency Graph</a></li>
|
|
36
|
+
</ul>
|
|
37
|
+
</nav>
|
|
38
|
+
|
|
39
|
+
<!-- Main Content Area -->
|
|
40
|
+
<main class="content">
|
|
41
|
+
|
|
42
|
+
<!-- Dashboard View -->
|
|
43
|
+
<section id="view-dashboard" class="view active">
|
|
44
|
+
<h1 class="glitch" data-text="LORE SCORE">LORE SCORE</h1>
|
|
45
|
+
<div class="score-card">
|
|
46
|
+
<div class="main-score">
|
|
47
|
+
<span id="score-value">--</span><span class="muted">/100</span>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<div class="metrics-grid">
|
|
52
|
+
<div class="metric">
|
|
53
|
+
<h3>COVERAGE</h3>
|
|
54
|
+
<div class="progress-bar"><div class="fill" id="fill-coverage"></div></div>
|
|
55
|
+
<p class="stats" id="stat-coverage">--%</p>
|
|
56
|
+
</div>
|
|
57
|
+
<div class="metric">
|
|
58
|
+
<h3>FRESHNESS</h3>
|
|
59
|
+
<div class="progress-bar"><div class="fill" id="fill-freshness"></div></div>
|
|
60
|
+
<p class="stats" id="stat-freshness">--%</p>
|
|
61
|
+
</div>
|
|
62
|
+
<div class="metric">
|
|
63
|
+
<h3>DEPTH</h3>
|
|
64
|
+
<div class="progress-bar"><div class="fill" id="fill-depth"></div></div>
|
|
65
|
+
<p class="stats" id="stat-depth">--%</p>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
|
|
69
|
+
<div class="tips-container">
|
|
70
|
+
<h3>SYSTEM ALERTS</h3>
|
|
71
|
+
<ul id="score-tips" class="terminal-list">
|
|
72
|
+
<li>Loading heuristics...</li>
|
|
73
|
+
</ul>
|
|
74
|
+
</div>
|
|
75
|
+
</section>
|
|
76
|
+
|
|
77
|
+
<!-- Knowledge Base View -->
|
|
78
|
+
<section id="view-knowledge" class="view">
|
|
79
|
+
<h1>KNOWLEDGE BASE</h1>
|
|
80
|
+
<div class="controls">
|
|
81
|
+
<input type="text" id="kb-search" placeholder="Search memory..._">
|
|
82
|
+
<select id="kb-filter">
|
|
83
|
+
<option value="all">ALL TYPES</option>
|
|
84
|
+
<option value="decision">DECISIONS</option>
|
|
85
|
+
<option value="invariant">INVARIANTS</option>
|
|
86
|
+
<option value="gotcha">GOTCHAS</option>
|
|
87
|
+
<option value="graveyard">GRAVEYARD</option>
|
|
88
|
+
</select>
|
|
89
|
+
</div>
|
|
90
|
+
<div id="kb-list" class="entries-grid">
|
|
91
|
+
<!-- Entries injected here by JS -->
|
|
92
|
+
</div>
|
|
93
|
+
</section>
|
|
94
|
+
|
|
95
|
+
<!-- Drafts View -->
|
|
96
|
+
<section id="view-drafts" class="view">
|
|
97
|
+
<h1>PENDING DRAFTS</h1>
|
|
98
|
+
<p class="subtitle">Awaiting human approval.</p>
|
|
99
|
+
<div id="drafts-list" class="drafts-container">
|
|
100
|
+
<!-- Drafts injected here by JS -->
|
|
101
|
+
</div>
|
|
102
|
+
</section>
|
|
103
|
+
|
|
104
|
+
<!-- Graph View -->
|
|
105
|
+
<section id="view-graph" class="view">
|
|
106
|
+
<h1>DEPENDENCY GRAPH</h1>
|
|
107
|
+
<div id="network-container"></div>
|
|
108
|
+
</section>
|
|
109
|
+
|
|
110
|
+
</main>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
<!-- Modals / Tooltips -->
|
|
114
|
+
<div id="toast" class="toast hidden">Action successful</div>
|
|
115
|
+
|
|
116
|
+
<script src="app.js"></script>
|
|
117
|
+
</body>
|
|
118
|
+
</html>
|