devsmind-mcp 2.3.0 → 3.0.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 +326 -234
- package/dist/cli/activity.d.ts +9 -0
- package/dist/cli/activity.js +69 -0
- package/dist/cli/activity.js.map +1 -0
- package/dist/cli/describe.d.ts +31 -0
- package/dist/cli/describe.js +201 -0
- package/dist/cli/describe.js.map +1 -0
- package/dist/cli/diff.d.ts +14 -0
- package/dist/cli/diff.js +161 -0
- package/dist/cli/diff.js.map +1 -0
- package/dist/cli/embed.d.ts +6 -0
- package/dist/cli/embed.js +109 -0
- package/dist/cli/embed.js.map +1 -0
- package/dist/cli/extract-agent.d.ts +66 -0
- package/dist/cli/extract-agent.js +192 -0
- package/dist/cli/extract-agent.js.map +1 -0
- package/dist/cli/feedback.d.ts +11 -0
- package/dist/cli/feedback.js +90 -0
- package/dist/cli/feedback.js.map +1 -0
- package/dist/cli/index.js +128 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init.d.ts +30 -0
- package/dist/cli/init.js +98 -18
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/integrations/memory-topics.d.ts +44 -0
- package/dist/cli/integrations/memory-topics.js +295 -0
- package/dist/cli/integrations/memory-topics.js.map +1 -0
- package/dist/cli/integrations/memory.d.ts +11 -5
- package/dist/cli/integrations/memory.js +132 -36
- package/dist/cli/integrations/memory.js.map +1 -1
- package/dist/cli/integrations/prompt.d.ts +8 -0
- package/dist/cli/integrations/prompt.js +24 -3
- package/dist/cli/integrations/prompt.js.map +1 -1
- package/dist/cli/integrations/registry.d.ts +19 -5
- package/dist/cli/integrations/registry.js +23 -13
- package/dist/cli/integrations/registry.js.map +1 -1
- package/dist/cli/llm-client.d.ts +116 -0
- package/dist/cli/llm-client.js +322 -0
- package/dist/cli/llm-client.js.map +1 -0
- package/dist/cli/prune.js +4 -3
- package/dist/cli/prune.js.map +1 -1
- package/dist/cli/rule.d.ts +22 -2
- package/dist/cli/rule.js +116 -56
- package/dist/cli/rule.js.map +1 -1
- package/dist/cli/runner.d.ts +21 -0
- package/dist/cli/runner.js +94 -276
- package/dist/cli/runner.js.map +1 -1
- package/dist/cli/sync-progress.d.ts +9 -0
- package/dist/cli/sync-progress.js +41 -0
- package/dist/cli/sync-progress.js.map +1 -0
- package/dist/cli/sync.js +5 -2
- package/dist/cli/sync.js.map +1 -1
- package/dist/cli/view.js +3 -2
- package/dist/cli/view.js.map +1 -1
- package/dist/cli/workflow.js +26 -20
- package/dist/cli/workflow.js.map +1 -1
- package/dist/db/activity.d.ts +200 -0
- package/dist/db/activity.js +347 -0
- package/dist/db/activity.js.map +1 -0
- package/dist/db/analyze.js +25 -9
- package/dist/db/analyze.js.map +1 -1
- package/dist/db/database.d.ts +607 -79
- package/dist/db/database.js +1936 -488
- package/dist/db/database.js.map +1 -1
- package/dist/db/edges.d.ts +24 -0
- package/dist/db/edges.js +84 -0
- package/dist/db/edges.js.map +1 -1
- package/dist/db/embedder.d.ts +39 -0
- package/dist/db/embedder.js +0 -0
- package/dist/db/embedder.js.map +1 -0
- package/dist/db/feedback.d.ts +128 -0
- package/dist/db/feedback.js +182 -0
- package/dist/db/feedback.js.map +1 -0
- package/dist/db/file-diff.d.ts +32 -0
- package/dist/db/file-diff.js +110 -0
- package/dist/db/file-diff.js.map +1 -0
- package/dist/db/grep.d.ts +78 -0
- package/dist/db/grep.js +475 -0
- package/dist/db/grep.js.map +1 -0
- package/dist/db/message-revert.d.ts +63 -0
- package/dist/db/message-revert.js +258 -0
- package/dist/db/message-revert.js.map +1 -0
- package/dist/db/revert.d.ts +31 -0
- package/dist/db/revert.js +108 -0
- package/dist/db/revert.js.map +1 -0
- package/dist/db/schema.d.ts +49 -4
- package/dist/db/schema.js +125 -73
- package/dist/db/schema.js.map +1 -1
- package/dist/db/search-index.d.ts +65 -0
- package/dist/db/search-index.js +74 -0
- package/dist/db/search-index.js.map +1 -0
- package/dist/db/staging.d.ts +90 -5
- package/dist/db/staging.js +148 -22
- package/dist/db/staging.js.map +1 -1
- package/dist/mcp/server.d.ts +29 -7
- package/dist/mcp/server.js +2755 -745
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/vendor/3d-force-graph.min.js +5 -0
- package/dist/mcp/vendor/force-graph.min.js +5 -0
- package/dist/mcp/vendor/model/model_int8.onnx +0 -0
- package/dist/mcp/vendor/model/vocab.txt +30522 -0
- package/dist/mcp/vendor/three.min.js +7 -0
- package/dist/mcp/view.css +419 -0
- package/dist/mcp/view.html +161 -0
- package/dist/mcp/view.js +245 -0
- package/dist/mcp/view_chat.js +382 -0
- package/dist/mcp/view_graph.js +576 -0
- package/dist/mcp/visualizer.d.ts +25 -2
- package/dist/mcp/visualizer.js +31 -4
- package/dist/mcp/visualizer.js.map +1 -1
- package/dist/utils/ast.d.ts +187 -0
- package/dist/utils/ast.js +640 -20
- package/dist/utils/ast.js.map +1 -1
- package/dist/utils/diff.d.ts +44 -0
- package/dist/utils/diff.js +78 -0
- package/dist/utils/diff.js.map +1 -0
- package/dist/utils/edit.d.ts +47 -0
- package/dist/utils/edit.js +196 -0
- package/dist/utils/edit.js.map +1 -0
- package/dist/utils/scanner.js +12 -4
- package/dist/utils/scanner.js.map +1 -1
- package/dist/utils/tokenize.d.ts +45 -0
- package/dist/utils/tokenize.js +129 -0
- package/dist/utils/tokenize.js.map +1 -0
- package/dist/utils/version.d.ts +14 -0
- package/dist/utils/version.js +61 -0
- package/dist/utils/version.js.map +1 -0
- package/package.json +18 -5
- package/dist/mcp/visualizer_2d.html +0 -635
- package/dist/mcp/visualizer_3d.html +0 -613
|
@@ -0,0 +1,576 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Graph section — node-first instead of a whole-graph dump. A repo→type accordion drives
|
|
3
|
+
* selection; clicking a node opens a small 1-hop ego-graph (its uses / used-by only), with a
|
|
4
|
+
* "See whole graph (2D)" escape hatch for when you genuinely want the big picture.
|
|
5
|
+
*
|
|
6
|
+
* Relies on view.js for fetchJSON/escapeHtml/renderDiffBlock/withPath/formatDate.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
let gNodes = [];
|
|
10
|
+
let gConnections = [];
|
|
11
|
+
let gHistory = [];
|
|
12
|
+
let gNodesById = new Map();
|
|
13
|
+
let gHistoryByNode = new Map(); // node_id -> history entries, newest first (matches getAllHistory order)
|
|
14
|
+
let gDevelopersOf = new Map(); // node_id -> latest developer name (or null)
|
|
15
|
+
let gAllDevelopers = [];
|
|
16
|
+
let gAllTypes = [];
|
|
17
|
+
let gSelectedTypes = new Set();
|
|
18
|
+
let gSelectedDevs = new Set();
|
|
19
|
+
const savedGraphRange = loadLocal('graph-date-range', null);
|
|
20
|
+
let gDateFrom = savedGraphRange ? savedGraphRange.from : null;
|
|
21
|
+
let gDateTo = savedGraphRange ? savedGraphRange.to : null;
|
|
22
|
+
let gSelectedNodeId = null;
|
|
23
|
+
let gCurrentEgoIs3D = false;
|
|
24
|
+
let gGraphInstance = null;
|
|
25
|
+
let gWholeGraphInstance = null;
|
|
26
|
+
|
|
27
|
+
const TYPE_COLORS = {
|
|
28
|
+
function: '#38bdf8', method: '#38bdf8', class: '#a78bfa', interface: '#34d399',
|
|
29
|
+
type_alias: '#34d399', enum: '#fbbf24', variable: '#94a3b8', constant: '#94a3b8',
|
|
30
|
+
module: '#f472b6', namespace: '#f472b6'
|
|
31
|
+
};
|
|
32
|
+
function colorForType(type) {
|
|
33
|
+
if (TYPE_COLORS[type]) return TYPE_COLORS[type];
|
|
34
|
+
let hash = 0;
|
|
35
|
+
for (let i = 0; i < type.length; i++) hash = type.charCodeAt(i) + ((hash << 5) - hash);
|
|
36
|
+
return `hsl(${Math.abs(hash) % 360}, 60%, 62%)`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function saveGraphRange() {
|
|
40
|
+
saveLocal('graph-date-range', { from: gDateFrom, to: gDateTo });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Which quick chip (if any) the current from/to values match — null if it's a custom range. */
|
|
44
|
+
function graphQuickChipFor(from, to) {
|
|
45
|
+
if (!from && !to) return 'clear';
|
|
46
|
+
const today = todayKey();
|
|
47
|
+
if (from === addDaysKey(today, -1) && to === today) return '1d';
|
|
48
|
+
if (from === addDaysKey(today, -7) && to === today) return '7d';
|
|
49
|
+
if (from === addDaysKey(today, -30) && to === today) return '30d';
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function initGraph() {
|
|
54
|
+
document.getElementById('graph-filter-toggle').addEventListener('click', () => toggleFilterPanel('graph-filter-panel'));
|
|
55
|
+
document.getElementById('node-search').addEventListener('input', applyFilters);
|
|
56
|
+
|
|
57
|
+
const fromEl = document.getElementById('graph-date-from');
|
|
58
|
+
const toEl = document.getElementById('graph-date-to');
|
|
59
|
+
fromEl.value = gDateFrom || '';
|
|
60
|
+
toEl.value = gDateTo || '';
|
|
61
|
+
fromEl.addEventListener('change', () => { gDateFrom = fromEl.value || null; saveGraphRange(); setActiveGraphQuickChip(graphQuickChipFor(gDateFrom, gDateTo)); applyFilters(); });
|
|
62
|
+
toEl.addEventListener('change', () => { gDateTo = toEl.value || null; saveGraphRange(); setActiveGraphQuickChip(graphQuickChipFor(gDateFrom, gDateTo)); applyFilters(); });
|
|
63
|
+
document.querySelectorAll('[data-graph-quick]').forEach(btn => {
|
|
64
|
+
btn.addEventListener('click', () => {
|
|
65
|
+
const quick = btn.dataset.graphQuick;
|
|
66
|
+
if (quick === 'clear') { gDateFrom = null; gDateTo = null; }
|
|
67
|
+
else { gDateFrom = addDaysKey(todayKey(), -(quick === '1d' ? 1 : quick === '7d' ? 7 : 30)); gDateTo = todayKey(); }
|
|
68
|
+
fromEl.value = gDateFrom || '';
|
|
69
|
+
toEl.value = gDateTo || '';
|
|
70
|
+
saveGraphRange();
|
|
71
|
+
setActiveGraphQuickChip(quick);
|
|
72
|
+
applyFilters();
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
setActiveGraphQuickChip(graphQuickChipFor(gDateFrom, gDateTo));
|
|
76
|
+
|
|
77
|
+
document.querySelectorAll('[data-select-all]').forEach(btn => {
|
|
78
|
+
btn.addEventListener('click', () => setCheckGroup(btn.dataset.selectAll, true));
|
|
79
|
+
});
|
|
80
|
+
document.querySelectorAll('[data-select-none]').forEach(btn => {
|
|
81
|
+
btn.addEventListener('click', () => setCheckGroup(btn.dataset.selectNone, false));
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
document.getElementById('btn-toggle-3d').addEventListener('click', toggleEgoDimension);
|
|
85
|
+
document.getElementById('btn-whole-graph').addEventListener('click', showWholeGraph);
|
|
86
|
+
document.getElementById('btn-close-whole-graph').addEventListener('click', closeWholeGraph);
|
|
87
|
+
document.getElementById('details-close').addEventListener('click', closeDetails);
|
|
88
|
+
loadGraphData();
|
|
89
|
+
}
|
|
90
|
+
registerTab('graph', initGraph);
|
|
91
|
+
|
|
92
|
+
function setActiveGraphQuickChip(quick) {
|
|
93
|
+
document.querySelectorAll('[data-graph-quick]').forEach(b => b.classList.toggle('active', b.dataset.graphQuick === quick));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** "All" / "None" buttons for a checkbox filter group — checks/unchecks every box and reapplies. */
|
|
97
|
+
function setCheckGroup(groupId, checked) {
|
|
98
|
+
document.querySelectorAll(`#${groupId} input`).forEach(cb => { cb.checked = checked; });
|
|
99
|
+
const values = new Set(checked ? Array.from(document.querySelectorAll(`#${groupId} input`)).map(cb => cb.value) : []);
|
|
100
|
+
if (groupId === 'type-filters') { gSelectedTypes = values; saveLocal('graph-selected-types', Array.from(values)); }
|
|
101
|
+
else if (groupId === 'dev-filters') { gSelectedDevs = values; saveLocal('graph-selected-devs', Array.from(values)); }
|
|
102
|
+
applyFilters();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Restores a checkbox group's selection from localStorage, intersected with what's actually
|
|
107
|
+
* available right now (a saved type/developer that no longer exists in this graph is dropped
|
|
108
|
+
* rather than left dangling). No saved value at all means "leave the all-selected default alone".
|
|
109
|
+
*/
|
|
110
|
+
function restoreCheckGroup(storageKey, groupId, allValues, applySelection) {
|
|
111
|
+
const saved = loadLocal(storageKey, null);
|
|
112
|
+
if (saved === null) return;
|
|
113
|
+
const valid = new Set(saved.filter(v => allValues.includes(v)));
|
|
114
|
+
document.querySelectorAll(`#${groupId} input`).forEach(cb => { cb.checked = valid.has(cb.value); });
|
|
115
|
+
applySelection(valid);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function loadGraphData() {
|
|
119
|
+
const accEl = document.getElementById('node-accordion');
|
|
120
|
+
accEl.innerHTML = '<div class="empty-hint">Loading…</div>';
|
|
121
|
+
try {
|
|
122
|
+
const data = await fetchJSON('/api/graph-data');
|
|
123
|
+
gNodes = (data.nodes || []).filter(n => !n.deprecated);
|
|
124
|
+
gConnections = data.connections || [];
|
|
125
|
+
gHistory = data.history || [];
|
|
126
|
+
gNodesById = new Map(gNodes.map(n => [n.id, n]));
|
|
127
|
+
|
|
128
|
+
gHistoryByNode = new Map();
|
|
129
|
+
for (const h of gHistory) {
|
|
130
|
+
const arr = gHistoryByNode.get(h.node_id) || [];
|
|
131
|
+
arr.push(h);
|
|
132
|
+
gHistoryByNode.set(h.node_id, arr);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const devSet = new Set();
|
|
136
|
+
gDevelopersOf = new Map();
|
|
137
|
+
for (const [nodeId, entries] of gHistoryByNode.entries()) {
|
|
138
|
+
for (const h of entries) {
|
|
139
|
+
const dev = developerOf(h);
|
|
140
|
+
if (dev) {
|
|
141
|
+
devSet.add(dev);
|
|
142
|
+
if (!gDevelopersOf.has(nodeId)) gDevelopersOf.set(nodeId, dev); // entries[0] is newest — used for display only, not filtering
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
gAllDevelopers = Array.from(devSet).sort();
|
|
147
|
+
gAllTypes = Array.from(new Set(gNodes.map(n => n.type))).sort();
|
|
148
|
+
gSelectedTypes = new Set(gAllTypes);
|
|
149
|
+
gSelectedDevs = new Set(gAllDevelopers);
|
|
150
|
+
|
|
151
|
+
document.getElementById('graph-stats').textContent = `${gNodes.length} nodes · ${gConnections.length} connections`;
|
|
152
|
+
renderFilterChecks();
|
|
153
|
+
restoreCheckGroup('graph-selected-types', 'type-filters', gAllTypes, s => { gSelectedTypes = s; });
|
|
154
|
+
restoreCheckGroup('graph-selected-devs', 'dev-filters', gAllDevelopers, s => { gSelectedDevs = s; });
|
|
155
|
+
buildAccordion();
|
|
156
|
+
applyFilters(); // reflect any restored selection immediately, not just after the next change
|
|
157
|
+
} catch (err) {
|
|
158
|
+
accEl.innerHTML = `<div class="empty-hint">Failed to load: ${escapeHtml(err.message)}</div>`;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function renderFilterChecks() {
|
|
163
|
+
document.getElementById('type-filters').innerHTML = gAllTypes.map(t => `
|
|
164
|
+
<label><input type="checkbox" value="${escapeHtml(t)}" checked> ${escapeHtml(t)}</label>
|
|
165
|
+
`).join('') || '<span class="empty-hint">—</span>';
|
|
166
|
+
document.getElementById('dev-filters').innerHTML = gAllDevelopers.map(d => `
|
|
167
|
+
<label><input type="checkbox" value="${escapeHtml(d)}" checked> ${escapeHtml(d)}</label>
|
|
168
|
+
`).join('') || '<span class="empty-hint">no attributed developers yet</span>';
|
|
169
|
+
|
|
170
|
+
document.querySelectorAll('#type-filters input').forEach(cb => cb.addEventListener('change', () => {
|
|
171
|
+
gSelectedTypes = new Set(Array.from(document.querySelectorAll('#type-filters input:checked')).map(c => c.value));
|
|
172
|
+
saveLocal('graph-selected-types', Array.from(gSelectedTypes));
|
|
173
|
+
applyFilters();
|
|
174
|
+
}));
|
|
175
|
+
document.querySelectorAll('#dev-filters input').forEach(cb => cb.addEventListener('change', () => {
|
|
176
|
+
gSelectedDevs = new Set(Array.from(document.querySelectorAll('#dev-filters input:checked')).map(c => c.value));
|
|
177
|
+
saveLocal('graph-selected-devs', Array.from(gSelectedDevs));
|
|
178
|
+
applyFilters();
|
|
179
|
+
}));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function repoOf(nodeId) {
|
|
183
|
+
const m = /^\{([^}]+)\}\//.exec(nodeId);
|
|
184
|
+
return m ? m[1] : 'workspace';
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function buildAccordion() {
|
|
188
|
+
const byRepo = new Map();
|
|
189
|
+
for (const n of gNodes) {
|
|
190
|
+
const repo = repoOf(n.id);
|
|
191
|
+
if (!byRepo.has(repo)) byRepo.set(repo, new Map());
|
|
192
|
+
const byType = byRepo.get(repo);
|
|
193
|
+
if (!byType.has(n.type)) byType.set(n.type, []);
|
|
194
|
+
byType.get(n.type).push(n);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const repoNames = Array.from(byRepo.keys()).sort();
|
|
198
|
+
const html = repoNames.map(repo => {
|
|
199
|
+
const byType = byRepo.get(repo);
|
|
200
|
+
const typeNames = Array.from(byType.keys()).sort();
|
|
201
|
+
const total = typeNames.reduce((s, t) => s + byType.get(t).length, 0);
|
|
202
|
+
const typesHtml = typeNames.map(type => {
|
|
203
|
+
const nodesOfType = byType.get(type).slice().sort((a, b) => a.name.localeCompare(b.name));
|
|
204
|
+
const itemsHtml = nodesOfType.map(n => `
|
|
205
|
+
<div class="node-item" data-node="${escapeHtml(n.id)}" data-name="${escapeHtml(n.name.toLowerCase())}" title="${escapeHtml(n.id)}">${escapeHtml(n.name)}</div>
|
|
206
|
+
`).join('');
|
|
207
|
+
return `
|
|
208
|
+
<details class="type-group">
|
|
209
|
+
<summary>${escapeHtml(type)} <span class="count">${nodesOfType.length}</span></summary>
|
|
210
|
+
${itemsHtml}
|
|
211
|
+
</details>
|
|
212
|
+
`;
|
|
213
|
+
}).join('');
|
|
214
|
+
return `
|
|
215
|
+
<details class="repo-group">
|
|
216
|
+
<summary>${escapeHtml(repo)} <span class="count">${total}</span></summary>
|
|
217
|
+
${typesHtml}
|
|
218
|
+
</details>
|
|
219
|
+
`;
|
|
220
|
+
}).join('');
|
|
221
|
+
|
|
222
|
+
document.getElementById('node-accordion').innerHTML = html || '<div class="empty-hint">No nodes indexed yet.</div>';
|
|
223
|
+
document.querySelectorAll('.node-item').forEach(el => {
|
|
224
|
+
el.addEventListener('click', () => selectNode(el.dataset.node));
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function developerOf(historyEntry) {
|
|
229
|
+
const m = /Developer:\s*(.+)/i.exec(historyEntry.reasoning || '');
|
|
230
|
+
return m ? m[1].trim() : null;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Developer and date are ANDed against the SAME history entry — "changes by Abialidr on this
|
|
235
|
+
* date" means an edit that is both, not "node's latest edit is by Abialidr" AND separately
|
|
236
|
+
* "node's latest edit is on this date" (those can be two different edits).
|
|
237
|
+
*/
|
|
238
|
+
function nodePassesDevAndDateFilter(nodeId) {
|
|
239
|
+
const devActive = gSelectedDevs.size !== gAllDevelopers.length;
|
|
240
|
+
const dateActive = !!gDateFrom || !!gDateTo;
|
|
241
|
+
if (!devActive && !dateActive) return true;
|
|
242
|
+
|
|
243
|
+
const entries = gHistoryByNode.get(nodeId);
|
|
244
|
+
if (!entries || !entries.length) return false;
|
|
245
|
+
return entries.some(h => {
|
|
246
|
+
const devOk = !devActive || (developerOf(h) && gSelectedDevs.has(developerOf(h)));
|
|
247
|
+
const dateOk = !dateActive || dateInRange(h.updated_at, gDateFrom, gDateTo);
|
|
248
|
+
return devOk && dateOk;
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function applyFilters() {
|
|
253
|
+
const query = document.getElementById('node-search').value.trim().toLowerCase();
|
|
254
|
+
let anyFilterActive = query.length > 0
|
|
255
|
+
|| gSelectedTypes.size !== gAllTypes.length
|
|
256
|
+
|| gSelectedDevs.size !== gAllDevelopers.length
|
|
257
|
+
|| !!gDateFrom || !!gDateTo;
|
|
258
|
+
|
|
259
|
+
document.querySelectorAll('.node-item').forEach(el => {
|
|
260
|
+
const node = gNodesById.get(el.dataset.node);
|
|
261
|
+
if (!node) return;
|
|
262
|
+
const matchesSearch = !query || el.dataset.name.includes(query) || node.id.toLowerCase().includes(query);
|
|
263
|
+
const matchesType = gSelectedTypes.has(node.type);
|
|
264
|
+
const matchesDevAndDate = nodePassesDevAndDateFilter(node.id);
|
|
265
|
+
const visible = matchesSearch && matchesType && matchesDevAndDate;
|
|
266
|
+
el.classList.toggle('filtered-out', !visible);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
document.querySelectorAll('.type-group').forEach(group => {
|
|
270
|
+
const items = Array.from(group.querySelectorAll('.node-item'));
|
|
271
|
+
const visibleCount = items.filter(i => !i.classList.contains('filtered-out')).length;
|
|
272
|
+
const hasVisible = visibleCount > 0;
|
|
273
|
+
group.classList.toggle('filtered-out', !hasVisible);
|
|
274
|
+
group.querySelector(':scope > summary .count').textContent =
|
|
275
|
+
anyFilterActive ? `${visibleCount}/${items.length}` : String(items.length);
|
|
276
|
+
if (anyFilterActive && hasVisible) group.open = true;
|
|
277
|
+
});
|
|
278
|
+
document.querySelectorAll('.repo-group').forEach(group => {
|
|
279
|
+
const items = Array.from(group.querySelectorAll('.node-item'));
|
|
280
|
+
const visibleCount = items.filter(i => !i.classList.contains('filtered-out')).length;
|
|
281
|
+
const hasVisible = visibleCount > 0;
|
|
282
|
+
group.classList.toggle('filtered-out', !hasVisible);
|
|
283
|
+
group.querySelector(':scope > summary .count').textContent =
|
|
284
|
+
anyFilterActive ? `${visibleCount}/${items.length}` : String(items.length);
|
|
285
|
+
if (anyFilterActive && hasVisible) group.open = true;
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
if (gWholeGraphInstance) renderWholeGraph();
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function filteredNodeIds() {
|
|
292
|
+
return new Set(
|
|
293
|
+
Array.from(document.querySelectorAll('.node-item:not(.filtered-out)')).map(el => el.dataset.node)
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// ── Ego-graph ────────────────────────────────────────────────────────────
|
|
298
|
+
|
|
299
|
+
function egoDataFor(nodeId) {
|
|
300
|
+
const center = gNodesById.get(nodeId);
|
|
301
|
+
if (!center) return null;
|
|
302
|
+
const uses = gConnections.filter(c => c.source_node_id === nodeId)
|
|
303
|
+
.map(c => gNodesById.get(c.target_node_id)).filter(Boolean);
|
|
304
|
+
const usedBy = gConnections.filter(c => c.target_node_id === nodeId)
|
|
305
|
+
.map(c => gNodesById.get(c.source_node_id)).filter(Boolean);
|
|
306
|
+
|
|
307
|
+
const nodeMap = new Map();
|
|
308
|
+
nodeMap.set(center.id, { id: center.id, name: center.name, role: 'center' });
|
|
309
|
+
for (const u of uses) if (!nodeMap.has(u.id)) nodeMap.set(u.id, { id: u.id, name: u.name, role: 'uses' });
|
|
310
|
+
for (const u of usedBy) if (!nodeMap.has(u.id)) nodeMap.set(u.id, { id: u.id, name: u.name, role: 'usedby' });
|
|
311
|
+
|
|
312
|
+
const links = [
|
|
313
|
+
...uses.map(u => ({ source: center.id, target: u.id })),
|
|
314
|
+
...usedBy.map(u => ({ source: u.id, target: center.id }))
|
|
315
|
+
];
|
|
316
|
+
return { nodes: Array.from(nodeMap.values()), links, uses, usedBy, center };
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function egoNodeColor(n) {
|
|
320
|
+
return n.role === 'center' ? '#f43f5e' : n.role === 'uses' ? '#38bdf8' : '#a78bfa';
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// ── Always-on node labels ──────────────────────────────────────────────────
|
|
324
|
+
// A bare colored dot with no name is unreadable the moment there's more than one node — every
|
|
325
|
+
// node draws its name underneath it, in 2D and 3D, instead of relying on a hover tooltip.
|
|
326
|
+
|
|
327
|
+
function nodeRadius(n) { return n.role === 'center' ? 7 : 5; }
|
|
328
|
+
|
|
329
|
+
/** 2D canvas node paint: a dot + its name, sized so text stays legible at any zoom level. */
|
|
330
|
+
function paint2DNode(node, ctx, globalScale, colorFn) {
|
|
331
|
+
const r = nodeRadius(node);
|
|
332
|
+
ctx.beginPath();
|
|
333
|
+
ctx.arc(node.x, node.y, r, 0, 2 * Math.PI, false);
|
|
334
|
+
ctx.fillStyle = colorFn(node);
|
|
335
|
+
ctx.fill();
|
|
336
|
+
|
|
337
|
+
const label = node.name || node.id;
|
|
338
|
+
const fontSize = Math.max(11 / globalScale, 3);
|
|
339
|
+
ctx.font = `${fontSize}px -apple-system, "Segoe UI", sans-serif`;
|
|
340
|
+
ctx.textAlign = 'center';
|
|
341
|
+
ctx.textBaseline = 'top';
|
|
342
|
+
ctx.fillStyle = '#e2e8f0';
|
|
343
|
+
ctx.fillText(label, node.x, node.y + r + 1.5);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/** Matching hit-area for paint2DNode — covers the dot plus a little of the label below it. */
|
|
347
|
+
function paintPointerArea(node, color, ctx) {
|
|
348
|
+
const r = nodeRadius(node);
|
|
349
|
+
ctx.fillStyle = color;
|
|
350
|
+
ctx.beginPath();
|
|
351
|
+
ctx.arc(node.x, node.y, r + 4, 0, 2 * Math.PI, false);
|
|
352
|
+
ctx.fill();
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/** 3D label: a canvas-drawn dot + name baked into a sprite texture (three.js has no native text). */
|
|
356
|
+
function makeNode3DSprite(node, colorFn) {
|
|
357
|
+
const label = node.name || node.id;
|
|
358
|
+
const color = colorFn(node);
|
|
359
|
+
const canvas = document.createElement('canvas');
|
|
360
|
+
const ctx = canvas.getContext('2d');
|
|
361
|
+
const fontSize = 30;
|
|
362
|
+
ctx.font = `${fontSize}px sans-serif`;
|
|
363
|
+
const textWidth = ctx.measureText(label).width;
|
|
364
|
+
canvas.width = textWidth + 34;
|
|
365
|
+
canvas.height = fontSize + 14;
|
|
366
|
+
ctx.font = `${fontSize}px sans-serif`;
|
|
367
|
+
ctx.fillStyle = color;
|
|
368
|
+
ctx.beginPath();
|
|
369
|
+
ctx.arc(14, canvas.height / 2, 10, 0, 2 * Math.PI);
|
|
370
|
+
ctx.fill();
|
|
371
|
+
ctx.fillStyle = '#e2e8f0';
|
|
372
|
+
ctx.textBaseline = 'middle';
|
|
373
|
+
ctx.fillText(label, 30, canvas.height / 2);
|
|
374
|
+
|
|
375
|
+
const texture = new THREE.CanvasTexture(canvas);
|
|
376
|
+
const material = new THREE.SpriteMaterial({ map: texture, depthWrite: false });
|
|
377
|
+
const sprite = new THREE.Sprite(material);
|
|
378
|
+
sprite.scale.set(canvas.width / 9, canvas.height / 9, 1);
|
|
379
|
+
return sprite;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function selectNode(nodeId) {
|
|
383
|
+
const node = gNodesById.get(nodeId);
|
|
384
|
+
if (!node) return;
|
|
385
|
+
gSelectedNodeId = nodeId;
|
|
386
|
+
document.querySelectorAll('.node-item').forEach(el => el.classList.toggle('selected', el.dataset.node === nodeId));
|
|
387
|
+
|
|
388
|
+
document.getElementById('graph-empty').classList.add('hidden');
|
|
389
|
+
document.getElementById('ego-legend').classList.remove('hidden');
|
|
390
|
+
document.getElementById('graph-canvas').classList.remove('hidden');
|
|
391
|
+
|
|
392
|
+
const ego = egoDataFor(nodeId);
|
|
393
|
+
renderEgoGraph(ego);
|
|
394
|
+
renderDetailsPane(node, ego);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function renderEgoGraph(ego) {
|
|
398
|
+
const container = document.getElementById('graph-canvas');
|
|
399
|
+
container.innerHTML = '';
|
|
400
|
+
const width = container.clientWidth || 600, height = container.clientHeight || 500;
|
|
401
|
+
|
|
402
|
+
const graphPayload = {
|
|
403
|
+
nodes: ego.nodes.map(n => ({ ...n })),
|
|
404
|
+
links: ego.links.map(l => ({ ...l }))
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
if (gCurrentEgoIs3D && window.ForceGraph3D) {
|
|
408
|
+
gGraphInstance = ForceGraph3D()(container)
|
|
409
|
+
.backgroundColor('#0b0f19')
|
|
410
|
+
.width(width).height(height)
|
|
411
|
+
.graphData(graphPayload)
|
|
412
|
+
.nodeLabel(n => n.name)
|
|
413
|
+
.linkDirectionalArrowLength(4)
|
|
414
|
+
.linkDirectionalArrowRelPos(1)
|
|
415
|
+
.linkColor(() => 'rgba(148,163,184,0.5)')
|
|
416
|
+
.onNodeClick(n => { if (n.id !== gSelectedNodeId) selectNode(n.id); });
|
|
417
|
+
if (window.THREE) {
|
|
418
|
+
gGraphInstance.nodeThreeObject(n => makeNode3DSprite(n, egoNodeColor));
|
|
419
|
+
} else {
|
|
420
|
+
gGraphInstance.nodeColor(egoNodeColor); // THREE missing — fall back to a plain colored sphere
|
|
421
|
+
}
|
|
422
|
+
} else if (window.ForceGraph) {
|
|
423
|
+
gGraphInstance = ForceGraph()(container)
|
|
424
|
+
.width(width).height(height)
|
|
425
|
+
.backgroundColor('#0b0f19')
|
|
426
|
+
.graphData(graphPayload)
|
|
427
|
+
.nodeLabel(n => n.name)
|
|
428
|
+
.nodeCanvasObject((n, ctx, gs) => paint2DNode(n, ctx, gs, egoNodeColor))
|
|
429
|
+
.nodeCanvasObjectMode(() => 'replace')
|
|
430
|
+
.nodePointerAreaPaint(paintPointerArea)
|
|
431
|
+
.linkDirectionalArrowLength(5)
|
|
432
|
+
.linkDirectionalArrowRelPos(1)
|
|
433
|
+
.linkColor(() => 'rgba(148,163,184,0.5)')
|
|
434
|
+
.onNodeClick(n => { if (n.id !== gSelectedNodeId) selectNode(n.id); });
|
|
435
|
+
} else {
|
|
436
|
+
container.innerHTML = '<div class="empty-hint">Graph library failed to load — check /vendor assets.</div>';
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function toggleEgoDimension() {
|
|
441
|
+
gCurrentEgoIs3D = !gCurrentEgoIs3D;
|
|
442
|
+
document.getElementById('btn-toggle-3d').textContent = gCurrentEgoIs3D ? '📐 2D' : '🌌 3D';
|
|
443
|
+
if (gSelectedNodeId) renderEgoGraph(egoDataFor(gSelectedNodeId));
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// ── Details pane ─────────────────────────────────────────────────────────
|
|
447
|
+
|
|
448
|
+
function closeDetails() {
|
|
449
|
+
document.getElementById('details-pane').classList.add('hidden');
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function renderDetailsPane(node, ego) {
|
|
453
|
+
const pane = document.getElementById('details-pane');
|
|
454
|
+
pane.classList.remove('hidden');
|
|
455
|
+
const history = (gHistoryByNode.get(node.id) || []);
|
|
456
|
+
|
|
457
|
+
const connLink = (n, dir) => `<button class="conn-link" data-node="${escapeHtml(n.id)}">${dir === 'in' ? '←' : '→'} ${escapeHtml(n.name)}</button>`;
|
|
458
|
+
|
|
459
|
+
document.getElementById('details-content').innerHTML = `
|
|
460
|
+
<span class="detail-type-badge" style="background:${colorForType(node.type)}22;color:${colorForType(node.type)}">${escapeHtml(node.type)}</span>
|
|
461
|
+
<div class="detail-name">${escapeHtml(node.name)}</div>
|
|
462
|
+
<div class="detail-id">${escapeHtml(node.id)}</div>
|
|
463
|
+
<div class="detail-field">
|
|
464
|
+
<div class="detail-field-label">File</div>
|
|
465
|
+
<div class="detail-field-value">${escapeHtml(node.file_path)}</div>
|
|
466
|
+
</div>
|
|
467
|
+
${node.signature ? `<div class="detail-field"><div class="detail-field-label">Signature</div><div class="detail-field-value">${escapeHtml(node.signature)}</div></div>` : ''}
|
|
468
|
+
<div class="detail-field">
|
|
469
|
+
<div class="detail-field-label">Uses (${ego.uses.length})</div>
|
|
470
|
+
${ego.uses.length ? ego.uses.map(n => connLink(n, 'out')).join('') : '<span class="empty-hint">none</span>'}
|
|
471
|
+
</div>
|
|
472
|
+
<div class="detail-field">
|
|
473
|
+
<div class="detail-field-label">Used by (${ego.usedBy.length})</div>
|
|
474
|
+
${ego.usedBy.length ? ego.usedBy.map(n => connLink(n, 'in')).join('') : '<span class="empty-hint">none</span>'}
|
|
475
|
+
</div>
|
|
476
|
+
<div class="detail-field">
|
|
477
|
+
<div class="detail-field-label">History (${history.length})</div>
|
|
478
|
+
${history.length ? history.map(renderHistoryEntry).join('') : '<span class="empty-hint">no recorded edits yet</span>'}
|
|
479
|
+
</div>
|
|
480
|
+
`;
|
|
481
|
+
|
|
482
|
+
document.getElementById('details-content').querySelectorAll('.conn-link').forEach(el => {
|
|
483
|
+
el.addEventListener('click', () => selectNode(el.dataset.node));
|
|
484
|
+
});
|
|
485
|
+
document.getElementById('details-content').querySelectorAll('[data-load-diff]').forEach(el => {
|
|
486
|
+
el.addEventListener('click', () => loadNodeDiff(el.dataset.loadDiff));
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function renderHistoryEntry(h) {
|
|
491
|
+
const devMatch = /Developer:\s*(.+)/i.exec(h.reasoning || '');
|
|
492
|
+
const whatMatch = /what_changed:\s*(.+)/i.exec(h.reasoning || '');
|
|
493
|
+
return `
|
|
494
|
+
<div class="history-entry">
|
|
495
|
+
<div class="when">${formatDate(h.updated_at)} ${devMatch ? `· <span class="dev-badge">${escapeHtml(devMatch[1].trim())}</span>` : ''}</div>
|
|
496
|
+
${whatMatch ? `<div>${escapeHtml(whatMatch[1].trim())}</div>` : ''}
|
|
497
|
+
${h.edit_count > 0
|
|
498
|
+
? `<button class="btn-icon small" data-load-diff="${escapeHtml(h.id)}">View changes (${h.edit_count})</button><div id="node-diff-${escapeHtml(h.id)}"></div>`
|
|
499
|
+
: ''}
|
|
500
|
+
</div>
|
|
501
|
+
`;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
async function loadNodeDiff(historyId) {
|
|
505
|
+
const el = document.getElementById(`node-diff-${historyId}`);
|
|
506
|
+
if (!el) return;
|
|
507
|
+
if (el.dataset.loaded) { el.classList.toggle('hidden'); return; }
|
|
508
|
+
el.innerHTML = '<div class="empty-hint">Loading…</div>';
|
|
509
|
+
try {
|
|
510
|
+
const data = await fetchJSON(`/api/node-diff?history_id=${encodeURIComponent(historyId)}`);
|
|
511
|
+
el.dataset.loaded = '1';
|
|
512
|
+
el.innerHTML = data.edits.map(e => `
|
|
513
|
+
${renderDiffBlock(e.lines)}
|
|
514
|
+
${e.revertable
|
|
515
|
+
? `<button class="btn danger" data-revert-history="${escapeHtml(historyId)}">⎌ Revert this change</button>`
|
|
516
|
+
: e.blocked_reason ? `<div class="drift-note">${escapeHtml(e.blocked_reason)}</div>` : ''}
|
|
517
|
+
`).join('');
|
|
518
|
+
const revertBtn = el.querySelector('[data-revert-history]');
|
|
519
|
+
if (revertBtn) revertBtn.addEventListener('click', () => revertNodeEdit(historyId));
|
|
520
|
+
} catch (err) {
|
|
521
|
+
el.innerHTML = `<div class="empty-hint">Failed to load: ${escapeHtml(err.message)}</div>`;
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
async function revertNodeEdit(historyId) {
|
|
526
|
+
if (!confirm('Revert this change? This restores the code to before it and erases the change from history.')) return;
|
|
527
|
+
const res = await fetch(withPath('/api/revert'), {
|
|
528
|
+
method: 'POST',
|
|
529
|
+
headers: { 'Content-Type': 'application/json', 'X-Devsmind-Token': DEVSMIND_TOKEN },
|
|
530
|
+
body: JSON.stringify({ history_id: historyId })
|
|
531
|
+
});
|
|
532
|
+
const data = await res.json().catch(() => ({}));
|
|
533
|
+
if (!res.ok) { alert(`Revert failed: ${data.error || 'unknown error'}`); return; }
|
|
534
|
+
await loadGraphData();
|
|
535
|
+
if (gSelectedNodeId) selectNode(gSelectedNodeId);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// ── Whole graph (2D only) ────────────────────────────────────────────────
|
|
539
|
+
|
|
540
|
+
function showWholeGraph() {
|
|
541
|
+
const overlay = document.getElementById('whole-graph-overlay');
|
|
542
|
+
overlay.classList.remove('hidden');
|
|
543
|
+
renderWholeGraph();
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/** (Re)draws the whole-graph overlay from the current filter state. Called on open and whenever
|
|
547
|
+
* applyFilters() runs while the overlay is open, so filter changes are reflected live. */
|
|
548
|
+
function renderWholeGraph() {
|
|
549
|
+
const container = document.getElementById('whole-graph-canvas');
|
|
550
|
+
if (!window.ForceGraph) { container.innerHTML = '<div class="empty-hint">Graph library failed to load.</div>'; return; }
|
|
551
|
+
|
|
552
|
+
const ids = filteredNodeIds();
|
|
553
|
+
const nodeSet = gNodes.filter(n => ids.has(n.id));
|
|
554
|
+
const nodeIdSet = new Set(nodeSet.map(n => n.id));
|
|
555
|
+
const links = gConnections.filter(c => nodeIdSet.has(c.source_node_id) && nodeIdSet.has(c.target_node_id))
|
|
556
|
+
.map(c => ({ source: c.source_node_id, target: c.target_node_id }));
|
|
557
|
+
|
|
558
|
+
container.innerHTML = '';
|
|
559
|
+
const wholeGraphColor = n => colorForType(n.type);
|
|
560
|
+
gWholeGraphInstance = ForceGraph()(container)
|
|
561
|
+
.width(container.clientWidth || 900).height(container.clientHeight || 600)
|
|
562
|
+
.backgroundColor('#0b0f19')
|
|
563
|
+
.graphData({ nodes: nodeSet.map(n => ({ id: n.id, name: n.name, type: n.type })), links })
|
|
564
|
+
.nodeLabel(n => `${n.name} (${n.type})`)
|
|
565
|
+
.nodeCanvasObject((n, ctx, gs) => paint2DNode(n, ctx, gs, wholeGraphColor))
|
|
566
|
+
.nodeCanvasObjectMode(() => 'replace')
|
|
567
|
+
.nodePointerAreaPaint(paintPointerArea)
|
|
568
|
+
.linkColor(() => 'rgba(148,163,184,0.35)')
|
|
569
|
+
.linkDirectionalArrowLength(3)
|
|
570
|
+
.onNodeClick(n => { closeWholeGraph(); selectNode(n.id); });
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
function closeWholeGraph() {
|
|
574
|
+
document.getElementById('whole-graph-overlay').classList.add('hidden');
|
|
575
|
+
gWholeGraphInstance = null;
|
|
576
|
+
}
|
package/dist/mcp/visualizer.d.ts
CHANGED
|
@@ -1,2 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* The directory this module's assets live in (view.html, view.css, view.js, view_chat.js,
|
|
3
|
+
* view_graph.js, vendor/*) — `src/mcp` under `tsx` dev, `dist/mcp` once built. server.ts's
|
|
4
|
+
* static `/app/:file` and `/vendor/:file` routes read from here.
|
|
5
|
+
*/
|
|
6
|
+
export declare const ASSETS_DIR: string;
|
|
7
|
+
/**
|
|
8
|
+
* Gate for the routes that write to source files (/api/revert, /api/message-revert,
|
|
9
|
+
* /api/message-unrevert).
|
|
10
|
+
*
|
|
11
|
+
* The server listens on loopback only, which keeps the network out — but not another site in the
|
|
12
|
+
* same browser, which can POST cross-origin without ever reading the response. This token is
|
|
13
|
+
* minted per process and handed only to a page this server itself served, so a request that
|
|
14
|
+
* cannot echo it did not come from the view app.
|
|
15
|
+
*/
|
|
16
|
+
export declare const DEVSMIND_TOKEN: string;
|
|
17
|
+
/**
|
|
18
|
+
* Renders view.html fresh off disk on every call, rather than once at module load, so it never
|
|
19
|
+
* drifts out of sync with the JS/CSS assets — which server.ts's `/app/:file` route already reads
|
|
20
|
+
* fresh per-request. Caching the shell here would mean whichever page shipped at process start
|
|
21
|
+
* keeps being served until a restart, even after the on-disk HTML changes to match newer JS —
|
|
22
|
+
* exactly the kind of stale-pairing bug ("elements the JS expects aren't there") this avoids.
|
|
23
|
+
* The file is a few KB; re-reading it per request costs nothing that matters for a local tool.
|
|
24
|
+
*/
|
|
25
|
+
export declare function getViewHtml(): string;
|
package/dist/mcp/visualizer.js
CHANGED
|
@@ -33,10 +33,37 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.
|
|
36
|
+
exports.DEVSMIND_TOKEN = exports.ASSETS_DIR = void 0;
|
|
37
|
+
exports.getViewHtml = getViewHtml;
|
|
38
|
+
const crypto = __importStar(require("crypto"));
|
|
37
39
|
const fs = __importStar(require("fs"));
|
|
38
40
|
const path = __importStar(require("path"));
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
/**
|
|
42
|
+
* The directory this module's assets live in (view.html, view.css, view.js, view_chat.js,
|
|
43
|
+
* view_graph.js, vendor/*) — `src/mcp` under `tsx` dev, `dist/mcp` once built. server.ts's
|
|
44
|
+
* static `/app/:file` and `/vendor/:file` routes read from here.
|
|
45
|
+
*/
|
|
46
|
+
exports.ASSETS_DIR = __dirname;
|
|
47
|
+
/**
|
|
48
|
+
* Gate for the routes that write to source files (/api/revert, /api/message-revert,
|
|
49
|
+
* /api/message-unrevert).
|
|
50
|
+
*
|
|
51
|
+
* The server listens on loopback only, which keeps the network out — but not another site in the
|
|
52
|
+
* same browser, which can POST cross-origin without ever reading the response. This token is
|
|
53
|
+
* minted per process and handed only to a page this server itself served, so a request that
|
|
54
|
+
* cannot echo it did not come from the view app.
|
|
55
|
+
*/
|
|
56
|
+
exports.DEVSMIND_TOKEN = crypto.randomBytes(24).toString('hex');
|
|
57
|
+
/**
|
|
58
|
+
* Renders view.html fresh off disk on every call, rather than once at module load, so it never
|
|
59
|
+
* drifts out of sync with the JS/CSS assets — which server.ts's `/app/:file` route already reads
|
|
60
|
+
* fresh per-request. Caching the shell here would mean whichever page shipped at process start
|
|
61
|
+
* keeps being served until a restart, even after the on-disk HTML changes to match newer JS —
|
|
62
|
+
* exactly the kind of stale-pairing bug ("elements the JS expects aren't there") this avoids.
|
|
63
|
+
* The file is a few KB; re-reading it per request costs nothing that matters for a local tool.
|
|
64
|
+
*/
|
|
65
|
+
function getViewHtml() {
|
|
66
|
+
const raw = fs.readFileSync(path.join(exports.ASSETS_DIR, 'view.html'), 'utf8');
|
|
67
|
+
return raw.replace('<!--DEVSMIND_TOKEN-->', `<script>const DEVSMIND_TOKEN = ${JSON.stringify(exports.DEVSMIND_TOKEN)};</script>`);
|
|
68
|
+
}
|
|
42
69
|
//# sourceMappingURL=visualizer.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"visualizer.js","sourceRoot":"","sources":["../../src/mcp/visualizer.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"visualizer.js","sourceRoot":"","sources":["../../src/mcp/visualizer.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8BA,kCAMC;AApCD,+CAAiC;AACjC,uCAAyB;AACzB,2CAA6B;AAE7B;;;;GAIG;AACU,QAAA,UAAU,GAAG,SAAS,CAAC;AAEpC;;;;;;;;GAQG;AACU,QAAA,cAAc,GAAG,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAErE;;;;;;;GAOG;AACH,SAAgB,WAAW;IACzB,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,kBAAU,EAAE,WAAW,CAAC,EAAE,MAAM,CAAC,CAAC;IACxE,OAAO,GAAG,CAAC,OAAO,CAChB,uBAAuB,EACvB,kCAAkC,IAAI,CAAC,SAAS,CAAC,sBAAc,CAAC,YAAY,CAC7E,CAAC;AACJ,CAAC"}
|