project-graph-mcp 2.3.2 → 2.4.1

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 (279) hide show
  1. package/package.json +3 -2
  2. package/src/analysis/analysis-cache.ctx +9 -0
  3. package/src/analysis/analysis-cache.js +1 -1
  4. package/src/analysis/complexity.ctx +6 -0
  5. package/src/analysis/complexity.js +1 -1
  6. package/src/analysis/custom-rules.ctx +14 -0
  7. package/src/analysis/custom-rules.js +1 -1
  8. package/src/analysis/db-analysis.ctx +7 -0
  9. package/src/analysis/db-analysis.js +1 -1
  10. package/src/analysis/dead-code.ctx +6 -0
  11. package/src/analysis/dead-code.js +1 -1
  12. package/src/analysis/full-analysis.ctx +9 -0
  13. package/src/analysis/full-analysis.js +1 -1
  14. package/src/analysis/jsdoc-checker.ctx +10 -0
  15. package/src/analysis/jsdoc-checker.js +1 -1
  16. package/src/analysis/jsdoc-generator.ctx +9 -0
  17. package/src/analysis/jsdoc-generator.js +1 -1
  18. package/src/analysis/large-files.ctx +6 -0
  19. package/src/analysis/large-files.js +1 -1
  20. package/src/analysis/outdated-patterns.ctx +7 -0
  21. package/src/analysis/outdated-patterns.js +1 -1
  22. package/src/analysis/similar-functions.ctx +6 -0
  23. package/src/analysis/similar-functions.js +1 -1
  24. package/src/analysis/test-annotations.ctx +11 -0
  25. package/src/analysis/test-annotations.js +1 -1
  26. package/src/analysis/type-checker.ctx +6 -0
  27. package/src/analysis/type-checker.js +1 -1
  28. package/src/analysis/undocumented.ctx +8 -0
  29. package/src/analysis/undocumented.js +1 -1
  30. package/src/cli/cli-handlers.ctx +7 -0
  31. package/src/cli/cli-handlers.js +1 -1
  32. package/src/cli/cli.ctx +6 -0
  33. package/src/cli/cli.js +1 -1
  34. package/src/compact/ai-context.ctx +6 -0
  35. package/src/compact/ai-context.js +1 -1
  36. package/src/compact/compact-migrate.ctx +8 -0
  37. package/src/compact/compact-migrate.js +1 -1
  38. package/src/compact/compact.ctx +11 -0
  39. package/src/compact/compact.js +1 -1
  40. package/src/compact/compress.ctx +7 -0
  41. package/src/compact/compress.js +1 -1
  42. package/src/compact/ctx-resolver.ctx +2 -0
  43. package/src/compact/ctx-resolver.js +1 -1
  44. package/src/compact/ctx-to-jsdoc.ctx +11 -0
  45. package/src/compact/ctx-to-jsdoc.js +1 -1
  46. package/src/compact/doc-dialect.ctx +11 -0
  47. package/src/compact/doc-dialect.js +2 -2
  48. package/src/compact/expand.ctx +14 -0
  49. package/src/compact/expand.js +1 -1
  50. package/src/compact/framework-references.ctx +7 -0
  51. package/src/compact/framework-references.js +1 -1
  52. package/src/compact/instructions.ctx +6 -0
  53. package/src/compact/instructions.js +1 -1
  54. package/src/compact/jsdoc-builder.ctx +4 -0
  55. package/src/compact/jsdoc-builder.js +1 -1
  56. package/src/compact/mode-config.ctx +8 -0
  57. package/src/compact/mode-config.js +1 -1
  58. package/src/compact/split-declarations.ctx +6 -0
  59. package/src/compact/split-declarations.js +1 -1
  60. package/src/compact/validate-pipeline.ctx +12 -0
  61. package/src/compact/validate-pipeline.js +1 -1
  62. package/src/core/event-bus.ctx +9 -0
  63. package/src/core/event-bus.js +1 -1
  64. package/src/core/file-walker.ctx +1 -0
  65. package/src/core/file-walker.js +1 -1
  66. package/src/core/filters.ctx +12 -0
  67. package/src/core/filters.js +1 -1
  68. package/src/core/graph-builder.ctx +7 -0
  69. package/src/core/graph-builder.js +1 -1
  70. package/src/core/parser.ctx +12 -0
  71. package/src/core/parser.js +1 -1
  72. package/src/core/utils.ctx +1 -0
  73. package/src/core/utils.js +1 -1
  74. package/src/core/workspace.ctx +7 -0
  75. package/src/core/workspace.js +1 -1
  76. package/src/lang/lang-go.ctx +8 -0
  77. package/src/lang/lang-go.js +1 -1
  78. package/src/lang/lang-python.ctx +5 -0
  79. package/src/lang/lang-python.js +1 -1
  80. package/src/lang/lang-sql.ctx +10 -0
  81. package/src/lang/lang-sql.js +1 -1
  82. package/src/lang/lang-typescript.ctx +6 -0
  83. package/src/lang/lang-typescript.js +1 -1
  84. package/src/lang/lang-utils.ctx +5 -0
  85. package/src/lang/lang-utils.js +1 -1
  86. package/src/mcp/mcp-server.ctx +6 -0
  87. package/src/mcp/mcp-server.js +1 -1
  88. package/src/mcp/tool-defs.ctx +2 -0
  89. package/src/mcp/tool-defs.js +1 -1
  90. package/src/mcp/tools.ctx +13 -0
  91. package/src/mcp/tools.js +1 -1
  92. package/src/network/backend-lifecycle.ctx +10 -0
  93. package/src/network/backend-lifecycle.js +1 -1
  94. package/src/network/backend.ctx +5 -0
  95. package/src/network/backend.js +1 -1
  96. package/src/network/local-gateway.ctx +9 -0
  97. package/src/network/local-gateway.js +1 -1
  98. package/src/network/mdns.ctx +6 -0
  99. package/src/network/mdns.js +1 -1
  100. package/src/network/server.ctx +2 -0
  101. package/src/network/server.js +2 -2
  102. package/src/network/web-server.ctx +17 -0
  103. package/src/network/web-server.js +2 -2
  104. package/web/follow-controller.js +94 -25
  105. package/web/panels/dep-graph.js +207 -21
  106. package/project-graph-mcp-2.3.0.tgz +0 -0
  107. package/vendor/symbiote-node/CHANGELOG.md +0 -31
  108. package/vendor/symbiote-node/LICENSE +0 -21
  109. package/vendor/symbiote-node/README.md +0 -206
  110. package/vendor/symbiote-node/canvas/AutoLayout.js +0 -725
  111. package/vendor/symbiote-node/canvas/Breadcrumb/Breadcrumb.css.js +0 -73
  112. package/vendor/symbiote-node/canvas/Breadcrumb/Breadcrumb.js +0 -93
  113. package/vendor/symbiote-node/canvas/Breadcrumb/Breadcrumb.tpl.js +0 -9
  114. package/vendor/symbiote-node/canvas/CanvasConnectionRenderer.js +0 -962
  115. package/vendor/symbiote-node/canvas/ConnectionRenderer.js +0 -1468
  116. package/vendor/symbiote-node/canvas/FlowSimulator.js +0 -323
  117. package/vendor/symbiote-node/canvas/ForceLayout.js +0 -189
  118. package/vendor/symbiote-node/canvas/ForceWorker.js +0 -1325
  119. package/vendor/symbiote-node/canvas/GraphTabs/GraphTabs.css.js +0 -97
  120. package/vendor/symbiote-node/canvas/GraphTabs/GraphTabs.js +0 -176
  121. package/vendor/symbiote-node/canvas/GraphTabs/GraphTabs.tpl.js +0 -12
  122. package/vendor/symbiote-node/canvas/LODManager.js +0 -88
  123. package/vendor/symbiote-node/canvas/Minimap/Minimap.css.js +0 -71
  124. package/vendor/symbiote-node/canvas/Minimap/Minimap.js +0 -207
  125. package/vendor/symbiote-node/canvas/Minimap/Minimap.tpl.js +0 -9
  126. package/vendor/symbiote-node/canvas/NodeCanvas/NodeCanvas.css.js +0 -261
  127. package/vendor/symbiote-node/canvas/NodeCanvas/NodeCanvas.js +0 -1840
  128. package/vendor/symbiote-node/canvas/NodeCanvas/NodeCanvas.tpl.js +0 -22
  129. package/vendor/symbiote-node/canvas/NodeSearch/NodeSearch.css.js +0 -97
  130. package/vendor/symbiote-node/canvas/NodeSearch/NodeSearch.js +0 -132
  131. package/vendor/symbiote-node/canvas/NodeSearch/NodeSearch.tpl.js +0 -21
  132. package/vendor/symbiote-node/canvas/NodeViewManager.js +0 -584
  133. package/vendor/symbiote-node/canvas/PinExpansion.js +0 -131
  134. package/vendor/symbiote-node/canvas/PseudoConnection.js +0 -80
  135. package/vendor/symbiote-node/canvas/SubgraphManager.js +0 -201
  136. package/vendor/symbiote-node/canvas/SubgraphRouter.js +0 -443
  137. package/vendor/symbiote-node/canvas/ViewportActions.js +0 -446
  138. package/vendor/symbiote-node/core/Connection.js +0 -45
  139. package/vendor/symbiote-node/core/Editor.js +0 -451
  140. package/vendor/symbiote-node/core/Frame.js +0 -31
  141. package/vendor/symbiote-node/core/GraphMermaid.js +0 -348
  142. package/vendor/symbiote-node/core/GraphText.js +0 -210
  143. package/vendor/symbiote-node/core/Node.js +0 -143
  144. package/vendor/symbiote-node/core/Portal.js +0 -104
  145. package/vendor/symbiote-node/core/Socket.js +0 -185
  146. package/vendor/symbiote-node/core/SubgraphNode.js +0 -125
  147. package/vendor/symbiote-node/engine/AgentUICommands.js +0 -100
  148. package/vendor/symbiote-node/engine/Executor.js +0 -371
  149. package/vendor/symbiote-node/engine/Graph.js +0 -314
  150. package/vendor/symbiote-node/engine/GraphServer.js +0 -353
  151. package/vendor/symbiote-node/engine/HandlerLoader.js +0 -145
  152. package/vendor/symbiote-node/engine/History.js +0 -83
  153. package/vendor/symbiote-node/engine/Lifecycle.js +0 -118
  154. package/vendor/symbiote-node/engine/Persistence.js +0 -84
  155. package/vendor/symbiote-node/engine/Registry.js +0 -264
  156. package/vendor/symbiote-node/engine/SocketTypes.js +0 -79
  157. package/vendor/symbiote-node/engine/cli.js +0 -404
  158. package/vendor/symbiote-node/engine/index.js +0 -56
  159. package/vendor/symbiote-node/engine/nanoid.js +0 -28
  160. package/vendor/symbiote-node/engine/package.json +0 -26
  161. package/vendor/symbiote-node/engine/packs/ai/beat-detect.handler.js +0 -215
  162. package/vendor/symbiote-node/engine/packs/ai/content-adapt.handler.js +0 -238
  163. package/vendor/symbiote-node/engine/packs/ai/face-detect.handler.js +0 -287
  164. package/vendor/symbiote-node/engine/packs/ai/grok-generate.handler.js +0 -565
  165. package/vendor/symbiote-node/engine/packs/ai/kling-lipsync.handler.js +0 -414
  166. package/vendor/symbiote-node/engine/packs/ai/lesson-generate.handler.js +0 -343
  167. package/vendor/symbiote-node/engine/packs/ai/opencode.handler.js +0 -164
  168. package/vendor/symbiote-node/engine/packs/ai/replicate-lipsync.handler.js +0 -341
  169. package/vendor/symbiote-node/engine/packs/ai/tts.handler.js +0 -241
  170. package/vendor/symbiote-node/engine/packs/ai/whisper.handler.js +0 -191
  171. package/vendor/symbiote-node/engine/packs/data/db-query.handler.js +0 -67
  172. package/vendor/symbiote-node/engine/packs/data/news-accumulate.handler.js +0 -281
  173. package/vendor/symbiote-node/engine/packs/data/personas.handler.js +0 -160
  174. package/vendor/symbiote-node/engine/packs/data/prompt-loader.handler.js +0 -193
  175. package/vendor/symbiote-node/engine/packs/data/roles.handler.js +0 -216
  176. package/vendor/symbiote-node/engine/packs/data/rss-feed.handler.js +0 -244
  177. package/vendor/symbiote-node/engine/packs/debug/inject.handler.js +0 -52
  178. package/vendor/symbiote-node/engine/packs/flow/agent.handler.js +0 -73
  179. package/vendor/symbiote-node/engine/packs/flow/if.handler.js +0 -107
  180. package/vendor/symbiote-node/engine/packs/flow/loop.handler.js +0 -58
  181. package/vendor/symbiote-node/engine/packs/flow/merge.handler.js +0 -60
  182. package/vendor/symbiote-node/engine/packs/flow/retry.handler.js +0 -65
  183. package/vendor/symbiote-node/engine/packs/flow/switch.handler.js +0 -64
  184. package/vendor/symbiote-node/engine/packs/flow/wait-all.handler.js +0 -39
  185. package/vendor/symbiote-node/engine/packs/io/http-request.handler.js +0 -82
  186. package/vendor/symbiote-node/engine/packs/io/read-file.handler.js +0 -60
  187. package/vendor/symbiote-node/engine/packs/io/write-file.handler.js +0 -63
  188. package/vendor/symbiote-node/engine/packs/transform/anchor-match.handler.js +0 -494
  189. package/vendor/symbiote-node/engine/packs/transform/effects-skeleton.handler.js +0 -417
  190. package/vendor/symbiote-node/engine/packs/transform/json-parse.handler.js +0 -43
  191. package/vendor/symbiote-node/engine/packs/transform/lipsync-select.handler.js +0 -339
  192. package/vendor/symbiote-node/engine/packs/transform/riopla-adapt.handler.js +0 -432
  193. package/vendor/symbiote-node/engine/packs/transform/set.handler.js +0 -57
  194. package/vendor/symbiote-node/engine/packs/transform/template-builder.handler.js +0 -134
  195. package/vendor/symbiote-node/engine/packs/transform/template.handler.js +0 -79
  196. package/vendor/symbiote-node/engine/packs/transform/timeline-build.handler.js +0 -399
  197. package/vendor/symbiote-node/engine/packs/util/delay.handler.js +0 -39
  198. package/vendor/symbiote-node/engine/packs/util/log.handler.js +0 -44
  199. package/vendor/symbiote-node/engine/packs/video-pack.js +0 -323
  200. package/vendor/symbiote-node/index.js +0 -103
  201. package/vendor/symbiote-node/inspector/InspectorPanel/InspectorPanel.css.js +0 -361
  202. package/vendor/symbiote-node/inspector/InspectorPanel/InspectorPanel.js +0 -332
  203. package/vendor/symbiote-node/inspector/InspectorPanel/InspectorPanel.tpl.js +0 -96
  204. package/vendor/symbiote-node/inspector/TemplatePreview/TemplatePreview.css.js +0 -104
  205. package/vendor/symbiote-node/inspector/TemplatePreview/TemplatePreview.js +0 -133
  206. package/vendor/symbiote-node/inspector/TemplatePreview/TemplatePreview.tpl.js +0 -33
  207. package/vendor/symbiote-node/interactions/ConnectFlow.js +0 -307
  208. package/vendor/symbiote-node/interactions/Drag.js +0 -102
  209. package/vendor/symbiote-node/interactions/Selector.js +0 -132
  210. package/vendor/symbiote-node/interactions/SnapGrid.js +0 -65
  211. package/vendor/symbiote-node/interactions/Zoom.js +0 -140
  212. package/vendor/symbiote-node/layout/ActionZone/ActionZone.css.js +0 -88
  213. package/vendor/symbiote-node/layout/ActionZone/ActionZone.js +0 -254
  214. package/vendor/symbiote-node/layout/ActionZone/ActionZone.tpl.js +0 -11
  215. package/vendor/symbiote-node/layout/Layout/Layout.css.js +0 -88
  216. package/vendor/symbiote-node/layout/Layout/Layout.js +0 -622
  217. package/vendor/symbiote-node/layout/Layout/Layout.tpl.js +0 -25
  218. package/vendor/symbiote-node/layout/LayoutNode/LayoutNode.css.js +0 -293
  219. package/vendor/symbiote-node/layout/LayoutNode/LayoutNode.js +0 -467
  220. package/vendor/symbiote-node/layout/LayoutNode/LayoutNode.tpl.js +0 -33
  221. package/vendor/symbiote-node/layout/LayoutPreview/LayoutPreview.css.js +0 -46
  222. package/vendor/symbiote-node/layout/LayoutPreview/LayoutPreview.js +0 -102
  223. package/vendor/symbiote-node/layout/LayoutPreview/LayoutPreview.tpl.js +0 -6
  224. package/vendor/symbiote-node/layout/LayoutRouter/LayoutRouter.js +0 -156
  225. package/vendor/symbiote-node/layout/LayoutRouter/routerSync.js +0 -250
  226. package/vendor/symbiote-node/layout/LayoutSidebar/LayoutSidebar.css.js +0 -379
  227. package/vendor/symbiote-node/layout/LayoutSidebar/LayoutSidebar.js +0 -263
  228. package/vendor/symbiote-node/layout/LayoutSidebar/LayoutSidebar.tpl.js +0 -20
  229. package/vendor/symbiote-node/layout/LayoutSidebar/SidebarSection.js +0 -183
  230. package/vendor/symbiote-node/layout/LayoutTree.js +0 -246
  231. package/vendor/symbiote-node/layout/PanelMenu/PanelMenu.css.js +0 -43
  232. package/vendor/symbiote-node/layout/PanelMenu/PanelMenu.js +0 -89
  233. package/vendor/symbiote-node/layout/PanelMenu/PanelMenu.tpl.js +0 -14
  234. package/vendor/symbiote-node/layout/index.js +0 -16
  235. package/vendor/symbiote-node/menu/ContextMenu/ContextMenu.css.js +0 -61
  236. package/vendor/symbiote-node/menu/ContextMenu/ContextMenu.js +0 -79
  237. package/vendor/symbiote-node/menu/ContextMenu/ContextMenu.tpl.js +0 -19
  238. package/vendor/symbiote-node/node/CtrlItem/CtrlItem.css.js +0 -41
  239. package/vendor/symbiote-node/node/CtrlItem/CtrlItem.js +0 -24
  240. package/vendor/symbiote-node/node/CtrlItem/CtrlItem.tpl.js +0 -16
  241. package/vendor/symbiote-node/node/GraphFrame/GraphFrame.css.js +0 -65
  242. package/vendor/symbiote-node/node/GraphFrame/GraphFrame.js +0 -29
  243. package/vendor/symbiote-node/node/GraphFrame/GraphFrame.tpl.js +0 -13
  244. package/vendor/symbiote-node/node/GraphNode/GraphNode.css.js +0 -683
  245. package/vendor/symbiote-node/node/GraphNode/GraphNode.js +0 -92
  246. package/vendor/symbiote-node/node/GraphNode/GraphNode.tpl.js +0 -17
  247. package/vendor/symbiote-node/node/NodeSocket/NodeSocket.js +0 -25
  248. package/vendor/symbiote-node/node/NodeSocket/NodeSocket.tpl.js +0 -7
  249. package/vendor/symbiote-node/node/PortItem/PortItem.css.js +0 -90
  250. package/vendor/symbiote-node/node/PortItem/PortItem.js +0 -87
  251. package/vendor/symbiote-node/node/PortItem/PortItem.tpl.js +0 -10
  252. package/vendor/symbiote-node/package.json +0 -59
  253. package/vendor/symbiote-node/palette/PaletteBrowser/PaletteBrowser.css.js +0 -143
  254. package/vendor/symbiote-node/palette/PaletteBrowser/PaletteBrowser.js +0 -131
  255. package/vendor/symbiote-node/palette/PaletteBrowser/PaletteBrowser.tpl.js +0 -16
  256. package/vendor/symbiote-node/plugins/History.js +0 -384
  257. package/vendor/symbiote-node/plugins/Readonly.js +0 -59
  258. package/vendor/symbiote-node/shapes/CircleShape.js +0 -80
  259. package/vendor/symbiote-node/shapes/CommentShape.js +0 -35
  260. package/vendor/symbiote-node/shapes/DiamondShape.js +0 -115
  261. package/vendor/symbiote-node/shapes/NodeShape.js +0 -80
  262. package/vendor/symbiote-node/shapes/PillShape.js +0 -91
  263. package/vendor/symbiote-node/shapes/RectShape.js +0 -72
  264. package/vendor/symbiote-node/shapes/SVGShape.js +0 -494
  265. package/vendor/symbiote-node/shapes/index.js +0 -53
  266. package/vendor/symbiote-node/themes/Palette.js +0 -32
  267. package/vendor/symbiote-node/themes/Skin.js +0 -113
  268. package/vendor/symbiote-node/themes/Theme.js +0 -84
  269. package/vendor/symbiote-node/themes/carbon.js +0 -137
  270. package/vendor/symbiote-node/themes/dark.js +0 -137
  271. package/vendor/symbiote-node/themes/ebook.js +0 -138
  272. package/vendor/symbiote-node/themes/grey.js +0 -137
  273. package/vendor/symbiote-node/themes/light.js +0 -137
  274. package/vendor/symbiote-node/themes/neon.js +0 -138
  275. package/vendor/symbiote-node/themes/pcb.js +0 -273
  276. package/vendor/symbiote-node/themes/synthwave.js +0 -137
  277. package/vendor/symbiote-node/toolbar/QuickToolbar/QuickToolbar.css.js +0 -86
  278. package/vendor/symbiote-node/toolbar/QuickToolbar/QuickToolbar.js +0 -128
  279. package/vendor/symbiote-node/toolbar/QuickToolbar/QuickToolbar.tpl.js +0 -29
