project-graph-mcp 2.3.2 → 2.4.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 (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,79 +0,0 @@
1
- /**
2
- * transform/template — String template interpolation
3
- *
4
- * Replaces {{variable}} placeholders in template string with values from data object.
5
- * Supports nested access via dot notation: {{user.name}}.
6
- *
7
- * @module symbiote-node/packs/transform/template
8
- */
9
-
10
- export default {
11
- type: 'transform/template',
12
- category: 'transform',
13
- icon: 'text_snippet',
14
-
15
- driver: {
16
- description: 'Template interpolation — replace {{var}} with data values',
17
- inputs: [
18
- { name: 'template', type: 'string' },
19
- { name: 'data', type: 'any' },
20
- ],
21
- outputs: [
22
- { name: 'result', type: 'string' },
23
- { name: 'data', type: 'any' },
24
- ],
25
- params: {
26
- template: { type: 'textarea', default: '', description: 'Message template ({{var}} syntax)' },
27
- replyMarkup: { type: 'textarea', default: '', description: 'Inline keyboard JSON (Telegram reply_markup)' },
28
- },
29
- },
30
-
31
- lifecycle: {
32
- // No validate: template comes from params.template or inputs.template
33
- // Execute handles both cases
34
-
35
- cacheKey: (inputs) =>
36
- `tpl:${inputs.template}:${JSON.stringify(inputs.data)}`,
37
-
38
- execute: async (inputs, params) => {
39
- const template = params?.template || inputs.template;
40
- const { data } = inputs;
41
-
42
- const result = template.replace(/\{\{([^}]+)\}\}/g, (match, key) => {
43
- const trimmed = key.trim();
44
- // Support dot notation: {{user.name}}
45
- const value = trimmed.split('.').reduce((obj, k) => {
46
- if (obj === null || obj === undefined) return undefined;
47
- return obj[k];
48
- }, data);
49
-
50
- if (value === undefined) {
51
- console.warn(`[template] ⚠️ Missing variable "${trimmed}" in data keys: [${data ? Object.keys(data).join(', ') : 'NO DATA'}]`);
52
- return match;
53
- }
54
- if (typeof value === 'object') return JSON.stringify(value);
55
- return String(value);
56
- });
57
-
58
- // Output rendered text in both formats:
59
- // - result: raw string (for chaining)
60
- // - data: full context with text field (for telegram/chat)
61
- const outputField = params?.outputField || 'text';
62
- const outputData = { ...(typeof data === 'object' ? data : {}), [outputField]: result };
63
-
64
- // Attach inline keyboard if configured
65
- if (params?.replyMarkup) {
66
- try {
67
- outputData.reply_markup = JSON.parse(params.replyMarkup);
68
- } catch (e) {
69
- console.warn('[template] ⚠️ Invalid replyMarkup JSON:', e.message);
70
- }
71
- }
72
-
73
- return {
74
- result,
75
- data: outputData,
76
- };
77
- },
78
- },
79
- };
@@ -1,399 +0,0 @@
1
- /**
2
- * transform/timeline-build — Whisper + beats → timeline segments
3
- *
4
- * Combines word timestamps from ai/whisper with beat data from ai/beat-detect
5
- * to produce a continuous timeline of segments with 100% coverage.
6
- *
7
- * Core logic:
8
- * 1. Build phrases from whisper words (punctuation-based splitting)
9
- * 2. Fill gaps between phrases with beat-snapped segments
10
- * 3. Enforce minimum/maximum segment duration (merge/split)
11
- * 4. Calculate coverage statistics
12
- *
13
- * Simplified port of TimelineGenerator from
14
- * Mr-Computer/modules/ai-music-video/src/services/timeline-generator.js
15
- *
16
- * @module agi-graph/packs/transform/timeline-build
17
- */
18
-
19
- export default {
20
- type: 'transform/timeline-build',
21
- category: 'transform',
22
- icon: 'view_timeline',
23
-
24
- driver: {
25
- description: 'Whisper words + beat data → timeline segments with 100% coverage',
26
- inputs: [
27
- { name: 'whisperData', type: 'any' },
28
- { name: 'beatData', type: 'any' },
29
- ],
30
- outputs: [
31
- { name: 'segments', type: 'any' },
32
- { name: 'stats', type: 'any' },
33
- { name: 'error', type: 'string' },
34
- ],
35
- params: {
36
- minSegmentDuration: { type: 'number', default: 1.8, description: 'Min segment duration (seconds)' },
37
- maxSegmentDuration: { type: 'number', default: 5.0, description: 'Max segment duration (seconds)' },
38
- shortMergeThreshold: { type: 'number', default: 1.2, description: 'Merge lyrics segments shorter than this' },
39
- gapType: { type: 'string', default: 'beat', description: 'Type label for gap-fill segments' },
40
- },
41
- },
42
-
43
- lifecycle: {
44
- validate: (inputs) => {
45
- if (!inputs.whisperData) return false;
46
- return true;
47
- },
48
-
49
- cacheKey: (inputs, params) => {
50
- const wd = inputs.whisperData;
51
- const bd = inputs.beatData;
52
- return `timeline:${wd.duration || 0}:${bd?.tempo || 0}:${params.minSegmentDuration}`;
53
- },
54
-
55
- execute: async (inputs, params) => {
56
- try {
57
- const { whisperData, beatData } = inputs;
58
- const words = whisperData.words || [];
59
- const duration = whisperData.duration || beatData?.duration || 0;
60
- const beats = beatData?.beats || [];
61
-
62
- if (words.length === 0) {
63
- return { segments: null, stats: null, error: 'No whisper words provided' };
64
- }
65
-
66
- // Step 1: Build phrases from words
67
- let segments = buildPhrases(words);
68
-
69
- // Step 2: Fill gaps with beat-snapped segments
70
- segments = fillGaps(segments, beats, duration, params);
71
-
72
- // Step 3: Remove overlaps
73
- segments = removeOverlaps(segments);
74
-
75
- // Step 4: Merge short segments
76
- segments = mergeShort(segments, params.shortMergeThreshold || 1.2);
77
-
78
- // Step 5: Enforce min duration
79
- segments = enforceMinDuration(segments, params.minSegmentDuration || 1.8);
80
-
81
- // Step 6: Cap max duration
82
- segments = capMaxDuration(segments, params.maxSegmentDuration || 5.0);
83
-
84
- // Sort final
85
- segments.sort((a, b) => a.start - b.start);
86
-
87
- // Stats
88
- const stats = calculateStats(segments, duration);
89
-
90
- return { segments, stats, error: null };
91
- } catch (err) {
92
- return { segments: null, stats: null, error: err.message };
93
- }
94
- },
95
- },
96
- };
97
-
98
- /**
99
- * Build phrases from whisper words using punctuation-based splitting
100
- * @param {Array<{word: string, start: number, end: number}>} words
101
- * @returns {Array<{start: number, end: number, text: string, type: string, wordCount: number}>}
102
- */
103
- function buildPhrases(words) {
104
- const phrases = [];
105
- let current = null;
106
-
107
- for (const w of words) {
108
- if (!current) {
109
- current = {
110
- start: w.start,
111
- end: w.end,
112
- words: [w.word],
113
- type: 'lyrics',
114
- };
115
- } else {
116
- current.end = w.end;
117
- current.words.push(w.word);
118
- }
119
-
120
- // Split on sentence endings, commas, or long pauses
121
- const endsWithPunct = /[.!?;]$/.test(w.word);
122
- const nextWord = words[words.indexOf(w) + 1];
123
- const hasGap = nextWord && (nextWord.start - w.end > 0.8);
124
-
125
- if (endsWithPunct || hasGap || current.words.length >= 12) {
126
- phrases.push({
127
- start: current.start,
128
- end: current.end,
129
- text: current.words.join(' '),
130
- type: 'lyrics',
131
- wordCount: current.words.length,
132
- });
133
- current = null;
134
- }
135
- }
136
-
137
- // Close last phrase
138
- if (current && current.words.length > 0) {
139
- phrases.push({
140
- start: current.start,
141
- end: current.end,
142
- text: current.words.join(' '),
143
- type: 'lyrics',
144
- wordCount: current.words.length,
145
- });
146
- }
147
-
148
- return phrases;
149
- }
150
-
151
- /**
152
- * Snap a time to the nearest beat
153
- * @param {number} time - Seconds
154
- * @param {number[]} beats - Beat timestamps
155
- * @returns {number} Snapped time
156
- */
157
- function snapToBeat(time, beats) {
158
- if (!beats || beats.length === 0) return time;
159
-
160
- let closest = beats[0];
161
- let minDist = Math.abs(beats[0] - time);
162
-
163
- for (const beat of beats) {
164
- const dist = Math.abs(beat - time);
165
- if (dist < minDist) {
166
- minDist = dist;
167
- closest = beat;
168
- }
169
- if (beat > time + minDist) break;
170
- }
171
-
172
- // Only snap if within 0.3s of a beat
173
- return minDist < 0.3 ? closest : time;
174
- }
175
-
176
- /**
177
- * Fill gaps between segments with beat-snapped segments
178
- * @param {Array} segments
179
- * @param {number[]} beats
180
- * @param {number} duration
181
- * @param {Object} params
182
- * @returns {Array}
183
- */
184
- function fillGaps(segments, beats, duration, params) {
185
- if (segments.length === 0) return segments;
186
-
187
- const result = [];
188
- const gapType = params.gapType || 'beat';
189
-
190
- // Gap at start?
191
- if (segments[0].start > 0.1) {
192
- result.push({
193
- start: 0,
194
- end: snapToBeat(segments[0].start, beats),
195
- text: '',
196
- type: gapType,
197
- wordCount: 0,
198
- });
199
- }
200
-
201
- for (let i = 0; i < segments.length; i++) {
202
- result.push(segments[i]);
203
-
204
- // Gap to next segment?
205
- const next = segments[i + 1];
206
- if (next) {
207
- const gapStart = segments[i].end;
208
- const gapEnd = next.start;
209
- const gapSize = gapEnd - gapStart;
210
-
211
- if (gapSize > 0.2) {
212
- result.push({
213
- start: snapToBeat(gapStart, beats),
214
- end: snapToBeat(gapEnd, beats),
215
- text: '',
216
- type: gapType,
217
- wordCount: 0,
218
- });
219
- }
220
- }
221
- }
222
-
223
- // Gap at end?
224
- const lastEnd = segments[segments.length - 1].end;
225
- if (duration > 0 && duration - lastEnd > 0.2) {
226
- result.push({
227
- start: snapToBeat(lastEnd, beats),
228
- end: duration,
229
- text: '',
230
- type: gapType,
231
- wordCount: 0,
232
- });
233
- }
234
-
235
- return result;
236
- }
237
-
238
- /**
239
- * Remove overlapping segments (trim shorter one)
240
- * @param {Array} segments
241
- * @returns {Array}
242
- */
243
- function removeOverlaps(segments) {
244
- if (segments.length < 2) return segments;
245
-
246
- segments.sort((a, b) => a.start - b.start);
247
-
248
- for (let i = 1; i < segments.length; i++) {
249
- const prev = segments[i - 1];
250
- const curr = segments[i];
251
-
252
- if (curr.start < prev.end) {
253
- // Overlap: trim the gap segment, or split at midpoint
254
- if (prev.type !== 'lyrics' && curr.type === 'lyrics') {
255
- prev.end = curr.start;
256
- } else if (prev.type === 'lyrics' && curr.type !== 'lyrics') {
257
- curr.start = prev.end;
258
- } else {
259
- const mid = (prev.end + curr.start) / 2;
260
- prev.end = mid;
261
- curr.start = mid;
262
- }
263
- }
264
- }
265
-
266
- // Remove zero/negative duration segments
267
- return segments.filter(s => s.end - s.start > 0.05);
268
- }
269
-
270
- /**
271
- * Merge short lyrics segments into neighbors
272
- * @param {Array} segments
273
- * @param {number} threshold
274
- * @returns {Array}
275
- */
276
- function mergeShort(segments, threshold) {
277
- if (segments.length < 2) return segments;
278
-
279
- const result = [segments[0]];
280
-
281
- for (let i = 1; i < segments.length; i++) {
282
- const curr = segments[i];
283
- const prev = result[result.length - 1];
284
- const currDuration = curr.end - curr.start;
285
-
286
- // Merge short lyrics into previous
287
- if (curr.type === 'lyrics' && currDuration < threshold && prev.type === 'lyrics') {
288
- prev.end = curr.end;
289
- prev.text = prev.text + ' ' + curr.text;
290
- prev.wordCount = (prev.wordCount || 0) + (curr.wordCount || 0);
291
- } else {
292
- result.push(curr);
293
- }
294
- }
295
-
296
- return result;
297
- }
298
-
299
- /**
300
- * Enforce minimum duration by merging
301
- * @param {Array} segments
302
- * @param {number} minDuration
303
- * @returns {Array}
304
- */
305
- function enforceMinDuration(segments, minDuration) {
306
- if (segments.length < 2) return segments;
307
-
308
- const result = [segments[0]];
309
-
310
- for (let i = 1; i < segments.length; i++) {
311
- const prev = result[result.length - 1];
312
- const prevDuration = prev.end - prev.start;
313
-
314
- if (prevDuration < minDuration) {
315
- // Extend previous to absorb current
316
- prev.end = segments[i].end;
317
- if (segments[i].text) {
318
- prev.text = (prev.text ? prev.text + ' ' : '') + segments[i].text;
319
- }
320
- if (segments[i].type === 'lyrics') prev.type = 'lyrics';
321
- } else {
322
- result.push(segments[i]);
323
- }
324
- }
325
-
326
- return result;
327
- }
328
-
329
- /**
330
- * Cap segments at max duration by splitting evenly
331
- * @param {Array} segments
332
- * @param {number} maxDuration
333
- * @returns {Array}
334
- */
335
- function capMaxDuration(segments, maxDuration) {
336
- const result = [];
337
-
338
- for (const seg of segments) {
339
- const duration = seg.end - seg.start;
340
-
341
- if (duration <= maxDuration) {
342
- result.push(seg);
343
- continue;
344
- }
345
-
346
- // Split evenly
347
- const parts = Math.ceil(duration / maxDuration);
348
- const partDuration = duration / parts;
349
-
350
- for (let i = 0; i < parts; i++) {
351
- result.push({
352
- ...seg,
353
- start: seg.start + i * partDuration,
354
- end: seg.start + (i + 1) * partDuration,
355
- text: i === 0 ? seg.text : '',
356
- _splitPart: i + 1,
357
- _splitTotal: parts,
358
- });
359
- }
360
- }
361
-
362
- return result;
363
- }
364
-
365
- /**
366
- * Calculate timeline coverage statistics
367
- * @param {Array} segments
368
- * @param {number} audioDuration
369
- * @returns {Object}
370
- */
371
- function calculateStats(segments, audioDuration) {
372
- const totalSegments = segments.length;
373
- const lyricsSegments = segments.filter(s => s.type === 'lyrics').length;
374
- const gapSegments = totalSegments - lyricsSegments;
375
-
376
- const coveredDuration = segments.reduce((sum, s) => sum + (s.end - s.start), 0);
377
- const lyricsDuration = segments
378
- .filter(s => s.type === 'lyrics')
379
- .reduce((sum, s) => sum + (s.end - s.start), 0);
380
-
381
- const coverage = audioDuration > 0
382
- ? Math.round((coveredDuration / audioDuration) * 100)
383
- : 0;
384
-
385
- const avgDuration = totalSegments > 0
386
- ? Math.round((coveredDuration / totalSegments) * 100) / 100
387
- : 0;
388
-
389
- return {
390
- totalSegments,
391
- lyricsSegments,
392
- gapSegments,
393
- coveredDuration: Math.round(coveredDuration * 100) / 100,
394
- audioDuration: Math.round(audioDuration * 100) / 100,
395
- coverage,
396
- lyricsDuration: Math.round(lyricsDuration * 100) / 100,
397
- avgDuration,
398
- };
399
- }
@@ -1,39 +0,0 @@
1
- /**
2
- * util/delay — Pause pipeline execution
3
- *
4
- * Waits for specified milliseconds, then passes input value through.
5
- * Useful for rate limiting, animation timing, and API cooldowns.
6
- *
7
- * @module agi-graph/packs/util/delay
8
- */
9
-
10
- export default {
11
- type: 'util/delay',
12
- category: 'util',
13
- icon: 'hourglass_empty',
14
-
15
- driver: {
16
- description: 'Pause execution for N milliseconds, pass value through',
17
- inputs: [
18
- { name: 'value', type: 'any' },
19
- ],
20
- outputs: [
21
- { name: 'value', type: 'any' },
22
- ],
23
- params: {
24
- ms: { type: 'int', default: 1000, description: 'Delay in milliseconds' },
25
- },
26
- },
27
-
28
- lifecycle: {
29
- validate: () => true,
30
-
31
- // Never cache delays
32
- cacheKey: null,
33
-
34
- execute: async (inputs, params) => {
35
- await new Promise(resolve => setTimeout(resolve, params.ms || 1000));
36
- return { value: inputs.value };
37
- },
38
- },
39
- };
@@ -1,44 +0,0 @@
1
- /**
2
- * util/log — Console logger passthrough
3
- *
4
- * Logs input value to console and passes it through unchanged.
5
- * Useful for debugging pipelines.
6
- *
7
- * @module symbiote-node/packs/util/log */
8
-
9
- export default {
10
- type: 'util/log',
11
- category: 'util',
12
- icon: 'terminal',
13
-
14
- driver: {
15
- description: 'Log value to console and pass through',
16
- inputs: [
17
- { name: 'value', type: 'any' },
18
- ],
19
- outputs: [
20
- { name: 'value', type: 'any' },
21
- ],
22
- params: {
23
- label: { type: 'string', default: '', description: 'Log label prefix' },
24
- level: { type: 'string', default: 'info', description: 'Log level: log | info | warn | error' },
25
- },
26
- },
27
-
28
- lifecycle: {
29
- validate: () => true,
30
- cacheKey: null,
31
-
32
- execute: async (inputs, params) => {
33
- const label = params.label ? `[${params.label}]` : '[symbiote-node]'; const method = params.level || 'info';
34
-
35
- const logFn = console[method] || console.log;
36
- logFn(label, typeof inputs.value === 'object'
37
- ? JSON.stringify(inputs.value, null, 2)
38
- : inputs.value
39
- );
40
-
41
- return { value: inputs.value };
42
- },
43
- },
44
- };