codesynapt 0.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.
Files changed (61) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/LICENSE +686 -0
  3. package/LICENSES.md +141 -0
  4. package/README.md +331 -0
  5. package/electron/main.cjs +2849 -0
  6. package/electron/plugin-loader.cjs +184 -0
  7. package/electron/preload.cjs +108 -0
  8. package/package.json +216 -0
  9. package/packages/core/bin/codesynapt-mcp.cjs +611 -0
  10. package/packages/core/bin/codesynapt.cjs +1933 -0
  11. package/packages/core/legacy.js +300 -0
  12. package/packages/core/lib/control-server.cjs +1539 -0
  13. package/packages/core/lib/embedding.cjs +89 -0
  14. package/packages/core/lib/logger.cjs +63 -0
  15. package/packages/core/lib/search-cache.cjs +140 -0
  16. package/packages/core/lib/search-worker.cjs +255 -0
  17. package/packages/core/lib/search.cjs +211 -0
  18. package/packages/core/lib/symbol-graph.cjs +402 -0
  19. package/packages/core/lib/symbol-parser-js.cjs +542 -0
  20. package/packages/core/lib/symbol-parser-misc.cjs +394 -0
  21. package/packages/core/lib/symbol-parser-py.cjs +215 -0
  22. package/packages/core/lib/symbol-parser-treesitter.cjs +658 -0
  23. package/packages/core/lib/symbol-parser-tsc.cjs +332 -0
  24. package/packages/core/monorepo.js +310 -0
  25. package/packages/core/parser.js +2234 -0
  26. package/packages/core/scanner.js +623 -0
  27. package/plugin-api/LICENSE +21 -0
  28. package/plugin-api/README.md +114 -0
  29. package/plugin-api/docs/01-getting-started.md +197 -0
  30. package/plugin-api/docs/02-concepts.md +269 -0
  31. package/plugin-api/docs/api-reference.md +463 -0
  32. package/plugin-api/docs/troubleshooting.md +332 -0
  33. package/plugin-api/docs/types/exporter.md +377 -0
  34. package/plugin-api/docs/types/theme.md +312 -0
  35. package/plugin-api/examples/hello-world-plugin/README.md +70 -0
  36. package/plugin-api/examples/hello-world-plugin/main.js +36 -0
  37. package/plugin-api/examples/hello-world-plugin/manifest.json +12 -0
  38. package/plugin-api/examples/mermaid-exporter/README.md +125 -0
  39. package/plugin-api/examples/mermaid-exporter/main.js +58 -0
  40. package/plugin-api/examples/mermaid-exporter/manifest.json +12 -0
  41. package/plugin-api/examples/rust-parser/README.md +71 -0
  42. package/plugin-api/examples/rust-parser/main.js +123 -0
  43. package/plugin-api/examples/rust-parser/manifest.json +12 -0
  44. package/plugin-api/examples/sunset-theme/README.md +95 -0
  45. package/plugin-api/examples/sunset-theme/manifest.json +12 -0
  46. package/plugin-api/examples/sunset-theme/theme.css +31 -0
  47. package/plugin-api/package.json +20 -0
  48. package/plugin-api/types.d.ts +395 -0
  49. package/public/app.js +6837 -0
  50. package/public/backend.js +285 -0
  51. package/public/index.html +647 -0
  52. package/public/plugin-host.js +321 -0
  53. package/public/style.css +4359 -0
  54. package/public/vendor/three.module.js +53044 -0
  55. package/scripts/competitor-watch.mjs +144 -0
  56. package/scripts/copy-vendor.js +21 -0
  57. package/scripts/download-bundled-node.cjs +53 -0
  58. package/scripts/fuses-after-pack.cjs +34 -0
  59. package/scripts/license-check.js +119 -0
  60. package/scripts/perf-test.js +200 -0
  61. package/server.js +132 -0
