@hugobatist/smartcode 0.1.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.
Files changed (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +292 -0
  3. package/dist/cli.js +4324 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/index.d.ts +374 -0
  6. package/dist/index.js +1167 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/static/annotations-panel.js +133 -0
  9. package/dist/static/annotations-svg.js +108 -0
  10. package/dist/static/annotations.css +367 -0
  11. package/dist/static/annotations.js +367 -0
  12. package/dist/static/app-init.js +497 -0
  13. package/dist/static/breakpoints.css +69 -0
  14. package/dist/static/breakpoints.js +197 -0
  15. package/dist/static/clipboard.js +94 -0
  16. package/dist/static/collapse-ui.js +325 -0
  17. package/dist/static/command-history.js +89 -0
  18. package/dist/static/context-menu.js +334 -0
  19. package/dist/static/custom-renderer.js +201 -0
  20. package/dist/static/dagre-layout.js +291 -0
  21. package/dist/static/diagram-dom.js +241 -0
  22. package/dist/static/diagram-editor.js +368 -0
  23. package/dist/static/editor-panel.js +107 -0
  24. package/dist/static/editor-popovers.js +187 -0
  25. package/dist/static/event-bus.js +57 -0
  26. package/dist/static/export.js +181 -0
  27. package/dist/static/file-tree.js +470 -0
  28. package/dist/static/ghost-paths.js +397 -0
  29. package/dist/static/heatmap.css +116 -0
  30. package/dist/static/heatmap.js +308 -0
  31. package/dist/static/icons.js +66 -0
  32. package/dist/static/inline-edit.js +294 -0
  33. package/dist/static/interaction-state.js +155 -0
  34. package/dist/static/interaction-tracker.js +93 -0
  35. package/dist/static/live.html +239 -0
  36. package/dist/static/main-layout.css +220 -0
  37. package/dist/static/main.css +334 -0
  38. package/dist/static/mcp-sessions.js +202 -0
  39. package/dist/static/modal.css +109 -0
  40. package/dist/static/modal.js +171 -0
  41. package/dist/static/node-drag.js +293 -0
  42. package/dist/static/pan-zoom.js +199 -0
  43. package/dist/static/renderer.js +280 -0
  44. package/dist/static/search.css +103 -0
  45. package/dist/static/search.js +304 -0
  46. package/dist/static/selection.js +353 -0
  47. package/dist/static/session-player.css +137 -0
  48. package/dist/static/session-player.js +411 -0
  49. package/dist/static/sidebar.css +248 -0
  50. package/dist/static/svg-renderer.js +313 -0
  51. package/dist/static/svg-shapes.js +218 -0
  52. package/dist/static/tokens.css +76 -0
  53. package/dist/static/vendor/dagre-bundle.js +43 -0
  54. package/dist/static/vendor/dagre.min.js +3 -0
  55. package/dist/static/vendor/graphlib.min.js +2 -0
  56. package/dist/static/viewport-transform.js +107 -0
  57. package/dist/static/workspace-switcher.js +202 -0
  58. package/dist/static/ws-client.js +71 -0
  59. package/dist/static/ws-handler.js +125 -0
  60. package/package.json +74 -0
@@ -0,0 +1,367 @@
1
+ /**
2
+ * SmartCode Annotations -- Flag, Status, Breakpoint, Risk annotation system.
3
+ * Dependencies: diagram-dom.js, event-bus.js, annotations-svg.js, annotations-panel.js.
4
+ * Exposed as window.SmartCodeAnnotations
5
+ */
6
+ (function () {
7
+ 'use strict';
8
+
9
+ var ANNOTATION_START = '%% --- ANNOTATIONS (auto-managed by SmartCode) ---';
10
+ var ANNOTATION_END = '%% --- END ANNOTATIONS ---';
11
+ var FLAG_REGEX = /^%%\s*@flag\s+(\S+)\s+"([^"]*)"$/;
12
+ var STATUS_REGEX = /^%%\s*@status\s+(\S+)\s+(\S+)$/;
13
+ var BREAKPOINT_REGEX = /^%%\s*@breakpoint\s+(\S+)$/;
14
+ var RISK_REGEX = /^%%\s*@risk\s+(\S+)\s+(high|medium|low)\s+"([^"]*)"$/;
15
+ var GHOST_REGEX = /^%%\s*@ghost\s+(\S+)\s+(\S+)\s+"([^"]*)"$/;
16
+
17
+ var state = {
18
+ flagMode: false,
19
+ flags: new Map(), // nodeId -> { message, timestamp }
20
+ statuses: new Map(), // nodeId -> statusValue string
21
+ breakpoints: new Set(), // nodeId set
22
+ risks: new Map(), // nodeId -> { level, reason }
23
+ ghosts: [], // [{ fromNodeId, toNodeId, label }]
24
+ panelOpen: false, popover: null,
25
+ popoverOutsideHandler: null, // stored ref to remove on close
26
+ };
27
+
28
+ var hooks = {
29
+ getEditor: function() { return document.getElementById('editor'); },
30
+ getCurrentFile: function() { return window.currentFile || ''; },
31
+ getLastContent: function() { return window.lastContent || ''; },
32
+ setLastContent: function(v) { window.lastContent = v; },
33
+ saveFile: null,
34
+ renderDiagram: null,
35
+ };
36
+
37
+ // ── Parsing & Serialization ──
38
+
39
+ function parseAnnotations(content) {
40
+ var flags = new Map(), statuses = new Map(), breakpoints = new Set(), risks = new Map();
41
+ var ghosts = [];
42
+ var lines = content.split('\n');
43
+ var inBlock = false;
44
+ for (var li = 0; li < lines.length; li++) {
45
+ var trimmed = lines[li].trim();
46
+ if (trimmed === ANNOTATION_START) { inBlock = true; continue; }
47
+ if (trimmed === ANNOTATION_END) { inBlock = false; continue; }
48
+ if (inBlock) {
49
+ var fm = trimmed.match(FLAG_REGEX);
50
+ if (fm) { flags.set(fm[1], { message: fm[2], timestamp: Date.now() }); continue; }
51
+ var sm = trimmed.match(STATUS_REGEX);
52
+ if (sm) { statuses.set(sm[1], sm[2]); continue; }
53
+ var bm = trimmed.match(BREAKPOINT_REGEX);
54
+ if (bm) { breakpoints.add(bm[1]); continue; }
55
+ var rm = trimmed.match(RISK_REGEX);
56
+ if (rm) { risks.set(rm[1], { level: rm[2], reason: rm[3] }); continue; }
57
+ var gm = trimmed.match(GHOST_REGEX);
58
+ if (gm) { ghosts.push({ fromNodeId: gm[1], toNodeId: gm[2], label: gm[3] }); continue; }
59
+ }
60
+ }
61
+ return { flags, statuses, breakpoints, risks, ghosts };
62
+ }
63
+
64
+ function stripAnnotations(content) {
65
+ var lines = content.split('\n'), result = [];
66
+ var inBlock = false;
67
+ for (var si = 0; si < lines.length; si++) {
68
+ var t = lines[si].trim();
69
+ if (t === ANNOTATION_START) { inBlock = true; continue; }
70
+ if (t === ANNOTATION_END) { inBlock = false; continue; }
71
+ if (!inBlock) result.push(lines[si]);
72
+ }
73
+ while (result.length > 0 && result[result.length - 1].trim() === '') result.pop();
74
+ return result.join('\n');
75
+ }
76
+
77
+ function injectAnnotations(content, flags, statuses, ghosts) {
78
+ var clean = stripAnnotations(content), statusMap = statuses || state.statuses;
79
+ var ghostList = ghosts || state.ghosts || [];
80
+ var hasAnnotations = flags.size > 0 || statusMap.size > 0 || state.breakpoints.size > 0 || state.risks.size > 0 || ghostList.length > 0;
81
+ if (!hasAnnotations) return clean;
82
+ var lines = ['', ANNOTATION_START];
83
+ flags.forEach(function(val, nid) { lines.push('%% @flag ' + nid + ' "' + val.message.replace(/"/g, "''") + '"'); });
84
+ statusMap.forEach(function(sv, nid) { lines.push('%% @status ' + nid + ' ' + sv); });
85
+ state.breakpoints.forEach(function(nid) { lines.push('%% @breakpoint ' + nid); });
86
+ state.risks.forEach(function(val, nid) { lines.push('%% @risk ' + nid + ' ' + val.level + ' "' + val.reason.replace(/"/g, "''") + '"'); });
87
+ for (var gi = 0; gi < ghostList.length; gi++) {
88
+ var g = ghostList[gi];
89
+ lines.push('%% @ghost ' + g.fromNodeId + ' ' + g.toNodeId + ' "' + (g.label || '').replace(/"/g, "''") + '"');
90
+ }
91
+ lines.push(ANNOTATION_END);
92
+ return clean + '\n' + lines.join('\n');
93
+ }
94
+
95
+ function getCleanContent(content) { return stripAnnotations(content); }
96
+
97
+ // ── SVG Post-processing (delegates to SmartCodeAnnotationsSVG) ──
98
+
99
+ function applyFlagsToSVG() {
100
+ SmartCodeAnnotationsSVG.applyFlagsToSVG(state.flags);
101
+ }
102
+
103
+ // ── Panel rendering (delegates to SmartCodeAnnotationsPanel) ──
104
+
105
+ function renderPanel() {
106
+ SmartCodeAnnotationsPanel.renderPanel(state, hooks, removeFlag);
107
+ }
108
+
109
+ function updateBadge() {
110
+ SmartCodeAnnotationsPanel.updateBadge(state.flags.size);
111
+ }
112
+
113
+ function togglePanel() {
114
+ SmartCodeAnnotationsPanel.togglePanel(state);
115
+ }
116
+
117
+ // ── Popover ──
118
+
119
+ function closePopover() {
120
+ if (state.popoverOutsideHandler) {
121
+ document.removeEventListener('mousedown', state.popoverOutsideHandler);
122
+ state.popoverOutsideHandler = null;
123
+ }
124
+ if (state.popover) { state.popover.remove(); state.popover = null; }
125
+ }
126
+
127
+ function showPopover(nodeInfo, clientX, clientY) {
128
+ closePopover();
129
+ var existing = state.flags.get(nodeInfo.id);
130
+ var isExisting = !!existing;
131
+ var isNode = nodeInfo.type === 'node' || nodeInfo.type === 'subgraph';
132
+ var pop = document.createElement('div');
133
+ pop.className = 'flag-popover';
134
+ pop.style.left = Math.min(clientX + 12, window.innerWidth - 380) + 'px';
135
+ pop.style.top = Math.min(clientY - 20, window.innerHeight - 320) + 'px';
136
+
137
+ var typeLabel = nodeInfo.type === 'edge' ? 'Edge' : nodeInfo.type === 'subgraph' ? 'Subgraph' : 'Node';
138
+ var hasMmdEditor = !!window.MmdEditor;
139
+
140
+ // Build popover content using DOM methods for safety
141
+ var titleDiv = document.createElement('div');
142
+ titleDiv.className = 'flag-popover-title';
143
+ var titleSpan = document.createElement('span');
144
+ titleSpan.textContent = isExisting ? 'Edit Flag' : 'Flag ' + typeLabel;
145
+ titleDiv.appendChild(titleSpan);
146
+ var idSpan = document.createElement('span');
147
+ idSpan.className = 'node-id';
148
+ idSpan.textContent = nodeInfo.id;
149
+ titleDiv.appendChild(idSpan);
150
+ pop.appendChild(titleDiv);
151
+
152
+ var textarea = document.createElement('textarea');
153
+ textarea.className = 'flag-note';
154
+ textarea.placeholder = 'Describe the issue (optional)...';
155
+ textarea.value = isExisting ? existing.message : '';
156
+ pop.appendChild(textarea);
157
+
158
+ if (hasMmdEditor) {
159
+ var corrDiv = document.createElement('div');
160
+ corrDiv.className = 'correction-actions';
161
+ var corrLabel = document.createElement('span');
162
+ corrLabel.style.cssText = 'font-size:10px;font-weight:700;color:var(--text-secondary);text-transform:uppercase;letter-spacing:1px';
163
+ corrLabel.textContent = 'Actions';
164
+ corrDiv.appendChild(corrLabel);
165
+ var btnsDiv = document.createElement('div');
166
+ btnsDiv.className = 'correction-btns';
167
+ if (isNode) {
168
+ var btnEditText = document.createElement('button');
169
+ btnEditText.className = 'btn-correction';
170
+ btnEditText.dataset.action = 'edit-text';
171
+ btnEditText.title = 'Edit node text';
172
+ btnEditText.textContent = 'Edit Text';
173
+ btnsDiv.appendChild(btnEditText);
174
+ var btnConnect = document.createElement('button');
175
+ btnConnect.className = 'btn-correction';
176
+ btnConnect.dataset.action = 'connect-from';
177
+ btnConnect.title = 'Create new edge from this node';
178
+ btnConnect.innerHTML = 'New Edge ' + SmartCodeIcons.arrowRight;
179
+ btnsDiv.appendChild(btnConnect);
180
+ }
181
+ var btnDelete = document.createElement('button');
182
+ btnDelete.className = 'btn-correction btn-correction-danger';
183
+ btnDelete.dataset.action = 'delete';
184
+ btnDelete.title = 'Remove ' + typeLabel.toLowerCase() + ' from diagram';
185
+ btnDelete.textContent = 'Remove ' + typeLabel;
186
+ btnsDiv.appendChild(btnDelete);
187
+ corrDiv.appendChild(btnsDiv);
188
+ pop.appendChild(corrDiv);
189
+ }
190
+
191
+ var actionsDiv = document.createElement('div');
192
+ actionsDiv.className = 'flag-popover-actions';
193
+ if (isExisting) {
194
+ var btnRemove = document.createElement('button');
195
+ btnRemove.className = 'btn-flag remove';
196
+ btnRemove.dataset.action = 'remove-flag';
197
+ btnRemove.textContent = 'Remove Flag';
198
+ actionsDiv.appendChild(btnRemove);
199
+ }
200
+ var btnCancel = document.createElement('button');
201
+ btnCancel.className = 'btn-flag secondary';
202
+ btnCancel.dataset.action = 'cancel';
203
+ btnCancel.textContent = 'Cancel';
204
+ actionsDiv.appendChild(btnCancel);
205
+ var btnFlag = document.createElement('button');
206
+ btnFlag.className = 'btn-flag primary';
207
+ btnFlag.dataset.action = 'flag';
208
+ btnFlag.textContent = isExisting ? 'Update' : 'Flag';
209
+ actionsDiv.appendChild(btnFlag);
210
+ pop.appendChild(actionsDiv);
211
+
212
+ document.body.appendChild(pop);
213
+ state.popover = pop;
214
+ textarea.focus();
215
+
216
+ textarea.addEventListener('keydown', function(e) {
217
+ if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); doFlag(nodeInfo, textarea.value.trim()); }
218
+ if (e.key === 'Escape') closePopover();
219
+ });
220
+
221
+ pop.addEventListener('click', function(e) {
222
+ var btn = e.target.closest('[data-action]');
223
+ if (!btn) return;
224
+ var action = btn.dataset.action;
225
+ if (action === 'cancel') closePopover();
226
+ else if (action === 'flag') doFlag(nodeInfo, textarea.value.trim());
227
+ else if (action === 'remove-flag') removeFlag(nodeInfo.id);
228
+ else if (action === 'delete') { closePopover(); MmdEditor.doRemoveNode(nodeInfo.id); }
229
+ else if (action === 'edit-text') { closePopover(); MmdEditor.doEditNodeText(nodeInfo.id); }
230
+ else if (action === 'connect-from') { closePopover(); MmdEditor.startConnectFrom(nodeInfo.id); }
231
+ });
232
+
233
+ setTimeout(function() {
234
+ function outsideClick(e) {
235
+ if (pop.contains(e.target)) return;
236
+ closePopover();
237
+ }
238
+ state.popoverOutsideHandler = outsideClick;
239
+ document.addEventListener('mousedown', outsideClick);
240
+ }, 50);
241
+ }
242
+
243
+ // ── Flag Operations ──
244
+
245
+ function doFlag(nodeInfo, message) {
246
+ state.flags.set(nodeInfo.id, { message: message, timestamp: Date.now() });
247
+ closePopover();
248
+ onFlagsChanged();
249
+ }
250
+
251
+ function removeFlag(nodeId) {
252
+ state.flags.delete(nodeId);
253
+ closePopover();
254
+ onFlagsChanged();
255
+ }
256
+
257
+ async function onFlagsChanged() {
258
+ var editor = hooks.getEditor();
259
+ if (!editor) return;
260
+ var newContent = injectAnnotations(editor.value, state.flags, state.statuses);
261
+ editor.value = newContent;
262
+ hooks.setLastContent(newContent);
263
+ if (hooks.saveFile) await hooks.saveFile();
264
+ applyFlagsToSVG();
265
+ renderPanel();
266
+ updateBadge();
267
+ if (window.SmartCodeEventBus) {
268
+ SmartCodeEventBus.emit('flags:changed', { flags: state.flags, statuses: state.statuses });
269
+ }
270
+ }
271
+
272
+ // ── Merge (auto-sync preserves user flags) ──
273
+
274
+ function mergeIncomingContent(incomingContent) {
275
+ var incoming = parseAnnotations(incomingContent);
276
+ var mergedFlags = new Map(incoming.flags);
277
+ for (var entry of state.flags) mergedFlags.set(entry[0], entry[1]);
278
+ state.flags = mergedFlags;
279
+ var mergedStatuses = new Map(incoming.statuses);
280
+ for (var sEntry of state.statuses) mergedStatuses.set(sEntry[0], sEntry[1]);
281
+ state.statuses = mergedStatuses;
282
+ state.breakpoints = new Set([...incoming.breakpoints, ...state.breakpoints]);
283
+ if (window.SmartCodeBreakpoints) SmartCodeBreakpoints.updateBreakpoints(state.breakpoints);
284
+ var mergedRisks = new Map(incoming.risks);
285
+ for (var rEntry of state.risks) mergedRisks.set(rEntry[0], rEntry[1]);
286
+ state.risks = mergedRisks;
287
+ if (window.SmartCodeHeatmap) SmartCodeHeatmap.updateRisks(state.risks);
288
+ // Ghosts from file take precedence (file is source of truth)
289
+ state.ghosts = incoming.ghosts || [];
290
+ if (window.SmartCodeGhostPaths) {
291
+ var file = window.SmartCodeFileTree ? SmartCodeFileTree.getCurrentFile() : null;
292
+ if (file) SmartCodeGhostPaths.updateGhostPaths(file, state.ghosts);
293
+ }
294
+ var cleanIncoming = stripAnnotations(incomingContent);
295
+ return injectAnnotations(cleanIncoming, mergedFlags, mergedStatuses);
296
+ }
297
+
298
+ // ── Click Handler (flag mode) ──
299
+
300
+ function handlePreviewClick(e) {
301
+ if (!state.flagMode) return;
302
+ if (e.target.closest('.zoom-controls') || e.target.closest('.flag-popover')) return;
303
+ var nodeInfo = DiagramDOM.extractNodeId(e.target);
304
+ if (!nodeInfo) return;
305
+ e.preventDefault();
306
+ e.stopPropagation();
307
+ showPopover(nodeInfo, e.clientX, e.clientY);
308
+ }
309
+
310
+ // ── Flag Mode Toggle ──
311
+
312
+ function toggleFlagMode() {
313
+ state.flagMode = !state.flagMode;
314
+ document.body.classList.toggle('flag-mode', state.flagMode);
315
+ var btn = document.getElementById('btnFlags');
316
+ if (btn) btn.classList.toggle('active', state.flagMode);
317
+ if (!state.flagMode) closePopover();
318
+ if (window.toast) window.toast(state.flagMode ? 'Flag Mode ON -- click on a node' : 'Flag Mode OFF');
319
+ }
320
+
321
+ // ── Init ──
322
+
323
+ function init(options) {
324
+ if (options) Object.assign(hooks, options);
325
+ var editor = hooks.getEditor();
326
+ if (editor && editor.value) {
327
+ var parsed = parseAnnotations(editor.value);
328
+ state.flags = parsed.flags;
329
+ state.statuses = parsed.statuses;
330
+ state.breakpoints = parsed.breakpoints;
331
+ state.risks = parsed.risks;
332
+ state.ghosts = parsed.ghosts || [];
333
+ if (window.SmartCodeBreakpoints) SmartCodeBreakpoints.updateBreakpoints(parsed.breakpoints);
334
+ if (window.SmartCodeHeatmap) SmartCodeHeatmap.updateRisks(parsed.risks);
335
+ }
336
+ var container = document.getElementById('preview-container');
337
+ if (container) container.addEventListener('click', handlePreviewClick);
338
+ renderPanel();
339
+ updateBadge();
340
+
341
+ if (window.SmartCodeEventBus) {
342
+ SmartCodeEventBus.on('diagram:rendered', function() {
343
+ applyFlagsToSVG();
344
+ });
345
+ }
346
+ }
347
+
348
+ // ── Status Operations ──
349
+
350
+ function getStatusMap() { return state.statuses; }
351
+ function setStatus(nodeId, statusValue) { state.statuses.set(nodeId, statusValue); onFlagsChanged(); }
352
+ function removeStatus(nodeId) { state.statuses.delete(nodeId); onFlagsChanged(); }
353
+
354
+ // ── Public API ──
355
+
356
+ window.SmartCodeAnnotations = {
357
+ init: init, getState: function() { return state; },
358
+ parseAnnotations: parseAnnotations, stripAnnotations: stripAnnotations,
359
+ injectAnnotations: injectAnnotations, getCleanContent: getCleanContent,
360
+ applyFlagsToSVG: applyFlagsToSVG, mergeIncomingContent: mergeIncomingContent,
361
+ toggleFlagMode: toggleFlagMode, togglePanel: togglePanel,
362
+ renderPanel: renderPanel, updateBadge: updateBadge,
363
+ extractNodeId: function(element) { return DiagramDOM.extractNodeId(element); },
364
+ closePopover: closePopover, getRisks: function() { return state.risks; },
365
+ getStatusMap: getStatusMap, setStatus: setStatus, removeStatus: removeStatus,
366
+ };
367
+ })();