@@ -1,10 +1,11 @@
1
1
  // @ctx .context/web/follow-controller.ctx
2
2
  /**
3
- * FollowController — Central orchestrator for Follow Mode.
3
+ * FollowController — Central orchestrator for Follow Mode (Magic Automation Theater).
4
4
  *
5
- * Classifies incoming tool-events and dispatches debounced focus-change
6
- * signals to subscribed panels (graph, code-viewer, monitor).
7
- * Also manages the status ribbon text shown during active follow.
5
+ * Classifies incoming tool-events and drives the UI:
6
+ * 1. Switches the active panel (graph, code, analysis) via hash routing.
7
+ * 2. Dispatches debounced focus-change signals to visible panels.
8
+ * 3. Manages the status ribbon text shown during active follow.
8
9
  *
9
10
  * NOTE: Does NOT import from app.js to avoid circular dependency.
10
11
  * Call init(events, emit) before enable().
@@ -13,6 +14,9 @@
13
14
  /** Debounce delay for heavy visual updates (camera, code loading) */
14
15
  const HEAVY_DEBOUNCE = 800;
15
16
 
17
+ /** Delay to allow panel mount after hash change before sending focus */
18
+ const PANEL_MOUNT_DELAY = 300;
19
+
16
20
  class FollowController {
17
21
  /** @type {boolean} */
18
22
  enabled = false;
@@ -30,6 +34,8 @@ class FollowController {
30
34
  _events = null;
31
35
  /** @type {Function|null} */
32
36
  _emit = null;
37
+ /** @type {string} Current active panel type in follow mode */
38
+ _activePanel = 'graph';
33
39
 
34
40
  /**
35
41
  * Late-bind events bus and emit function (breaks circular import).
@@ -48,6 +54,7 @@ class FollowController {
48
54
 
49
55
  // Save current location for restoring later
50
56
  this._previousHash = location.hash;
57
+ this._activePanel = 'graph';
51
58
 
52
59
  // Bind tool-event listener
53
60
  this._boundHandler = (e) => this._onToolEvent(e.detail);
@@ -105,12 +112,54 @@ class FollowController {
105
112
  // Visual focus — classify and dispatch (debounced for heavy ops)
106
113
  const action = this._classify(shortName, args, isCall, isResult, event);
107
114
  if (action) {
108
- if (action.immediate) {
109
- this._emitFocusNow(action.focus);
110
- } else {
111
- this._emitFocusDebounced(action.focus, action.debounce || HEAVY_DEBOUNCE);
115
+ this._routeAndFocus(action);
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Route to the correct panel, then emit focus.
121
+ * Two-phase: if the target panel differs from current, switch hash first,
122
+ * then wait for mount before emitting focus.
123
+ * @param {{focus: object, debounce?: number, immediate?: boolean}} action
124
+ */
125
+ _routeAndFocus(action) {
126
+ const targetPanel = action.focus.type === 'file' ? 'code' : action.focus.type || 'graph';
127
+
128
+ const needsSwitch = this._activePanel !== targetPanel;
129
+ this._activePanel = targetPanel;
130
+
131
+ if (needsSwitch) {
132
+ // Build new hash for the follow section
133
+ const hashSection = this._panelToHash(targetPanel);
134
+ // Use replaceState to avoid polluting browser history during follow
135
+ const newHash = `#${hashSection}`;
136
+ if (location.hash !== newHash) {
137
+ // We need to trigger hashchange so the layout system picks it up
138
+ location.hash = hashSection;
112
139
  }
113
140
  }
141
+
142
+ // Emit focus — with delay if panel needed mounting, otherwise respect debounce
143
+ if (needsSwitch) {
144
+ // Wait for panel to mount after layout change
145
+ setTimeout(() => {
146
+ this._emitFocusNow(action.focus);
147
+ }, PANEL_MOUNT_DELAY);
148
+ } else if (action.immediate) {
149
+ this._emitFocusNow(action.focus);
150
+ } else {
151
+ this._emitFocusDebounced(action.focus, action.debounce || HEAVY_DEBOUNCE);
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Map panel type to hash section.
157
+ * In follow mode, graph/code/analysis all live under the 'follow' section
158
+ * but we can use the hash to give routing hints.
159
+ */
160
+ _panelToHash(panel) {
161
+ // All panels are within the 'follow' layout, no need to switch section
162
+ return 'follow';
114
163
  }
115
164
 
116
165
  /**
@@ -136,6 +185,9 @@ class FollowController {
136
185
  if (args.action === 'call_chain' && args.from && args.to) {
137
186
  return { focus: { type: 'graph', target: { from: args.from, to: args.to }, action: 'chain' }, immediate: true };
138
187
  }
188
+ if (args.action === 'sub_projects') {
189
+ return { focus: { type: 'graph', action: 'fit' }, immediate: true };
190
+ }
139
191
  }
140
192
 
141
193
  // === Skeleton / Overview ===
@@ -148,9 +200,25 @@ class FollowController {
148
200
  return { focus: { type: 'file', target: args.path }, debounce: HEAVY_DEBOUNCE };
149
201
  }
150
202
 
203
+ // === Documentation ===
204
+ if (tool === 'docs' && args.file) {
205
+ return { focus: { type: 'file', target: args.file }, debounce: HEAVY_DEBOUNCE };
206
+ }
207
+
151
208
  // === Analysis ===
152
209
  if (tool === 'analyze') {
153
- return { focus: { type: 'analysis' }, immediate: true };
210
+ // Analysis events should pulse the graph to show "thinking"
211
+ return { focus: { type: 'graph', action: 'fit' }, immediate: true };
212
+ }
213
+
214
+ // === JSDoc ===
215
+ if (tool === 'jsdoc' && args.path) {
216
+ return { focus: { type: 'file', target: args.path }, debounce: HEAVY_DEBOUNCE };
217
+ }
218
+
219
+ // === Focus zone ===
220
+ if (tool === 'get_focus_zone') {
221
+ return { focus: { type: 'graph', action: 'fit' }, immediate: true };
154
222
  }
155
223
 
156
224
  return null;
@@ -169,23 +237,24 @@ class FollowController {
169
237
 
170
238
  switch (tool) {
171
239
  case 'navigate': {
172
- if (args.action === 'expand') return `Expanding ${args.symbol}`;
173
- if (args.action === 'deps') return `Tracing deps of ${args.symbol}`;
174
- if (args.action === 'usages') return `Finding usages of ${args.symbol}`;
175
- if (args.action === 'call_chain') return `Tracing ${args.from} → ${args.to}`;
176
- if (args.action === 'sub_projects') return `Scanning sub-projects`;
177
- return `Navigating graph`;
240
+ if (args.action === 'expand') return `🔍 Expanding ${args.symbol}`;
241
+ if (args.action === 'deps') return `🔗 Tracing deps of ${args.symbol}`;
242
+ if (args.action === 'usages') return `📡 Finding usages of ${args.symbol}`;
243
+ if (args.action === 'call_chain') return `⛓ Tracing ${args.from} → ${args.to}`;
244
+ if (args.action === 'sub_projects') return `📦 Scanning sub-projects`;
245
+ return `🧭 Navigating graph`;
178
246
  }
179
- case 'get_skeleton': return `Scanning project structure`;
180
- case 'get_ai_context': return `Loading AI context`;
181
- case 'compact': return `Compacting ${short}`;
182
- case 'analyze': return `Analyzing: ${args.action || ''}`;
183
- case 'docs': return `Documentation: ${args.action || ''}`;
184
- case 'jsdoc': return `JSDoc: ${args.action || ''}`;
185
- case 'db': return `Database: ${args.action || ''}`;
186
- case 'testing': return `Tests: ${args.action || ''}`;
187
- case 'filters': return `Filters: ${args.action || ''}`;
188
- default: return tool ? `Running ${tool}` : '';
247
+ case 'get_skeleton': return `🗺️ Scanning project structure`;
248
+ case 'get_ai_context': return `🧠 Loading AI context`;
249
+ case 'get_focus_zone': return `🎯 Analyzing recent changes`;
250
+ case 'compact': return `📄 Reading ${short}`;
251
+ case 'analyze': return `📊 Analyzing: ${args.action || ''}`;
252
+ case 'docs': return `📝 Documentation: ${args.action || ''}`;
253
+ case 'jsdoc': return `📋 JSDoc: ${args.action || ''}`;
254
+ case 'db': return `🗄️ Database: ${args.action || ''}`;
255
+ case 'testing': return `🧪 Tests: ${args.action || ''}`;
256
+ case 'filters': return `⚙️ Filters: ${args.action || ''}`;
257
+ default: return tool ? `⚡ ${tool}` : '';
189
258
  }
190
259
  }
191
260
 
@@ -115,6 +115,154 @@ function buildBasenameIndex(knownFiles) {
115
115
  return _basenameIndex;
116
116
  }
117
117
 
118
+ /**
119
+ * Build a file-level graph from skeleton data.
120
+ * Each file becomes a Node, each import relationship becomes a Connection.
121
+ *
122
+ * @param {object} skeleton - skeleton from get_skeleton
123
+ * @returns {{ editor: NodeEditor, fileMap: Map<string, string> }}
124
+ */
125
+ function buildFileGraph(skeleton) {
126
+ const editor = new NodeEditor();
127
+ const fileMap = new Map(); // filePath → nodeId
128
+ const dirMap = new Map(); // dirPath → nodeId (hub nodes)
129
+
130
+ // Collect all files that have symbols
131
+ const files = new Set();
132
+ const assetFiles = new Set(); // non-source files (.css, .html, .json, .md, etc.)
133
+ // From nodes (classes) — each has .f (file) property
134
+ for (const data of Object.values(skeleton.n || {})) {
135
+ if (data.f) files.add(data.f);
136
+ }
137
+ // From exports map — keys are files
138
+ for (const file of Object.keys(skeleton.X || {})) {
139
+ files.add(file);
140
+ }
141
+ // From source files without symbols
142
+ for (const [dir, names] of Object.entries(skeleton.f || {})) {
143
+ for (const name of names) {
144
+ files.add(dir === './' ? name : dir + name);
145
+ }
146
+ }
147
+ // From non-source/asset files (.css, .html, .json, .md, etc.)
148
+ for (const [dir, names] of Object.entries(skeleton.a || {})) {
149
+ for (const name of names) {
150
+ const fullPath = dir === './' ? name : dir + name;
151
+ files.add(fullPath);
152
+ assetFiles.add(fullPath);
153
+ }
154
+ }
155
+
156
+ if (files.size === 0) return { editor, fileMap };
157
+
158
+ // Group files by directory
159
+ const dirFiles = new Map();
160
+ for (const file of files) {
161
+ const dir = dirOf(file);
162
+ if (!dirFiles.has(dir)) dirFiles.set(dir, []);
163
+ dirFiles.get(dir).push(file);
164
+ }
165
+
166
+ // TODO Phase 2: Create directory hub nodes when LOD zoom expansion is ready
167
+ // Hub nodes without connections create disconnected groups — skip for now
168
+ // for (const [dir, dirFileList] of dirFiles) {
169
+ // if (dirFileList.length < 2) continue;
170
+ // const dirLabel = dir.replace(/\/$/, '').split('/').pop() || 'root';
171
+ // const hubNode = new Node(dirLabel, { type: 'directory', category: 'server', shape: 'hexagon' });
172
+ // hubNode.params = { path: dir, dir, isHub: true };
173
+ // hubNode.addOutput('out', new Output(S_EXPORT, ''));
174
+ // hubNode.addInput('in', new Input(S_IMPORT, ''));
175
+ // editor.addNode(hubNode);
176
+ // dirMap.set(dir, hubNode.id);
177
+ // }
178
+
179
+ // Create file nodes (standard HTML nodes with icons)
180
+ for (const file of files) {
181
+ const dir = dirOf(file);
182
+ const label = baseName(file);
183
+ const isAsset = assetFiles.has(file);
184
+ const node = new Node(label, {
185
+ type: isAsset ? 'asset' : 'file',
186
+ category: isAsset ? 'asset' : 'file',
187
+ });
188
+ node.params = { path: file, dir };
189
+
190
+ // Every file has one output (exports) and one input (imports)
191
+ node.addOutput('out', new Output(S_EXPORT, ''));
192
+ node.addInput('in', new Input(S_IMPORT, ''));
193
+
194
+ editor.addNode(node);
195
+ fileMap.set(file, node.id);
196
+ }
197
+
198
+ // Build import edges from skeleton.I (file-level import map)
199
+ // skeleton.I[file] = [source1, source2, ...]
200
+ const edgesAdded = new Set();
201
+ for (const [srcFile, sources] of Object.entries(skeleton.I || {})) {
202
+ const srcId = fileMap.get(srcFile);
203
+ if (!srcId) continue;
204
+
205
+ for (const impPath of sources) {
206
+ // Skip node builtins and external packages
207
+ if (impPath.startsWith('node:') || (!impPath.startsWith('.') && !impPath.startsWith('/'))) continue;
208
+
209
+ const targetFile = resolveImport(impPath, srcFile, files);
210
+ if (!targetFile) continue;
211
+
212
+ const tgtId = fileMap.get(targetFile);
213
+ if (!tgtId || tgtId === srcId) continue;
214
+
215
+ const edgeKey = `${srcId}->${tgtId}`;
216
+ if (edgesAdded.has(edgeKey)) continue;
217
+ edgesAdded.add(edgeKey);
218
+
219
+ const srcNode = editor.getNode(srcId);
220
+ const tgtNode = editor.getNode(tgtId);
221
+ try {
222
+ const conn = new Connection(srcNode, 'out', tgtNode, 'in');
223
+ // Phase 3: tag cross-directory connections as "via"
224
+ const srcDir = dirOf(srcFile);
225
+ const tgtDir = dirOf(targetFile);
226
+ if (srcDir !== tgtDir) {
227
+ conn._via = true;
228
+ conn._srcDir = srcDir;
229
+ conn._tgtDir = tgtDir;
230
+ }
231
+ editor.addConnection(conn);
232
+ } catch {
233
+ // Skip invalid connections
234
+ }
235
+ }
236
+ }
237
+
238
+ // Hub node: find node with highest connectivity → module category
239
+ const connCounts = new Map();
240
+ for (const conn of editor.getConnections()) {
241
+ connCounts.set(conn.from, (connCounts.get(conn.from) || 0) + 1);
242
+ connCounts.set(conn.to, (connCounts.get(conn.to) || 0) + 1);
243
+ }
244
+ let maxConns = 0;
245
+ let hubId = null;
246
+ for (const [nodeId, count] of connCounts) {
247
+ if (count > maxConns) {
248
+ maxConns = count;
249
+ hubId = nodeId;
250
+ }
251
+ }
252
+ if (hubId) {
253
+ const hubNode = editor.getNode(hubId);
254
+ if (hubNode && hubNode.options) {
255
+ hubNode.options.category = 'module';
256
+ }
257
+ }
258
+
259
+ // ── Build Reverse ID Lookup ──
260
+ const idToPath = new Map();
261
+ for (const [path, id] of fileMap.entries()) idToPath.set(id, path);
262
+
263
+ return { editor, fileMap, dirMap, dirFiles, idToPath };
264
+ }
265
+
118
266
  /**
119
267
  * Build a hierarchical SubgraphNode graph:
120
268
  * Level 0: directories (SubgraphNode)
@@ -723,7 +871,11 @@ export class DepGraph extends Symbiote {
723
871
  if (!loader) return;
724
872
  loader.setAttribute('data-hidden', '');
725
873
  // Remove from DOM after fade to avoid blocking pointer events
726
- setTimeout(() => loader.remove(), 350);
874
+ setTimeout(() => {
875
+ loader.remove();
876
+ // Replay any follow-focus that arrived while the graph was building
877
+ this._replayPendingFollowFocus();
878
+ }, 350);
727
879
  }
728
880
 
729
881
  /** Show (or re-show) the PCB preloader overlay — safe to call multiple times */
@@ -891,18 +1043,20 @@ export class DepGraph extends Symbiote {
891
1043
  viewModeBtn.removeAttribute('data-active');
892
1044
  }
893
1045
  }
894
- // Hide structured-only buttons in flat mode
895
1046
  this._updateStructuredOnlyVisibility(this._viewMode);
896
- viewModeBtn?.addEventListener('click', () => {
897
- const wantFlat = this._viewMode !== 'flat';
898
- this._viewMode = wantFlat ? 'flat' : 'structured';
1047
+
1048
+ this._setMode = (newMode) => {
1049
+ if (this._viewMode === newMode) return;
1050
+ this._viewMode = newMode;
899
1051
  const label = this._viewMode === 'flat' ? 'FLAT' : 'TREE';
900
1052
  const icon = this._viewMode === 'flat' ? 'account_tree' : 'grid_view';
901
- viewModeBtn.innerHTML = `<span class="material-symbols-outlined">${icon}</span>${label}`;
902
- if (this._viewMode === 'structured') {
903
- viewModeBtn.setAttribute('data-active', '');
904
- } else {
905
- viewModeBtn.removeAttribute('data-active');
1053
+ if (viewModeBtn) {
1054
+ viewModeBtn.innerHTML = `<span class="material-symbols-outlined">${icon}</span>${label}`;
1055
+ if (this._viewMode === 'structured') {
1056
+ viewModeBtn.setAttribute('data-active', '');
1057
+ } else {
1058
+ viewModeBtn.removeAttribute('data-active');
1059
+ }
906
1060
  }
907
1061
  this._updateStructuredOnlyVisibility(this._viewMode);
908
1062
 
@@ -923,6 +1077,11 @@ export class DepGraph extends Symbiote {
923
1077
  if (state.skeleton) {
924
1078
  this._buildGraph(state.skeleton);
925
1079
  }
1080
+ };
1081
+
1082
+ viewModeBtn?.addEventListener('click', () => {
1083
+ const wantFlat = this._viewMode !== 'flat';
1084
+ this._setMode(wantFlat ? 'flat' : 'structured');
926
1085
  });
927
1086
 
928
1087
  // Connection Path Style toggling
@@ -981,8 +1140,14 @@ export class DepGraph extends Symbiote {
981
1140
  requestAnimationFrame(() => this._buildGraph(e.detail));
982
1141
  };
983
1142
 
1143
+ this._onFollowStateChanged = (e) => {
1144
+ const enabled = e.detail?.enabled;
1145
+ if (enabled && this._viewMode !== 'flat') {
1146
+ this._setMode('flat');
1147
+ }
1148
+ };
1149
+
984
1150
  this._onFollowFocusChanged = (e) => {
985
- if (this.style.display === 'none' || this.offsetWidth === 0) return;
986
1151
  this._handleFollowFocus(e.detail);
987
1152
  };
988
1153
 
@@ -1018,6 +1183,7 @@ export class DepGraph extends Symbiote {
1018
1183
 
1019
1184
  // Autopilot: listen for orchestrator events
1020
1185
  events.addEventListener('follow-focus-changed', this._onFollowFocusChanged);
1186
+ events.addEventListener('follow-state-changed', this._onFollowStateChanged);
1021
1187
 
1022
1188
  // Update route within graph section
1023
1189
  // On node click → save file path (just focusing)
@@ -1049,10 +1215,7 @@ export class DepGraph extends Symbiote {
1049
1215
  // Root level: path goes into ?focus= parameter
1050
1216
  this._updateHashParam('focus', path);
1051
1217
  this._updateHashParam('in', null);
1052
- // Pan/zoom to the clicked node — it's already visible, no need to drill
1053
- if (nodeId && this._canvas?.flyToNode) {
1054
- this._canvas.flyToNode(nodeId, { zoom: 0.9 });
1055
- }
1218
+
1056
1219
  } else {
1057
1220
  // Inside a group: preserve drill context URL, set &focus= with relative name
1058
1221
  const drillBase = window.location.hash.split('?')[0]; // e.g. #graph/src/analysis/
@@ -1063,10 +1226,7 @@ export class DepGraph extends Symbiote {
1063
1226
  this._updateHashParam('focus', relativeName);
1064
1227
  this._updateHashParam('in', '1');
1065
1228
 
1066
- // Pan/zoom to the clicked node within current subgraph
1067
- if (nodeId && this._canvas?.flyToNode) {
1068
- this._canvas.flyToNode(nodeId, { zoom: 0.9 });
1069
- }
1229
+
1070
1230
  }
1071
1231
  // Sync: highlight file in the tree sidebar
1072
1232
  emit('file-selected', { path, source: 'canvas' });
@@ -1155,6 +1315,7 @@ export class DepGraph extends Symbiote {
1155
1315
  disconnectedCallback() {
1156
1316
  super.disconnectedCallback?.();
1157
1317
  if (this._onSkeletonLoaded) events.removeEventListener('skeleton-loaded', this._onSkeletonLoaded);
1318
+ if (this._onFollowStateChanged) events.removeEventListener('follow-state-changed', this._onFollowStateChanged);
1158
1319
  if (this._onFollowFocusChanged) events.removeEventListener('follow-focus-changed', this._onFollowFocusChanged);
1159
1320
  if (this._onFileSelected) events.removeEventListener('file-selected', this._onFileSelected);
1160
1321
  if (this._onHashChange) window.removeEventListener('hashchange', this._onHashChange);
@@ -1558,6 +1719,8 @@ export class DepGraph extends Symbiote {
1558
1719
  this._setProgress(15, 'Parsing graph…', isStructured ? 'structured mode' : 'flat mode');
1559
1720
  if (isStructured) {
1560
1721
  ({ editor, fileMap, dirFiles, dirNodeMap, idToPath, symbolMap } = buildStructuredGraph(skeleton));
1722
+ } else {
1723
+ ({ editor, fileMap, dirFiles, idToPath, symbolMap: symbolMap = new Map() } = buildFileGraph(skeleton));
1561
1724
  }
1562
1725
 
1563
1726
  this._setProgress(40, 'Building nodes…', `${editor.getNodes().length} nodes`);
@@ -2492,9 +2655,21 @@ export class DepGraph extends Symbiote {
2492
2655
  * @param {object} detail
2493
2656
  */
2494
2657
  _handleFollowFocus({ type, target, action }) {
2495
- if (!this._editor || !this._canvas) return;
2496
- if (type !== 'graph' && type !== 'file') return; // React to graph and file actions
2658
+ if (type !== 'graph' && type !== 'file') return;
2659
+
2660
+ // If graph isn't ready yet, queue the focus and replay after build
2661
+ if (!this._editor || !this._canvas || !this._graphBuilt) {
2662
+ this._pendingFollowFocus = { type, target, action };
2663
+ return;
2664
+ }
2497
2665
 
2666
+ this._executeFollowFocus(type, target, action);
2667
+ }
2668
+
2669
+ /**
2670
+ * Execute a follow focus action (called when graph is confirmed ready).
2671
+ */
2672
+ _executeFollowFocus(type, target, action) {
2498
2673
  if (type === 'graph') {
2499
2674
  if (action === 'focus' && target) {
2500
2675
  this._focusSymbol(target);
@@ -2511,6 +2686,17 @@ export class DepGraph extends Symbiote {
2511
2686
  }
2512
2687
  }
2513
2688
 
2689
+ /**
2690
+ * Replay any pending follow focus that was queued before the graph was ready.
2691
+ */
2692
+ _replayPendingFollowFocus() {
2693
+ if (this._pendingFollowFocus) {
2694
+ const { type, target, action } = this._pendingFollowFocus;
2695
+ this._pendingFollowFocus = null;
2696
+ this._executeFollowFocus(type, target, action);
2697
+ }
2698
+ }
2699
+
2514
2700
  // ── Phase 4: Camera Animation & Code Drill-down ──
2515
2701
 
2516
2702
  /**
Binary file
@@ -1,31 +0,0 @@
1
- # Changelog
2
-
3
- All notable changes to symbiote-node will be documented in this file.
4
-
5
- ## [0.3.0-alpha.0] — 2026-04-18
6
-
7
- ### Fixed
8
- - **Memory leak**: zombie `setTimeout` loops in SubgraphNode preview rendering — replaced with on-demand redraws
9
- - **Memory leak**: event listener accumulation in `NodeCanvas.setEditor()` — added explicit unsubscribe on context switch
10
- - **Memory leak**: incorrect `cancelAnimationFrame` cleanup for `setTimeout` IDs in `NodeViewManager.removeView()`
11
- - **Layout overlap**: nodes measured as 4px height (DOM not ready) caused overlap — enforced minimum `nodeHeight` floor in `getSize()`
12
- - **Inspector z-index**: panel header overlapped toolbar buttons — removed header, added toolbar-aware padding
13
-
14
- ### Added
15
- - `Editor.removeAllListeners()` — clean teardown method for editor event system
16
- - `computeTreeLayout()` — directory-hierarchy-aware tree layout with indent levels
17
- - Shape primitives: `CircleShape`, `DiamondShape`, `PillShape`, `RectShape`
18
- - PCB dark theme enhancements: improved node styling, copper trace connections
19
-
20
- ### Breaking
21
- - `InspectorPanel` no longer renders a title header bar — consumers relying on `.insp-header` CSS should update
22
-
23
- ## [0.2.1] — 2026-04-13
24
-
25
- - Initial open-source release
26
- - Node graph editor with Symbiote.js web components
27
- - Sugiyama-based auto layout (`computeAutoLayout`)
28
- - PCB/Carbon theming system
29
- - Inspector panel with resize handle
30
- - Subgraph navigation (drill-down/drill-up)
31
- - Execution engine with topological sorting
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 RND-PRO
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.