@@ -0,0 +1,321 @@
1
+ // ═══════════════════════════════════════════════════════════
2
+ // Plugin host — renderer process side
3
+ //
4
+ // Activates plugins discovered by the main process. Themes get
5
+ // their CSS injected into the document. Code plugins get an
6
+ // isolated `context` object exposing only the approved API
7
+ // surface; they cannot reach into app internals directly.
8
+ //
9
+ // This is not a hard security boundary (anything in the renderer
10
+ // shares the same JS realm) but it is a clear contract that
11
+ // plugins are expected to use. Anything reading via global window
12
+ // digging is unsupported and may break across versions.
13
+ // ═══════════════════════════════════════════════════════════
14
+
15
+ // Renderer-side plugin host. Uses window.__bus for events.
16
+
17
+ // Internal registries — written by plugins, read by the app
18
+ export const pluginRegistry = {
19
+ themes: new Map(), // id -> { manifest, css }
20
+ exporters: new Map(), // id -> { manifest, opts }
21
+ parsers: new Map(), // id -> { manifest, opts }
22
+ layouts: new Map(), // id -> { manifest, opts }
23
+ panels: new Map(), // id -> { manifest, handle }
24
+ contextMenuItems: [], // list of { plugin, opts }
25
+ commands: new Map(), // id -> { plugin, opts }
26
+ loaded: [], // raw discovery result for the settings UI
27
+ }
28
+
29
+ // Track plugin lifecycle so we can deactivate cleanly
30
+ const activations = new Map() // pluginId -> { deactivate, disposables }
31
+
32
+ /**
33
+ * Build a per-plugin context object. This is what plugins see via
34
+ * the `ctx` argument to their activate() function.
35
+ */
36
+ function makeContext(manifest, hostHelpers) {
37
+ const disposables = []
38
+ const perms = new Set(manifest.permissions || [])
39
+
40
+ const requirePerm = (perm, action) => {
41
+ if (!perms.has(perm)) {
42
+ throw new Error(`Plugin "${manifest.id}" attempted "${action}" but does not declare permission "${perm}"`)
43
+ }
44
+ }
45
+
46
+ // Storage scoped to this plugin id
47
+ const storagePrefix = `codesynapt:plugin:${manifest.id}:`
48
+ const storage = {
49
+ get(key) {
50
+ try {
51
+ const raw = localStorage.getItem(storagePrefix + key)
52
+ return raw == null ? null : JSON.parse(raw)
53
+ } catch { return null }
54
+ },
55
+ set(key, value) {
56
+ try {
57
+ localStorage.setItem(storagePrefix + key, JSON.stringify(value))
58
+ } catch { /* quota */ }
59
+ },
60
+ delete(key) {
61
+ try { localStorage.removeItem(storagePrefix + key) } catch {}
62
+ },
63
+ clear() {
64
+ try {
65
+ const remove = []
66
+ for (let i = 0; i < localStorage.length; i++) {
67
+ const k = localStorage.key(i)
68
+ if (k && k.startsWith(storagePrefix)) remove.push(k)
69
+ }
70
+ for (const k of remove) localStorage.removeItem(k)
71
+ } catch {}
72
+ },
73
+ keys() {
74
+ const keys = []
75
+ try {
76
+ for (let i = 0; i < localStorage.length; i++) {
77
+ const k = localStorage.key(i)
78
+ if (k && k.startsWith(storagePrefix)) keys.push(k.slice(storagePrefix.length))
79
+ }
80
+ } catch {}
81
+ return keys
82
+ },
83
+ }
84
+
85
+ // Graph API — wraps the app's state in a read-only-looking facade
86
+ const graph = {
87
+ get root() { return hostHelpers.getState().root },
88
+ get nodes() { return hostHelpers.getState().byIdx },
89
+ get edges() { return hostHelpers.getState().edges },
90
+ get selectedId() { return hostHelpers.getState().selectedId },
91
+ get activeSet() { return hostHelpers.getEffectiveActiveSet() },
92
+ async readFile(id) {
93
+ requirePerm('read-files', 'readFile')
94
+ return hostHelpers.readFile(id)
95
+ },
96
+ getNode(id) { return hostHelpers.getState().nodes.get(id) || null },
97
+ outgoing(id) {
98
+ const result = []
99
+ for (const e of hostHelpers.getState().edges) {
100
+ if (e.s === id) result.push(e)
101
+ }
102
+ return result
103
+ },
104
+ incoming(id) {
105
+ const result = []
106
+ for (const e of hostHelpers.getState().edges) {
107
+ if (e.t === id) result.push(e)
108
+ }
109
+ return result
110
+ },
111
+ }
112
+
113
+ // UI registration
114
+ const ui = {
115
+ registerPanel(opts) {
116
+ requirePerm('ui-panel', 'registerPanel')
117
+ const handle = hostHelpers.createPanel(manifest, opts)
118
+ disposables.push(() => handle.dispose())
119
+ return handle
120
+ },
121
+ registerContextMenuItem(opts) {
122
+ requirePerm('context-menu', 'registerContextMenuItem')
123
+ const entry = { plugin: manifest.id, opts }
124
+ pluginRegistry.contextMenuItems.push(entry)
125
+ disposables.push(() => {
126
+ const i = pluginRegistry.contextMenuItems.indexOf(entry)
127
+ if (i >= 0) pluginRegistry.contextMenuItems.splice(i, 1)
128
+ })
129
+ return { dispose() {
130
+ const i = pluginRegistry.contextMenuItems.indexOf(entry)
131
+ if (i >= 0) pluginRegistry.contextMenuItems.splice(i, 1)
132
+ }}
133
+ },
134
+ registerCommand(opts) {
135
+ pluginRegistry.commands.set(opts.id, { plugin: manifest.id, opts })
136
+ disposables.push(() => pluginRegistry.commands.delete(opts.id))
137
+ return { dispose() { pluginRegistry.commands.delete(opts.id) } }
138
+ },
139
+ }
140
+
141
+ // Exporter registry
142
+ const exporters = {
143
+ register(opts) {
144
+ requirePerm('export', 'register exporter')
145
+ pluginRegistry.exporters.set(opts.name, { manifest, opts })
146
+ disposables.push(() => pluginRegistry.exporters.delete(opts.name))
147
+ return { dispose() { pluginRegistry.exporters.delete(opts.name) } }
148
+ },
149
+ }
150
+
151
+ // Parser registry
152
+ const parsers = {
153
+ register(opts) {
154
+ requirePerm('parse', 'register parser')
155
+ pluginRegistry.parsers.set(opts.name, { manifest, opts })
156
+ disposables.push(() => pluginRegistry.parsers.delete(opts.name))
157
+ return { dispose() { pluginRegistry.parsers.delete(opts.name) } }
158
+ },
159
+ }
160
+
161
+ // Layout registry
162
+ const layouts = {
163
+ register(opts) {
164
+ pluginRegistry.layouts.set(opts.id, { manifest, opts })
165
+ disposables.push(() => pluginRegistry.layouts.delete(opts.id))
166
+ return { dispose() { pluginRegistry.layouts.delete(opts.id) } }
167
+ },
168
+ }
169
+
170
+ // Event bus — proxy to the app's bus but restrict to read-only events
171
+ const events = {
172
+ on(name, handler) {
173
+ const off = hostHelpers.busOn(name, handler)
174
+ disposables.push(off)
175
+ return off
176
+ },
177
+ off(name, handler) { hostHelpers.busOff(name, handler) },
178
+ }
179
+
180
+ const ctx = {
181
+ manifest,
182
+ appVersion: hostHelpers.appVersion,
183
+ graph,
184
+ ui,
185
+ exporters,
186
+ parsers,
187
+ layouts,
188
+ events,
189
+ storage,
190
+ toast: (msg) => hostHelpers.toast(`[${manifest.id}] ${msg}`),
191
+ log: (...args) => console.log(`[plugin:${manifest.id}]`, ...args),
192
+ }
193
+
194
+ return { ctx, disposables }
195
+ }
196
+
197
+ /**
198
+ * Activate one plugin. Themes just inject CSS; code plugins are
199
+ * compiled as an ESM module via dynamic blob URL.
200
+ */
201
+ async function activatePlugin(record, hostHelpers) {
202
+ const { manifest, entryContent, error } = record
203
+ if (error) {
204
+ console.warn(`[plugin] skipping ${manifest.id}: ${error}`)
205
+ return
206
+ }
207
+
208
+ // Theme — inject CSS, register in the theme registry, done
209
+ if (manifest.type === 'theme') {
210
+ const styleId = `plugin-theme-${manifest.id}`
211
+ let style = document.getElementById(styleId)
212
+ if (!style) {
213
+ style = document.createElement('style')
214
+ style.id = styleId
215
+ style.dataset.pluginId = manifest.id
216
+ document.head.appendChild(style)
217
+ }
218
+ style.textContent = entryContent
219
+ pluginRegistry.themes.set(manifest.id, { manifest, css: entryContent })
220
+ activations.set(manifest.id, {
221
+ deactivate: () => style.remove(),
222
+ disposables: [],
223
+ })
224
+ return
225
+ }
226
+
227
+ // Code plugin — execute as an ES module via a Blob URL.
228
+ // This isolates the plugin from the global window scope (it gets
229
+ // its own module record) while still letting it use modern JS.
230
+ let module
231
+ try {
232
+ const blob = new Blob([entryContent], { type: 'text/javascript' })
233
+ const url = URL.createObjectURL(blob)
234
+ module = await import(/* @vite-ignore */ url)
235
+ URL.revokeObjectURL(url)
236
+ } catch (err) {
237
+ console.error(`[plugin] ${manifest.id} failed to load:`, err)
238
+ hostHelpers.toast(`Plugin "${manifest.name}" failed to load`)
239
+ return
240
+ }
241
+
242
+ const plugin = module.default
243
+ if (!plugin || typeof plugin.activate !== 'function') {
244
+ console.warn(`[plugin] ${manifest.id} has no default export with activate()`)
245
+ return
246
+ }
247
+
248
+ const { ctx, disposables } = makeContext(manifest, hostHelpers)
249
+ try {
250
+ await plugin.activate(ctx)
251
+ } catch (err) {
252
+ console.error(`[plugin] ${manifest.id} activate() threw:`, err)
253
+ hostHelpers.toast(`Plugin "${manifest.name}" crashed during activation`)
254
+ // Run any cleanup that was registered before the crash
255
+ for (const fn of disposables) {
256
+ try { fn() } catch {}
257
+ }
258
+ return
259
+ }
260
+
261
+ activations.set(manifest.id, {
262
+ deactivate: async () => {
263
+ try {
264
+ if (typeof plugin.deactivate === 'function') await plugin.deactivate()
265
+ } catch (err) {
266
+ console.error(`[plugin] ${manifest.id} deactivate() threw:`, err)
267
+ }
268
+ for (const fn of disposables) {
269
+ try { fn() } catch {}
270
+ }
271
+ },
272
+ disposables,
273
+ })
274
+ }
275
+
276
+ /**
277
+ * Public entry point — discover all plugins via IPC, then activate
278
+ * each one. Idempotent: calling twice deactivates first.
279
+ */
280
+ export async function initPlugins(hostHelpers) {
281
+ // Deactivate anything currently running
282
+ for (const [, info] of activations) {
283
+ try { await info.deactivate() } catch {}
284
+ }
285
+ activations.clear()
286
+ pluginRegistry.themes.clear()
287
+ pluginRegistry.exporters.clear()
288
+ pluginRegistry.parsers.clear()
289
+ pluginRegistry.layouts.clear()
290
+ pluginRegistry.panels.clear()
291
+ pluginRegistry.contextMenuItems.length = 0
292
+ pluginRegistry.commands.clear()
293
+
294
+ if (!window.codesynapt || !window.codesynapt.listPlugins) {
295
+ // Browser dev mode — no plugins available
296
+ pluginRegistry.loaded = []
297
+ return
298
+ }
299
+
300
+ let discovered = []
301
+ try {
302
+ discovered = await window.codesynapt.listPlugins()
303
+ } catch (err) {
304
+ console.error('[plugin] discovery failed:', err)
305
+ return
306
+ }
307
+ pluginRegistry.loaded = discovered
308
+
309
+ for (const record of discovered) {
310
+ await activatePlugin(record, hostHelpers)
311
+ }
312
+
313
+ console.log(`[plugin] loaded ${activations.size} plugin(s)`)
314
+ }
315
+
316
+ export async function deactivatePlugin(id) {
317
+ const info = activations.get(id)
318
+ if (!info) return
319
+ await info.deactivate()
320
+ activations.delete(id)
321
+ }