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.
- package/package.json +3 -2
- package/src/analysis/analysis-cache.ctx +9 -0
- package/src/analysis/analysis-cache.js +1 -1
- package/src/analysis/complexity.ctx +6 -0
- package/src/analysis/complexity.js +1 -1
- package/src/analysis/custom-rules.ctx +14 -0
- package/src/analysis/custom-rules.js +1 -1
- package/src/analysis/db-analysis.ctx +7 -0
- package/src/analysis/db-analysis.js +1 -1
- package/src/analysis/dead-code.ctx +6 -0
- package/src/analysis/dead-code.js +1 -1
- package/src/analysis/full-analysis.ctx +9 -0
- package/src/analysis/full-analysis.js +1 -1
- package/src/analysis/jsdoc-checker.ctx +10 -0
- package/src/analysis/jsdoc-checker.js +1 -1
- package/src/analysis/jsdoc-generator.ctx +9 -0
- package/src/analysis/jsdoc-generator.js +1 -1
- package/src/analysis/large-files.ctx +6 -0
- package/src/analysis/large-files.js +1 -1
- package/src/analysis/outdated-patterns.ctx +7 -0
- package/src/analysis/outdated-patterns.js +1 -1
- package/src/analysis/similar-functions.ctx +6 -0
- package/src/analysis/similar-functions.js +1 -1
- package/src/analysis/test-annotations.ctx +11 -0
- package/src/analysis/test-annotations.js +1 -1
- package/src/analysis/type-checker.ctx +6 -0
- package/src/analysis/type-checker.js +1 -1
- package/src/analysis/undocumented.ctx +8 -0
- package/src/analysis/undocumented.js +1 -1
- package/src/cli/cli-handlers.ctx +7 -0
- package/src/cli/cli-handlers.js +1 -1
- package/src/cli/cli.ctx +6 -0
- package/src/cli/cli.js +1 -1
- package/src/compact/ai-context.ctx +6 -0
- package/src/compact/ai-context.js +1 -1
- package/src/compact/compact-migrate.ctx +8 -0
- package/src/compact/compact-migrate.js +1 -1
- package/src/compact/compact.ctx +11 -0
- package/src/compact/compact.js +1 -1
- package/src/compact/compress.ctx +7 -0
- package/src/compact/compress.js +1 -1
- package/src/compact/ctx-resolver.ctx +2 -0
- package/src/compact/ctx-resolver.js +1 -1
- package/src/compact/ctx-to-jsdoc.ctx +11 -0
- package/src/compact/ctx-to-jsdoc.js +1 -1
- package/src/compact/doc-dialect.ctx +11 -0
- package/src/compact/doc-dialect.js +2 -2
- package/src/compact/expand.ctx +14 -0
- package/src/compact/expand.js +1 -1
- package/src/compact/framework-references.ctx +7 -0
- package/src/compact/framework-references.js +1 -1
- package/src/compact/instructions.ctx +6 -0
- package/src/compact/instructions.js +1 -1
- package/src/compact/jsdoc-builder.ctx +4 -0
- package/src/compact/jsdoc-builder.js +1 -1
- package/src/compact/mode-config.ctx +8 -0
- package/src/compact/mode-config.js +1 -1
- package/src/compact/split-declarations.ctx +6 -0
- package/src/compact/split-declarations.js +1 -1
- package/src/compact/validate-pipeline.ctx +12 -0
- package/src/compact/validate-pipeline.js +1 -1
- package/src/core/event-bus.ctx +9 -0
- package/src/core/event-bus.js +1 -1
- package/src/core/file-walker.ctx +1 -0
- package/src/core/file-walker.js +1 -1
- package/src/core/filters.ctx +12 -0
- package/src/core/filters.js +1 -1
- package/src/core/graph-builder.ctx +7 -0
- package/src/core/graph-builder.js +1 -1
- package/src/core/parser.ctx +12 -0
- package/src/core/parser.js +1 -1
- package/src/core/utils.ctx +1 -0
- package/src/core/utils.js +1 -1
- package/src/core/workspace.ctx +7 -0
- package/src/core/workspace.js +1 -1
- package/src/lang/lang-go.ctx +8 -0
- package/src/lang/lang-go.js +1 -1
- package/src/lang/lang-python.ctx +5 -0
- package/src/lang/lang-python.js +1 -1
- package/src/lang/lang-sql.ctx +10 -0
- package/src/lang/lang-sql.js +1 -1
- package/src/lang/lang-typescript.ctx +6 -0
- package/src/lang/lang-typescript.js +1 -1
- package/src/lang/lang-utils.ctx +5 -0
- package/src/lang/lang-utils.js +1 -1
- package/src/mcp/mcp-server.ctx +6 -0
- package/src/mcp/mcp-server.js +1 -1
- package/src/mcp/tool-defs.ctx +2 -0
- package/src/mcp/tool-defs.js +1 -1
- package/src/mcp/tools.ctx +13 -0
- package/src/mcp/tools.js +1 -1
- package/src/network/backend-lifecycle.ctx +10 -0
- package/src/network/backend-lifecycle.js +1 -1
- package/src/network/backend.ctx +5 -0
- package/src/network/backend.js +1 -1
- package/src/network/local-gateway.ctx +9 -0
- package/src/network/local-gateway.js +1 -1
- package/src/network/mdns.ctx +6 -0
- package/src/network/mdns.js +1 -1
- package/src/network/server.ctx +2 -0
- package/src/network/server.js +2 -2
- package/src/network/web-server.ctx +17 -0
- package/src/network/web-server.js +2 -2
- package/web/follow-controller.js +94 -25
- package/web/panels/dep-graph.js +207 -21
- package/project-graph-mcp-2.3.0.tgz +0 -0
- package/vendor/symbiote-node/CHANGELOG.md +0 -31
- package/vendor/symbiote-node/LICENSE +0 -21
- package/vendor/symbiote-node/README.md +0 -206
- package/vendor/symbiote-node/canvas/AutoLayout.js +0 -725
- package/vendor/symbiote-node/canvas/Breadcrumb/Breadcrumb.css.js +0 -73
- package/vendor/symbiote-node/canvas/Breadcrumb/Breadcrumb.js +0 -93
- package/vendor/symbiote-node/canvas/Breadcrumb/Breadcrumb.tpl.js +0 -9
- package/vendor/symbiote-node/canvas/CanvasConnectionRenderer.js +0 -962
- package/vendor/symbiote-node/canvas/ConnectionRenderer.js +0 -1468
- package/vendor/symbiote-node/canvas/FlowSimulator.js +0 -323
- package/vendor/symbiote-node/canvas/ForceLayout.js +0 -189
- package/vendor/symbiote-node/canvas/ForceWorker.js +0 -1325
- package/vendor/symbiote-node/canvas/GraphTabs/GraphTabs.css.js +0 -97
- package/vendor/symbiote-node/canvas/GraphTabs/GraphTabs.js +0 -176
- package/vendor/symbiote-node/canvas/GraphTabs/GraphTabs.tpl.js +0 -12
- package/vendor/symbiote-node/canvas/LODManager.js +0 -88
- package/vendor/symbiote-node/canvas/Minimap/Minimap.css.js +0 -71
- package/vendor/symbiote-node/canvas/Minimap/Minimap.js +0 -207
- package/vendor/symbiote-node/canvas/Minimap/Minimap.tpl.js +0 -9
- package/vendor/symbiote-node/canvas/NodeCanvas/NodeCanvas.css.js +0 -261
- package/vendor/symbiote-node/canvas/NodeCanvas/NodeCanvas.js +0 -1840
- package/vendor/symbiote-node/canvas/NodeCanvas/NodeCanvas.tpl.js +0 -22
- package/vendor/symbiote-node/canvas/NodeSearch/NodeSearch.css.js +0 -97
- package/vendor/symbiote-node/canvas/NodeSearch/NodeSearch.js +0 -132
- package/vendor/symbiote-node/canvas/NodeSearch/NodeSearch.tpl.js +0 -21
- package/vendor/symbiote-node/canvas/NodeViewManager.js +0 -584
- package/vendor/symbiote-node/canvas/PinExpansion.js +0 -131
- package/vendor/symbiote-node/canvas/PseudoConnection.js +0 -80
- package/vendor/symbiote-node/canvas/SubgraphManager.js +0 -201
- package/vendor/symbiote-node/canvas/SubgraphRouter.js +0 -443
- package/vendor/symbiote-node/canvas/ViewportActions.js +0 -446
- package/vendor/symbiote-node/core/Connection.js +0 -45
- package/vendor/symbiote-node/core/Editor.js +0 -451
- package/vendor/symbiote-node/core/Frame.js +0 -31
- package/vendor/symbiote-node/core/GraphMermaid.js +0 -348
- package/vendor/symbiote-node/core/GraphText.js +0 -210
- package/vendor/symbiote-node/core/Node.js +0 -143
- package/vendor/symbiote-node/core/Portal.js +0 -104
- package/vendor/symbiote-node/core/Socket.js +0 -185
- package/vendor/symbiote-node/core/SubgraphNode.js +0 -125
- package/vendor/symbiote-node/engine/AgentUICommands.js +0 -100
- package/vendor/symbiote-node/engine/Executor.js +0 -371
- package/vendor/symbiote-node/engine/Graph.js +0 -314
- package/vendor/symbiote-node/engine/GraphServer.js +0 -353
- package/vendor/symbiote-node/engine/HandlerLoader.js +0 -145
- package/vendor/symbiote-node/engine/History.js +0 -83
- package/vendor/symbiote-node/engine/Lifecycle.js +0 -118
- package/vendor/symbiote-node/engine/Persistence.js +0 -84
- package/vendor/symbiote-node/engine/Registry.js +0 -264
- package/vendor/symbiote-node/engine/SocketTypes.js +0 -79
- package/vendor/symbiote-node/engine/cli.js +0 -404
- package/vendor/symbiote-node/engine/index.js +0 -56
- package/vendor/symbiote-node/engine/nanoid.js +0 -28
- package/vendor/symbiote-node/engine/package.json +0 -26
- package/vendor/symbiote-node/engine/packs/ai/beat-detect.handler.js +0 -215
- package/vendor/symbiote-node/engine/packs/ai/content-adapt.handler.js +0 -238
- package/vendor/symbiote-node/engine/packs/ai/face-detect.handler.js +0 -287
- package/vendor/symbiote-node/engine/packs/ai/grok-generate.handler.js +0 -565
- package/vendor/symbiote-node/engine/packs/ai/kling-lipsync.handler.js +0 -414
- package/vendor/symbiote-node/engine/packs/ai/lesson-generate.handler.js +0 -343
- package/vendor/symbiote-node/engine/packs/ai/opencode.handler.js +0 -164
- package/vendor/symbiote-node/engine/packs/ai/replicate-lipsync.handler.js +0 -341
- package/vendor/symbiote-node/engine/packs/ai/tts.handler.js +0 -241
- package/vendor/symbiote-node/engine/packs/ai/whisper.handler.js +0 -191
- package/vendor/symbiote-node/engine/packs/data/db-query.handler.js +0 -67
- package/vendor/symbiote-node/engine/packs/data/news-accumulate.handler.js +0 -281
- package/vendor/symbiote-node/engine/packs/data/personas.handler.js +0 -160
- package/vendor/symbiote-node/engine/packs/data/prompt-loader.handler.js +0 -193
- package/vendor/symbiote-node/engine/packs/data/roles.handler.js +0 -216
- package/vendor/symbiote-node/engine/packs/data/rss-feed.handler.js +0 -244
- package/vendor/symbiote-node/engine/packs/debug/inject.handler.js +0 -52
- package/vendor/symbiote-node/engine/packs/flow/agent.handler.js +0 -73
- package/vendor/symbiote-node/engine/packs/flow/if.handler.js +0 -107
- package/vendor/symbiote-node/engine/packs/flow/loop.handler.js +0 -58
- package/vendor/symbiote-node/engine/packs/flow/merge.handler.js +0 -60
- package/vendor/symbiote-node/engine/packs/flow/retry.handler.js +0 -65
- package/vendor/symbiote-node/engine/packs/flow/switch.handler.js +0 -64
- package/vendor/symbiote-node/engine/packs/flow/wait-all.handler.js +0 -39
- package/vendor/symbiote-node/engine/packs/io/http-request.handler.js +0 -82
- package/vendor/symbiote-node/engine/packs/io/read-file.handler.js +0 -60
- package/vendor/symbiote-node/engine/packs/io/write-file.handler.js +0 -63
- package/vendor/symbiote-node/engine/packs/transform/anchor-match.handler.js +0 -494
- package/vendor/symbiote-node/engine/packs/transform/effects-skeleton.handler.js +0 -417
- package/vendor/symbiote-node/engine/packs/transform/json-parse.handler.js +0 -43
- package/vendor/symbiote-node/engine/packs/transform/lipsync-select.handler.js +0 -339
- package/vendor/symbiote-node/engine/packs/transform/riopla-adapt.handler.js +0 -432
- package/vendor/symbiote-node/engine/packs/transform/set.handler.js +0 -57
- package/vendor/symbiote-node/engine/packs/transform/template-builder.handler.js +0 -134
- package/vendor/symbiote-node/engine/packs/transform/template.handler.js +0 -79
- package/vendor/symbiote-node/engine/packs/transform/timeline-build.handler.js +0 -399
- package/vendor/symbiote-node/engine/packs/util/delay.handler.js +0 -39
- package/vendor/symbiote-node/engine/packs/util/log.handler.js +0 -44
- package/vendor/symbiote-node/engine/packs/video-pack.js +0 -323
- package/vendor/symbiote-node/index.js +0 -103
- package/vendor/symbiote-node/inspector/InspectorPanel/InspectorPanel.css.js +0 -361
- package/vendor/symbiote-node/inspector/InspectorPanel/InspectorPanel.js +0 -332
- package/vendor/symbiote-node/inspector/InspectorPanel/InspectorPanel.tpl.js +0 -96
- package/vendor/symbiote-node/inspector/TemplatePreview/TemplatePreview.css.js +0 -104
- package/vendor/symbiote-node/inspector/TemplatePreview/TemplatePreview.js +0 -133
- package/vendor/symbiote-node/inspector/TemplatePreview/TemplatePreview.tpl.js +0 -33
- package/vendor/symbiote-node/interactions/ConnectFlow.js +0 -307
- package/vendor/symbiote-node/interactions/Drag.js +0 -102
- package/vendor/symbiote-node/interactions/Selector.js +0 -132
- package/vendor/symbiote-node/interactions/SnapGrid.js +0 -65
- package/vendor/symbiote-node/interactions/Zoom.js +0 -140
- package/vendor/symbiote-node/layout/ActionZone/ActionZone.css.js +0 -88
- package/vendor/symbiote-node/layout/ActionZone/ActionZone.js +0 -254
- package/vendor/symbiote-node/layout/ActionZone/ActionZone.tpl.js +0 -11
- package/vendor/symbiote-node/layout/Layout/Layout.css.js +0 -88
- package/vendor/symbiote-node/layout/Layout/Layout.js +0 -622
- package/vendor/symbiote-node/layout/Layout/Layout.tpl.js +0 -25
- package/vendor/symbiote-node/layout/LayoutNode/LayoutNode.css.js +0 -293
- package/vendor/symbiote-node/layout/LayoutNode/LayoutNode.js +0 -467
- package/vendor/symbiote-node/layout/LayoutNode/LayoutNode.tpl.js +0 -33
- package/vendor/symbiote-node/layout/LayoutPreview/LayoutPreview.css.js +0 -46
- package/vendor/symbiote-node/layout/LayoutPreview/LayoutPreview.js +0 -102
- package/vendor/symbiote-node/layout/LayoutPreview/LayoutPreview.tpl.js +0 -6
- package/vendor/symbiote-node/layout/LayoutRouter/LayoutRouter.js +0 -156
- package/vendor/symbiote-node/layout/LayoutRouter/routerSync.js +0 -250
- package/vendor/symbiote-node/layout/LayoutSidebar/LayoutSidebar.css.js +0 -379
- package/vendor/symbiote-node/layout/LayoutSidebar/LayoutSidebar.js +0 -263
- package/vendor/symbiote-node/layout/LayoutSidebar/LayoutSidebar.tpl.js +0 -20
- package/vendor/symbiote-node/layout/LayoutSidebar/SidebarSection.js +0 -183
- package/vendor/symbiote-node/layout/LayoutTree.js +0 -246
- package/vendor/symbiote-node/layout/PanelMenu/PanelMenu.css.js +0 -43
- package/vendor/symbiote-node/layout/PanelMenu/PanelMenu.js +0 -89
- package/vendor/symbiote-node/layout/PanelMenu/PanelMenu.tpl.js +0 -14
- package/vendor/symbiote-node/layout/index.js +0 -16
- package/vendor/symbiote-node/menu/ContextMenu/ContextMenu.css.js +0 -61
- package/vendor/symbiote-node/menu/ContextMenu/ContextMenu.js +0 -79
- package/vendor/symbiote-node/menu/ContextMenu/ContextMenu.tpl.js +0 -19
- package/vendor/symbiote-node/node/CtrlItem/CtrlItem.css.js +0 -41
- package/vendor/symbiote-node/node/CtrlItem/CtrlItem.js +0 -24
- package/vendor/symbiote-node/node/CtrlItem/CtrlItem.tpl.js +0 -16
- package/vendor/symbiote-node/node/GraphFrame/GraphFrame.css.js +0 -65
- package/vendor/symbiote-node/node/GraphFrame/GraphFrame.js +0 -29
- package/vendor/symbiote-node/node/GraphFrame/GraphFrame.tpl.js +0 -13
- package/vendor/symbiote-node/node/GraphNode/GraphNode.css.js +0 -683
- package/vendor/symbiote-node/node/GraphNode/GraphNode.js +0 -92
- package/vendor/symbiote-node/node/GraphNode/GraphNode.tpl.js +0 -17
- package/vendor/symbiote-node/node/NodeSocket/NodeSocket.js +0 -25
- package/vendor/symbiote-node/node/NodeSocket/NodeSocket.tpl.js +0 -7
- package/vendor/symbiote-node/node/PortItem/PortItem.css.js +0 -90
- package/vendor/symbiote-node/node/PortItem/PortItem.js +0 -87
- package/vendor/symbiote-node/node/PortItem/PortItem.tpl.js +0 -10
- package/vendor/symbiote-node/package.json +0 -59
- package/vendor/symbiote-node/palette/PaletteBrowser/PaletteBrowser.css.js +0 -143
- package/vendor/symbiote-node/palette/PaletteBrowser/PaletteBrowser.js +0 -131
- package/vendor/symbiote-node/palette/PaletteBrowser/PaletteBrowser.tpl.js +0 -16
- package/vendor/symbiote-node/plugins/History.js +0 -384
- package/vendor/symbiote-node/plugins/Readonly.js +0 -59
- package/vendor/symbiote-node/shapes/CircleShape.js +0 -80
- package/vendor/symbiote-node/shapes/CommentShape.js +0 -35
- package/vendor/symbiote-node/shapes/DiamondShape.js +0 -115
- package/vendor/symbiote-node/shapes/NodeShape.js +0 -80
- package/vendor/symbiote-node/shapes/PillShape.js +0 -91
- package/vendor/symbiote-node/shapes/RectShape.js +0 -72
- package/vendor/symbiote-node/shapes/SVGShape.js +0 -494
- package/vendor/symbiote-node/shapes/index.js +0 -53
- package/vendor/symbiote-node/themes/Palette.js +0 -32
- package/vendor/symbiote-node/themes/Skin.js +0 -113
- package/vendor/symbiote-node/themes/Theme.js +0 -84
- package/vendor/symbiote-node/themes/carbon.js +0 -137
- package/vendor/symbiote-node/themes/dark.js +0 -137
- package/vendor/symbiote-node/themes/ebook.js +0 -138
- package/vendor/symbiote-node/themes/grey.js +0 -137
- package/vendor/symbiote-node/themes/light.js +0 -137
- package/vendor/symbiote-node/themes/neon.js +0 -138
- package/vendor/symbiote-node/themes/pcb.js +0 -273
- package/vendor/symbiote-node/themes/synthwave.js +0 -137
- package/vendor/symbiote-node/toolbar/QuickToolbar/QuickToolbar.css.js +0 -86
- package/vendor/symbiote-node/toolbar/QuickToolbar/QuickToolbar.js +0 -128
- package/vendor/symbiote-node/toolbar/QuickToolbar/QuickToolbar.tpl.js +0 -29
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* io/write-file — Write content to file
|
|
3
|
-
*
|
|
4
|
-
* Writes string or JSON content to disk. Creates directories if needed.
|
|
5
|
-
*
|
|
6
|
-
* @module agi-graph/packs/io/write-file
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { promises as fs } from 'fs';
|
|
10
|
-
import path from 'path';
|
|
11
|
-
|
|
12
|
-
export default {
|
|
13
|
-
type: 'io/write-file',
|
|
14
|
-
category: 'io',
|
|
15
|
-
icon: 'save',
|
|
16
|
-
|
|
17
|
-
driver: {
|
|
18
|
-
description: 'Write content to file (auto-creates directories)',
|
|
19
|
-
inputs: [
|
|
20
|
-
{ name: 'path', type: 'string' },
|
|
21
|
-
{ name: 'content', type: 'any' },
|
|
22
|
-
],
|
|
23
|
-
outputs: [
|
|
24
|
-
{ name: 'success', type: 'boolean' },
|
|
25
|
-
{ name: 'path', type: 'string' },
|
|
26
|
-
{ name: 'error', type: 'string' },
|
|
27
|
-
],
|
|
28
|
-
params: {
|
|
29
|
-
encoding: { type: 'string', default: 'utf8', description: 'File encoding' },
|
|
30
|
-
pretty: { type: 'boolean', default: true, description: 'Pretty-print JSON' },
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
|
|
34
|
-
lifecycle: {
|
|
35
|
-
validate: (inputs) => {
|
|
36
|
-
if (!inputs.path) return false;
|
|
37
|
-
if (inputs.content === undefined || inputs.content === null) return false;
|
|
38
|
-
return true;
|
|
39
|
-
},
|
|
40
|
-
|
|
41
|
-
cacheKey: null,
|
|
42
|
-
|
|
43
|
-
execute: async (inputs, params) => {
|
|
44
|
-
try {
|
|
45
|
-
await fs.mkdir(path.dirname(inputs.path), { recursive: true });
|
|
46
|
-
|
|
47
|
-
let data;
|
|
48
|
-
if (typeof inputs.content === 'object') {
|
|
49
|
-
data = JSON.stringify(inputs.content, null, params.pretty ? 2 : 0);
|
|
50
|
-
} else {
|
|
51
|
-
data = String(inputs.content);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
await fs.writeFile(inputs.path, data, params.encoding || 'utf8');
|
|
55
|
-
|
|
56
|
-
return { success: true, path: inputs.path, error: null };
|
|
57
|
-
|
|
58
|
-
} catch (err) {
|
|
59
|
-
return { success: false, path: inputs.path, error: err.message };
|
|
60
|
-
}
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
|
-
};
|
|
@@ -1,494 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* transform/anchor-match — Lyrics ↔ Audio alignment with word-level karaoke timing
|
|
3
|
-
*
|
|
4
|
-
* Aligns reference lyrics with Whisper transcription timestamps.
|
|
5
|
-
* Supports fuzzy matching (edit distance) and AI correction (OpenRouter).
|
|
6
|
-
* Produces word-level timing data for karaoke rendering.
|
|
7
|
-
*
|
|
8
|
-
* Ported from Mr-Computer/modules/ai-music-video/src/services/anchor-matcher.js
|
|
9
|
-
*
|
|
10
|
-
* @module agi-graph/packs/transform/anchor-match
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
export default {
|
|
14
|
-
type: 'transform/anchor-match',
|
|
15
|
-
category: 'transform',
|
|
16
|
-
icon: 'lyrics',
|
|
17
|
-
|
|
18
|
-
driver: {
|
|
19
|
-
description: 'Align lyrics with Whisper timestamps — word-level karaoke timing',
|
|
20
|
-
inputs: [
|
|
21
|
-
{ name: 'lyrics', type: 'string' },
|
|
22
|
-
{ name: 'whisperWords', type: 'any' },
|
|
23
|
-
],
|
|
24
|
-
outputs: [
|
|
25
|
-
{ name: 'phrases', type: 'any' },
|
|
26
|
-
{ name: 'segments', type: 'any' },
|
|
27
|
-
{ name: 'stats', type: 'any' },
|
|
28
|
-
{ name: 'error', type: 'string' },
|
|
29
|
-
],
|
|
30
|
-
params: {
|
|
31
|
-
operation: { type: 'string', default: 'align', description: 'Operation: align | align-fuzzy | parse-lyrics' },
|
|
32
|
-
apiKey: { type: 'string', default: '', description: 'OpenRouter API key for AI correction' },
|
|
33
|
-
model: { type: 'string', default: 'deepseek/deepseek-v3.2', description: 'AI model for correction' },
|
|
34
|
-
maxPhraseWords: { type: 'int', default: 8, description: 'Max words per phrase for subtitle readability' },
|
|
35
|
-
},
|
|
36
|
-
},
|
|
37
|
-
|
|
38
|
-
lifecycle: {
|
|
39
|
-
validate: (inputs) => {
|
|
40
|
-
if (!inputs.lyrics && !inputs.whisperWords) return false;
|
|
41
|
-
return true;
|
|
42
|
-
},
|
|
43
|
-
|
|
44
|
-
cacheKey: (inputs, params) => {
|
|
45
|
-
const lLen = (inputs.lyrics || '').length;
|
|
46
|
-
const wLen = (inputs.whisperWords || []).length;
|
|
47
|
-
return `anchor:${params.operation}:${lLen}:${wLen}`;
|
|
48
|
-
},
|
|
49
|
-
|
|
50
|
-
execute: async (inputs, params) => {
|
|
51
|
-
try {
|
|
52
|
-
const op = params.operation;
|
|
53
|
-
|
|
54
|
-
if (op === 'parse-lyrics') {
|
|
55
|
-
const segments = parseLyrics(inputs.lyrics);
|
|
56
|
-
return { phrases: null, segments, stats: { sections: segments.length }, error: null };
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (!inputs.whisperWords || !Array.isArray(inputs.whisperWords) || inputs.whisperWords.length === 0) {
|
|
60
|
-
return { phrases: null, segments: null, stats: null, error: 'whisperWords array is required for alignment' };
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const segments = parseLyrics(inputs.lyrics);
|
|
64
|
-
|
|
65
|
-
if (op === 'align-fuzzy') {
|
|
66
|
-
const phrases = alignWithFuzzy(segments, inputs.whisperWords);
|
|
67
|
-
return {
|
|
68
|
-
phrases,
|
|
69
|
-
segments,
|
|
70
|
-
stats: { mode: 'fuzzy', phraseCount: phrases.length, sectionCount: segments.length },
|
|
71
|
-
error: null,
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Default: hybrid align (fuzzy + optional AI correction)
|
|
76
|
-
const phrases = await alignHybrid(inputs.lyrics, inputs.whisperWords, segments, params);
|
|
77
|
-
return {
|
|
78
|
-
phrases,
|
|
79
|
-
segments,
|
|
80
|
-
stats: {
|
|
81
|
-
mode: params.apiKey ? 'hybrid-ai' : 'fuzzy-corrected',
|
|
82
|
-
phraseCount: phrases.length,
|
|
83
|
-
sectionCount: segments.length,
|
|
84
|
-
totalWords: inputs.whisperWords.length,
|
|
85
|
-
},
|
|
86
|
-
error: null,
|
|
87
|
-
};
|
|
88
|
-
} catch (err) {
|
|
89
|
-
return { phrases: null, segments: null, stats: null, error: err.message };
|
|
90
|
-
}
|
|
91
|
-
},
|
|
92
|
-
},
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
// --- Core alignment functions (ported from anchor-matcher.js) ---
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Parse lyrics into structured segments
|
|
99
|
-
* @param {string} lyricsText - Raw lyrics with [Section] markers
|
|
100
|
-
* @returns {Array<{section: string, lines: string[]}>}
|
|
101
|
-
*/
|
|
102
|
-
function parseLyrics(lyricsText) {
|
|
103
|
-
if (!lyricsText) return [];
|
|
104
|
-
const lines = lyricsText.split('\n');
|
|
105
|
-
const segments = [];
|
|
106
|
-
let currentSection = null;
|
|
107
|
-
let currentLines = [];
|
|
108
|
-
|
|
109
|
-
for (const line of lines) {
|
|
110
|
-
const trimmed = line.trim();
|
|
111
|
-
if (!trimmed) continue;
|
|
112
|
-
|
|
113
|
-
const sectionMatch = trimmed.match(/^\[(.+)\]$/);
|
|
114
|
-
if (sectionMatch) {
|
|
115
|
-
if (currentSection) {
|
|
116
|
-
segments.push({ section: currentSection, lines: currentLines });
|
|
117
|
-
}
|
|
118
|
-
currentSection = sectionMatch[1];
|
|
119
|
-
currentLines = [];
|
|
120
|
-
} else {
|
|
121
|
-
currentLines.push(trimmed);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (currentSection && currentLines.length > 0) {
|
|
126
|
-
segments.push({ section: currentSection, lines: currentLines });
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return segments;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Extract singable text (remove [markers], keep (parenthesis) content)
|
|
134
|
-
* @param {string} line
|
|
135
|
-
* @returns {string}
|
|
136
|
-
*/
|
|
137
|
-
function extractSingableText(line) {
|
|
138
|
-
return line
|
|
139
|
-
.replace(/\[.*?\]/g, '')
|
|
140
|
-
.replace(/\*\*/g, '')
|
|
141
|
-
.replace(/\(([^)]+)\)/g, '$1')
|
|
142
|
-
.trim();
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Normalize word for comparison
|
|
147
|
-
* @param {string} word
|
|
148
|
-
* @returns {string}
|
|
149
|
-
*/
|
|
150
|
-
function normalizeWord(word) {
|
|
151
|
-
return word.toLowerCase()
|
|
152
|
-
.replace(/[.,!?¿¡'"()]/g, '')
|
|
153
|
-
.replace(/[áà]/g, 'a')
|
|
154
|
-
.replace(/[éè]/g, 'e')
|
|
155
|
-
.replace(/[íì]/g, 'i')
|
|
156
|
-
.replace(/[óò]/g, 'o')
|
|
157
|
-
.replace(/[úù]/g, 'u')
|
|
158
|
-
.replace(/ñ/g, 'n');
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Levenshtein edit distance
|
|
163
|
-
* @param {string} s1
|
|
164
|
-
* @param {string} s2
|
|
165
|
-
* @returns {number}
|
|
166
|
-
*/
|
|
167
|
-
function editDistance(s1, s2) {
|
|
168
|
-
const m = s1.length;
|
|
169
|
-
const n = s2.length;
|
|
170
|
-
const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));
|
|
171
|
-
|
|
172
|
-
for (let i = 0; i <= m; i++) dp[i][0] = i;
|
|
173
|
-
for (let j = 0; j <= n; j++) dp[0][j] = j;
|
|
174
|
-
|
|
175
|
-
for (let i = 1; i <= m; i++) {
|
|
176
|
-
for (let j = 1; j <= n; j++) {
|
|
177
|
-
if (s1[i - 1] === s2[j - 1]) {
|
|
178
|
-
dp[i][j] = dp[i - 1][j - 1];
|
|
179
|
-
} else {
|
|
180
|
-
dp[i][j] = 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
return dp[m][n];
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Check word similarity (fuzzy match)
|
|
189
|
-
* @param {string} word1
|
|
190
|
-
* @param {string} word2
|
|
191
|
-
* @returns {boolean}
|
|
192
|
-
*/
|
|
193
|
-
function wordsSimilar(word1, word2) {
|
|
194
|
-
const w1 = word1.toLowerCase().replace(/[^a-záéíóúñü]/g, '');
|
|
195
|
-
const w2 = word2.toLowerCase().replace(/[^a-záéíóúñü]/g, '');
|
|
196
|
-
if (w1 === w2) return true;
|
|
197
|
-
if (w1.length < 3 || w2.length < 3) return w1 === w2;
|
|
198
|
-
if (w1.includes(w2) || w2.includes(w1)) return true;
|
|
199
|
-
const maxErrors = Math.floor(Math.max(w1.length, w2.length) / 4);
|
|
200
|
-
return editDistance(w1, w2) <= maxErrors;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Infer section from timestamp
|
|
205
|
-
* @param {number} timestamp
|
|
206
|
-
* @param {Array} segments
|
|
207
|
-
* @returns {string}
|
|
208
|
-
*/
|
|
209
|
-
function inferSection(timestamp, segments) {
|
|
210
|
-
if (!segments || segments.length === 0) return 'Unknown';
|
|
211
|
-
|
|
212
|
-
for (let i = segments.length - 1; i >= 0; i--) {
|
|
213
|
-
if (segments[i].startTime !== undefined && timestamp >= segments[i].startTime) {
|
|
214
|
-
return segments[i].section;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
const estimatedSectionDuration = 15;
|
|
219
|
-
const sectionIndex = Math.floor(timestamp / estimatedSectionDuration);
|
|
220
|
-
if (sectionIndex >= 0 && sectionIndex < segments.length) {
|
|
221
|
-
return segments[sectionIndex].section;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
return segments[segments.length - 1].section;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Calculate section timing from word positions
|
|
229
|
-
* @param {Array} segments
|
|
230
|
-
* @param {Array} correctedWords
|
|
231
|
-
* @returns {Array}
|
|
232
|
-
*/
|
|
233
|
-
function calculateSectionTiming(segments, correctedWords) {
|
|
234
|
-
if (!segments || segments.length === 0) return segments;
|
|
235
|
-
|
|
236
|
-
let wordIndex = 0;
|
|
237
|
-
return segments.map(seg => {
|
|
238
|
-
let startTime = null;
|
|
239
|
-
|
|
240
|
-
for (const line of seg.lines) {
|
|
241
|
-
const singable = extractSingableText(line);
|
|
242
|
-
if (!singable) continue;
|
|
243
|
-
|
|
244
|
-
const firstWord = normalizeWord(singable.split(/\s+/)[0]);
|
|
245
|
-
|
|
246
|
-
for (let i = wordIndex; i < correctedWords.length && i < wordIndex + 50; i++) {
|
|
247
|
-
const cw = correctedWords[i];
|
|
248
|
-
if (normalizeWord(cw.word) === firstWord ||
|
|
249
|
-
editDistance(normalizeWord(cw.word), firstWord) <= 1) {
|
|
250
|
-
startTime = cw.start;
|
|
251
|
-
wordIndex = i;
|
|
252
|
-
break;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
if (startTime !== null) break;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
return { ...seg, startTime };
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
/**
|
|
264
|
-
* Hybrid align: vocabulary correction + phrase building
|
|
265
|
-
* @param {string} referenceLyrics
|
|
266
|
-
* @param {Array} whisperWords
|
|
267
|
-
* @param {Array} segments
|
|
268
|
-
* @param {Object} params
|
|
269
|
-
* @returns {Promise<Array>}
|
|
270
|
-
*/
|
|
271
|
-
async function alignHybrid(referenceLyrics, whisperWords, segments, params) {
|
|
272
|
-
// Build lyrics vocabulary
|
|
273
|
-
const lyricsVocabulary = new Map();
|
|
274
|
-
for (const seg of segments) {
|
|
275
|
-
for (const line of seg.lines) {
|
|
276
|
-
const singable = extractSingableText(line);
|
|
277
|
-
if (!singable) continue;
|
|
278
|
-
const words = singable.match(/[\w\u00C0-\u024F]+[.,!?']*|[.,!?]+/gi) || [];
|
|
279
|
-
words.forEach(w => {
|
|
280
|
-
if (w.trim()) {
|
|
281
|
-
const norm = normalizeWord(w);
|
|
282
|
-
if (!lyricsVocabulary.has(norm) || /[.,!?']$/.test(w)) {
|
|
283
|
-
lyricsVocabulary.set(norm, {
|
|
284
|
-
original: w,
|
|
285
|
-
hasPunctuation: /[.,!?']$/.test(w),
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
});
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// Correct Whisper words using vocabulary
|
|
294
|
-
const correctedWords = whisperWords.map(w => {
|
|
295
|
-
const whisperNorm = normalizeWord(w.word);
|
|
296
|
-
|
|
297
|
-
// Exact match
|
|
298
|
-
if (lyricsVocabulary.has(whisperNorm)) {
|
|
299
|
-
const match = lyricsVocabulary.get(whisperNorm);
|
|
300
|
-
return {
|
|
301
|
-
word: match.original,
|
|
302
|
-
start: w.start,
|
|
303
|
-
end: w.end,
|
|
304
|
-
original: w.word,
|
|
305
|
-
isConfident: true,
|
|
306
|
-
endsPhrase: match.hasPunctuation,
|
|
307
|
-
};
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// Fuzzy search vocabulary
|
|
311
|
-
let bestMatch = null;
|
|
312
|
-
let bestDistance = Infinity;
|
|
313
|
-
|
|
314
|
-
for (const [norm, lyric] of lyricsVocabulary) {
|
|
315
|
-
const dist = editDistance(whisperNorm, norm);
|
|
316
|
-
if (dist < bestDistance) {
|
|
317
|
-
bestDistance = dist;
|
|
318
|
-
bestMatch = lyric;
|
|
319
|
-
}
|
|
320
|
-
if (dist === 0) break;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
const isConfident = bestDistance <= 1 ||
|
|
324
|
-
(bestDistance === 2 && whisperNorm.length > 5);
|
|
325
|
-
|
|
326
|
-
const correctedWord = isConfident && bestMatch ? bestMatch.original : w.word;
|
|
327
|
-
|
|
328
|
-
return {
|
|
329
|
-
word: correctedWord,
|
|
330
|
-
start: w.start,
|
|
331
|
-
end: w.end,
|
|
332
|
-
original: w.word,
|
|
333
|
-
isConfident,
|
|
334
|
-
endsPhrase: bestMatch?.hasPunctuation || false,
|
|
335
|
-
};
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
// Calculate section timing
|
|
339
|
-
const segmentsWithTiming = calculateSectionTiming(segments, correctedWords);
|
|
340
|
-
|
|
341
|
-
// Build phrases with word-level timing
|
|
342
|
-
return buildPhrasesFromCorrectedWords(correctedWords, segmentsWithTiming, params.maxPhraseWords);
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
/**
|
|
346
|
-
* Build phrases from corrected words with punctuation-based splitting
|
|
347
|
-
* @param {Array} correctedWords
|
|
348
|
-
* @param {Array} segments
|
|
349
|
-
* @param {number} maxWords
|
|
350
|
-
* @returns {Array}
|
|
351
|
-
*/
|
|
352
|
-
function buildPhrasesFromCorrectedWords(correctedWords, segments, maxWords = 8) {
|
|
353
|
-
const alignments = [];
|
|
354
|
-
let currentPhrase = [];
|
|
355
|
-
let phraseStart = null;
|
|
356
|
-
let lastEnd = 0;
|
|
357
|
-
|
|
358
|
-
for (let i = 0; i < correctedWords.length; i++) {
|
|
359
|
-
const w = correctedWords[i];
|
|
360
|
-
|
|
361
|
-
if (phraseStart === null) {
|
|
362
|
-
phraseStart = w.start;
|
|
363
|
-
}
|
|
364
|
-
currentPhrase.push(w.word);
|
|
365
|
-
lastEnd = w.end;
|
|
366
|
-
|
|
367
|
-
const shouldEndPhrase = w.endsPhrase || currentPhrase.length >= maxWords;
|
|
368
|
-
|
|
369
|
-
if (shouldEndPhrase) {
|
|
370
|
-
alignments.push({
|
|
371
|
-
line: currentPhrase.join(' '),
|
|
372
|
-
start: phraseStart,
|
|
373
|
-
end: w.end,
|
|
374
|
-
section: inferSection(phraseStart, segments),
|
|
375
|
-
confidence: 0.9,
|
|
376
|
-
words: correctedWords.slice(i - currentPhrase.length + 1, i + 1).map(cw => ({
|
|
377
|
-
word: cw.word,
|
|
378
|
-
start: cw.start,
|
|
379
|
-
end: cw.end,
|
|
380
|
-
})),
|
|
381
|
-
});
|
|
382
|
-
currentPhrase = [];
|
|
383
|
-
phraseStart = null;
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
// Last phrase
|
|
388
|
-
if (currentPhrase.length > 0 && phraseStart !== null) {
|
|
389
|
-
const startIdx = correctedWords.length - currentPhrase.length;
|
|
390
|
-
alignments.push({
|
|
391
|
-
line: currentPhrase.join(' '),
|
|
392
|
-
start: phraseStart,
|
|
393
|
-
end: lastEnd,
|
|
394
|
-
section: 'Outro',
|
|
395
|
-
confidence: 0.9,
|
|
396
|
-
words: correctedWords.slice(startIdx).map(cw => ({
|
|
397
|
-
word: cw.word,
|
|
398
|
-
start: cw.start,
|
|
399
|
-
end: cw.end,
|
|
400
|
-
})),
|
|
401
|
-
});
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
return alignments;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
/**
|
|
408
|
-
* Fuzzy alignment without AI — line-level matching
|
|
409
|
-
* @param {Array} segments - Parsed lyrics segments
|
|
410
|
-
* @param {Array} whisperWords - [{word, start, end}]
|
|
411
|
-
* @returns {Array}
|
|
412
|
-
*/
|
|
413
|
-
function alignWithFuzzy(segments, whisperWords) {
|
|
414
|
-
const results = [];
|
|
415
|
-
let whisperIndex = 0;
|
|
416
|
-
|
|
417
|
-
for (const seg of segments) {
|
|
418
|
-
for (const line of seg.lines) {
|
|
419
|
-
const singable = extractSingableText(line);
|
|
420
|
-
if (!singable) continue;
|
|
421
|
-
|
|
422
|
-
const isExclamation = line.trim().startsWith('(');
|
|
423
|
-
const words = singable.split(/\s+/).filter(w => w.length > 0);
|
|
424
|
-
if (words.length === 0) continue;
|
|
425
|
-
|
|
426
|
-
const firstWord = normalizeWord(words[0]);
|
|
427
|
-
|
|
428
|
-
let bestMatch = null;
|
|
429
|
-
let bestScore = Infinity;
|
|
430
|
-
|
|
431
|
-
for (let i = whisperIndex; i < whisperWords.length; i++) {
|
|
432
|
-
const whisperWord = normalizeWord(whisperWords[i].word);
|
|
433
|
-
|
|
434
|
-
const isExactMatch = firstWord === whisperWord;
|
|
435
|
-
const isPartialMatch = firstWord.startsWith(whisperWord) || whisperWord.startsWith(firstWord);
|
|
436
|
-
|
|
437
|
-
if (isExactMatch) {
|
|
438
|
-
bestMatch = { index: i, word: whisperWords[i] };
|
|
439
|
-
bestScore = 0;
|
|
440
|
-
break;
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
if (isPartialMatch && firstWord.length >= 2) {
|
|
444
|
-
if (!bestMatch || i < bestMatch.index) {
|
|
445
|
-
bestMatch = { index: i, word: whisperWords[i] };
|
|
446
|
-
bestScore = 1;
|
|
447
|
-
}
|
|
448
|
-
continue;
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
const distance = editDistance(firstWord, whisperWord);
|
|
452
|
-
const threshold = Math.max(1, Math.floor(firstWord.length / 3));
|
|
453
|
-
if (distance <= threshold && distance < bestScore) {
|
|
454
|
-
bestScore = distance;
|
|
455
|
-
bestMatch = { index: i, word: whisperWords[i] };
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
if (i - whisperIndex > 50 && bestMatch) break;
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
if (bestMatch) {
|
|
462
|
-
const startTime = bestMatch.word.start;
|
|
463
|
-
let endIndex = bestMatch.index + Math.min(words.length - 1, 5);
|
|
464
|
-
if (endIndex >= whisperWords.length) endIndex = whisperWords.length - 1;
|
|
465
|
-
const endTime = whisperWords[endIndex].end;
|
|
466
|
-
|
|
467
|
-
results.push({
|
|
468
|
-
line: singable,
|
|
469
|
-
start: startTime,
|
|
470
|
-
end: endTime,
|
|
471
|
-
section: seg.section,
|
|
472
|
-
confidence: bestScore === 0 ? 0.95 : (bestScore === 1 ? 0.85 : 0.7),
|
|
473
|
-
isExclamation,
|
|
474
|
-
});
|
|
475
|
-
|
|
476
|
-
whisperIndex = bestMatch.index + 1;
|
|
477
|
-
} else {
|
|
478
|
-
const lastResult = results[results.length - 1];
|
|
479
|
-
if (lastResult) {
|
|
480
|
-
results.push({
|
|
481
|
-
line: singable,
|
|
482
|
-
start: lastResult.end + 0.5,
|
|
483
|
-
end: lastResult.end + 2.0,
|
|
484
|
-
section: seg.section,
|
|
485
|
-
confidence: 0.3,
|
|
486
|
-
isExclamation,
|
|
487
|
-
});
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
return results;
|
|
494
|
-
}
|