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,123 @@
1
+ // Rust parser plugin for filegraph3d.
2
+ //
3
+ // Recognizes:
4
+ // - `use path::to::thing;` → an import to "path::to::thing"
5
+ // - `mod foo;` → an import to the sibling/child module
6
+ // - `extern crate foo;` → an import to the named crate
7
+ //
8
+ // Skips:
9
+ // - Statements inside line comments (`// use foo`)
10
+ // - Statements inside block comments (`/* use foo */`)
11
+ // - Use statements inside macro definitions (rare, would need a
12
+ // real parser; we accept some false positives)
13
+
14
+ export default {
15
+ activate(ctx) {
16
+ ctx.parsers.register({
17
+ name: 'Rust',
18
+ extensions: ['rs'],
19
+ parse(filePath, content) {
20
+ // Strip comments first so we don't match `use` statements
21
+ // that are commented out.
22
+ const stripped = stripComments(content)
23
+
24
+ const imports = []
25
+
26
+ // `use path::to::thing;` — match the path portion only.
27
+ // Groups, aliases, and wildcards complicate this; we capture
28
+ // the leading path which is enough for graph edges.
29
+ const useRegex = /^\s*(?:pub\s+)?use\s+([a-zA-Z_][\w:]*)/gm
30
+ let m
31
+ while ((m = useRegex.exec(stripped)) !== null) {
32
+ imports.push({
33
+ path: m[1],
34
+ kind: 'import',
35
+ line: lineNumberAt(stripped, m.index),
36
+ })
37
+ }
38
+
39
+ // `mod foo;` — local module declaration
40
+ const modRegex = /^\s*(?:pub\s+)?mod\s+([a-zA-Z_]\w*)\s*;/gm
41
+ while ((m = modRegex.exec(stripped)) !== null) {
42
+ imports.push({
43
+ path: m[1],
44
+ kind: 'import',
45
+ line: lineNumberAt(stripped, m.index),
46
+ })
47
+ }
48
+
49
+ // `extern crate foo;` — older crate import syntax
50
+ const externRegex = /^\s*extern\s+crate\s+([a-zA-Z_]\w*)/gm
51
+ while ((m = externRegex.exec(stripped)) !== null) {
52
+ imports.push({
53
+ path: m[1],
54
+ kind: 'import',
55
+ line: lineNumberAt(stripped, m.index),
56
+ })
57
+ }
58
+
59
+ return { imports }
60
+ }
61
+ })
62
+
63
+ ctx.log('Rust parser registered — .rs files will now be parsed')
64
+ }
65
+ }
66
+
67
+ // Replace comments with spaces so regex line numbers stay accurate
68
+ function stripComments(src) {
69
+ let result = ''
70
+ let i = 0
71
+ while (i < src.length) {
72
+ // Line comment: //...\n
73
+ if (src[i] === '/' && src[i + 1] === '/') {
74
+ while (i < src.length && src[i] !== '\n') {
75
+ result += ' '
76
+ i++
77
+ }
78
+ continue
79
+ }
80
+ // Block comment: /* ... */ (non-nested; Rust allows nested but
81
+ // for our purposes flat handling is sufficient)
82
+ if (src[i] === '/' && src[i + 1] === '*') {
83
+ result += ' '
84
+ i += 2
85
+ while (i < src.length - 1 && !(src[i] === '*' && src[i + 1] === '/')) {
86
+ result += src[i] === '\n' ? '\n' : ' '
87
+ i++
88
+ }
89
+ result += ' '
90
+ i += 2
91
+ continue
92
+ }
93
+ // String literal — skip past it so `use` inside strings doesn't
94
+ // produce a false positive
95
+ if (src[i] === '"') {
96
+ result += '"'
97
+ i++
98
+ while (i < src.length && src[i] !== '"') {
99
+ if (src[i] === '\\' && i + 1 < src.length) {
100
+ result += ' '
101
+ i += 2
102
+ continue
103
+ }
104
+ result += src[i] === '\n' ? '\n' : ' '
105
+ i++
106
+ }
107
+ result += '"'
108
+ i++
109
+ continue
110
+ }
111
+ result += src[i]
112
+ i++
113
+ }
114
+ return result
115
+ }
116
+
117
+ function lineNumberAt(str, offset) {
118
+ let line = 1
119
+ for (let i = 0; i < offset && i < str.length; i++) {
120
+ if (str[i] === '\n') line++
121
+ }
122
+ return line
123
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "id": "rust-parser",
3
+ "name": "Rust Parser",
4
+ "version": "1.0.0",
5
+ "author": "filegraph3d",
6
+ "description": "Parse Rust `use` statements and mod declarations to build the dependency graph",
7
+ "type": "parser",
8
+ "main": "main.js",
9
+ "minAppVersion": "0.10.0",
10
+ "license": "MIT",
11
+ "permissions": ["parse"]
12
+ }
@@ -0,0 +1,95 @@
1
+ # sunset-theme
2
+
3
+ A warm coral-orange theme inspired by sunset over the ocean. Useful
4
+ as a starting point for your own theme plugin — copy and modify.
5
+
6
+ ## What it looks like
7
+
8
+ | Element | Color |
9
+ |---|---|
10
+ | Background | Deep brown `#1A0F0A` |
11
+ | Foreground | Cream `#FFE4C4` |
12
+ | Primary accent | Warm orange `#FF8C42` |
13
+ | Active set color | Amber `#FFB347` |
14
+ | Warning color | Coral red `#FF6B6B` |
15
+
16
+ The theme uses 4px rounded corners and no decorative corner brackets,
17
+ giving it a softer feel than the default Observatory theme.
18
+
19
+ ## Install
20
+
21
+ Copy this folder into your filegraph3d plugins directory:
22
+
23
+ ```
24
+ macOS: ~/Library/Application Support/FileGraph 3D/plugins/
25
+ Windows: %APPDATA%\FileGraph 3D\plugins\
26
+ Linux: ~/.config/FileGraph 3D/plugins/
27
+ ```
28
+
29
+ Quit and reopen filegraph3d. Open **Settings → Appearance** — "Sunset"
30
+ appears in the theme grid. Click it.
31
+
32
+ ## Files
33
+
34
+ - **`manifest.json`** — declares the plugin as type `theme`. Theme
35
+ plugins don't need any permissions because they only contribute CSS.
36
+
37
+ - **`theme.css`** — overrides CSS variables under the
38
+ `body[data-theme="sunset-theme"]` selector. The selector value
39
+ matches the `id` in the manifest.
40
+
41
+ ## What to change
42
+
43
+ Open `theme.css` and tweak. Some easy starting points:
44
+
45
+ ### Different color family
46
+
47
+ The whole theme is built around `--accent: #FF8C42`. Swap that for any
48
+ other hue and pick three colors that:
49
+
50
+ - `--bg`: a deep version of (or complement to) the accent
51
+ - `--fg`: a light tint of the accent or a neutral
52
+ - `--accent-warm`: a brighter sibling color
53
+ - `--accent-pink`: contrast color for warnings
54
+
55
+ ### Add corner brackets
56
+
57
+ ```css
58
+ body[data-theme="sunset-theme"] {
59
+ --decoration: 1; /* show corner brackets on panels */
60
+ }
61
+ ```
62
+
63
+ ### Make it brutalist
64
+
65
+ ```css
66
+ body[data-theme="sunset-theme"] {
67
+ --decoration: 1;
68
+ --radius: 0; /* sharp corners */
69
+ }
70
+ ```
71
+
72
+ ### Lighter background
73
+
74
+ If you want a "sunrise" version instead:
75
+
76
+ ```css
77
+ body[data-theme="sunset-theme"] {
78
+ --bg: #FFF4E6;
79
+ --fg: #4A2810;
80
+ --grain: 0;
81
+ /* ... and adjust borders/accents accordingly */
82
+ }
83
+ body[data-theme="sunset-theme"]::before {
84
+ opacity: 0 !important; /* turn off the dark-mode grain */
85
+ }
86
+ ```
87
+
88
+ ## License
89
+
90
+ MIT — fork, modify, redistribute freely.
91
+
92
+ ## More
93
+
94
+ For the complete list of CSS variables and theming guidance, see
95
+ [../../docs/types/theme.md](../../docs/types/theme.md).
@@ -0,0 +1,12 @@
1
+ {
2
+ "id": "sunset-theme",
3
+ "name": "Sunset",
4
+ "version": "1.0.0",
5
+ "author": "filegraph3d",
6
+ "description": "Warm orange-coral theme inspired by sunset over the ocean",
7
+ "type": "theme",
8
+ "main": "theme.css",
9
+ "minAppVersion": "0.10.0",
10
+ "license": "MIT",
11
+ "permissions": []
12
+ }
@@ -0,0 +1,31 @@
1
+ /* Sunset theme — warm coral/orange palette */
2
+ /* Loaded automatically when user selects "Sunset" in Appearance */
3
+
4
+ body[data-theme="sunset-theme"] {
5
+ --bg: #1A0F0A;
6
+ --bg-deep: #100805;
7
+ --bg-elev: rgba(40, 22, 14, 0.85);
8
+ --bg-glass: rgba(26, 15, 10, 0.85);
9
+ --bg-solid: #261810;
10
+
11
+ --border: rgba(255, 140, 66, 0.18);
12
+ --border-hot: rgba(255, 140, 66, 0.55);
13
+ --border-edge: rgba(255, 140, 66, 0.06);
14
+
15
+ --fg: #FFE4C4;
16
+ --fg-dim: #D8B080;
17
+ --fg-mute: #A88060;
18
+ --fg-faint: #685040;
19
+
20
+ --accent: #FF8C42; /* warm orange */
21
+ --accent-warm: #FFB347; /* amber */
22
+ --accent-pink: #FF6B6B; /* coral red */
23
+ --accent-cool: #E8A87C; /* dusty rose */
24
+ --danger: #FF4444;
25
+
26
+ --decoration: 0;
27
+ --grain: 0.03;
28
+ --motion-scale: 0.6;
29
+
30
+ --radius: 4px;
31
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "@filegraph3d/plugin-api",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "TypeScript types and helpers for filegraph3d plugins",
6
+ "types": "types.d.ts",
7
+ "files": [
8
+ "types.d.ts",
9
+ "README.md",
10
+ "LICENSE",
11
+ "examples/"
12
+ ],
13
+ "license": "MIT",
14
+ "keywords": ["filegraph3d", "plugin", "api", "code-visualization"],
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/YOUR_USER/filegraph3d.git",
18
+ "directory": "plugin-api"
19
+ }
20
+ }
@@ -0,0 +1,395 @@
1
+ // @filegraph3d/plugin-api — public API surface for plugins
2
+ // MIT licensed — free to use, fork, distribute.
3
+
4
+ /* ════════════════════════════════════════════════════════════
5
+ Plugin manifest
6
+ ════════════════════════════════════════════════════════════ */
7
+
8
+ export interface PluginManifest {
9
+ /** Unique identifier (kebab-case, e.g. "tokyo-night-theme") */
10
+ id: string
11
+
12
+ /** Display name */
13
+ name: string
14
+
15
+ /** Plugin version (semver) */
16
+ version: string
17
+
18
+ /** Author name (no email required) */
19
+ author: string
20
+
21
+ /** One-line description shown in plugin list */
22
+ description: string
23
+
24
+ /** Plugin type — determines what API surface it gets */
25
+ type: 'theme' | 'exporter' | 'parser' | 'layout' | 'panel' | 'action'
26
+
27
+ /**
28
+ * Entry point file (relative to manifest.json):
29
+ * - For theme: a .css file
30
+ * - For others: a .js module
31
+ */
32
+ main: string
33
+
34
+ /** Minimum filegraph3d app version (semver range) */
35
+ minAppVersion: string
36
+
37
+ /** SPDX license identifier (MIT, Apache-2.0, etc) */
38
+ license: string
39
+
40
+ /** Optional URL for the plugin homepage / repo */
41
+ homepage?: string
42
+
43
+ /** Optional: which app capabilities does this plugin need? */
44
+ permissions?: PluginPermission[]
45
+ }
46
+
47
+ export type PluginPermission =
48
+ | 'read-files' // Read source file contents
49
+ | 'read-graph' // Read the current node/edge state
50
+ | 'modify-graph' // Add nodes/edges (rare, dangerous)
51
+ | 'ui-panel' // Render in a side panel
52
+ | 'context-menu' // Add right-click items
53
+ | 'export' // Provide an export format
54
+ | 'parse' // Provide a language parser
55
+
56
+ /* ════════════════════════════════════════════════════════════
57
+ Plugin context — passed to every plugin's activate() function
58
+ ════════════════════════════════════════════════════════════ */
59
+
60
+ export interface PluginContext {
61
+ /** This plugin's manifest */
62
+ manifest: PluginManifest
63
+
64
+ /** App version at load time */
65
+ appVersion: string
66
+
67
+ /** Read-only access to graph state */
68
+ graph: GraphAPI
69
+
70
+ /** UI registration */
71
+ ui: UIAPI
72
+
73
+ /** Export format registration */
74
+ exporters: ExporterRegistry
75
+
76
+ /** Parser registration */
77
+ parsers: ParserRegistry
78
+
79
+ /** Layout algorithm registration */
80
+ layouts: LayoutRegistry
81
+
82
+ /** Event subscription */
83
+ events: EventBus
84
+
85
+ /** Persistent per-plugin storage (localStorage-backed) */
86
+ storage: PluginStorage
87
+
88
+ /** Helper: show a toast message */
89
+ toast: (message: string) => void
90
+
91
+ /** Helper: log to plugin-specific console */
92
+ log: (...args: unknown[]) => void
93
+ }
94
+
95
+ /* ════════════════════════════════════════════════════════════
96
+ Graph API
97
+ ════════════════════════════════════════════════════════════ */
98
+
99
+ export interface GraphNode {
100
+ /** File path relative to project root */
101
+ id: string
102
+
103
+ /** File extension (without dot) */
104
+ ext: string
105
+
106
+ /** File size in bytes */
107
+ size: number
108
+
109
+ /** Lines of code */
110
+ loc: number
111
+
112
+ /** Display color (hex) */
113
+ hex: string
114
+ }
115
+
116
+ export interface GraphEdge {
117
+ /** Source node id */
118
+ s: string
119
+
120
+ /** Target node id */
121
+ t: string
122
+
123
+ /** Edge kind: 'import' | 'require' | 'css' | etc */
124
+ k: string
125
+ }
126
+
127
+ export interface GraphAPI {
128
+ /** Current project root path */
129
+ readonly root: string
130
+
131
+ /** All nodes (read-only) */
132
+ readonly nodes: ReadonlyArray<GraphNode>
133
+
134
+ /** All edges (read-only) */
135
+ readonly edges: ReadonlyArray<GraphEdge>
136
+
137
+ /** Currently selected node id (or null) */
138
+ readonly selectedId: string | null
139
+
140
+ /** Currently active set of file ids (or null if disabled) */
141
+ readonly activeSet: ReadonlySet<string> | null
142
+
143
+ /** Read file contents (requires "read-files" permission) */
144
+ readFile(id: string): Promise<string>
145
+
146
+ /** Get node by id, or null */
147
+ getNode(id: string): GraphNode | null
148
+
149
+ /** Get all outgoing edges from a node */
150
+ outgoing(id: string): GraphEdge[]
151
+
152
+ /** Get all incoming edges to a node */
153
+ incoming(id: string): GraphEdge[]
154
+ }
155
+
156
+ /* ════════════════════════════════════════════════════════════
157
+ UI API
158
+ ════════════════════════════════════════════════════════════ */
159
+
160
+ export interface UIAPI {
161
+ /**
162
+ * Register a panel that appears in the right rail.
163
+ * Returns a handle for later removal.
164
+ */
165
+ registerPanel(opts: PanelOptions): PanelHandle
166
+
167
+ /**
168
+ * Register a context-menu item that appears when right-clicking
169
+ * a node in the graph.
170
+ */
171
+ registerContextMenuItem(opts: ContextMenuOptions): MenuItemHandle
172
+
173
+ /**
174
+ * Register a command (shows up in command palette if available).
175
+ */
176
+ registerCommand(opts: CommandOptions): CommandHandle
177
+ }
178
+
179
+ export interface PanelOptions {
180
+ /** Unique id within this plugin */
181
+ id: string
182
+
183
+ /** Title shown in panel header */
184
+ title: string
185
+
186
+ /** Position (default: 'right') */
187
+ position?: 'right' | 'left' | 'bottom'
188
+
189
+ /**
190
+ * Render function — return HTML string or DOM element.
191
+ * Called when the panel becomes visible or when refresh() is called.
192
+ */
193
+ render: (container: HTMLElement) => void
194
+
195
+ /** Whether the panel is visible by default */
196
+ defaultVisible?: boolean
197
+ }
198
+
199
+ export interface PanelHandle {
200
+ /** Force re-render */
201
+ refresh(): void
202
+
203
+ /** Show the panel */
204
+ show(): void
205
+
206
+ /** Hide the panel */
207
+ hide(): void
208
+
209
+ /** Remove the panel entirely */
210
+ dispose(): void
211
+ }
212
+
213
+ export interface ContextMenuOptions {
214
+ /** Display label */
215
+ label: string
216
+
217
+ /** Optional icon (single character or short string) */
218
+ icon?: string
219
+
220
+ /** When to show — return true to enable */
221
+ enabled?: (nodeId: string) => boolean
222
+
223
+ /** Click handler */
224
+ action: (nodeId: string) => void | Promise<void>
225
+ }
226
+
227
+ export interface MenuItemHandle {
228
+ dispose(): void
229
+ }
230
+
231
+ export interface CommandOptions {
232
+ /** Unique command id */
233
+ id: string
234
+
235
+ /** Display name in command palette */
236
+ name: string
237
+
238
+ /** Optional keyboard shortcut suggestion */
239
+ shortcut?: string
240
+
241
+ /** Handler */
242
+ action: () => void | Promise<void>
243
+ }
244
+
245
+ export interface CommandHandle {
246
+ dispose(): void
247
+ }
248
+
249
+ /* ════════════════════════════════════════════════════════════
250
+ Exporter registration
251
+ ════════════════════════════════════════════════════════════ */
252
+
253
+ export interface ExporterRegistry {
254
+ register(opts: ExporterOptions): ExporterHandle
255
+ }
256
+
257
+ export interface ExporterOptions {
258
+ /** Display name (e.g. "Mermaid diagram") */
259
+ name: string
260
+
261
+ /** File extension (e.g. "mmd") */
262
+ extension: string
263
+
264
+ /** MIME type for the download */
265
+ mimeType: string
266
+
267
+ /**
268
+ * Generate the export content.
269
+ * Receives the current graph state and returns the file content.
270
+ */
271
+ generate: (graph: GraphAPI) => string | Promise<string>
272
+ }
273
+
274
+ export interface ExporterHandle {
275
+ dispose(): void
276
+ }
277
+
278
+ /* ════════════════════════════════════════════════════════════
279
+ Parser registration
280
+ ════════════════════════════════════════════════════════════ */
281
+
282
+ export interface ParserRegistry {
283
+ register(opts: ParserOptions): ParserHandle
284
+ }
285
+
286
+ export interface ParserOptions {
287
+ /** Language name (e.g. "Rust") */
288
+ name: string
289
+
290
+ /** File extensions this parser handles (without dot) */
291
+ extensions: string[]
292
+
293
+ /**
294
+ * Parse a file and return its imports.
295
+ * Resolution from import path to file id is done by the app.
296
+ */
297
+ parse: (filePath: string, content: string) => ParseResult
298
+ }
299
+
300
+ export interface ParseResult {
301
+ /** Import statements found in this file */
302
+ imports: ParseImport[]
303
+
304
+ /** Optional: lines of code count (if not given, app counts newlines) */
305
+ loc?: number
306
+ }
307
+
308
+ export interface ParseImport {
309
+ /** The raw import path (e.g. "./foo", "react", "@/utils") */
310
+ path: string
311
+
312
+ /** Import kind */
313
+ kind: 'import' | 'require' | 'dynamic' | 'css' | 'asset' | 'other'
314
+
315
+ /** Source line number (optional, for debugging) */
316
+ line?: number
317
+ }
318
+
319
+ export interface ParserHandle {
320
+ dispose(): void
321
+ }
322
+
323
+ /* ════════════════════════════════════════════════════════════
324
+ Layout algorithm registration
325
+ ════════════════════════════════════════════════════════════ */
326
+
327
+ export interface LayoutRegistry {
328
+ register(opts: LayoutOptions): LayoutHandle
329
+ }
330
+
331
+ export interface LayoutOptions {
332
+ /** Display name (e.g. "Hierarchical") */
333
+ name: string
334
+
335
+ /** Unique id */
336
+ id: string
337
+
338
+ /**
339
+ * Compute positions for all nodes.
340
+ * Called once when this layout is activated, then again whenever
341
+ * the graph topology changes.
342
+ *
343
+ * Should return a Map from node id to {x, y, z} coordinates.
344
+ */
345
+ compute: (nodes: ReadonlyArray<GraphNode>, edges: ReadonlyArray<GraphEdge>)
346
+ => Map<string, { x: number; y: number; z: number }>
347
+ | Promise<Map<string, { x: number; y: number; z: number }>>
348
+ }
349
+
350
+ export interface LayoutHandle {
351
+ dispose(): void
352
+ }
353
+
354
+ /* ════════════════════════════════════════════════════════════
355
+ Event bus
356
+ ════════════════════════════════════════════════════════════ */
357
+
358
+ export type EventName =
359
+ | 'snapshot:applied' // New graph data loaded — { root: string }
360
+ | 'selection:changed' // User selected a node — string | null
361
+ | 'filter:changed' // Filter text changed
362
+ | 'focus:changed' // Focused node changed
363
+ | 'graph:cleared' // Graph was cleared
364
+ | 'activeset:changed' // Active set updated
365
+
366
+ export interface EventBus {
367
+ on<T = unknown>(event: EventName, handler: (payload: T) => void): () => void
368
+ off(event: EventName, handler: (payload: unknown) => void): void
369
+ }
370
+
371
+ /* ════════════════════════════════════════════════════════════
372
+ Plugin storage (persistent per-plugin key-value store)
373
+ ════════════════════════════════════════════════════════════ */
374
+
375
+ export interface PluginStorage {
376
+ get<T = unknown>(key: string): T | null
377
+ set<T = unknown>(key: string, value: T): void
378
+ delete(key: string): void
379
+ clear(): void
380
+ keys(): string[]
381
+ }
382
+
383
+ /* ════════════════════════════════════════════════════════════
384
+ Plugin entry-point signature
385
+ ════════════════════════════════════════════════════════════ */
386
+
387
+ /**
388
+ * Every plugin's main module must default-export an object with at
389
+ * least an `activate` function. `deactivate` is optional, called when
390
+ * the user disables or uninstalls the plugin.
391
+ */
392
+ export interface Plugin {
393
+ activate(ctx: PluginContext): void | Promise<void>
394
+ deactivate?(): void | Promise<void>
395
+ }