kirograph 0.12.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/README.md +1171 -0
- package/dist/architecture/index.d.ts +11 -0
- package/dist/architecture/index.d.ts.map +1 -0
- package/dist/architecture/index.js +207 -0
- package/dist/architecture/index.js.map +7 -0
- package/dist/architecture/layers/csharp.d.ts +6 -0
- package/dist/architecture/layers/csharp.d.ts.map +1 -0
- package/dist/architecture/layers/csharp.js +100 -0
- package/dist/architecture/layers/csharp.js.map +7 -0
- package/dist/architecture/layers/elixir.js +116 -0
- package/dist/architecture/layers/elixir.js.map +7 -0
- package/dist/architecture/layers/go.d.ts +7 -0
- package/dist/architecture/layers/go.d.ts.map +1 -0
- package/dist/architecture/layers/go.js +117 -0
- package/dist/architecture/layers/go.js.map +7 -0
- package/dist/architecture/layers/index.d.ts +30 -0
- package/dist/architecture/layers/index.d.ts.map +1 -0
- package/dist/architecture/layers/index.js +100 -0
- package/dist/architecture/layers/index.js.map +7 -0
- package/dist/architecture/layers/java.d.ts +7 -0
- package/dist/architecture/layers/java.d.ts.map +1 -0
- package/dist/architecture/layers/java.js +119 -0
- package/dist/architecture/layers/java.js.map +7 -0
- package/dist/architecture/layers/python.d.ts +7 -0
- package/dist/architecture/layers/python.d.ts.map +1 -0
- package/dist/architecture/layers/python.js +111 -0
- package/dist/architecture/layers/python.js.map +7 -0
- package/dist/architecture/layers/ruby.d.ts +6 -0
- package/dist/architecture/layers/ruby.d.ts.map +1 -0
- package/dist/architecture/layers/ruby.js +95 -0
- package/dist/architecture/layers/ruby.js.map +7 -0
- package/dist/architecture/layers/rust.d.ts +6 -0
- package/dist/architecture/layers/rust.d.ts.map +1 -0
- package/dist/architecture/layers/rust.js +98 -0
- package/dist/architecture/layers/rust.js.map +7 -0
- package/dist/architecture/layers/types.d.ts +2 -0
- package/dist/architecture/layers/types.d.ts.map +1 -0
- package/dist/architecture/layers/types.js +17 -0
- package/dist/architecture/layers/types.js.map +7 -0
- package/dist/architecture/layers/typescript.d.ts +9 -0
- package/dist/architecture/layers/typescript.d.ts.map +1 -0
- package/dist/architecture/layers/typescript.js +143 -0
- package/dist/architecture/layers/typescript.js.map +7 -0
- package/dist/architecture/manifest/cargo.d.ts +3 -0
- package/dist/architecture/manifest/cargo.d.ts.map +1 -0
- package/dist/architecture/manifest/cargo.js +94 -0
- package/dist/architecture/manifest/cargo.js.map +7 -0
- package/dist/architecture/manifest/csproj.d.ts +3 -0
- package/dist/architecture/manifest/csproj.d.ts.map +1 -0
- package/dist/architecture/manifest/csproj.js +75 -0
- package/dist/architecture/manifest/csproj.js.map +7 -0
- package/dist/architecture/manifest/go.d.ts +3 -0
- package/dist/architecture/manifest/go.d.ts.map +1 -0
- package/dist/architecture/manifest/go.js +85 -0
- package/dist/architecture/manifest/go.js.map +7 -0
- package/dist/architecture/manifest/gradle.d.ts +3 -0
- package/dist/architecture/manifest/gradle.d.ts.map +1 -0
- package/dist/architecture/manifest/gradle.js +80 -0
- package/dist/architecture/manifest/gradle.js.map +7 -0
- package/dist/architecture/manifest/index.d.ts +12 -0
- package/dist/architecture/manifest/index.d.ts.map +1 -0
- package/dist/architecture/manifest/index.js +130 -0
- package/dist/architecture/manifest/index.js.map +7 -0
- package/dist/architecture/manifest/maven.d.ts +3 -0
- package/dist/architecture/manifest/maven.d.ts.map +1 -0
- package/dist/architecture/manifest/maven.js +76 -0
- package/dist/architecture/manifest/maven.js.map +7 -0
- package/dist/architecture/manifest/npm.d.ts +3 -0
- package/dist/architecture/manifest/npm.d.ts.map +1 -0
- package/dist/architecture/manifest/npm.js +103 -0
- package/dist/architecture/manifest/npm.js.map +7 -0
- package/dist/architecture/manifest/python.d.ts +3 -0
- package/dist/architecture/manifest/python.d.ts.map +1 -0
- package/dist/architecture/manifest/python.js +105 -0
- package/dist/architecture/manifest/python.js.map +7 -0
- package/dist/architecture/manifest/types.d.ts +2 -0
- package/dist/architecture/manifest/types.d.ts.map +1 -0
- package/dist/architecture/manifest/types.js +17 -0
- package/dist/architecture/manifest/types.js.map +7 -0
- package/dist/architecture/types.d.ts +91 -0
- package/dist/architecture/types.d.ts.map +1 -0
- package/dist/architecture/types.js +17 -0
- package/dist/architecture/types.js.map +7 -0
- package/dist/assets/logo.png +0 -0
- package/dist/banner.d.ts +6 -0
- package/dist/banner.d.ts.map +1 -0
- package/dist/banner.js +67 -0
- package/dist/banner.js.map +1 -0
- package/dist/bin/banner.d.ts +6 -0
- package/dist/bin/banner.d.ts.map +1 -0
- package/dist/bin/banner.js +88 -0
- package/dist/bin/banner.js.map +7 -0
- package/dist/bin/commands/affected.d.ts +3 -0
- package/dist/bin/commands/affected.d.ts.map +1 -0
- package/dist/bin/commands/affected.js +78 -0
- package/dist/bin/commands/affected.js.map +7 -0
- package/dist/bin/commands/architecture.d.ts +3 -0
- package/dist/bin/commands/architecture.d.ts.map +1 -0
- package/dist/bin/commands/architecture.js +125 -0
- package/dist/bin/commands/architecture.js.map +7 -0
- package/dist/bin/commands/caveman.js +136 -0
- package/dist/bin/commands/caveman.js.map +7 -0
- package/dist/bin/commands/context.d.ts +3 -0
- package/dist/bin/commands/context.d.ts.map +1 -0
- package/dist/bin/commands/context.js +81 -0
- package/dist/bin/commands/context.js.map +7 -0
- package/dist/bin/commands/coupling.d.ts +3 -0
- package/dist/bin/commands/coupling.d.ts.map +1 -0
- package/dist/bin/commands/coupling.js +164 -0
- package/dist/bin/commands/coupling.js.map +7 -0
- package/dist/bin/commands/dashboard.d.ts +3 -0
- package/dist/bin/commands/dashboard.d.ts.map +1 -0
- package/dist/bin/commands/dashboard.js +209 -0
- package/dist/bin/commands/dashboard.js.map +7 -0
- package/dist/bin/commands/dead-code.js +77 -0
- package/dist/bin/commands/dead-code.js.map +7 -0
- package/dist/bin/commands/export.js +2620 -0
- package/dist/bin/commands/export.js.map +7 -0
- package/dist/bin/commands/files.d.ts +3 -0
- package/dist/bin/commands/files.d.ts.map +1 -0
- package/dist/bin/commands/files.js +104 -0
- package/dist/bin/commands/files.js.map +7 -0
- package/dist/bin/commands/help.d.ts +4 -0
- package/dist/bin/commands/help.d.ts.map +1 -0
- package/dist/bin/commands/help.js +212 -0
- package/dist/bin/commands/help.js.map +7 -0
- package/dist/bin/commands/hotspots.js +77 -0
- package/dist/bin/commands/hotspots.js.map +7 -0
- package/dist/bin/commands/index.d.ts +3 -0
- package/dist/bin/commands/index.d.ts.map +1 -0
- package/dist/bin/commands/index.js +58 -0
- package/dist/bin/commands/index.js.map +7 -0
- package/dist/bin/commands/init.d.ts +3 -0
- package/dist/bin/commands/init.d.ts.map +1 -0
- package/dist/bin/commands/init.js +68 -0
- package/dist/bin/commands/init.js.map +7 -0
- package/dist/bin/commands/install.d.ts +3 -0
- package/dist/bin/commands/install.d.ts.map +1 -0
- package/dist/bin/commands/install.js +34 -0
- package/dist/bin/commands/install.js.map +7 -0
- package/dist/bin/commands/mark-dirty.d.ts +3 -0
- package/dist/bin/commands/mark-dirty.d.ts.map +1 -0
- package/dist/bin/commands/mark-dirty.js +51 -0
- package/dist/bin/commands/mark-dirty.js.map +7 -0
- package/dist/bin/commands/package.d.ts +3 -0
- package/dist/bin/commands/package.d.ts.map +1 -0
- package/dist/bin/commands/package.js +139 -0
- package/dist/bin/commands/package.js.map +7 -0
- package/dist/bin/commands/path.js +93 -0
- package/dist/bin/commands/path.js.map +7 -0
- package/dist/bin/commands/qdrant.d.ts +3 -0
- package/dist/bin/commands/qdrant.d.ts.map +1 -0
- package/dist/bin/commands/qdrant.js +159 -0
- package/dist/bin/commands/qdrant.js.map +1 -0
- package/dist/bin/commands/query.d.ts +3 -0
- package/dist/bin/commands/query.d.ts.map +1 -0
- package/dist/bin/commands/query.js +47 -0
- package/dist/bin/commands/query.js.map +7 -0
- package/dist/bin/commands/serve.d.ts +3 -0
- package/dist/bin/commands/serve.d.ts.map +1 -0
- package/dist/bin/commands/serve.js +59 -0
- package/dist/bin/commands/serve.js.map +7 -0
- package/dist/bin/commands/snapshot.js +122 -0
- package/dist/bin/commands/snapshot.js.map +7 -0
- package/dist/bin/commands/status.d.ts +3 -0
- package/dist/bin/commands/status.d.ts.map +1 -0
- package/dist/bin/commands/status.js +107 -0
- package/dist/bin/commands/status.js.map +7 -0
- package/dist/bin/commands/stop.d.ts +3 -0
- package/dist/bin/commands/stop.d.ts.map +1 -0
- package/dist/bin/commands/stop.js +81 -0
- package/dist/bin/commands/stop.js.map +1 -0
- package/dist/bin/commands/surprising.js +79 -0
- package/dist/bin/commands/surprising.js.map +7 -0
- package/dist/bin/commands/sync-if-dirty.d.ts +3 -0
- package/dist/bin/commands/sync-if-dirty.d.ts.map +1 -0
- package/dist/bin/commands/sync-if-dirty.js +67 -0
- package/dist/bin/commands/sync-if-dirty.js.map +7 -0
- package/dist/bin/commands/sync.d.ts +3 -0
- package/dist/bin/commands/sync.d.ts.map +1 -0
- package/dist/bin/commands/sync.js +81 -0
- package/dist/bin/commands/sync.js.map +7 -0
- package/dist/bin/commands/typesense.d.ts +3 -0
- package/dist/bin/commands/typesense.d.ts.map +1 -0
- package/dist/bin/commands/typesense.js +126 -0
- package/dist/bin/commands/typesense.js.map +1 -0
- package/dist/bin/commands/uninit.d.ts +4 -0
- package/dist/bin/commands/uninit.d.ts.map +1 -0
- package/dist/bin/commands/uninit.js +123 -0
- package/dist/bin/commands/uninit.js.map +7 -0
- package/dist/bin/commands/unlock.d.ts +3 -0
- package/dist/bin/commands/unlock.d.ts.map +1 -0
- package/dist/bin/commands/unlock.js +53 -0
- package/dist/bin/commands/unlock.js.map +7 -0
- package/dist/bin/commands/utils.d.ts +12 -0
- package/dist/bin/commands/utils.d.ts.map +1 -0
- package/dist/bin/commands/utils.js +56 -0
- package/dist/bin/commands/utils.js.map +7 -0
- package/dist/bin/installer/archive.js +230 -0
- package/dist/bin/installer/archive.js.map +7 -0
- package/dist/bin/installer/caveman.js +57 -0
- package/dist/bin/installer/caveman.js.map +7 -0
- package/dist/bin/installer/cli-agent.d.ts +15 -0
- package/dist/bin/installer/cli-agent.d.ts.map +1 -0
- package/dist/bin/installer/cli-agent.js +89 -0
- package/dist/bin/installer/cli-agent.js.map +7 -0
- package/dist/bin/installer/config-prompt.d.ts +13 -0
- package/dist/bin/installer/config-prompt.d.ts.map +1 -0
- package/dist/bin/installer/config-prompt.js +158 -0
- package/dist/bin/installer/config-prompt.js.map +7 -0
- package/dist/bin/installer/dashboard.d.ts +3 -0
- package/dist/bin/installer/dashboard.d.ts.map +1 -0
- package/dist/bin/installer/dashboard.js +149 -0
- package/dist/bin/installer/dashboard.js.map +7 -0
- package/dist/bin/installer/hooks.d.ts +5 -0
- package/dist/bin/installer/hooks.d.ts.map +1 -0
- package/dist/bin/installer/hooks.js +155 -0
- package/dist/bin/installer/hooks.js.map +7 -0
- package/dist/bin/installer/index.d.ts +11 -0
- package/dist/bin/installer/index.d.ts.map +1 -0
- package/dist/bin/installer/index.js +228 -0
- package/dist/bin/installer/index.js.map +7 -0
- package/dist/bin/installer/mcp.d.ts +5 -0
- package/dist/bin/installer/mcp.d.ts.map +1 -0
- package/dist/bin/installer/mcp.js +80 -0
- package/dist/bin/installer/mcp.js.map +7 -0
- package/dist/bin/installer/prompts.d.ts +28 -0
- package/dist/bin/installer/prompts.d.ts.map +1 -0
- package/dist/bin/installer/prompts.js +134 -0
- package/dist/bin/installer/prompts.js.map +7 -0
- package/dist/bin/installer/qdrant-dashboard.d.ts +4 -0
- package/dist/bin/installer/qdrant-dashboard.d.ts.map +1 -0
- package/dist/bin/installer/qdrant-dashboard.js +115 -0
- package/dist/bin/installer/qdrant-dashboard.js.map +7 -0
- package/dist/bin/installer/steering.d.ts +5 -0
- package/dist/bin/installer/steering.d.ts.map +1 -0
- package/dist/bin/installer/steering.js +283 -0
- package/dist/bin/installer/steering.js.map +7 -0
- package/dist/bin/kirograph.d.ts +6 -0
- package/dist/bin/kirograph.d.ts.map +1 -0
- package/dist/bin/kirograph.js +95 -0
- package/dist/bin/kirograph.js.map +7 -0
- package/dist/bin/progress.d.ts +14 -0
- package/dist/bin/progress.d.ts.map +1 -0
- package/dist/bin/progress.js +201 -0
- package/dist/bin/progress.js.map +7 -0
- package/dist/bin/ui.d.ts +11 -0
- package/dist/bin/ui.d.ts.map +1 -0
- package/dist/bin/ui.js +71 -0
- package/dist/bin/ui.js.map +7 -0
- package/dist/config.d.ts +48 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +273 -0
- package/dist/config.js.map +7 -0
- package/dist/context/index.d.ts +61 -0
- package/dist/context/index.d.ts.map +1 -0
- package/dist/context/index.js +224 -0
- package/dist/context/index.js.map +7 -0
- package/dist/core/file-tree.d.ts +15 -0
- package/dist/core/file-tree.d.ts.map +1 -0
- package/dist/core/file-tree.js +69 -0
- package/dist/core/file-tree.js.map +7 -0
- package/dist/core/lock-manager.d.ts +20 -0
- package/dist/core/lock-manager.d.ts.map +1 -0
- package/dist/core/lock-manager.js +120 -0
- package/dist/core/lock-manager.js.map +7 -0
- package/dist/core/pipeline.d.ts +37 -0
- package/dist/core/pipeline.d.ts.map +1 -0
- package/dist/core/pipeline.js +375 -0
- package/dist/core/pipeline.js.map +7 -0
- package/dist/core/snapshot.js +141 -0
- package/dist/core/snapshot.js.map +7 -0
- package/dist/db/database.d.ts +133 -0
- package/dist/db/database.d.ts.map +1 -0
- package/dist/db/database.js +929 -0
- package/dist/db/database.js.map +7 -0
- package/dist/db/schema.sql +174 -0
- package/dist/errors.d.ts +49 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +160 -0
- package/dist/errors.js.map +7 -0
- package/dist/extraction/extractor.d.ts +29 -0
- package/dist/extraction/extractor.d.ts.map +1 -0
- package/dist/extraction/extractor.js +764 -0
- package/dist/extraction/extractor.js.map +7 -0
- package/dist/extraction/grammars.d.ts +48 -0
- package/dist/extraction/grammars.d.ts.map +1 -0
- package/dist/extraction/grammars.js +166 -0
- package/dist/extraction/grammars.js.map +7 -0
- package/dist/extraction/languages.d.ts +9 -0
- package/dist/extraction/languages.d.ts.map +1 -0
- package/dist/extraction/languages.js +103 -0
- package/dist/extraction/languages.js.map +7 -0
- package/dist/extraction/wasm/tree-sitter-pascal.wasm +0 -0
- package/dist/frameworks/csharp.d.ts +8 -0
- package/dist/frameworks/csharp.d.ts.map +1 -0
- package/dist/frameworks/csharp.js +93 -0
- package/dist/frameworks/csharp.js.map +7 -0
- package/dist/frameworks/elixir.js +142 -0
- package/dist/frameworks/elixir.js.map +7 -0
- package/dist/frameworks/express.d.ts +8 -0
- package/dist/frameworks/express.d.ts.map +1 -0
- package/dist/frameworks/express.js +143 -0
- package/dist/frameworks/express.js.map +7 -0
- package/dist/frameworks/go.d.ts +8 -0
- package/dist/frameworks/go.d.ts.map +1 -0
- package/dist/frameworks/go.js +85 -0
- package/dist/frameworks/go.js.map +7 -0
- package/dist/frameworks/index.d.ts +30 -0
- package/dist/frameworks/index.d.ts.map +1 -0
- package/dist/frameworks/index.js +243 -0
- package/dist/frameworks/index.js.map +7 -0
- package/dist/frameworks/java.d.ts +8 -0
- package/dist/frameworks/java.d.ts.map +1 -0
- package/dist/frameworks/java.js +87 -0
- package/dist/frameworks/java.js.map +7 -0
- package/dist/frameworks/laravel.d.ts +9 -0
- package/dist/frameworks/laravel.d.ts.map +1 -0
- package/dist/frameworks/laravel.js +115 -0
- package/dist/frameworks/laravel.js.map +7 -0
- package/dist/frameworks/python.d.ts +10 -0
- package/dist/frameworks/python.d.ts.map +1 -0
- package/dist/frameworks/python.js +158 -0
- package/dist/frameworks/python.js.map +7 -0
- package/dist/frameworks/react.d.ts +9 -0
- package/dist/frameworks/react.d.ts.map +1 -0
- package/dist/frameworks/react.js +230 -0
- package/dist/frameworks/react.js.map +7 -0
- package/dist/frameworks/ruby.d.ts +8 -0
- package/dist/frameworks/ruby.d.ts.map +1 -0
- package/dist/frameworks/ruby.js +136 -0
- package/dist/frameworks/ruby.js.map +7 -0
- package/dist/frameworks/rust.d.ts +8 -0
- package/dist/frameworks/rust.d.ts.map +1 -0
- package/dist/frameworks/rust.js +82 -0
- package/dist/frameworks/rust.js.map +7 -0
- package/dist/frameworks/svelte.d.ts +8 -0
- package/dist/frameworks/svelte.d.ts.map +1 -0
- package/dist/frameworks/svelte.js +174 -0
- package/dist/frameworks/svelte.js.map +7 -0
- package/dist/frameworks/swift.d.ts +10 -0
- package/dist/frameworks/swift.d.ts.map +1 -0
- package/dist/frameworks/swift.js +151 -0
- package/dist/frameworks/swift.js.map +7 -0
- package/dist/frameworks/types.d.ts +37 -0
- package/dist/frameworks/types.d.ts.map +1 -0
- package/dist/frameworks/types.js +17 -0
- package/dist/frameworks/types.js.map +7 -0
- package/dist/graph/queries.d.ts +53 -0
- package/dist/graph/queries.d.ts.map +1 -0
- package/dist/graph/queries.js +224 -0
- package/dist/graph/queries.js.map +7 -0
- package/dist/graph/traversal.d.ts +35 -0
- package/dist/graph/traversal.d.ts.map +1 -0
- package/dist/graph/traversal.js +148 -0
- package/dist/graph/traversal.js.map +7 -0
- package/dist/index.d.ts +102 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +303 -0
- package/dist/index.js.map +7 -0
- package/dist/installer/index.d.ts +10 -0
- package/dist/installer/index.d.ts.map +1 -0
- package/dist/installer/index.js +526 -0
- package/dist/installer/index.js.map +1 -0
- package/dist/mcp/server.d.ts +16 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +116 -0
- package/dist/mcp/server.js.map +7 -0
- package/dist/mcp/tools.d.ts +37 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +779 -0
- package/dist/mcp/tools.js.map +7 -0
- package/dist/mcp/transport.d.ts +29 -0
- package/dist/mcp/transport.d.ts.map +1 -0
- package/dist/mcp/transport.js +70 -0
- package/dist/mcp/transport.js.map +7 -0
- package/dist/resolution/index.d.ts +56 -0
- package/dist/resolution/index.d.ts.map +1 -0
- package/dist/resolution/index.js +384 -0
- package/dist/resolution/index.js.map +7 -0
- package/dist/resolution/name-matcher.d.ts +25 -0
- package/dist/resolution/name-matcher.d.ts.map +1 -0
- package/dist/resolution/name-matcher.js +60 -0
- package/dist/resolution/name-matcher.js.map +7 -0
- package/dist/scripts/postinstall.js +64 -0
- package/dist/search/query-utils.d.ts +21 -0
- package/dist/search/query-utils.d.ts.map +1 -0
- package/dist/search/query-utils.js +219 -0
- package/dist/search/query-utils.js.map +7 -0
- package/dist/search/searcher.d.ts +15 -0
- package/dist/search/searcher.d.ts.map +1 -0
- package/dist/search/searcher.js +49 -0
- package/dist/search/searcher.js.map +7 -0
- package/dist/sync/index.d.ts +33 -0
- package/dist/sync/index.d.ts.map +1 -0
- package/dist/sync/index.js +200 -0
- package/dist/sync/index.js.map +7 -0
- package/dist/types.d.ts +131 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +37 -0
- package/dist/types.js.map +7 -0
- package/dist/utils.d.ts +52 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +254 -0
- package/dist/utils.js.map +7 -0
- package/dist/vectors/index.d.ts +71 -0
- package/dist/vectors/index.d.ts.map +1 -0
- package/dist/vectors/index.js +480 -0
- package/dist/vectors/index.js.map +7 -0
- package/dist/vectors/lancedb-index.d.ts +50 -0
- package/dist/vectors/lancedb-index.d.ts.map +1 -0
- package/dist/vectors/lancedb-index.js +153 -0
- package/dist/vectors/lancedb-index.js.map +7 -0
- package/dist/vectors/orama-index.d.ts +54 -0
- package/dist/vectors/orama-index.d.ts.map +1 -0
- package/dist/vectors/orama-index.js +213 -0
- package/dist/vectors/orama-index.js.map +7 -0
- package/dist/vectors/pglite-index.d.ts +53 -0
- package/dist/vectors/pglite-index.d.ts.map +1 -0
- package/dist/vectors/pglite-index.js +194 -0
- package/dist/vectors/pglite-index.js.map +7 -0
- package/dist/vectors/qdrant-index.d.ts +70 -0
- package/dist/vectors/qdrant-index.d.ts.map +1 -0
- package/dist/vectors/qdrant-index.js +364 -0
- package/dist/vectors/qdrant-index.js.map +7 -0
- package/dist/vectors/typesense-index.d.ts +75 -0
- package/dist/vectors/typesense-index.d.ts.map +1 -0
- package/dist/vectors/typesense-index.js +453 -0
- package/dist/vectors/typesense-index.js.map +7 -0
- package/dist/vectors/vec-index.d.ts +52 -0
- package/dist/vectors/vec-index.d.ts.map +1 -0
- package/dist/vectors/vec-index.js +198 -0
- package/dist/vectors/vec-index.js.map +7 -0
- package/package.json +67 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/bin/commands/export.ts"],
|
|
4
|
+
"sourcesContent": ["import * as fs from 'fs';\nimport * as path from 'path';\nimport { spawn } from 'child_process';\nimport { Command } from 'commander';\nimport { bold, dim, green, reset, violet } from '../ui';\n\nfunction openBrowser(filePath: string): void {\n const cmd = process.platform === 'darwin' ? 'open'\n : process.platform === 'win32' ? 'start'\n : 'xdg-open';\n spawn(cmd, [filePath], { stdio: 'ignore', detached: true, shell: process.platform === 'win32' }).unref();\n}\n\nasync function generateExport(\n projectPath: string | undefined,\n opts: { output?: string; includeContains: boolean },\n): Promise<string> {\n const KiroGraph = (await Promise.resolve().then(() => require('../../index.js'))).default;\n const target = path.resolve(projectPath ?? process.cwd());\n const cg = await KiroGraph.open(target);\n\n const nodes = cg.getAllNodes();\n const edges = cg.getAllEdges();\n cg.close();\n\n const projectName = path.basename(target);\n\n // Embed logo as base64 if it exists next to this package\n let logoBase64: string | undefined;\n const logoCandidates = [\n path.join(__dirname, '../../assets/logo.png'),\n path.join(__dirname, '../../../assets/logo.png'),\n ];\n for (const p of logoCandidates) {\n if (fs.existsSync(p)) {\n logoBase64 = fs.readFileSync(p).toString('base64');\n break;\n }\n }\n\n // Build file modification time map (Feature 5)\n const fileModified: Record<string, number> = {};\n const uniquePaths = [...new Set(nodes.map((n: any) => n.filePath as string))];\n for (const fp of uniquePaths) {\n try {\n fileModified[fp] = fs.statSync(path.join(target, fp)).mtimeMs;\n } catch {\n fileModified[fp] = 0;\n }\n }\n\n const outDir = opts.output\n ? path.resolve(opts.output)\n : path.join(target, '.kirograph', 'export');\n\n fs.mkdirSync(outDir, { recursive: true });\n\n const { html, css, js } = buildFiles(nodes, edges, projectName, opts.includeContains, logoBase64, fileModified);\n\n fs.writeFileSync(path.join(outDir, 'index.html'), html, 'utf8');\n fs.writeFileSync(path.join(outDir, 'app.css'), css, 'utf8');\n fs.writeFileSync(path.join(outDir, 'app.js'), js, 'utf8');\n\n const indexPath = path.join(outDir, 'index.html');\n\n const edgeCount = opts.includeContains\n ? edges.length\n : edges.filter((e: any) => e.kind !== 'contains').length;\n\n console.log();\n console.log(` ${violet}${bold}Graph exported${reset}`);\n console.log(` ${dim}nodes ${reset}${bold}${nodes.length}${reset}`);\n console.log(` ${dim}edges ${reset}${bold}${edgeCount}${reset}${opts.includeContains ? '' : dim + ' (contains edges excluded)' + reset}`);\n console.log(` ${dim}output ${reset}${green}${outDir}${reset}`);\n console.log();\n\n return indexPath;\n}\n\n// \u2500\u2500 Color palettes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst KIND_COLOR: Record<string, string> = {\n class: '#9b59b6',\n struct: '#8e44ad',\n interface: '#6c3483',\n trait: '#7d3c98',\n protocol: '#6c3483',\n function: '#5b6abf',\n method: '#7986cb',\n component: '#26a69a',\n route: '#2e7d32',\n variable: '#546e7a',\n constant: '#455a64',\n property: '#607d8b',\n field: '#78909c',\n enum: '#e67e22',\n enum_member: '#f39c12',\n type_alias: '#00838f',\n namespace: '#00695c',\n parameter: '#616161',\n import: '#37474f',\n export: '#37474f',\n file: '#263238',\n module: '#1c313a',\n};\n\nconst EDGE_COLOR: Record<string, string> = {\n calls: '#7986cb',\n imports: '#546e7a',\n exports: '#546e7a',\n extends: '#ab47bc',\n implements: '#8e24aa',\n references: '#455a64',\n type_of: '#00838f',\n returns: '#0277bd',\n instantiates: '#6a1b9a',\n overrides: '#ad1457',\n decorates: '#e67e22',\n contains: '#263238',\n};\n\nconst EDGE_DASHED: Record<string, boolean> = {\n imports: true,\n references: true,\n type_of: true,\n returns: true,\n};\n\nfunction lighten(hex: string): string {\n const n = parseInt(hex.slice(1), 16);\n const r = Math.min(255, ((n >> 16) & 0xff) + 60);\n const g = Math.min(255, ((n >> 8) & 0xff) + 60);\n const b = Math.min(255, ( n & 0xff) + 60);\n return `#${r.toString(16).padStart(2,'0')}${g.toString(16).padStart(2,'0')}${b.toString(16).padStart(2,'0')}`;\n}\n\nfunction escHtml(s: string): string {\n return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"');\n}\n\nfunction buildFiles(\n nodes: any[], edges: any[], projectName: string, includeContains: boolean, logoBase64?: string,\n fileModified?: Record<string, number>,\n): { html: string; css: string; js: string } {\n const filteredEdges = includeContains ? edges : edges.filter(e => e.kind !== 'contains');\n\n const degree = new Map<string, number>();\n for (const e of filteredEdges) {\n degree.set(e.source, (degree.get(e.source) ?? 0) + 1);\n degree.set(e.target, (degree.get(e.target) ?? 0) + 1);\n }\n const maxDegree = Math.max(0, ...degree.values());\n\n const visNodes = nodes.map(n => {\n const fp: string = n.filePath ?? '';\n const parts = fp.split('/').filter(Boolean);\n const dir = parts.length >= 2 ? parts.slice(0, 2).join('/') : (parts[0] ?? '');\n return {\n id: n.id,\n label: n.name,\n color: {\n background: KIND_COLOR[n.kind] ?? '#424242',\n border: lighten(KIND_COLOR[n.kind] ?? '#424242'),\n highlight: { background: '#e040fb', border: '#ea80fc' },\n hover: { background: lighten(KIND_COLOR[n.kind] ?? '#424242'), border: '#ea80fc' },\n },\n size: Math.max(8, Math.min(40, 8 + (degree.get(n.id) ?? 0) * 1.5)),\n font: { size: 11, color: '#e0e0e0', face: 'monospace' },\n kind: n.kind,\n filePath: n.filePath,\n dir,\n startLine: n.startLine,\n qualifiedName: n.qualifiedName,\n signature: n.signature ?? null,\n isExported: n.isExported ?? false,\n degree: degree.get(n.id) ?? 0,\n borderWidth: n.isExported ? 2 : 1,\n borderWidthSelected: 3,\n lastModified: fileModified ? (fileModified[fp] ?? 0) : 0,\n };\n });\n\n const visEdges = filteredEdges.map((e: any, i: number) => ({\n id: i,\n ekind: e.kind,\n from: e.source,\n to: e.target,\n label: e.kind,\n dashes: EDGE_DASHED[e.kind] ?? false,\n color: { color: EDGE_COLOR[e.kind] ?? '#546e7a', opacity: e.kind === 'contains' ? 0.2 : 0.6 },\n width: ['extends', 'implements', 'calls'].includes(e.kind) ? 2 : 1,\n font: { size: 9, color: '#546e7a', align: 'middle' },\n arrows: e.kind !== 'contains' ? { to: { enabled: true, scaleFactor: 0.5 } } : {},\n smooth: { type: 'curvedCW', roundness: 0.1 },\n }));\n\n const allNodeKinds = [...new Set(nodes.map((n: any) => n.kind))].sort();\n const allEdgeKinds = [...new Set(filteredEdges.map((e: any) => e.kind))].sort();\n\n const logoTag = logoBase64\n ? `<img src=\"data:image/png;base64,${logoBase64}\" alt=\"KiroGraph\">`\n : `<h1>\u2B21 KiroGraph</h1>`;\n\n const loaderLogoTag = logoBase64\n ? `<img src=\"data:image/png;base64,${logoBase64}\" alt=\"KiroGraph\">`\n : `<div style=\"color:#c792ea;font-size:18px;font-weight:600;letter-spacing:.05em\">\u2B21 KiroGraph</div>`;\n\n const nodeLegend = allNodeKinds.map(k => `<div class=\"legend-item\" data-nkind=\"${k}\">\n <div class=\"legend-dot\" style=\"background:${KIND_COLOR[k] ?? '#424242'}\"></div>\n <span>${k}</span>\n <span class=\"legend-count\">${nodes.filter((n: any) => n.kind === k).length}</span>\n </div>`).join('');\n\n const edgeLegend = allEdgeKinds.map(k => `<div class=\"legend-item\" data-ekind=\"${k}\">\n <div class=\"legend-line\" style=\"${EDGE_DASHED[k] ? `border-top:2px dashed ${EDGE_COLOR[k] ?? '#546e7a'};height:0` : `background:${EDGE_COLOR[k] ?? '#546e7a'}`}\"></div>\n <span>${k}</span>\n <span class=\"legend-count\">${filteredEdges.filter((e: any) => e.kind === k).length}</span>\n </div>`).join('');\n\n const html = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>KiroGraph \u2014 ${escHtml(projectName)}</title>\n<link rel=\"stylesheet\" href=\"./app.css\">\n<link rel=\"preload\" href=\"https://unpkg.com/vis-network@9.1.9/standalone/umd/vis-network.min.js\" as=\"script\">\n<script src=\"https://unpkg.com/vis-network@9.1.9/standalone/umd/vis-network.min.js\"></script>\n</head>\n<body>\n\n<div id=\"init-loader\">\n ${loaderLogoTag}\n <div class=\"init-title\">${visNodes.length} nodes \u00B7 ${visEdges.length} edges \u00B7 ${escHtml(projectName)}</div>\n <div id=\"init-progress-wrap\"><div id=\"init-progress-bar\"></div></div>\n <div id=\"init-status\">Loading\u2026</div>\n</div>\n\n<div id=\"main\">\n <div id=\"loader\"><div class=\"spinner\"></div></div>\n <div id=\"path-bar\">Click a node to set start\u2026</div>\n <div id=\"graph\"></div>\n\n <!-- Floating left sidebar: brand + search + tools, all stacked -->\n <div id=\"float-sidebar\">\n <div id=\"float-brand\">\n ${logoTag}\n <div id=\"float-brand-info\">\n <span id=\"proj\">${escHtml(projectName)}</span>\n <span id=\"stats\">${visNodes.length} nodes \u00B7 ${visEdges.length} edges</span>\n </div>\n </div>\n\n <div id=\"float-left\">\n <input id=\"search\" type=\"text\" placeholder=\"\uD83D\uDD0D Search symbols\u2026\">\n\n <div class=\"float-group vertical\">\n <button class=\"fbtn\" id=\"btn-fit\" title=\"Fit graph to view\">\u229E <span class=\"fbtn-label\">Fit</span></button>\n <button class=\"fbtn active\" id=\"btn-physics\" title=\"Toggle physics\">\u26A1 <span class=\"fbtn-label\">Physics</span></button>\n\n <div class=\"fslider-row\" id=\"physics-speed-row\">\n <span class=\"fslider-label\">Slow</span>\n <input type=\"range\" id=\"physics-speed\" min=\"1\" max=\"10\" value=\"5\" step=\"1\" title=\"Physics speed\">\n <span class=\"fslider-label\">Fast</span>\n </div>\n\n <button class=\"fbtn\" id=\"btn-png\" title=\"Export PNG\">\uD83D\uDCF7 <span class=\"fbtn-label\">PNG</span></button>\n </div>\n\n <div class=\"float-group vertical\">\n <button class=\"fbtn\" id=\"btn-focus\" title=\"Focus on node and neighbors\">\u25CE <span class=\"fbtn-label\">Focus</span></button>\n <button class=\"fbtn\" id=\"btn-path\" title=\"Find path between two nodes\">\u27F6 <span class=\"fbtn-label\">Path</span></button>\n <button class=\"fbtn\" id=\"btn-cluster\" title=\"Cluster by directory\">\u2B21 <span class=\"fbtn-label\">Cluster</span></button>\n <button class=\"fbtn\" id=\"btn-heat\" title=\"Heat map by file recency\">\uD83C\uDF21 <span class=\"fbtn-label\">Heat</span></button>\n <button class=\"fbtn\" id=\"btn-charts\" title=\"Analytics charts\">\uD83D\uDCCA <span class=\"fbtn-label\">Charts</span></button>\n </div>\n\n <div id=\"heat-legend\">\n <div class=\"heat-legend-title\">\uD83C\uDF21 File recency</div>\n <div id=\"heat-gradient\"></div>\n <div class=\"heat-stops\">\n <div class=\"heat-stop\" data-tip=\"Modified in the last 24 hours\"><span class=\"heat-stop-dot\" style=\"background:#e74c3c\"></span>Today</div>\n <div class=\"heat-stop\" data-tip=\"Modified in the last week\"><span class=\"heat-stop-dot\" style=\"background:#e67e22\"></span>This week</div>\n <div class=\"heat-stop\" data-tip=\"Modified in the last month\"><span class=\"heat-stop-dot\" style=\"background:#f1c40f\"></span>This month</div>\n <div class=\"heat-stop\" data-tip=\"Modified in the last 6 months\"><span class=\"heat-stop-dot\" style=\"background:#27ae60\"></span>6 months</div>\n <div class=\"heat-stop\" data-tip=\"Modified more than 6 months ago\"><span class=\"heat-stop-dot\" style=\"background:#2980b9\"></span>Older</div>\n </div>\n <div id=\"heat-tip\"></div>\n </div>\n </div>\n </div><!-- /float-sidebar -->\n\n <div id=\"minimap\"><canvas id=\"minimap-canvas\" width=\"180\" height=\"120\"></canvas></div>\n <div id=\"ctx-menu\"></div>\n\n <!-- Charts modal -->\n <div id=\"charts-modal\" style=\"display:none\">\n <div id=\"charts-panel\">\n <div id=\"charts-header\">\n <span>\uD83D\uDCCA Graph Analytics</span>\n <button id=\"charts-close\">\u2715</button>\n </div>\n <div id=\"charts-body\">\n <div class=\"chart-block span-3\">\n <div class=\"chart-title\">Top 15 Most Connected Symbols</div>\n\n <div class=\"chart-insight\">Symbols with the highest combined in + out degree are the load-bearing pillars of your codebase.<br>A very long bar on an internal helper is a red flag \u2014 it means many things depend on one place that wasn't designed as an API.</div>\n <canvas id=\"chart-bar\" width=\"1080\" height=\"280\"></canvas>\n </div>\n <div class=\"chart-block span-1\">\n <div class=\"chart-title\">Node Distribution by Kind</div>\n <div class=\"chart-insight\">Shows what your codebase is made of at a glance \u2014 heavy on functions vs. classes vs. types.<br>A dominant slice (e.g. 80% variables) can indicate over-abstraction or, conversely, too little structure.</div>\n <canvas id=\"chart-pie\" width=\"540\" height=\"280\"></canvas>\n </div>\n <div class=\"chart-block span-2\">\n <div class=\"chart-title\">Degree Distribution \u2014 Connectivity Curve</div>\n <div class=\"chart-insight\">Healthy codebases show a steep left peak (most symbols have few connections) and a long tail.<br>A flat or multi-modal curve signals coupling problems \u2014 too many symbols are over-connected.</div>\n <canvas id=\"chart-line\" width=\"720\" height=\"280\"></canvas>\n </div>\n\n <div class=\"chart-block span-3\">\n <div class=\"chart-title\">Top 15 Callers \u2014 Highest Out-Degree</div>\n <div class=\"chart-insight\">High out-degree means this symbol knows too much about the rest of the system \u2014 a classic god-object signal.<br>If the top callers are entry points or orchestrators that's fine; if they're utility helpers, consider splitting them.</div>\n <canvas id=\"chart-callers\" width=\"1080\" height=\"280\"></canvas>\n </div>\n\n <div class=\"chart-block span-3\">\n <div class=\"chart-title\">Top 15 Callees \u2014 Highest In-Degree</div>\n <div class=\"chart-insight\">These are your real public API \u2014 change them and many things break. High in-degree is expected for core utilities.<br>Surprise entries here (a private helper, a constant) indicate hidden coupling you should make explicit.</div>\n <canvas id=\"chart-callees\" width=\"1080\" height=\"280\"></canvas>\n </div>\n\n <div class=\"chart-block span-3\">\n <div class=\"chart-title\">Files by Symbol Count</div>\n <div class=\"chart-insight\">Files with many symbols are candidates for splitting \u2014 they likely handle multiple responsibilities.<br>Cross-reference with the coupling matrix: a large file that also couples to many directories is a high-priority refactor target.</div>\n <canvas id=\"chart-files\" width=\"1080\" height=\"280\"></canvas>\n </div>\n\n <div class=\"chart-block span-1\">\n <div class=\"chart-title\">Edge Kind Distribution</div>\n <div class=\"chart-insight\">Tells you which relationship patterns dominate: calls, imports, inheritance, containment.<br>An unusually high import ratio vs. calls can mean modules are tightly wired at the file level without using each other's symbols.</div>\n <canvas id=\"chart-edgekinds\" width=\"400\" height=\"280\"></canvas>\n </div>\n\n <div class=\"chart-block span-1\">\n <div class=\"chart-title\">Dead Code by Kind</div>\n <div class=\"chart-insight\">These symbols are never referenced from within the indexed codebase \u2014 strong deletion candidates.<br>Focus on functions and classes; variables and constants are more likely to be used via dynamic patterns not captured by static analysis.</div>\n <canvas id=\"chart-deadcode\" width=\"400\" height=\"280\"></canvas>\n </div>\n\n <div class=\"chart-block span-1\">\n <div class=\"chart-title\">Symbol Count per Directory</div>\n <div class=\"chart-insight\">A single directory holding most symbols is a monolith smell \u2014 the rest of the structure may be cosmetic.<br>Balanced distribution suggests good separation of concerns; a very tall single bar suggests your folder structure doesn't reflect your actual architecture.</div>\n <canvas id=\"chart-dirs\" width=\"400\" height=\"280\"></canvas>\n </div>\n\n <div class=\"chart-block span-3\">\n <div class=\"chart-title\">Exported vs Unexported by Kind</div>\n <div class=\"chart-insight\">Shows how much of each symbol type is intentionally public. A large unexported slice is healthy \u2014 it means most logic is encapsulated.<br>If nearly everything is exported, your module boundaries are weak and consumers have too much surface area to depend on.</div>\n <canvas id=\"chart-exported\" width=\"1080\" height=\"260\"></canvas>\n </div>\n\n <div class=\"chart-block span-3\">\n <div class=\"chart-title\">Directory Coupling Matrix</div>\n <div class=\"chart-insight\">Each row is a source directory, each column is a target \u2014 a bright cell means many edges cross from that row's folder into that column's folder.<br>Symmetrically bright off-diagonal pairs (A\u2194B and B\u2194A) reveal circular coupling between modules that should be cleanly layered.</div>\n <canvas id=\"chart-coupling\" width=\"1080\" height=\"420\"></canvas>\n </div>\n\n <div class=\"chart-block span-2\">\n <div class=\"chart-title\">In-Degree vs Out-Degree Scatter</div>\n <div class=\"chart-insight\">Top-left = Sinks (referenced a lot, calls nothing) \u2014 typical for pure data types or leaf utilities.<br>Bottom-right = Sources (calls a lot, rarely referenced) \u2014 often entry points or wiring code. Top-right = Hubs \u2014 investigate these first.</div>\n <canvas id=\"chart-scatter\" width=\"720\" height=\"320\"></canvas>\n </div>\n\n <div class=\"chart-block span-1\">\n <div class=\"chart-title\">Average Degree per File</div>\n <div class=\"chart-insight\">Files with high average degree are deeply entangled regardless of their size \u2014 even a 2-symbol file can score high.<br>This metric catches hidden complexity better than raw symbol count: a small file with a huge average is often the real coupling hotspot.</div>\n <canvas id=\"chart-avgdeg\" width=\"400\" height=\"320\"></canvas>\n </div>\n\n </div>\n </div>\n </div>\n\n <div id=\"panel\">\n <div id=\"panel-tabs\">\n <div class=\"tab active\" data-tab=\"detail\">Detail</div>\n <div class=\"tab\" data-tab=\"legend\">Legend</div>\n <div class=\"tab\" data-tab=\"filters\">Filters</div>\n </div>\n <div id=\"panel-content\">\n\n <!-- Detail tab -->\n <div id=\"tab-detail\">\n <div id=\"history-nav\">\n <button class=\"hist-btn\" id=\"hist-back\" disabled>\u2039</button>\n <span id=\"hist-label\">no selection</span>\n <button class=\"hist-btn\" id=\"hist-forward\" disabled>\u203A</button>\n </div>\n <p class=\"detail-empty\">Click a node to inspect it.</p>\n </div>\n\n <!-- Legend tab -->\n <div id=\"tab-legend\" style=\"display:none\">\n <div class=\"legend-section\">\n <div class=\"legend-title\">Node kinds <span style=\"color:#37474f;font-size:9px\">(click to toggle)</span></div>\n ${nodeLegend}\n </div>\n <div class=\"legend-section\">\n <div class=\"legend-title\">Edge kinds <span style=\"color:#37474f;font-size:9px\">(click to toggle)</span></div>\n ${edgeLegend}\n </div>\n </div>\n\n <!-- Filters tab -->\n <div id=\"tab-filters\" style=\"display:none\">\n <div class=\"filter-section\">\n <div class=\"filter-label\">\n Min degree\n <span class=\"filter-value\" id=\"degree-val\">0</span>\n </div>\n <input type=\"range\" id=\"degree-slider\" min=\"0\" max=\"${maxDegree}\" value=\"0\" step=\"1\">\n <div class=\"filter-hint\">Hide nodes with fewer than N connections. Use to surface the most-connected symbols.</div>\n </div>\n </div>\n\n </div>\n </div>\n</div>\n\n<script src=\"./app.js\"></script>\n</body>\n</html>`;\n\n const css = buildCss();\n const js = buildJs(visNodes, visEdges, KIND_COLOR, EDGE_COLOR);\n\n return { html, css, js };\n}\n\nfunction buildCss(): string {\n return `*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n\nbody {\n background: #0a0a0f;\n color: #e0e0e0;\n font-family: 'SF Mono','Fira Code','Consolas',monospace;\n font-size: 13px;\n height: 100vh;\n overflow: hidden;\n}\n\n/* \u2500\u2500 Main fills full viewport \u2500\u2500 */\n#main { display: flex; width: 100vw; height: 100vh; overflow: hidden; position: relative; }\n#graph { flex: 1; background: #0a0a0f; }\n\n/* \u2500\u2500 Left sidebar: single stacking container \u2500\u2500 */\n#float-sidebar {\n position: absolute;\n top: 12px;\n left: 12px;\n z-index: 50;\n display: flex;\n flex-direction: column;\n gap: 8px;\n max-height: calc(100vh - 24px);\n overflow-y: auto;\n overflow-x: visible;\n}\n#float-sidebar::-webkit-scrollbar { display: none; }\n\n/* \u2500\u2500 Brand card \u2500\u2500 */\n#float-brand {\n display: flex;\n align-items: center;\n gap: 10px;\n background: rgba(8, 8, 18, 0.97);\n border: 1px solid #3a3a5e;\n border-radius: 12px;\n padding: 8px 14px 8px 8px;\n box-shadow: 0 4px 24px rgba(0,0,0,.75), 0 0 0 1px rgba(121,134,203,.08);\n flex-shrink: 0;\n}\n#float-brand img {\n height: 46px;\n width: auto;\n object-fit: contain;\n border-radius: 6px;\n}\n#float-brand h1 { font-size: 13px; font-weight: 600; color: #c792ea; letter-spacing: .05em; }\n#float-brand-info { display: flex; flex-direction: column; gap: 2px; }\n#proj { color: #c792ea; font-size: 12px; font-weight: 600; white-space: nowrap; }\n#stats { color: #6272a4; font-size: 10px; white-space: nowrap; }\n\n/* \u2500\u2500 Search + buttons column \u2500\u2500 */\n#float-left {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n/* \u2500\u2500 Search inside left column \u2500\u2500 */\n#search {\n background: rgba(8, 8, 18, 0.97);\n border: 1px solid #3a3a5e;\n border-radius: 10px;\n color: #e8e8f0;\n padding: 8px 14px;\n font-family: inherit;\n font-size: 12px;\n width: 200px;\n outline: none;\n transition: border-color .2s, box-shadow .2s;\n box-shadow: 0 4px 24px rgba(0,0,0,.75);\n}\n#search:focus { border-color: #7986cb; box-shadow: 0 0 0 2px rgba(121,134,203,.3); }\n#search::placeholder { color: #4a4a6a; }\n\n/* \u2500\u2500 Shared float group \u2500\u2500 */\n.float-group {\n display: flex;\n flex-direction: column;\n background: rgba(8, 8, 18, 0.97);\n border: 1px solid #3a3a5e;\n border-radius: 12px;\n overflow: hidden;\n box-shadow: 0 4px 24px rgba(0,0,0,.75), 0 0 0 1px rgba(121,134,203,.06);\n width: 200px;\n}\n\n/* \u2500\u2500 Float button \u2500\u2500 */\n.fbtn {\n display: flex;\n align-items: center;\n gap: 10px;\n background: transparent;\n border: none;\n border-bottom: 1px solid #252538;\n color: #9da8cc;\n padding: 9px 14px;\n font-family: inherit;\n font-size: 14px;\n cursor: pointer;\n transition: background .12s, color .12s;\n white-space: nowrap;\n text-align: left;\n}\n.fbtn:last-child { border-bottom: none; }\n.fbtn:hover { background: #16162a; color: #ffffff; }\n.fbtn.active { background: #1e1e40; color: #c792ea; border-left: 3px solid #c792ea; padding-left: 11px; }\n.fbtn.warn { color: #e67e22; }\n.fslider-row {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 7px 14px;\n border-bottom: 1px solid #252538;\n}\n.fslider-label { font-size: 9px; color: #7a86aa; flex-shrink: 0; letter-spacing: .03em; }\n#physics-speed {\n flex: 1;\n -webkit-appearance: none;\n appearance: none;\n height: 3px;\n background: #2e2e52;\n border-radius: 2px;\n outline: none;\n cursor: pointer;\n}\n#physics-speed::-webkit-slider-thumb {\n -webkit-appearance: none;\n width: 12px;\n height: 12px;\n border-radius: 50%;\n background: #7986cb;\n cursor: pointer;\n border: 2px solid #c792ea;\n}\n#physics-speed::-moz-range-thumb {\n width: 12px;\n height: 12px;\n border-radius: 50%;\n background: #7986cb;\n cursor: pointer;\n border: 2px solid #c792ea;\n}\n.fbtn-label {\n font-size: 11px;\n font-weight: 500;\n letter-spacing: .02em;\n}\n\n/* compat: keep .btn for any remaining uses */\n.btn { display: none; }\n\n/* \u2500\u2500 Init loader (full screen, shown on page load) \u2500\u2500 */\n#init-loader {\n position: fixed;\n inset: 0;\n background: #0a0a0f;\n z-index: 1000;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 20px;\n transition: opacity .4s ease;\n}\n#init-loader.fade-out { opacity: 0; pointer-events: none; }\n#init-loader img { max-height: 180px; max-width: 320px; width: auto; height: auto; opacity: .95; }\n#init-loader .init-title { color: #546e7a; font-size: 12px; letter-spacing: .05em; }\n#init-progress-wrap {\n width: 220px;\n background: #1a1a2e;\n border-radius: 4px;\n height: 3px;\n overflow: hidden;\n}\n#init-progress-bar {\n height: 100%;\n width: 0%;\n background: linear-gradient(90deg, #7986cb, #c792ea);\n border-radius: 4px;\n transition: width .1s linear;\n}\n#init-status { color: #37474f; font-size: 11px; }\n\n/* \u2500\u2500 Operation loader (translucent overlay for filter ops) \u2500\u2500 */\n#loader {\n display: none;\n position: absolute;\n inset: 0;\n background: rgba(10,10,15,.55);\n backdrop-filter: blur(2px);\n z-index: 200;\n align-items: center;\n justify-content: center;\n pointer-events: none;\n}\n.spinner {\n width: 26px; height: 26px;\n border: 3px solid #1e1e2e;\n border-top-color: #c792ea;\n border-radius: 50%;\n animation: spin .7s linear infinite;\n}\n@keyframes spin { to { transform: rotate(360deg); } }\n\n/* \u2500\u2500 Path mode bar \u2500\u2500 */\n#path-bar {\n display: none;\n position: absolute;\n bottom: 14px;\n left: 50%;\n transform: translateX(-50%);\n background: #12121a;\n border: 1px solid #7986cb;\n border-radius: 8px;\n padding: 8px 16px;\n font-size: 12px;\n color: #c792ea;\n z-index: 50;\n pointer-events: none;\n box-shadow: 0 4px 20px rgba(0,0,0,.6);\n}\n\n/* \u2500\u2500 Side panel \u2500\u2500 */\n#panel {\n width: 280px;\n background: #12121a;\n border-left: 1px solid #1e1e2e;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n flex-shrink: 0;\n transition: width .2s;\n}\n\n#panel-tabs { display: flex; border-bottom: 1px solid #1e1e2e; flex-shrink: 0; }\n.tab {\n flex: 1; padding: 8px; text-align: center;\n font-size: 11px; color: #546e7a; cursor: pointer;\n border-bottom: 2px solid transparent; transition: all .15s;\n}\n.tab.active { color: #c792ea; border-bottom-color: #c792ea; }\n#panel-content { flex: 1; overflow-y: auto; padding: 12px; }\n\n/* \u2500\u2500 Detail tab \u2500\u2500 */\n.detail-empty { color: #37474f; font-size: 12px; padding: 8px 0; }\n.detail-name { font-size: 15px; font-weight: 600; color: #c792ea; margin-bottom: 4px; word-break: break-all; }\n.detail-kind {\n display: inline-block; font-size: 10px; padding: 2px 7px;\n border-radius: 10px; background: #1e1e2e; color: #7986cb; margin-bottom: 10px;\n}\n.detail-row { display: flex; gap: 8px; margin-bottom: 5px; font-size: 11px; }\n.detail-label { color: #37474f; min-width: 60px; flex-shrink: 0; }\n.detail-val { color: #90a4ae; word-break: break-all; }\n.detail-sig {\n margin-top: 8px; background: #0d0d1a; border: 1px solid #1e1e2e;\n border-radius: 5px; padding: 8px; font-size: 11px; color: #7986cb;\n word-break: break-all; white-space: pre-wrap;\n}\n.detail-actions { display: flex; gap: 6px; margin-top: 10px; flex-wrap: wrap; }\n.action-btn {\n background: #1a1a2e; border: 1px solid #2a2a3e; border-radius: 4px;\n color: #7986cb; padding: 4px 8px; font-size: 11px; cursor: pointer;\n font-family: inherit; transition: all .15s;\n}\n.action-btn:hover { border-color: #7986cb; color: #e0e0e0; }\n\n/* \u2500\u2500 History breadcrumb \u2500\u2500 */\n#history-nav {\n display: flex; align-items: center; gap: 6px;\n margin-bottom: 10px; padding-bottom: 10px;\n border-bottom: 1px solid #1e1e2e;\n}\n.hist-btn {\n background: none; border: 1px solid #2a2a3e; border-radius: 4px;\n color: #546e7a; padding: 2px 7px; font-size: 12px; cursor: pointer;\n transition: all .15s;\n}\n.hist-btn:not(:disabled):hover { border-color: #7986cb; color: #e0e0e0; }\n.hist-btn:disabled { opacity: 0.3; cursor: default; }\n#hist-label { font-size: 10px; color: #37474f; flex: 1; text-align: center; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n\n/* \u2500\u2500 Path result \u2500\u2500 */\n.path-result { margin-top: 10px; }\n.path-step {\n display: flex; align-items: flex-start; gap: 6px;\n margin-bottom: 2px; font-size: 11px;\n}\n.path-step-num { color: #37474f; min-width: 18px; text-align: right; flex-shrink: 0; }\n.path-step-name { color: #c792ea; font-weight: 600; cursor: pointer; }\n.path-step-name:hover { text-decoration: underline; }\n.path-step-kind { color: #546e7a; }\n.path-connector { color: #2a2a3e; margin-left: 22px; font-size: 10px; margin-bottom: 2px; }\n\n/* \u2500\u2500 Path endpoint cards \u2500\u2500 */\n.path-endpoints { margin-top: 14px; border-top: 1px solid #1e1e2e; padding-top: 12px; }\n.path-endpoint-label { font-size: 9px; color: #37474f; text-transform: uppercase; letter-spacing: .1em; margin-bottom: 4px; }\n.node-card {\n background: #0d0d1a; border: 1px solid #1e1e2e; border-radius: 5px;\n padding: 8px 10px; margin-bottom: 4px;\n}\n.node-card-name { font-size: 13px; font-weight: 600; color: #c792ea; margin-bottom: 4px; word-break: break-all; }\n\n/* \u2500\u2500 Legend tab \u2500\u2500 */\n.legend-section { margin-bottom: 14px; }\n.legend-title { font-size: 10px; color: #546e7a; text-transform: uppercase; letter-spacing: .08em; margin-bottom: 7px; }\n.legend-item {\n display: flex; align-items: center; gap: 7px; margin-bottom: 3px;\n font-size: 11px; color: #90a4ae; cursor: pointer; padding: 2px 4px;\n border-radius: 4px; transition: background .1s;\n}\n.legend-item:hover { background: #1a1a2e; }\n.legend-item.dimmed { opacity: .3; }\n.legend-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }\n.legend-line { width: 18px; height: 2px; flex-shrink: 0; }\n.legend-count{ margin-left: auto; color: #37474f; font-size: 10px; }\n\n/* \u2500\u2500 Filters tab \u2500\u2500 */\n.filter-section { margin-bottom: 18px; }\n.filter-label { font-size: 10px; color: #546e7a; text-transform: uppercase; letter-spacing: .08em; margin-bottom: 10px; display: flex; justify-content: space-between; align-items: center; }\n.filter-value { color: #c792ea; font-size: 12px; }\ninput[type=range] {\n width: 100%; accent-color: #7986cb;\n background: transparent; cursor: pointer;\n}\n.filter-hint { font-size: 10px; color: #37474f; margin-top: 5px; }\n\n/* \u2500\u2500 Scrollbar \u2500\u2500 */\n::-webkit-scrollbar { width: 4px; }\n::-webkit-scrollbar-track { background: transparent; }\n::-webkit-scrollbar-thumb { background: #2a2a3e; border-radius: 3px; }\n\n/* \u2500\u2500 Tooltip \u2500\u2500 */\n.vis-tooltip {\n background: #12121a !important; border: 1px solid #2a2a3e !important;\n color: #e0e0e0 !important; font-family: 'SF Mono','Fira Code',monospace !important;\n font-size: 12px !important; border-radius: 6px !important; padding: 8px 10px !important;\n}\n\n/* \u2500\u2500 Minimap \u2500\u2500 */\n#minimap {\n position: absolute;\n bottom: 14px;\n left: 14px;\n width: 180px;\n height: 120px;\n background: rgba(10,10,15,.85);\n border: 1px solid #2a2a3e;\n border-radius: 6px;\n overflow: hidden;\n z-index: 40;\n cursor: crosshair;\n}\n#minimap-canvas { display: block; width: 180px; height: 120px; }\n\n/* \u2500\u2500 Context menu \u2500\u2500 */\n#ctx-menu {\n display: none;\n position: fixed;\n z-index: 500;\n background: #12121a;\n border: 1px solid #2a2a3e;\n border-radius: 6px;\n padding: 4px 0;\n min-width: 180px;\n box-shadow: 0 6px 24px rgba(0,0,0,.7);\n font-size: 12px;\n}\n.ctx-item {\n padding: 6px 14px;\n color: #90a4ae;\n cursor: pointer;\n white-space: nowrap;\n transition: background .1s;\n}\n.ctx-item:hover { background: #1a1a2e; color: #e0e0e0; }\n.ctx-sep { height: 1px; background: #1e1e2e; margin: 3px 0; }\n\n/* \u2500\u2500 Charts modal \u2500\u2500 */\n#charts-modal {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 8, 0.88);\n backdrop-filter: blur(8px);\n z-index: 600;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n#charts-panel {\n background: #0e0e1c;\n border: 1px solid #3a3a5e;\n border-radius: 14px;\n width: 80vw;\n max-height: 90vh;\n display: flex;\n flex-direction: column;\n box-shadow: 0 24px 80px rgba(0,0,0,.95), 0 0 0 1px rgba(121,134,203,.12);\n overflow: hidden;\n}\n#charts-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 14px 18px;\n border-bottom: 1px solid #252540;\n font-size: 14px;\n font-weight: 700;\n color: #e0d0ff;\n letter-spacing: .03em;\n flex-shrink: 0;\n background: #111120;\n}\n#charts-close {\n background: #1a1a30;\n border: 1px solid #3a3a5e;\n border-radius: 6px;\n color: #9da8cc;\n padding: 4px 10px;\n font-size: 13px;\n cursor: pointer;\n font-family: inherit;\n transition: all .15s;\n}\n#charts-close:hover { background: #252545; border-color: #c792ea; color: #ffffff; }\n#charts-body {\n overflow-y: auto;\n padding: 18px;\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 18px;\n}\n.chart-block.span-1 { grid-column: span 1; }\n.chart-block.span-2 { grid-column: span 2; }\n.chart-block.span-3 { grid-column: 1 / -1; }\n.chart-block {\n background: #0a0a1c;\n border: 1px solid #2e2e52;\n border-radius: 10px;\n padding: 16px;\n}\n.chart-title {\n font-size: 13px;\n font-weight: 700;\n color: #e8eeff;\n margin-bottom: 2px;\n}\n.chart-sub {\n font-size: 10px;\n color: #7a82aa;\n margin-bottom: 6px;\n}\n.chart-insight {\n font-size: 10px;\n color: #9ba3c8;\n background: rgba(121,134,203,0.08);\n border-left: 2px solid #5b6abf;\n padding: 5px 8px;\n margin-bottom: 12px;\n border-radius: 0 4px 4px 0;\n line-height: 1.5;\n}\n.chart-block canvas {\n display: block;\n width: 100%;\n height: auto;\n border-radius: 6px;\n}\n\n/* \u2500\u2500 Heat legend (inside sidebar) \u2500\u2500 */\n#heat-legend {\n display: none;\n background: rgba(8, 8, 18, 0.97);\n border: 1px solid #3a3a5e;\n border-radius: 12px;\n padding: 12px 14px;\n box-shadow: 0 4px 24px rgba(0,0,0,.75);\n width: 200px;\n}\n.heat-legend-title {\n font-size: 11px;\n font-weight: 600;\n color: #9da8cc;\n margin-bottom: 10px;\n}\n#heat-gradient {\n height: 6px;\n border-radius: 3px;\n background: linear-gradient(90deg, #e74c3c, #e67e22, #f1c40f, #27ae60, #2980b9);\n margin-bottom: 10px;\n}\n.heat-stops {\n display: flex;\n flex-direction: column;\n gap: 5px;\n}\n.heat-stop {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 11px;\n color: #7a86aa;\n cursor: default;\n padding: 2px 4px;\n border-radius: 4px;\n transition: background .1s, color .1s;\n}\n.heat-stop:hover { background: #16162a; color: #e0e0e0; }\n.heat-stop-dot {\n width: 9px; height: 9px;\n border-radius: 50%;\n flex-shrink: 0;\n}\n#heat-tip {\n margin-top: 8px;\n font-size: 10px;\n color: #4a4a6a;\n min-height: 14px;\n font-style: italic;\n}\n`;\n}\n\nfunction buildJs(\n visNodes: any[], visEdges: any[],\n kindColors: Record<string, string>, edgeColors: Record<string, string>,\n): string {\n return `// \u2500\u2500 Data \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst NODES_DATA = ${JSON.stringify(visNodes)};\nconst EDGES_DATA = ${JSON.stringify(visEdges)};\nconst KIND_COLORS = ${JSON.stringify(kindColors)};\nconst EDGE_COLORS = ${JSON.stringify(edgeColors)};\n\nconst nodesDS = new vis.DataSet(NODES_DATA);\nconst edgesDS = new vis.DataSet(EDGES_DATA);\n\n// \u2500\u2500 Precomputed lookups \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst nodeById = {};\nNODES_DATA.forEach(n => { nodeById[n.id] = n; });\n\n// Adjacency map (undirected) for focus + path BFS\nconst adj = {};\nEDGES_DATA.forEach(e => {\n if (!adj[e.from]) adj[e.from] = new Set();\n if (!adj[e.to]) adj[e.to] = new Set();\n adj[e.from].add(e.to);\n adj[e.to].add(e.from);\n});\n\n// Edge lookup for path edge highlighting: \"fromId|toId\" -> edge id\nconst edgeMap = {};\nEDGES_DATA.forEach(e => {\n edgeMap[e.from + '|' + e.to] = e.id;\n edgeMap[e.to + '|' + e.from] = e.id;\n});\n\n// Original colors for path restore\nconst originalColors = {};\nNODES_DATA.forEach(n => { originalColors[n.id] = n.color; });\nconst originalEdgeColors = {};\nEDGES_DATA.forEach(e => { originalEdgeColors[e.id] = e.color; });\n\n// \u2500\u2500 Filter state \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst hiddenNodeKinds = new Set();\nconst hiddenEdgeKinds = new Set();\nlet minDegree = 0;\nlet focusActive = false;\nlet focusSet = new Set();\nlet searchActive = false;\nlet searchIds = new Set();\nlet pathHighlightActive = false;\n\n// \u2500\u2500 Path mode state \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet pathMode = false;\nlet pathStep = 0;\nlet pathFromId = null;\nlet pathToId = null;\n\n// \u2500\u2500 History state \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst navHistory = [];\nlet histIdx = -1;\n\n// \u2500\u2500 vis.js network \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst network = new vis.Network(\n document.getElementById('graph'),\n { nodes: nodesDS, edges: edgesDS },\n {\n physics: {\n enabled: true,\n solver: 'forceAtlas2Based',\n forceAtlas2Based: { gravitationalConstant: -80, centralGravity: 0.005, springLength: 120, springConstant: 0.08, damping: 0.6, avoidOverlap: 0.6 },\n stabilization: { iterations: 150, fit: true },\n },\n interaction: { hover: true, tooltipDelay: 150, hideEdgesOnDrag: true, multiselect: false },\n nodes: { shape: 'dot', scaling: { min: 6, max: 40 }, shadow: { enabled: true, color: 'rgba(0,0,0,.6)', size: 8, x: 2, y: 2 } },\n edges: { smooth: { type: 'continuous' }, selectionWidth: 2, hoverWidth: 1.5 },\n layout: { improvedLayout: false },\n }\n);\n\n// \u2500\u2500 Init loader \u2014 driven by stabilization progress \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst initLoader = document.getElementById('init-loader');\nconst progressBar = document.getElementById('init-progress-bar');\nconst initStatus = document.getElementById('init-status');\n\nnetwork.on('stabilizationProgress', params => {\n const pct = Math.round((params.iterations / params.total) * 100);\n progressBar.style.width = pct + '%';\n initStatus.textContent = 'Laying out graph\u2026 ' + pct + '%';\n});\n\nnetwork.on('stabilizationIterationsDone', () => {\n progressBar.style.width = '100%';\n initStatus.textContent = 'Done';\n // Disable physics AND future stabilization so DataSet updates never re-trigger\n // layout passes \u2014 which would cause nodes to jump/drag on the next click.\n network.setOptions({ physics: { enabled: false, stabilization: { enabled: false } } });\n physicsOn = false;\n document.getElementById('btn-physics').classList.remove('active');\n initLoader.classList.add('fade-out');\n setTimeout(() => initLoader.remove(), 420);\n});\n\n// \u2500\u2500 Central filter \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction applyFilters(skipLoader) {\n withLoader(skipLoader, () => {\n const nodeUpdates = [];\n const edgeUpdates = [];\n\n NODES_DATA.forEach(n => {\n let hidden = false;\n if (hiddenNodeKinds.has(n.kind)) hidden = true;\n else if (n.degree < minDegree) hidden = true;\n else if (focusActive && !focusSet.has(n.id)) hidden = true;\n else if (searchActive && !searchIds.has(n.id)) hidden = true;\n nodeUpdates.push({ id: n.id, hidden });\n });\n\n EDGES_DATA.forEach(e => {\n const hidden = hiddenEdgeKinds.has(e.ekind);\n edgeUpdates.push({ id: e.id, hidden });\n });\n\n nodesDS.update(nodeUpdates);\n edgesDS.update(edgeUpdates);\n });\n}\n\n// \u2500\u2500 Loader helper \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction withLoader(skip, fn) {\n const loader = document.getElementById('loader');\n if (skip) { fn(); return; }\n loader.style.display = 'flex';\n requestAnimationFrame(() => requestAnimationFrame(() => { fn(); loader.style.display = 'none'; }));\n}\n\n// \u2500\u2500 Panel tabs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\ndocument.querySelectorAll('.tab').forEach(tab => {\n tab.addEventListener('click', () => {\n document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));\n tab.classList.add('active');\n const t = tab.dataset.tab;\n document.getElementById('tab-detail').style.display = t === 'detail' ? '' : 'none';\n document.getElementById('tab-legend').style.display = t === 'legend' ? '' : 'none';\n document.getElementById('tab-filters').style.display = t === 'filters' ? '' : 'none';\n });\n});\n\n// \u2500\u2500 History \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction pushHistory(nodeId) {\n navHistory.splice(histIdx + 1);\n navHistory.push(nodeId);\n if (navHistory.length > 50) navHistory.shift();\n histIdx = navHistory.length - 1;\n updateHistoryNav();\n}\n\nfunction updateHistoryNav() {\n const n = nodeById[navHistory[histIdx]];\n const label = document.getElementById('hist-label');\n const back = document.getElementById('hist-back');\n const fwd = document.getElementById('hist-forward');\n if (!label) return; // elements may not exist (e.g. panel is in empty/path state)\n label.textContent = n ? n.label : 'no selection';\n back.disabled = histIdx <= 0;\n fwd.disabled = histIdx >= navHistory.length - 1;\n}\n\ndocument.getElementById('hist-back').addEventListener('click', () => {\n if (histIdx > 0) { histIdx--; updateHistoryNav(); showDetail(navHistory[histIdx], false); network.selectNodes([navHistory[histIdx]]); }\n});\ndocument.getElementById('hist-forward').addEventListener('click', () => {\n if (histIdx < navHistory.length - 1) { histIdx++; updateHistoryNav(); showDetail(navHistory[histIdx], false); network.selectNodes([navHistory[histIdx]]); }\n});\n\n// \u2500\u2500 Detail panel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction showDetail(nodeId, addToHistory) {\n if (addToHistory !== false) pushHistory(nodeId);\n const n = nodeById[nodeId];\n if (!n) return;\n\n document.getElementById('tab-detail').innerHTML =\n '<div id=\"history-nav\">' +\n '<button class=\"hist-btn\" id=\"hist-back\"' + (histIdx <= 0 ? ' disabled' : '') + '>\u2039</button>' +\n '<span id=\"hist-label\" title=\"' + esc(n.label) + '\">' + esc(n.label) + '</span>' +\n '<button class=\"hist-btn\" id=\"hist-forward\"' + (histIdx >= navHistory.length - 1 ? ' disabled' : '') + '>\u203A</button>' +\n '</div>' +\n '<div class=\"detail-name\">' + esc(n.label) + '</div>' +\n '<span class=\"detail-kind\">' + esc(n.kind) + '</span>' +\n '<div class=\"detail-row\"><span class=\"detail-label\">file</span><span class=\"detail-val\">' + esc(n.filePath) + ':' + n.startLine + '</span></div>' +\n (n.qualifiedName !== n.label ? '<div class=\"detail-row\"><span class=\"detail-label\">qualified</span><span class=\"detail-val\">' + esc(n.qualifiedName) + '</span></div>' : '') +\n '<div class=\"detail-row\"><span class=\"detail-label\">degree</span><span class=\"detail-val\">' + n.degree + ' connections</span></div>' +\n '<div class=\"detail-row\"><span class=\"detail-label\">exported</span><span class=\"detail-val\">' + (n.isExported ? '\u2713' : '\u2014') + '</span></div>' +\n (n.signature ? '<div class=\"detail-sig\">' + esc(n.signature) + '</div>' : '') +\n '<div class=\"detail-actions\">' +\n '<button class=\"action-btn\" data-action=\"copy\" data-ref=\"' + esc(n.filePath + ':' + n.startLine) + '\">\u2398 Copy ref</button>' +\n '<button class=\"action-btn\" data-action=\"path-from\" data-nodeid=\"' + esc(nodeId) + '\">\u27F6 Path from here</button>' +\n '</div>';\n\n // Re-attach history buttons\n document.getElementById('hist-back').addEventListener('click', () => {\n if (histIdx > 0) { histIdx--; updateHistoryNav(); showDetail(navHistory[histIdx], false); network.selectNodes([navHistory[histIdx]]); }\n });\n document.getElementById('hist-forward').addEventListener('click', () => {\n if (histIdx < navHistory.length - 1) { histIdx++; updateHistoryNav(); showDetail(navHistory[histIdx], false); network.selectNodes([navHistory[histIdx]]); }\n });\n}\n\nfunction copyToClipboard(text) {\n navigator.clipboard.writeText(text).catch(() => {});\n}\n\n// \u2500\u2500 Panel event delegation (replaces all inline onclick) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\ndocument.getElementById('panel-content').addEventListener('click', function(e) {\n var el = e.target.closest('[data-action]');\n if (!el) return;\n var action = el.dataset.action;\n if (action === 'copy') { copyToClipboard(el.dataset.ref); }\n if (action === 'path-from') { setPathFrom(el.dataset.nodeid); }\n if (action === 'show-detail') { showDetail(el.dataset.nodeid, true); network.selectNodes([el.dataset.nodeid]); }\n if (action === 'reset') { resetToEmpty(); }\n});\n\n// \u2500\u2500 Network click handler \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet lastSelectedId = null;\n\nnetwork.on('click', params => {\n if (params.nodes.length === 0) {\n if (pathMode) exitPath(true);\n lastSelectedId = null;\n return;\n }\n\n const nodeId = params.nodes[0];\n\n if (pathMode) {\n handlePathClick(nodeId);\n return;\n }\n\n if (lastSelectedId && lastSelectedId !== nodeId) {\n runPathBFS(lastSelectedId, nodeId);\n lastSelectedId = null;\n return;\n }\n\n lastSelectedId = nodeId;\n showDetail(nodeId, true);\n network.focus(nodeId, { scale: 1.5, animation: { duration: 300, easingFunction: 'easeInOutQuad' } });\n\n document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));\n document.querySelector('[data-tab=\"detail\"]').classList.add('active');\n document.getElementById('tab-detail').style.display = '';\n document.getElementById('tab-legend').style.display = 'none';\n document.getElementById('tab-filters').style.display = 'none';\n});\n\nnetwork.on('doubleClick', params => {\n if (focusActive && params.nodes.length === 0) exitFocus();\n});\n\n// \u2500\u2500 Focus mode \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\ndocument.getElementById('btn-focus').addEventListener('click', () => {\n if (focusActive) { exitFocus(); return; }\n const selected = network.getSelectedNodes();\n if (selected.length === 0) {\n document.getElementById('btn-focus').classList.add('warn');\n setTimeout(() => document.getElementById('btn-focus').classList.remove('warn'), 800);\n return;\n }\n enterFocus(selected[0]);\n});\n\nfunction enterFocus(nodeId) {\n exitPath(false);\n focusActive = true;\n focusSet = new Set([nodeId, ...(adj[nodeId] ?? [])]);\n document.getElementById('btn-focus').classList.add('active');\n applyFilters();\n}\n\nfunction exitFocus() {\n focusActive = false;\n focusSet = new Set();\n document.getElementById('btn-focus').classList.remove('active');\n applyFilters();\n}\n\n// \u2500\u2500 Path mode \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\ndocument.getElementById('btn-path').addEventListener('click', () => {\n if (pathMode) { exitPath(true); } else { enterPath(); }\n});\n\nfunction enterPath() {\n exitFocus();\n pathMode = true;\n pathStep = 1;\n pathFromId = null;\n pathToId = null;\n clearPathHighlight();\n document.getElementById('btn-path').classList.add('active');\n document.getElementById('path-bar').style.display = 'block';\n document.getElementById('path-bar').textContent = 'Click a node to set start\u2026';\n}\n\nfunction exitPath(resetHighlight) {\n pathMode = false;\n pathStep = 0;\n document.getElementById('btn-path').classList.remove('active');\n document.getElementById('path-bar').style.display = 'none';\n if (resetHighlight) clearPathHighlight();\n}\n\nfunction setPathFrom(nodeId) {\n enterPath();\n handlePathClick(nodeId);\n}\n\nfunction handlePathClick(nodeId) {\n if (pathStep === 1) {\n pathFromId = nodeId;\n pathStep = 2;\n document.getElementById('path-bar').textContent = 'From: ' + (nodeById[nodeId] && nodeById[nodeId].label || nodeId) + ' \u2014 now click destination\u2026';\n network.selectNodes([nodeId]);\n } else if (pathStep === 2) {\n pathToId = nodeId;\n exitPath(false);\n runPathBFS(pathFromId, pathToId);\n }\n}\n\nfunction runPathBFS(fromId, toId) {\n if (fromId === toId) return;\n const prev = new Map();\n const queue = [fromId];\n const visited = new Set([fromId]);\n\n outer: while (queue.length > 0) {\n const cur = queue.shift();\n const neighbors = adj[cur] || new Set();\n for (const nb of neighbors) {\n if (!visited.has(nb)) {\n visited.add(nb);\n prev.set(nb, cur);\n if (nb === toId) break outer;\n queue.push(nb);\n }\n }\n }\n\n if (!prev.has(toId)) {\n showPathResult([], fromId, toId);\n return;\n }\n\n const pathIds = [];\n let cur = toId;\n while (cur !== undefined) { pathIds.unshift(cur); cur = prev.get(cur); }\n\n highlightPath(pathIds);\n showPathResult(pathIds, fromId, toId);\n}\n\nfunction highlightPath(pathIds) {\n clearPathHighlight();\n pathHighlightActive = true;\n\n const pathSet = new Set(pathIds);\n const DIM = { background: '#141420', border: '#1a1a2e', highlight: { background: '#1a1a2e', border: '#2a2a3e' } };\n const GOLD = { background: '#f39c12', border: '#e67e22', highlight: { background: '#ffd700', border: '#f39c12' } };\n\n const nodeUpdates = NODES_DATA.map(n => ({ id: n.id, color: pathSet.has(n.id) ? GOLD : DIM }));\n nodesDS.update(nodeUpdates);\n\n const pathEdgeIds = new Set();\n for (let i = 0; i < pathIds.length - 1; i++) {\n const eid = edgeMap[pathIds[i] + '|' + pathIds[i + 1]];\n if (eid !== undefined) pathEdgeIds.add(eid);\n }\n\n const edgeUpdates = EDGES_DATA.map(e => ({\n id: e.id,\n color: pathEdgeIds.has(e.id) ? { color: '#f39c12', opacity: 1 } : { color: '#1a1a2e', opacity: 0.15 },\n width: pathEdgeIds.has(e.id) ? 3 : 1,\n }));\n edgesDS.update(edgeUpdates);\n\n network.selectNodes(pathIds);\n network.fit({ nodes: pathIds, animation: { duration: 500, easingFunction: 'easeInOutQuad' } });\n}\n\nfunction clearPathHighlight() {\n if (!pathHighlightActive) return;\n pathHighlightActive = false;\n nodesDS.update(NODES_DATA.map(n => ({ id: n.id, color: originalColors[n.id], width: n.borderWidth })));\n edgesDS.update(EDGES_DATA.map(e => ({ id: e.id, color: originalEdgeColors[e.id], width: e.width })));\n}\n\nfunction nodeCard(n) {\n if (!n) return '';\n return '<div class=\"node-card\">' +\n '<div class=\"node-card-name\">' + esc(n.label) + '</div>' +\n '<span class=\"detail-kind\">' + esc(n.kind) + '</span>' +\n '<div class=\"detail-row\" style=\"margin-top:6px\"><span class=\"detail-label\">file</span><span class=\"detail-val\">' + esc(n.filePath) + ':' + n.startLine + '</span></div>' +\n '<div class=\"detail-row\"><span class=\"detail-label\">degree</span><span class=\"detail-val\">' + n.degree + ' connections</span></div>' +\n (n.signature ? '<div class=\"detail-sig\" style=\"margin-top:6px\">' + esc(n.signature) + '</div>' : '') +\n '</div>';\n}\n\nfunction showPathResult(pathIds, fromId, toId) {\n document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));\n document.querySelector('[data-tab=\"detail\"]').classList.add('active');\n document.getElementById('tab-detail').style.display = '';\n document.getElementById('tab-legend').style.display = 'none';\n document.getElementById('tab-filters').style.display = 'none';\n\n const from = nodeById[fromId];\n const to = nodeById[toId];\n\n if (pathIds.length === 0) {\n document.getElementById('tab-detail').innerHTML =\n '<div id=\"history-nav\">' +\n '<button class=\"hist-btn\" id=\"hist-back\" disabled>\u2039</button>' +\n '<span id=\"hist-label\">path result</span>' +\n '<button class=\"hist-btn\" id=\"hist-forward\" disabled>\u203A</button>' +\n '</div>' +\n '<p style=\"color:#e67e22;font-size:12px;margin-top:8px\">No path found between <b>' + esc(from && from.label) + '</b> and <b>' + esc(to && to.label) + '</b>.</p>' +\n nodeCard(from) +\n nodeCard(to) +\n '<button class=\"action-btn\" style=\"margin-top:10px\" data-action=\"reset\">\u2715 Clear</button>';\n return;\n }\n\n const steps = pathIds.map((id, i) => {\n const n = nodeById[id];\n return (i > 0 ? '<div class=\"path-connector\">\u2502</div>' : '') +\n '<div class=\"path-step\">' +\n '<span class=\"path-step-num\">' + (i + 1) + '.</span>' +\n '<span>' +\n '<span class=\"path-step-name\" data-action=\"show-detail\" data-nodeid=\"' + esc(id) + '\">' + esc(n && n.label || id) + '</span>' +\n '<span class=\"path-step-kind\"> ' + esc(n && n.kind || '') + '</span>' +\n '</span>' +\n '</div>';\n }).join('');\n\n document.getElementById('tab-detail').innerHTML =\n '<div id=\"history-nav\">' +\n '<button class=\"hist-btn\" id=\"hist-back\" disabled>\u2039</button>' +\n '<span id=\"hist-label\">path: ' + pathIds.length + ' hops</span>' +\n '<button class=\"hist-btn\" id=\"hist-forward\" disabled>\u203A</button>' +\n '</div>' +\n '<div style=\"font-size:11px;color:#546e7a;margin-bottom:12px\">' + esc(from && from.label) + ' \u2192 ' + esc(to && to.label) + '</div>' +\n '<div class=\"path-result\">' + steps + '</div>' +\n '<div class=\"path-endpoints\">' +\n '<div class=\"path-endpoint-label\">From</div>' + nodeCard(from) +\n '<div class=\"path-endpoint-label\" style=\"margin-top:10px\">To</div>' + nodeCard(to) +\n '</div>' +\n '<button class=\"action-btn\" style=\"margin-top:12px\" data-action=\"reset\">\u2715 Clear</button>';\n}\n\n// \u2500\u2500 Node kind filter \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\ndocument.querySelectorAll('.legend-item[data-nkind]').forEach(item => {\n item.addEventListener('click', () => {\n const kind = item.dataset.nkind;\n hiddenNodeKinds.has(kind) ? hiddenNodeKinds.delete(kind) : hiddenNodeKinds.add(kind);\n item.classList.toggle('dimmed', hiddenNodeKinds.has(kind));\n applyFilters();\n });\n});\n\n// \u2500\u2500 Edge kind filter \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\ndocument.querySelectorAll('.legend-item[data-ekind]').forEach(item => {\n item.addEventListener('click', () => {\n const kind = item.dataset.ekind;\n hiddenEdgeKinds.has(kind) ? hiddenEdgeKinds.delete(kind) : hiddenEdgeKinds.add(kind);\n item.classList.toggle('dimmed', hiddenEdgeKinds.has(kind));\n applyFilters();\n });\n});\n\n// \u2500\u2500 Degree slider \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\ndocument.getElementById('degree-slider').addEventListener('input', e => {\n minDegree = parseInt(e.target.value, 10);\n document.getElementById('degree-val').textContent = String(minDegree);\n applyFilters();\n});\n\n// \u2500\u2500 Search \u2014 Feature 1: glow instead of hide \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet searchTimer = null;\ndocument.getElementById('search').addEventListener('input', e => {\n clearTimeout(searchTimer);\n const q = e.target.value.trim().toLowerCase();\n searchTimer = setTimeout(() => {\n withLoader(false, () => {\n if (!q) {\n searchActive = false;\n searchIds = new Set();\n // Restore original colors\n nodesDS.update(NODES_DATA.map(n => ({\n id: n.id, color: originalColors[n.id], size: n.size, borderWidth: n.borderWidth,\n })));\n } else {\n const matches = NODES_DATA.filter(n =>\n n.label.toLowerCase().includes(q) ||\n n.qualifiedName.toLowerCase().includes(q) ||\n n.filePath.toLowerCase().includes(q)\n );\n searchActive = true;\n searchIds = new Set(matches.map(n => n.id));\n // Apply glow to matching, dim non-matching \u2014 no hiding\n const DIM_COLOR = { background: '#111118', border: '#1a1a2a', highlight: { background: '#111118', border: '#1a1a2a' }, hover: { background: '#111118', border: '#1a1a2a' } };\n const GLOW_BORDER = '#ffffff';\n nodesDS.update(NODES_DATA.map(n => {\n if (searchIds.has(n.id)) {\n return { id: n.id, color: { ...originalColors[n.id], border: GLOW_BORDER }, size: n.size * 1.4, borderWidth: 3 };\n } else {\n return { id: n.id, color: DIM_COLOR, size: n.size, borderWidth: n.borderWidth };\n }\n }));\n }\n // Still apply other filters (kind/degree) for hidden state\n applyFilters(true);\n if (searchActive && searchIds.size > 0) {\n network.fit({ nodes: [...searchIds], animation: { duration: 400, easingFunction: 'easeInOutQuad' } });\n } else if (!searchActive) {\n network.fit({ animation: { duration: 400, easingFunction: 'easeInOutQuad' } });\n }\n });\n }, 180);\n});\n\n// \u2500\u2500 Toolbar buttons \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\ndocument.getElementById('btn-fit').addEventListener('click', () =>\n network.fit({ animation: { duration: 600, easingFunction: 'easeInOutQuad' } })\n);\n\nlet physicsOn = true;\ndocument.getElementById('btn-physics').addEventListener('click', () => {\n physicsOn = !physicsOn;\n network.setOptions({ physics: { enabled: physicsOn } });\n document.getElementById('btn-physics').classList.toggle('active', physicsOn);\n document.getElementById('physics-speed-row').style.display = physicsOn ? 'flex' : 'none';\n});\n\ndocument.getElementById('physics-speed').addEventListener('input', function() {\n const v = parseInt(this.value); // 1..10\n // Damping: high value = slow (lots of friction), low value = fast (little friction)\n // Speed 1 \u2192 damping 0.9, Speed 10 \u2192 damping 0.05\n const damping = 0.9 - (v - 1) * (0.85 / 9);\n network.setOptions({ physics: { forceAtlas2Based: { damping: Math.round(damping * 100) / 100 } } });\n});\n\n\ndocument.getElementById('btn-png').addEventListener('click', () => {\n const canvas = document.querySelector('#graph canvas');\n if (!canvas) return;\n const tmp = document.createElement('canvas');\n tmp.width = canvas.width;\n tmp.height = canvas.height;\n const ctx = tmp.getContext('2d');\n ctx.fillStyle = '#0a0a0f';\n ctx.fillRect(0, 0, tmp.width, tmp.height);\n ctx.drawImage(canvas, 0, 0);\n const a = document.createElement('a');\n a.href = tmp.toDataURL('image/png');\n a.download = 'kirograph-' + Date.now() + '.png';\n a.click();\n});\n\n// \u2500\u2500 Feature 2: Cluster by directory \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet clusterActive = false;\nconst clusterIds = [];\nlet savedPositions = {};\n\nfunction setClusterButtonsVisible(visible) {\n ['btn-focus', 'btn-path', 'btn-heat'].forEach(id => {\n document.getElementById(id).style.display = visible ? '' : 'none';\n });\n}\n\ndocument.getElementById('btn-cluster').addEventListener('click', () => {\n if (clusterActive) {\n // \u2500\u2500 Uncluster \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n clusterIds.length = 0;\n clusterActive = false;\n document.getElementById('btn-cluster').classList.remove('active');\n\n // Restore nodes at saved positions with physics off so they snap back exactly\n network.setOptions({ physics: { enabled: false } });\n nodesDS.clear();\n edgesDS.clear();\n nodesDS.add(NODES_DATA.map(n => ({\n ...n,\n hidden: false,\n color: originalColors[n.id],\n x: savedPositions[n.id] ? savedPositions[n.id].x : undefined,\n y: savedPositions[n.id] ? savedPositions[n.id].y : undefined,\n })));\n edgesDS.add(EDGES_DATA.map(e => ({ ...e, hidden: false, color: originalEdgeColors[e.id] })));\n network.fit({ animation: { duration: 400, easingFunction: 'easeInOutQuad' } });\n\n // Restore hidden buttons\n setClusterButtonsVisible(true);\n\n } else {\n // \u2500\u2500 Cluster \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n // Deactivate incompatible modes before clustering\n if (heatActive) {\n heatActive = false;\n document.getElementById('btn-heat').classList.remove('active');\n document.getElementById('heat-legend').style.display = 'none';\n nodesDS.update(NODES_DATA.map(n => ({ id: n.id, color: originalColors[n.id] })));\n }\n if (focusActive) exitFocus();\n if (pathMode) exitPath(true);\n network.unselectAll();\n\n // Snapshot positions before clustering\n savedPositions = network.getPositions(NODES_DATA.map(n => n.id));\n\n // Hide incompatible buttons\n setClusterButtonsVisible(false);\n\n const dirs = [...new Set(NODES_DATA.map(n => n.dir).filter(Boolean))];\n dirs.forEach(dir => {\n const dirNodes = NODES_DATA.filter(n => n.dir === dir);\n if (dirNodes.length < 2) return;\n const nodeIds = new Set(dirNodes.map(n => n.id));\n const cid = 'cluster:' + dir;\n network.cluster({\n clusterNodeProperties: {\n id: cid,\n label: '\\\\uD83D\\\\uDCC1 ' + dir + ' (' + dirNodes.length + ')',\n shape: 'box',\n color: { background: '#1e1e3a', border: '#7986cb', highlight: { background: '#2a2a4e', border: '#c792ea' } },\n font: { size: 13, color: '#c792ea', face: 'monospace' },\n borderWidth: 2,\n size: 20,\n },\n clusterEdgeProperties: {\n color: { color: '#ffffff', opacity: 0.7 },\n dashes: true,\n width: 2,\n smooth: { type: 'continuous' },\n },\n joinCondition: function(nodeOptions) { return nodeIds.has(nodeOptions.id); },\n });\n clusterIds.push(cid);\n });\n clusterActive = true;\n document.getElementById('btn-cluster').classList.add('active');\n }\n});\n\n// \u2500\u2500 Feature 3: Minimap \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction drawMinimap() {\n const canvas = document.getElementById('minimap-canvas');\n const ctx = canvas.getContext('2d');\n const W = 180, H = 120;\n ctx.clearRect(0, 0, W, H);\n ctx.fillStyle = 'rgba(10,10,15,0.9)';\n ctx.fillRect(0, 0, W, H);\n\n const positions = network.getPositions();\n const ids = Object.keys(positions);\n if (ids.length === 0) return;\n\n let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;\n ids.forEach(id => {\n minX = Math.min(minX, positions[id].x); maxX = Math.max(maxX, positions[id].x);\n minY = Math.min(minY, positions[id].y); maxY = Math.max(maxY, positions[id].y);\n });\n const rangeX = maxX - minX || 1, rangeY = maxY - minY || 1;\n const pad = 10;\n const scaleX = (W - pad * 2) / rangeX, scaleY = (H - pad * 2) / rangeY;\n const scale = Math.min(scaleX, scaleY);\n\n function toCanvas(x, y) {\n return {\n cx: pad + (x - minX) * scale,\n cy: pad + (y - minY) * scale,\n };\n }\n\n // Draw edges as thin lines\n ctx.strokeStyle = 'rgba(121,134,203,0.2)';\n ctx.lineWidth = 0.5;\n EDGES_DATA.forEach(e => {\n const fp = positions[e.from], tp = positions[e.to];\n if (!fp || !tp) return;\n const f = toCanvas(fp.x, fp.y), t = toCanvas(tp.x, tp.y);\n ctx.beginPath(); ctx.moveTo(f.cx, f.cy); ctx.lineTo(t.cx, t.cy); ctx.stroke();\n });\n\n // Draw nodes as dots\n ids.forEach(id => {\n const p = positions[id];\n const c = toCanvas(p.x, p.y);\n const n = nodeById[id];\n ctx.beginPath();\n ctx.arc(c.cx, c.cy, 2, 0, Math.PI * 2);\n ctx.fillStyle = (n && n.color && n.color.background) ? n.color.background : '#546e7a';\n ctx.fill();\n });\n\n // Draw viewport rectangle\n const vp = network.getViewPosition();\n const vs = network.getScale();\n const graphEl = document.getElementById('graph');\n const vpW = graphEl.clientWidth / vs, vpH = graphEl.clientHeight / vs;\n const vpMinX = vp.x - vpW / 2, vpMinY = vp.y - vpH / 2;\n const tl = toCanvas(vpMinX, vpMinY);\n const br = toCanvas(vpMinX + vpW, vpMinY + vpH);\n ctx.strokeStyle = 'rgba(199,146,234,0.7)';\n ctx.lineWidth = 1;\n ctx.strokeRect(tl.cx, tl.cy, br.cx - tl.cx, br.cy - tl.cy);\n}\n\nnetwork.on('afterDrawing', drawMinimap);\n\ndocument.getElementById('minimap').addEventListener('click', function(e) {\n const rect = this.getBoundingClientRect();\n const W = 180, H = 120;\n const mx = e.clientX - rect.left, my = e.clientY - rect.top;\n const positions = network.getPositions();\n const ids = Object.keys(positions);\n if (ids.length === 0) return;\n let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;\n ids.forEach(id => {\n minX = Math.min(minX, positions[id].x); maxX = Math.max(maxX, positions[id].x);\n minY = Math.min(minY, positions[id].y); maxY = Math.max(maxY, positions[id].y);\n });\n const pad = 10;\n const rangeX = maxX - minX || 1, rangeY = maxY - minY || 1;\n const scaleX = (W - pad * 2) / rangeX, scaleY = (H - pad * 2) / rangeY;\n const scale = Math.min(scaleX, scaleY);\n const gx = (mx - pad) / scale + minX;\n const gy = (my - pad) / scale + minY;\n network.moveTo({ position: { x: gx, y: gy }, animation: { duration: 300, easingFunction: 'easeInOutQuad' } });\n});\n\n// \u2500\u2500 Feature 4: Right-click context menu \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst ctxMenu = document.getElementById('ctx-menu');\nlet ctxNodeId = null;\n\nnetwork.on('oncontext', function(params) {\n params.event.preventDefault();\n const nodeId = network.getNodeAt(params.pointer.DOM);\n ctxNodeId = nodeId || null;\n if (!nodeId) { ctxMenu.style.display = 'none'; return; }\n ctxMenu.innerHTML =\n '<div class=\"ctx-item\" data-ctx=\"focus-neighbors\" data-nodeid=\"' + nodeId + '\">\u25CE Focus neighbors</div>' +\n '<div class=\"ctx-item\" data-ctx=\"path-from\" data-nodeid=\"' + nodeId + '\">\u27F6 Path from here</div>' +\n '<div class=\"ctx-sep\"></div>' +\n '<div class=\"ctx-item\" data-ctx=\"copy-id\" data-nodeid=\"' + nodeId + '\">\u2398 Copy ID</div>' +\n '<div class=\"ctx-item\" data-ctx=\"copy-path\" data-nodeid=\"' + nodeId + '\">\u2398 Copy file path</div>' +\n '<div class=\"ctx-sep\"></div>' +\n '<div class=\"ctx-item\" data-ctx=\"highlight-kind\" data-nodeid=\"' + nodeId + '\">\u25C8 Highlight same kind</div>';\n ctxMenu.style.display = 'block';\n ctxMenu.style.left = params.event.clientX + 'px';\n ctxMenu.style.top = params.event.clientY + 'px';\n});\n\ndocument.addEventListener('click', () => { ctxMenu.style.display = 'none'; });\n\nctxMenu.addEventListener('click', function(e) {\n const item = e.target.closest('[data-ctx]');\n if (!item) return;\n const action = item.dataset.ctx;\n const nid = item.dataset.nodeid;\n ctxMenu.style.display = 'none';\n if (action === 'focus-neighbors') {\n enterFocus(nid);\n } else if (action === 'path-from') {\n setPathFrom(nid);\n } else if (action === 'copy-id') {\n copyToClipboard(nid);\n } else if (action === 'copy-path') {\n const n = nodeById[nid];\n copyToClipboard(n ? n.filePath : nid);\n } else if (action === 'highlight-kind') {\n const n = nodeById[nid];\n if (!n) return;\n const kind = n.kind;\n const DIM = { background: '#111118', border: '#1a1a2a', highlight: { background: '#111118', border: '#1a1a2a' }, hover: { background: '#111118', border: '#1a1a2a' } };\n pathHighlightActive = true;\n nodesDS.update(NODES_DATA.map(nd => ({\n id: nd.id,\n color: nd.kind === kind ? originalColors[nd.id] : DIM,\n })));\n }\n});\n\n// \u2500\u2500 Feature 5: Heat map overlay \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet heatActive = false;\n\nfunction hslToHex(h, s, l) {\n s /= 100; l /= 100;\n const a = s * Math.min(l, 1 - l);\n const f = n => { const k = (n + h / 30) % 12; const c = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1); return Math.round(255 * c).toString(16).padStart(2, '0'); };\n return '#' + f(0) + f(8) + f(4);\n}\n\ndocument.getElementById('btn-heat').addEventListener('click', () => {\n heatActive = !heatActive;\n document.getElementById('btn-heat').classList.toggle('active', heatActive);\n document.getElementById('heat-legend').style.display = heatActive ? 'block' : 'none';\n if (heatActive) {\n const times = NODES_DATA.map(n => n.lastModified).filter(t => t > 0);\n const minT = Math.min(...times), maxT = Math.max(...times);\n const range = maxT - minT || 1;\n nodesDS.update(NODES_DATA.map(n => {\n const t = n.lastModified || minT;\n const ratio = (t - minT) / range; // 1 = newest, 0 = oldest\n // newest = warm red (H=0), oldest = cool blue (H=210)\n const h = Math.round((1 - ratio) * 210);\n const bg = hslToHex(h, 70, 45);\n return { id: n.id, color: { background: bg, border: hslToHex(h, 70, 60), highlight: { background: bg, border: '#fff' }, hover: { background: hslToHex(h, 70, 55), border: '#fff' } } };\n }));\n } else {\n nodesDS.update(NODES_DATA.map(n => ({ id: n.id, color: originalColors[n.id] })));\n }\n});\n\n// Heat stop hover tooltips\ndocument.querySelectorAll('.heat-stop').forEach(function(el) {\n var tip = document.getElementById('heat-tip');\n el.addEventListener('mouseenter', function() { if (tip) tip.textContent = el.dataset.tip || ''; });\n el.addEventListener('mouseleave', function() { if (tip) tip.textContent = ''; });\n});\n\n// \u2500\u2500 Keyboard shortcuts \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\ndocument.addEventListener('keydown', e => {\n if (e.key === 'Escape') {\n if (pathMode) { exitPath(true); }\n if (focusActive) { exitFocus(); }\n }\n if (e.key === 'f' && !e.metaKey && !e.ctrlKey && e.target.tagName !== 'INPUT') {\n network.fit({ animation: { duration: 400, easingFunction: 'easeInOutQuad' } });\n }\n});\n\n// \u2500\u2500 Reset to initial state \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction resetToEmpty() {\n // 1. Restore every node and edge to its original color and make all visible\n pathHighlightActive = false;\n nodesDS.update(NODES_DATA.map(n => ({ id: n.id, hidden: false, color: originalColors[n.id] })));\n edgesDS.update(EDGES_DATA.map(e => ({ id: e.id, hidden: false, color: originalEdgeColors[e.id] })));\n\n // 2. Clear network selection and zoom back to full graph\n network.unselectAll();\n network.fit({ animation: { duration: 300, easingFunction: 'easeInOutQuad' } });\n\n // 3. Reset all state variables\n lastSelectedId = null;\n pathMode = false;\n pathStep = 0;\n pathFromId = null;\n pathToId = null;\n focusActive = false;\n focusSet = new Set();\n searchActive = false;\n searchIds = new Set();\n minDegree = 0;\n hiddenNodeKinds.clear();\n hiddenEdgeKinds.clear();\n\n // 4. Reset UI controls\n document.getElementById('btn-path').classList.remove('active');\n document.getElementById('btn-focus').classList.remove('active');\n document.getElementById('path-bar').style.display = 'none';\n document.getElementById('search').value = '';\n document.getElementById('degree-slider').value = '0';\n document.getElementById('degree-val').textContent = '0';\n document.querySelectorAll('.legend-item').forEach(el => el.classList.remove('dimmed'));\n\n // Reset heat map\n if (heatActive) {\n heatActive = false;\n document.getElementById('btn-heat').classList.remove('active');\n document.getElementById('heat-legend').style.display = 'none';\n }\n\n // Reset context menu\n document.getElementById('ctx-menu').style.display = 'none';\n ctxNodeId = null;\n\n // 5. Restore detail panel (must keep hist-nav in DOM so next showDetail doesn't crash)\n document.getElementById('tab-detail').innerHTML =\n '<div id=\"history-nav\">' +\n '<button class=\"hist-btn\" id=\"hist-back\" disabled>\u2039</button>' +\n '<span id=\"hist-label\">no selection</span>' +\n '<button class=\"hist-btn\" id=\"hist-forward\" disabled>\u203A</button>' +\n '</div>' +\n '<p class=\"detail-empty\">Click a node to inspect it.</p>';\n document.getElementById('hist-back').addEventListener('click', () => {\n if (histIdx > 0) { histIdx--; updateHistoryNav(); showDetail(navHistory[histIdx], false); network.selectNodes([navHistory[histIdx]]); }\n });\n document.getElementById('hist-forward').addEventListener('click', () => {\n if (histIdx < navHistory.length - 1) { histIdx++; updateHistoryNav(); showDetail(navHistory[histIdx], false); network.selectNodes([navHistory[histIdx]]); }\n });\n}\n\n// \u2500\u2500 In/out degree precomputation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nvar _inDeg = null, _outDeg = null;\nfunction ensureInOutDeg() {\n if (_inDeg) return;\n _inDeg = {}; _outDeg = {};\n NODES_DATA.forEach(function(n) { _inDeg[n.id] = 0; _outDeg[n.id] = 0; });\n EDGES_DATA.forEach(function(e) {\n _outDeg[e.from] = (_outDeg[e.from] || 0) + 1;\n _inDeg[e.to] = (_inDeg[e.to] || 0) + 1;\n });\n}\n\n// \u2500\u2500 Shared horizontal bar renderer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// rows: [{label, sublabel, value, color}], sorted descending, already sliced\nfunction drawHBar(canvasId, rows) {\n var canvas = document.getElementById(canvasId);\n if (!canvas) return;\n var ctx = canvas.getContext('2d');\n var W = canvas.width, H = canvas.height;\n ctx.clearRect(0, 0, W, H);\n if (!rows.length) {\n ctx.fillStyle = '#5a6280'; ctx.font = '12px monospace';\n ctx.textAlign = 'center'; ctx.textBaseline = 'middle';\n ctx.fillText('No data', W / 2, H / 2); return;\n }\n var maxVal = Math.max.apply(null, rows.map(function(r) { return r.value; })) || 1;\n var PAD_L = 210, PAD_R = 60, PAD_T = 14, PAD_B = 14;\n var barH = Math.floor((H - PAD_T - PAD_B) / rows.length);\n var barGap = Math.max(2, Math.floor(barH * 0.2));\n var barThick = barH - barGap;\n var barMaxW = W - PAD_L - PAD_R;\n\n ctx.strokeStyle = 'rgba(80,80,120,0.6)'; ctx.lineWidth = 1;\n for (var g = 0; g <= 4; g++) {\n var gx = PAD_L + Math.round((g / 4) * barMaxW);\n ctx.beginPath(); ctx.moveTo(gx, PAD_T); ctx.lineTo(gx, H - PAD_B); ctx.stroke();\n }\n\n rows.forEach(function(row, i) {\n var y = PAD_T + i * barH + Math.floor(barGap / 2);\n var w = Math.max(4, Math.round((row.value / maxVal) * barMaxW));\n var grad = ctx.createLinearGradient(PAD_L, 0, PAD_L + w, 0);\n grad.addColorStop(0, row.color); grad.addColorStop(1, row.color + '88');\n ctx.fillStyle = grad;\n ctx.beginPath(); ctx.roundRect(PAD_L, y, w, barThick, 3); ctx.fill();\n\n var lbl = row.label.length > 28 ? row.label.slice(0, 26) + '\\u2026' : row.label;\n ctx.fillStyle = '#c8d4f0'; ctx.font = '11px monospace';\n ctx.textAlign = 'right'; ctx.textBaseline = 'middle';\n ctx.fillText(lbl, PAD_L - 8, y + barThick / 2);\n ctx.fillStyle = '#c792ea'; ctx.font = 'bold 11px monospace';\n ctx.textAlign = 'left'; ctx.textBaseline = 'middle';\n ctx.fillText(String(row.value), PAD_L + w + 6, y + barThick / 2);\n });\n}\n\n// \u2500\u2500 Shared vertical bar renderer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// entries: [[label, value]], sorted descending, already sliced; colorFn(label,i)->color\nfunction drawVBar(canvasId, entries, colorFn) {\n var canvas = document.getElementById(canvasId);\n if (!canvas) return;\n var ctx = canvas.getContext('2d');\n var W = canvas.width, H = canvas.height;\n ctx.clearRect(0, 0, W, H);\n if (!entries.length) {\n ctx.fillStyle = '#5a6280'; ctx.font = '12px monospace';\n ctx.textAlign = 'center'; ctx.textBaseline = 'middle';\n ctx.fillText('No data', W / 2, H / 2); return;\n }\n var maxVal = Math.max.apply(null, entries.map(function(e) { return e[1]; })) || 1;\n var N = entries.length;\n var PAD_L = 32, PAD_R = 14, PAD_T = 14, PAD_B = 56;\n var plotW = W - PAD_L - PAD_R, plotH = H - PAD_T - PAD_B;\n var barW = Math.floor(plotW / N), barGap = Math.max(3, Math.floor(barW * 0.2));\n var barThick = barW - barGap;\n\n ctx.strokeStyle = 'rgba(80,80,120,0.6)'; ctx.lineWidth = 1;\n for (var g = 1; g <= 4; g++) {\n var gy = PAD_T + Math.round((1 - g / 4) * plotH);\n ctx.beginPath(); ctx.moveTo(PAD_L, gy); ctx.lineTo(W - PAD_R, gy); ctx.stroke();\n ctx.fillStyle = '#7a86aa'; ctx.font = '9px monospace';\n ctx.textAlign = 'right'; ctx.textBaseline = 'middle';\n ctx.fillText(String(Math.round(maxVal * g / 4)), PAD_L - 3, gy);\n }\n\n entries.forEach(function(entry, i) {\n var lbl = entry[0], val = entry[1];\n var x = PAD_L + i * barW + Math.floor(barGap / 2);\n var bh = Math.max(2, Math.round((val / maxVal) * plotH));\n var color = colorFn(lbl, i);\n var grad = ctx.createLinearGradient(0, PAD_T + plotH - bh, 0, PAD_T + plotH);\n grad.addColorStop(0, color); grad.addColorStop(1, color + '88');\n ctx.fillStyle = grad;\n ctx.beginPath(); ctx.roundRect(x, PAD_T + plotH - bh, barThick, bh, 3); ctx.fill();\n ctx.fillStyle = '#c792ea'; ctx.font = 'bold 10px monospace';\n ctx.textAlign = 'center'; ctx.textBaseline = 'bottom';\n ctx.fillText(String(val), x + barThick / 2, PAD_T + plotH - bh - 2);\n var short = lbl.length > 10 ? lbl.slice(0, 9) + '\\u2026' : lbl;\n ctx.save(); ctx.translate(x + barThick / 2, PAD_T + plotH + 6); ctx.rotate(Math.PI / 4);\n ctx.fillStyle = '#9ba3c8'; ctx.font = '9px monospace';\n ctx.textAlign = 'left'; ctx.textBaseline = 'top';\n ctx.fillText(short, 0, 0); ctx.restore();\n });\n}\n\n// \u2500\u2500 4. Top callers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction drawTopCallers() {\n ensureInOutDeg();\n var rows = NODES_DATA.slice()\n .sort(function(a, b) { return (_outDeg[b.id] || 0) - (_outDeg[a.id] || 0); })\n .slice(0, 15).filter(function(n) { return (_outDeg[n.id] || 0) > 0; })\n .map(function(n) { return { label: n.label, sublabel: n.kind, value: _outDeg[n.id] || 0, color: KIND_COLORS[n.kind] || '#546e7a' }; });\n drawHBar('chart-callers', rows);\n}\n\n// \u2500\u2500 5. Top callees \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction drawTopCallees() {\n ensureInOutDeg();\n var rows = NODES_DATA.slice()\n .sort(function(a, b) { return (_inDeg[b.id] || 0) - (_inDeg[a.id] || 0); })\n .slice(0, 15).filter(function(n) { return (_inDeg[n.id] || 0) > 0; })\n .map(function(n) { return { label: n.label, sublabel: n.kind, value: _inDeg[n.id] || 0, color: KIND_COLORS[n.kind] || '#546e7a' }; });\n drawHBar('chart-callees', rows);\n}\n\n// \u2500\u2500 6. Files by symbol count \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction drawFilesBySymbolCount() {\n var counts = {};\n NODES_DATA.forEach(function(n) { counts[n.filePath] = (counts[n.filePath] || 0) + 1; });\n var rows = Object.entries(counts)\n .sort(function(a, b) { return b[1] - a[1]; }).slice(0, 15)\n .map(function(e) {\n var parts = e[0].split('/');\n var short = parts.length > 2 ? '\\u2026/' + parts.slice(-2).join('/') : e[0];\n return { label: short, sublabel: e[0], value: e[1], color: '#7986cb' };\n });\n drawHBar('chart-files', rows);\n}\n\n// \u2500\u2500 7. Edge kind distribution \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction drawEdgeKindDistribution() {\n var canvas = document.getElementById('chart-edgekinds');\n if (!canvas) return;\n var ctx = canvas.getContext('2d');\n var W = canvas.width, H = canvas.height;\n ctx.clearRect(0, 0, W, H);\n\n var counts = {};\n EDGES_DATA.forEach(function(e) { counts[e.ekind] = (counts[e.ekind] || 0) + 1; });\n var entries = Object.entries(counts).sort(function(a, b) { return b[1] - a[1]; });\n var total = EDGES_DATA.length || 1;\n var cx = Math.round(W * 0.35), cy = H / 2;\n var outerR = Math.min(cx, cy) - 16, innerR = outerR * 0.5;\n var startAngle = -Math.PI / 2;\n\n entries.forEach(function(entry) {\n var kind = entry[0], count = entry[1];\n var angle = (count / total) * 2 * Math.PI;\n var color = EDGE_COLORS[kind] || '#546e7a';\n ctx.beginPath(); ctx.moveTo(cx, cy);\n ctx.arc(cx, cy, outerR, startAngle, startAngle + angle);\n ctx.closePath(); ctx.fillStyle = color; ctx.fill();\n ctx.strokeStyle = '#0d0d1a'; ctx.lineWidth = 2; ctx.stroke();\n startAngle += angle;\n });\n\n ctx.beginPath(); ctx.arc(cx, cy, innerR, 0, 2 * Math.PI);\n ctx.fillStyle = '#0d0d1a'; ctx.fill();\n ctx.fillStyle = '#c792ea'; ctx.font = 'bold 18px monospace';\n ctx.textAlign = 'center'; ctx.textBaseline = 'middle';\n ctx.fillText(String(EDGES_DATA.length), cx, cy - 7);\n ctx.fillStyle = '#7a86aa'; ctx.font = '10px monospace';\n ctx.fillText('edges', cx, cy + 9);\n\n var LEG_X = Math.round(W * 0.64), rowH = Math.floor((H - 28) / Math.min(entries.length, 12));\n entries.slice(0, 12).forEach(function(entry, i) {\n var kind = entry[0], count = entry[1];\n var y = 14 + i * rowH;\n var color = EDGE_COLORS[kind] || '#546e7a';\n ctx.fillStyle = color; ctx.fillRect(LEG_X, y + Math.floor(rowH / 2) - 2, 14, 3);\n ctx.fillStyle = '#b0c4d8'; ctx.font = '11px monospace';\n ctx.textAlign = 'left'; ctx.textBaseline = 'middle';\n ctx.fillText(kind, LEG_X + 18, y + rowH / 2);\n ctx.fillStyle = '#7a86aa'; ctx.font = '10px monospace'; ctx.textAlign = 'right';\n ctx.fillText(count + ' ' + ((count / total) * 100).toFixed(1) + '%', W - 6, y + rowH / 2);\n });\n}\n\n// \u2500\u2500 8. Dead code by kind \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction drawDeadCodeByKind() {\n ensureInOutDeg();\n var canvas = document.getElementById('chart-deadcode');\n if (!canvas) return;\n var ctx = canvas.getContext('2d');\n var W = canvas.width, H = canvas.height;\n ctx.clearRect(0, 0, W, H);\n\n var counts = {};\n NODES_DATA.forEach(function(n) {\n if (!n.isExported && (_inDeg[n.id] || 0) === 0)\n counts[n.kind] = (counts[n.kind] || 0) + 1;\n });\n var entries = Object.entries(counts).sort(function(a, b) { return b[1] - a[1]; });\n if (!entries.length) {\n ctx.fillStyle = '#27ae60'; ctx.font = '13px monospace';\n ctx.textAlign = 'center'; ctx.textBaseline = 'middle';\n ctx.fillText('\\u2713 No dead code detected', W / 2, H / 2); return;\n }\n drawVBar('chart-deadcode', entries, function(kind) { return KIND_COLORS[kind] || '#546e7a'; });\n}\n\n// \u2500\u2500 9. Exported vs unexported by kind \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction drawExportedRatio() {\n var canvas = document.getElementById('chart-exported');\n if (!canvas) return;\n var ctx = canvas.getContext('2d');\n var W = canvas.width, H = canvas.height;\n ctx.clearRect(0, 0, W, H);\n\n var kindData = {};\n NODES_DATA.forEach(function(n) {\n if (!kindData[n.kind]) kindData[n.kind] = { exp: 0, unexp: 0 };\n if (n.isExported) kindData[n.kind].exp++; else kindData[n.kind].unexp++;\n });\n var entries = Object.entries(kindData)\n .sort(function(a, b) { return (b[1].exp + b[1].unexp) - (a[1].exp + a[1].unexp); })\n .slice(0, 16);\n var N = entries.length;\n if (!N) return;\n\n var PAD_L = 96, PAD_R = 100, PAD_T = 32, PAD_B = 14;\n var rowH = Math.floor((H - PAD_T - PAD_B) / N);\n var rowGap = Math.max(2, Math.floor(rowH * 0.25));\n var barThick = rowH - rowGap;\n var barMaxW = W - PAD_L - PAD_R;\n\n // Legend\n ctx.fillStyle = '#c792ea'; ctx.font = 'bold 10px monospace';\n ctx.textAlign = 'left'; ctx.textBaseline = 'top';\n ctx.fillText('\\u25A0 Exported', PAD_L, 10);\n ctx.fillStyle = '#7a86aa'; ctx.fillText('\\u25A0 Unexported', PAD_L + 88, 10);\n\n entries.forEach(function(entry, i) {\n var kind = entry[0], d = entry[1];\n var total = d.exp + d.unexp;\n var y = PAD_T + i * rowH + Math.floor(rowGap / 2);\n var expW = Math.round((d.exp / total) * barMaxW);\n var color = KIND_COLORS[kind] || '#546e7a';\n\n if (expW > 0) {\n ctx.fillStyle = color;\n ctx.beginPath(); ctx.roundRect(PAD_L, y, expW, barThick, [3, 0, 0, 3]); ctx.fill();\n }\n if (expW < barMaxW) {\n ctx.fillStyle = color + '44';\n ctx.beginPath(); ctx.roundRect(PAD_L + expW, y, barMaxW - expW, barThick, [0, 3, 3, 0]); ctx.fill();\n }\n\n ctx.fillStyle = '#b0c4d8'; ctx.font = '11px monospace';\n ctx.textAlign = 'right'; ctx.textBaseline = 'middle';\n ctx.fillText(kind, PAD_L - 8, y + barThick / 2);\n ctx.fillStyle = '#7a86aa'; ctx.font = '10px monospace'; ctx.textAlign = 'left';\n ctx.fillText(d.exp + ' / ' + total, W - PAD_R + 8, y + barThick / 2);\n });\n}\n\n// \u2500\u2500 10. Directory coupling matrix \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction drawDirectoryCoupling() {\n var canvas = document.getElementById('chart-coupling');\n if (!canvas) return;\n var ctx = canvas.getContext('2d');\n var W = canvas.width, H = canvas.height;\n ctx.clearRect(0, 0, W, H);\n\n var dirCounts = {};\n NODES_DATA.forEach(function(n) { if (n.dir) dirCounts[n.dir] = (dirCounts[n.dir] || 0) + 1; });\n var dirs = Object.keys(dirCounts).sort(function(a, b) { return dirCounts[b] - dirCounts[a]; }).slice(0, 12);\n var N = dirs.length;\n if (N < 2) {\n ctx.fillStyle = '#5a6280'; ctx.font = '12px monospace';\n ctx.textAlign = 'center'; ctx.textBaseline = 'middle';\n ctx.fillText('Not enough directories', W / 2, H / 2); return;\n }\n\n var dirIdx = {};\n dirs.forEach(function(d, i) { dirIdx[d] = i; });\n var nodeDir = {};\n NODES_DATA.forEach(function(n) { if (n.dir) nodeDir[n.id] = n.dir; });\n\n var matrix = [];\n for (var i = 0; i < N; i++) { matrix.push([]); for (var j = 0; j < N; j++) matrix[i].push(0); }\n EDGES_DATA.forEach(function(e) {\n var fd = nodeDir[e.from], td = nodeDir[e.to];\n if (fd && td && fd !== td && dirIdx[fd] !== undefined && dirIdx[td] !== undefined)\n matrix[dirIdx[fd]][dirIdx[td]]++;\n });\n var maxVal = 0;\n for (var i = 0; i < N; i++) for (var j = 0; j < N; j++) if (matrix[i][j] > maxVal) maxVal = matrix[i][j];\n\n // Layout: row labels on the left, column labels at the bottom\n var PAD_TOP = 10, PAD_RIGHT = 14;\n var LABEL_W = 150; // left margin for row labels\n var LABEL_BOT = 100; // bottom margin for column labels (rotated 45\u00B0)\n\n var matrixW = W - LABEL_W - PAD_RIGHT;\n var matrixH = H - PAD_TOP - LABEL_BOT;\n var cellW = Math.floor(matrixW / N);\n var cellH = Math.floor(matrixH / N);\n var matrixTop = PAD_TOP;\n var matrixLeft = LABEL_W;\n\n // \u2500\u2500 Matrix cells \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n dirs.forEach(function(d, i) {\n var y = matrixTop + i * cellH;\n dirs.forEach(function(d2, j) {\n var x = matrixLeft + j * cellW;\n var val = matrix[i][j];\n var intensity = maxVal > 0 ? val / maxVal : 0;\n ctx.fillStyle = i === j\n ? 'rgba(20,20,48,0.8)'\n : 'rgba(121,134,203,' + (0.07 + intensity * 0.88).toFixed(2) + ')';\n ctx.fillRect(x + 1, y + 1, cellW - 2, cellH - 2);\n if (val > 0 && i !== j) {\n ctx.fillStyle = intensity > 0.45 ? '#ffffff' : '#b0badd';\n ctx.font = 'bold ' + (cellW > 42 ? '11' : '9') + 'px monospace';\n ctx.textAlign = 'center'; ctx.textBaseline = 'middle';\n ctx.fillText(String(val), x + cellW / 2, y + cellH / 2);\n }\n });\n });\n\n // \u2500\u2500 Row labels (left, right-aligned, white) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n dirs.forEach(function(d, i) {\n var y = matrixTop + i * cellH + cellH / 2;\n var short = d.length > 18 ? d.slice(0, 16) + '\\u2026' : d;\n ctx.fillStyle = '#ffffff'; ctx.font = '11px monospace';\n ctx.textAlign = 'right'; ctx.textBaseline = 'middle';\n ctx.fillText(short, matrixLeft - 8, y);\n });\n\n // \u2500\u2500 Column labels (bottom, rotated -45\u00B0, white) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n dirs.forEach(function(d, j) {\n var x = matrixLeft + j * cellW + cellW / 2;\n var y = matrixTop + matrixH + 8;\n var short = d.length > 18 ? d.slice(0, 16) + '\\u2026' : d;\n ctx.save();\n ctx.translate(x, y);\n ctx.rotate(Math.PI / 4); // 45\u00B0 downward-right \u2192 reads bottom-left to top-right\n ctx.fillStyle = '#ffffff'; ctx.font = '9px monospace';\n ctx.textAlign = 'left'; ctx.textBaseline = 'middle';\n ctx.fillText(short, 0, 0);\n ctx.restore();\n });\n}\n\n// \u2500\u2500 11. Symbol count per directory \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction drawSymbolCountPerDir() {\n var counts = {};\n NODES_DATA.forEach(function(n) { if (n.dir) counts[n.dir] = (counts[n.dir] || 0) + 1; });\n var entries = Object.entries(counts).sort(function(a, b) { return b[1] - a[1]; }).slice(0, 15);\n drawVBar('chart-dirs', entries, function(lbl, i) {\n var hue = (i * 37) % 360;\n return 'hsl(' + hue + ',55%,45%)';\n });\n}\n\n// \u2500\u2500 12. In/out scatter \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction drawInOutScatter() {\n ensureInOutDeg();\n var canvas = document.getElementById('chart-scatter');\n if (!canvas) return;\n var ctx = canvas.getContext('2d');\n var W = canvas.width, H = canvas.height;\n ctx.clearRect(0, 0, W, H);\n\n var PAD_L = 46, PAD_R = 14, PAD_T = 14, PAD_B = 46;\n var plotW = W - PAD_L - PAD_R, plotH = H - PAD_T - PAD_B;\n var maxOut = 1, maxIn = 1;\n NODES_DATA.forEach(function(n) {\n if ((_outDeg[n.id] || 0) > maxOut) maxOut = _outDeg[n.id];\n if ((_inDeg[n.id] || 0) > maxIn) maxIn = _inDeg[n.id];\n });\n\n // Grid + axis labels\n ctx.strokeStyle = 'rgba(80,80,120,0.6)'; ctx.lineWidth = 1;\n for (var g = 0; g <= 4; g++) {\n var gx = PAD_L + Math.round((g / 4) * plotW);\n var gy = PAD_T + Math.round((g / 4) * plotH);\n ctx.beginPath(); ctx.moveTo(gx, PAD_T); ctx.lineTo(gx, PAD_T + plotH); ctx.stroke();\n ctx.beginPath(); ctx.moveTo(PAD_L, gy); ctx.lineTo(PAD_L + plotW, gy); ctx.stroke();\n if (g > 0) {\n ctx.fillStyle = '#7a86aa'; ctx.font = '9px monospace';\n ctx.textAlign = 'center'; ctx.textBaseline = 'top';\n ctx.fillText(Math.round(maxOut * g / 4), gx, PAD_T + plotH + 4);\n ctx.textAlign = 'right'; ctx.textBaseline = 'middle';\n ctx.fillText(Math.round(maxIn * (1 - g / 4)), PAD_L - 3, PAD_T + Math.round((g / 4) * plotH));\n }\n }\n\n // Quadrant labels\n ctx.fillStyle = 'rgba(121,134,203,0.35)'; ctx.font = '10px monospace'; ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.fillText('Hubs', PAD_L + plotW * 0.75, PAD_T + plotH * 0.12);\n ctx.fillText('Sources', PAD_L + plotW * 0.75, PAD_T + plotH * 0.88);\n ctx.fillText('Sinks', PAD_L + plotW * 0.25, PAD_T + plotH * 0.12);\n ctx.fillText('Isolated',PAD_L + plotW * 0.25, PAD_T + plotH * 0.88);\n\n // Dots\n ctx.globalAlpha = 0.72;\n NODES_DATA.forEach(function(n) {\n var ox = _outDeg[n.id] || 0, iy = _inDeg[n.id] || 0;\n var x = PAD_L + Math.round((ox / maxOut) * plotW);\n var y = PAD_T + Math.round((1 - iy / maxIn) * plotH);\n ctx.beginPath(); ctx.arc(x, y, 3, 0, 2 * Math.PI);\n ctx.fillStyle = KIND_COLORS[n.kind] || '#546e7a'; ctx.fill();\n });\n ctx.globalAlpha = 1;\n\n // Axis labels\n ctx.fillStyle = '#7a86aa'; ctx.font = '10px monospace';\n ctx.textAlign = 'center'; ctx.textBaseline = 'bottom';\n ctx.fillText('out-degree (calls made)', PAD_L + plotW / 2, H);\n ctx.save(); ctx.translate(11, PAD_T + plotH / 2); ctx.rotate(-Math.PI / 2);\n ctx.textAlign = 'center'; ctx.textBaseline = 'top';\n ctx.fillText('in-degree (times referenced)', 0, 0); ctx.restore();\n}\n\n// \u2500\u2500 13. Average degree per file \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction drawAvgDegreePerFile() {\n var fileDeg = {}, fileCount = {};\n NODES_DATA.forEach(function(n) {\n fileDeg[n.filePath] = (fileDeg[n.filePath] || 0) + n.degree;\n fileCount[n.filePath] = (fileCount[n.filePath] || 0) + 1;\n });\n var rows = Object.keys(fileDeg).map(function(fp) {\n var avg = fileDeg[fp] / fileCount[fp];\n var parts = fp.split('/');\n var short = parts.length > 2 ? '\\u2026/' + parts.slice(-2).join('/') : fp;\n return { label: short, sublabel: fileCount[fp] + ' symbols', value: Math.round(avg * 10) / 10, color: '#00838f' };\n }).sort(function(a, b) { return b.value - a.value; }).slice(0, 15);\n drawHBar('chart-avgdeg', rows);\n}\n\n// \u2500\u2500 Charts modal \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\ndocument.getElementById('btn-charts').addEventListener('click', function() {\n document.getElementById('charts-modal').style.display = 'flex';\n drawCharts();\n});\ndocument.getElementById('charts-close').addEventListener('click', function() {\n document.getElementById('charts-modal').style.display = 'none';\n});\ndocument.getElementById('charts-modal').addEventListener('click', function(e) {\n if (e.target === this) this.style.display = 'none';\n});\n\nfunction drawCharts() {\n [\n drawBarChart, drawPieChart, drawLineChart,\n drawTopCallers, drawTopCallees, drawFilesBySymbolCount,\n drawEdgeKindDistribution, drawDeadCodeByKind, drawExportedRatio,\n drawDirectoryCoupling, drawSymbolCountPerDir, drawInOutScatter,\n drawAvgDegreePerFile,\n ].forEach(function(fn) {\n try { fn(); } catch(e) { console.warn('[kirograph chart] ' + fn.name + ':', e); }\n });\n}\n\n// \u2500\u2500 Bar chart: Top 15 most connected symbols \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction drawBarChart() {\n var canvas = document.getElementById('chart-bar');\n var ctx = canvas.getContext('2d');\n var W = canvas.width, H = canvas.height;\n ctx.clearRect(0, 0, W, H);\n\n var top = NODES_DATA.slice().sort(function(a, b) { return b.degree - a.degree; }).slice(0, 15);\n if (top.length === 0) return;\n\n var maxDeg = top[0].degree || 1;\n var PAD_L = 170, PAD_R = 54, PAD_T = 14, PAD_B = 14;\n var barH = Math.floor((H - PAD_T - PAD_B) / top.length);\n var barGap = Math.max(2, Math.floor(barH * 0.22));\n var barThick = barH - barGap;\n var barMaxW = W - PAD_L - PAD_R;\n\n // Grid lines\n ctx.strokeStyle = 'rgba(80,80,120,0.6)';\n ctx.lineWidth = 1;\n for (var g = 0; g <= 4; g++) {\n var gx = PAD_L + Math.round((g / 4) * barMaxW);\n ctx.beginPath(); ctx.moveTo(gx, PAD_T); ctx.lineTo(gx, H - PAD_B); ctx.stroke();\n }\n\n top.forEach(function(n, i) {\n var y = PAD_T + i * barH + Math.floor(barGap / 2);\n var w = Math.max(4, Math.round((n.degree / maxDeg) * barMaxW));\n var color = KIND_COLORS[n.kind] || '#546e7a';\n\n // Gradient bar\n var grad = ctx.createLinearGradient(PAD_L, 0, PAD_L + w, 0);\n grad.addColorStop(0, color);\n grad.addColorStop(1, color + '88');\n ctx.fillStyle = grad;\n ctx.beginPath();\n ctx.roundRect(PAD_L, y, w, barThick, 3);\n ctx.fill();\n\n // Node label\n var label = n.label.length > 22 ? n.label.slice(0, 20) + '\\\\u2026' : n.label;\n ctx.fillStyle = '#c8d4f0';\n ctx.font = '11px monospace';\n ctx.textAlign = 'right';\n ctx.textBaseline = 'middle';\n ctx.fillText(label, PAD_L - 8, y + barThick / 2);\n\n // Degree value\n ctx.fillStyle = '#c792ea';\n ctx.font = 'bold 11px monospace';\n ctx.textAlign = 'left';\n ctx.textBaseline = 'middle';\n ctx.fillText(String(n.degree), PAD_L + w + 6, y + barThick / 2);\n });\n}\n\n// \u2500\u2500 Pie chart: Node count by kind \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction drawPieChart() {\n var canvas = document.getElementById('chart-pie');\n var ctx = canvas.getContext('2d');\n var W = canvas.width, H = canvas.height;\n ctx.clearRect(0, 0, W, H);\n\n var counts = {};\n NODES_DATA.forEach(function(n) { counts[n.kind] = (counts[n.kind] || 0) + 1; });\n var entries = Object.entries(counts).sort(function(a, b) { return b[1] - a[1]; });\n var total = NODES_DATA.length || 1;\n\n var cx = Math.round(W * 0.36), cy = H / 2;\n var outerR = Math.min(cx, cy) - 16;\n var innerR = outerR * 0.52;\n\n var startAngle = -Math.PI / 2;\n\n entries.forEach(function(entry) {\n var kind = entry[0], count = entry[1];\n var angle = (count / total) * 2 * Math.PI;\n var color = KIND_COLORS[kind] || '#546e7a';\n\n ctx.beginPath();\n ctx.moveTo(cx, cy);\n ctx.arc(cx, cy, outerR, startAngle, startAngle + angle);\n ctx.closePath();\n ctx.fillStyle = color;\n ctx.fill();\n\n // Gap stroke\n ctx.strokeStyle = '#0d0d1a';\n ctx.lineWidth = 2;\n ctx.stroke();\n\n startAngle += angle;\n });\n\n // Donut hole\n ctx.beginPath();\n ctx.arc(cx, cy, innerR, 0, 2 * Math.PI);\n ctx.fillStyle = '#0d0d1a';\n ctx.fill();\n\n // Center label\n ctx.fillStyle = '#c792ea';\n ctx.font = 'bold 20px monospace';\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.fillText(String(total), cx, cy - 7);\n ctx.fillStyle = '#7a86aa';\n ctx.font = '10px monospace';\n ctx.fillText('nodes', cx, cy + 10);\n\n // Legend\n var LEG_X = Math.round(W * 0.68);\n var LEG_Y_START = 16;\n var rowH = Math.floor((H - LEG_Y_START * 2) / Math.min(entries.length, 14));\n\n entries.slice(0, 14).forEach(function(entry, i) {\n var kind = entry[0], count = entry[1];\n var y = LEG_Y_START + i * rowH;\n var color = KIND_COLORS[kind] || '#546e7a';\n ctx.fillStyle = color;\n ctx.beginPath();\n ctx.roundRect(LEG_X, y + Math.floor(rowH / 2) - 5, 10, 10, 2);\n ctx.fill();\n ctx.fillStyle = '#c8d4f0';\n ctx.font = '11px monospace';\n ctx.textAlign = 'left';\n ctx.textBaseline = 'middle';\n ctx.fillText(kind, LEG_X + 14, y + rowH / 2);\n var pct = ((count / total) * 100).toFixed(1);\n ctx.fillStyle = '#7a86aa';\n ctx.font = '10px monospace';\n ctx.textAlign = 'right';\n ctx.fillText(count + ' ' + pct + '%', W - 8, y + rowH / 2);\n });\n}\n\n// \u2500\u2500 Line chart: Degree distribution \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction drawLineChart() {\n var canvas = document.getElementById('chart-line');\n var ctx = canvas.getContext('2d');\n var W = canvas.width, H = canvas.height;\n ctx.clearRect(0, 0, W, H);\n\n var degMap = {};\n NODES_DATA.forEach(function(n) { degMap[n.degree] = (degMap[n.degree] || 0) + 1; });\n var maxDegVal = 0;\n Object.keys(degMap).forEach(function(k) { var v = Number(k); if (v > maxDegVal) maxDegVal = v; });\n if (maxDegVal === 0) return;\n\n var BINS = Math.min(40, maxDegVal + 1);\n var binSize = Math.ceil((maxDegVal + 1) / BINS);\n var bins = [];\n for (var b = 0; b < BINS; b++) {\n var cnt = 0;\n for (var d = b * binSize; d < (b + 1) * binSize; d++) cnt += degMap[d] || 0;\n bins.push({ deg: b * binSize, count: cnt });\n }\n var maxCount = 1;\n bins.forEach(function(b) { if (b.count > maxCount) maxCount = b.count; });\n\n var PAD_L = 44, PAD_R = 14, PAD_T = 14, PAD_B = 36;\n var plotW = W - PAD_L - PAD_R;\n var plotH = H - PAD_T - PAD_B;\n\n // Grid\n ctx.strokeStyle = 'rgba(80,80,120,0.6)';\n ctx.lineWidth = 1;\n for (var g = 0; g <= 4; g++) {\n var gy = PAD_T + Math.round((g / 4) * plotH);\n ctx.beginPath(); ctx.moveTo(PAD_L, gy); ctx.lineTo(W - PAD_R, gy); ctx.stroke();\n var gval = Math.round(maxCount * (1 - g / 4));\n ctx.fillStyle = '#7a86aa';\n ctx.font = '9px monospace';\n ctx.textAlign = 'right';\n ctx.textBaseline = 'middle';\n ctx.fillText(String(gval), PAD_L - 5, gy);\n }\n\n var pts = bins.map(function(bin, i) {\n return {\n x: PAD_L + Math.round((i / (BINS - 1 || 1)) * plotW),\n y: PAD_T + Math.round((1 - bin.count / maxCount) * plotH),\n };\n });\n\n // Area fill\n var areaGrad = ctx.createLinearGradient(0, PAD_T, 0, PAD_T + plotH);\n areaGrad.addColorStop(0, 'rgba(121,134,203,0.55)');\n areaGrad.addColorStop(1, 'rgba(121,134,203,0.04)');\n\n ctx.beginPath();\n ctx.moveTo(pts[0].x, PAD_T + plotH);\n pts.forEach(function(p) { ctx.lineTo(p.x, p.y); });\n ctx.lineTo(pts[pts.length - 1].x, PAD_T + plotH);\n ctx.closePath();\n ctx.fillStyle = areaGrad;\n ctx.fill();\n\n // Line\n ctx.beginPath();\n pts.forEach(function(p, i) { if (i === 0) ctx.moveTo(p.x, p.y); else ctx.lineTo(p.x, p.y); });\n ctx.strokeStyle = '#9fa8da';\n ctx.lineWidth = 2.5;\n ctx.lineJoin = 'round';\n ctx.stroke();\n\n // Peak dot\n var peakIdx = 0;\n bins.forEach(function(bin, i) { if (bin.count > bins[peakIdx].count) peakIdx = i; });\n ctx.beginPath();\n ctx.arc(pts[peakIdx].x, pts[peakIdx].y, 4, 0, 2 * Math.PI);\n ctx.fillStyle = '#c792ea';\n ctx.fill();\n\n // X axis labels\n var labelStep = Math.ceil(BINS / 8);\n ctx.fillStyle = '#7a86aa';\n ctx.font = '9px monospace';\n ctx.textAlign = 'center';\n ctx.textBaseline = 'top';\n bins.forEach(function(bin, i) {\n if (i % labelStep !== 0 && i !== bins.length - 1) return;\n ctx.fillText(String(bin.deg), pts[i].x, PAD_T + plotH + 6);\n });\n\n // X axis label\n ctx.fillStyle = '#9ba3c8';\n ctx.font = '10px monospace';\n ctx.textAlign = 'center';\n ctx.textBaseline = 'bottom';\n ctx.fillText('connections', PAD_L + plotW / 2, H);\n}\n\n// \u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction esc(s) {\n return String(s || '').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');\n}\n\n`;\n}\n\nfunction printExportHelp(): void {\n const c = {\n reset: '\\x1b[0m',\n bold: '\\x1b[1m',\n dim: '\\x1b[2m',\n violet: '\\x1b[38;5;99m',\n lavender: '\\x1b[38;5;141m',\n paleLavender: '\\x1b[38;5;183m',\n purple: '\\x1b[38;5;135m',\n gray: '\\x1b[90m',\n };\n\n console.log(`\\n${c.bold}${c.paleLavender}USAGE${c.reset}`);\n console.log(` ${c.lavender}kirograph export${c.reset} ${c.gray}<command>${c.reset} ${c.dim}[options] [path]${c.reset}\\n`);\n\n console.log(`${c.bold}${c.paleLavender}COMMANDS${c.reset}\\n`);\n\n const cmds = [\n { name: 'build', args: '[path]', desc: 'Generate the dashboard in .kirograph/export/' },\n { name: 'start', args: '[path]', desc: 'Generate the dashboard and open it in the browser' },\n ];\n const nameWidth = Math.max(...cmds.map(c => (c.name + ' ' + c.args).length)) + 2;\n for (const cmd of cmds) {\n const sig = cmd.name + ' ' + cmd.args;\n const pad = ' '.repeat(Math.max(0, nameWidth - sig.length));\n console.log(` ${c.lavender}${cmd.name}${c.reset} ${c.dim}${cmd.args}${c.reset}${pad}${c.gray}${cmd.desc}${c.reset}`);\n }\n\n console.log(`\\n${c.bold}${c.paleLavender}OPTIONS${c.reset}\\n`);\n console.log(` ${c.purple}-o, --output <dir>${c.reset} ${c.gray}Custom output directory${c.reset}`);\n console.log(` ${c.purple}--include-contains${c.reset} ${c.gray}Include structural contains edges (adds noise, off by default)${c.reset}`);\n console.log(` ${c.purple}-h, --help${c.reset} ${c.gray}Show this help${c.reset}\\n`);\n\n console.log(`${c.bold}${c.paleLavender}EXAMPLES${c.reset}\\n`);\n const examples: [string, string][] = [\n ['kirograph export start', 'Generate and open the dashboard in the browser'],\n ['kirograph export build', 'Generate only (no browser)'],\n ['kirograph export build -o /tmp/graph', 'Write dashboard files to a custom directory'],\n ['kirograph export start --include-contains', 'Include structural contains edges'],\n ];\n for (const [ex, desc] of examples) {\n console.log(` ${c.violet}$${c.reset} ${c.lavender}${ex}${c.reset}`);\n console.log(` ${c.dim}${desc}${c.reset}`);\n }\n console.log();\n}\n\nexport function register(program: Command): void {\n const exportCmd = program\n .command('export')\n .description('Export the graph as an interactive dashboard');\n\n exportCmd.configureHelp({ formatHelp: () => '' });\n exportCmd.helpInformation = () => { printExportHelp(); return ''; };\n\n exportCmd\n .command('build [projectPath]')\n .description('Generate the dashboard files in .kirograph/export/')\n .option('-o, --output <dir>', 'Output directory path')\n .option('--include-contains', 'Include structural contains edges (adds noise, off by default)', false)\n .action(async (projectPath, opts) => {\n await generateExport(projectPath, opts);\n });\n\n exportCmd\n .command('start [projectPath]')\n .description('Generate the dashboard files and open in the browser')\n .option('-o, --output <dir>', 'Output directory path')\n .option('--include-contains', 'Include structural contains edges (adds noise, off by default)', false)\n .action(async (projectPath, opts) => {\n const indexPath = await generateExport(projectPath, opts);\n console.log(` ${dim}Opening in browser\u2026${reset}\\n`);\n openBrowser(indexPath);\n });\n}\n"],
|
|
5
|
+
"mappings": "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AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA2gBT;AAEA,SAAS,QACP,UAAiB,UACjB,YAAoC,YAC5B;AACR,SAAO;AAAA,sBACa,KAAK,UAAU,QAAQ,CAAC;AAAA,sBACxB,KAAK,UAAU,QAAQ,CAAC;AAAA,sBACxB,KAAK,UAAU,UAAU,CAAC;AAAA,sBAC1B,KAAK,UAAU,UAAU,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA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",
|
|
6
|
+
"names": ["c"]
|
|
7
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../../../src/bin/commands/files.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA+CpC,wBAAgB,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAkC/C"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var files_exports = {};
|
|
30
|
+
__export(files_exports, {
|
|
31
|
+
register: () => register
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(files_exports);
|
|
34
|
+
var path = __toESM(require("path"));
|
|
35
|
+
var import_ui = require("../ui");
|
|
36
|
+
function printTree(nodes, prefix, metadata) {
|
|
37
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
38
|
+
const node = nodes[i];
|
|
39
|
+
const isLast = i === nodes.length - 1;
|
|
40
|
+
const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
|
|
41
|
+
const childPrefix = prefix + (isLast ? " " : "\u2502 ");
|
|
42
|
+
const meta = metadata && node.type === "file" && node.language ? ` \x1B[90m[${node.language}${node.symbolCount ? ` \xB7 ${node.symbolCount} symbols` : ""}]\x1B[0m` : "";
|
|
43
|
+
console.log(`${prefix}${connector}${node.name}${meta}`);
|
|
44
|
+
if (node.children?.length) printTree(node.children, childPrefix, metadata);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function printFlat(nodes, metadata, _prefix = "") {
|
|
48
|
+
for (const node of nodes) {
|
|
49
|
+
if (node.type === "file") {
|
|
50
|
+
const meta = metadata && node.language ? ` \x1B[90m[${node.language}]\x1B[0m` : "";
|
|
51
|
+
console.log(`${node.path}${meta}`);
|
|
52
|
+
}
|
|
53
|
+
if (node.children) printFlat(node.children, metadata);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function printGrouped(nodes, metadata) {
|
|
57
|
+
const byLang = /* @__PURE__ */ new Map();
|
|
58
|
+
function collect(ns) {
|
|
59
|
+
for (const n of ns) {
|
|
60
|
+
if (n.type === "file" && n.language) {
|
|
61
|
+
const arr = byLang.get(n.language) ?? [];
|
|
62
|
+
arr.push(n.path);
|
|
63
|
+
byLang.set(n.language, arr);
|
|
64
|
+
}
|
|
65
|
+
if (n.children) collect(n.children);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
collect(nodes);
|
|
69
|
+
for (const [lang, files] of byLang) {
|
|
70
|
+
console.log(`
|
|
71
|
+
${import_ui.violet}${import_ui.bold}${lang}${import_ui.reset} ${import_ui.dim}(${files.length})${import_ui.reset}`);
|
|
72
|
+
for (const f of files) console.log(` ${import_ui.dim}${f}${import_ui.reset}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function register(program) {
|
|
76
|
+
program.command("files [projectPath]").description("Show project file structure from the index").option("--format <fmt>", "Output format: tree, flat, grouped", "tree").option("--filter <path>", "Filter by directory prefix").option("--pattern <glob>", "Filter by glob pattern").option("--max-depth <n>", "Limit tree depth").option("--no-metadata", "Hide language/symbol counts").option("--json", "Output as JSON").action(async (projectPath, opts) => {
|
|
77
|
+
const KiroGraph = (await Promise.resolve().then(() => require("../../index.js"))).default;
|
|
78
|
+
const target = path.resolve(projectPath ?? process.cwd());
|
|
79
|
+
const cg = await KiroGraph.open(target);
|
|
80
|
+
const tree = cg.getFiles({
|
|
81
|
+
filterPath: opts.filter,
|
|
82
|
+
pattern: opts.pattern,
|
|
83
|
+
maxDepth: opts.maxDepth ? parseInt(opts.maxDepth) : void 0
|
|
84
|
+
});
|
|
85
|
+
if (opts.json) {
|
|
86
|
+
console.log(JSON.stringify(tree, null, 2));
|
|
87
|
+
cg.close();
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (opts.format === "flat") {
|
|
91
|
+
printFlat(tree, opts.metadata);
|
|
92
|
+
} else if (opts.format === "grouped") {
|
|
93
|
+
printGrouped(tree, opts.metadata);
|
|
94
|
+
} else {
|
|
95
|
+
printTree(tree, "", opts.metadata);
|
|
96
|
+
}
|
|
97
|
+
cg.close();
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
101
|
+
0 && (module.exports = {
|
|
102
|
+
register
|
|
103
|
+
});
|
|
104
|
+
//# sourceMappingURL=files.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/bin/commands/files.ts"],
|
|
4
|
+
"sourcesContent": ["import { Command } from 'commander';\nimport * as path from 'path';\nimport { violet, bold, dim, reset } from '../ui';\n\nfunction printTree(nodes: import('../../index').FileTreeNode[], prefix: string, metadata: boolean): void {\n for (let i = 0; i < nodes.length; i++) {\n const node = nodes[i];\n const isLast = i === nodes.length - 1;\n const connector = isLast ? '\u2514\u2500\u2500 ' : '\u251C\u2500\u2500 ';\n const childPrefix = prefix + (isLast ? ' ' : '\u2502 ');\n const meta = metadata && node.type === 'file' && node.language\n ? ` \\x1b[90m[${node.language}${node.symbolCount ? ` \u00B7 ${node.symbolCount} symbols` : ''}]\\x1b[0m`\n : '';\n console.log(`${prefix}${connector}${node.name}${meta}`);\n if (node.children?.length) printTree(node.children, childPrefix, metadata);\n }\n}\n\nfunction printFlat(nodes: import('../../index').FileTreeNode[], metadata: boolean, _prefix = ''): void {\n for (const node of nodes) {\n if (node.type === 'file') {\n const meta = metadata && node.language ? ` \\x1b[90m[${node.language}]\\x1b[0m` : '';\n console.log(`${node.path}${meta}`);\n }\n if (node.children) printFlat(node.children, metadata);\n }\n}\n\nfunction printGrouped(nodes: import('../../index').FileTreeNode[], metadata: boolean): void {\n const byLang = new Map<string, string[]>();\n function collect(ns: import('../../index').FileTreeNode[]): void {\n for (const n of ns) {\n if (n.type === 'file' && n.language) {\n const arr = byLang.get(n.language) ?? [];\n arr.push(n.path);\n byLang.set(n.language, arr);\n }\n if (n.children) collect(n.children);\n }\n }\n collect(nodes);\n for (const [lang, files] of byLang) {\n console.log(`\\n ${violet}${bold}${lang}${reset} ${dim}(${files.length})${reset}`);\n for (const f of files) console.log(` ${dim}${f}${reset}`);\n }\n}\n\nexport function register(program: Command): void {\n program\n .command('files [projectPath]')\n .description('Show project file structure from the index')\n .option('--format <fmt>', 'Output format: tree, flat, grouped', 'tree')\n .option('--filter <path>', 'Filter by directory prefix')\n .option('--pattern <glob>', 'Filter by glob pattern')\n .option('--max-depth <n>', 'Limit tree depth')\n .option('--no-metadata', 'Hide language/symbol counts')\n .option('--json', 'Output as JSON')\n .action(async (projectPath: string | undefined, opts: {\n format: string; filter?: string; pattern?: string;\n maxDepth?: string; metadata: boolean; json?: boolean;\n }) => {\n const KiroGraph = (await Promise.resolve().then(() => require('../../index.js'))).default;\n const target = path.resolve(projectPath ?? process.cwd());\n const cg = await KiroGraph.open(target);\n const tree = cg.getFiles({\n filterPath: opts.filter,\n pattern: opts.pattern,\n maxDepth: opts.maxDepth ? parseInt(opts.maxDepth) : undefined,\n });\n\n if (opts.json) { console.log(JSON.stringify(tree, null, 2)); cg.close(); return; }\n\n if (opts.format === 'flat') {\n printFlat(tree, opts.metadata);\n } else if (opts.format === 'grouped') {\n printGrouped(tree, opts.metadata);\n } else {\n printTree(tree, '', opts.metadata);\n }\n cg.close();\n });\n}\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,WAAsB;AACtB,gBAAyC;AAEzC,SAAS,UAAU,OAA6C,QAAgB,UAAyB;AACvG,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,UAAM,OAAO,MAAM,CAAC;AACpB,UAAM,SAAS,MAAM,MAAM,SAAS;AACpC,UAAM,YAAY,SAAS,wBAAS;AACpC,UAAM,cAAc,UAAU,SAAS,SAAS;AAChD,UAAM,OAAO,YAAY,KAAK,SAAS,UAAU,KAAK,WAClD,cAAc,KAAK,QAAQ,GAAG,KAAK,cAAc,SAAM,KAAK,WAAW,aAAa,EAAE,aACtF;AACJ,YAAQ,IAAI,GAAG,MAAM,GAAG,SAAS,GAAG,KAAK,IAAI,GAAG,IAAI,EAAE;AACtD,QAAI,KAAK,UAAU,OAAQ,WAAU,KAAK,UAAU,aAAa,QAAQ;AAAA,EAC3E;AACF;AAEA,SAAS,UAAU,OAA6C,UAAmB,UAAU,IAAU;AACrG,aAAW,QAAQ,OAAO;AACxB,QAAI,KAAK,SAAS,QAAQ;AACxB,YAAM,OAAO,YAAY,KAAK,WAAW,cAAc,KAAK,QAAQ,aAAa;AACjF,cAAQ,IAAI,GAAG,KAAK,IAAI,GAAG,IAAI,EAAE;AAAA,IACnC;AACA,QAAI,KAAK,SAAU,WAAU,KAAK,UAAU,QAAQ;AAAA,EACtD;AACF;AAEA,SAAS,aAAa,OAA6C,UAAyB;AAC1F,QAAM,SAAS,oBAAI,IAAsB;AACzC,WAAS,QAAQ,IAAgD;AAC/D,eAAW,KAAK,IAAI;AAClB,UAAI,EAAE,SAAS,UAAU,EAAE,UAAU;AACnC,cAAM,MAAM,OAAO,IAAI,EAAE,QAAQ,KAAK,CAAC;AACvC,YAAI,KAAK,EAAE,IAAI;AACf,eAAO,IAAI,EAAE,UAAU,GAAG;AAAA,MAC5B;AACA,UAAI,EAAE,SAAU,SAAQ,EAAE,QAAQ;AAAA,IACpC;AAAA,EACF;AACA,UAAQ,KAAK;AACb,aAAW,CAAC,MAAM,KAAK,KAAK,QAAQ;AAClC,YAAQ,IAAI;AAAA,IAAO,gBAAM,GAAG,cAAI,GAAG,IAAI,GAAG,eAAK,KAAK,aAAG,IAAI,MAAM,MAAM,IAAI,eAAK,EAAE;AAClF,eAAW,KAAK,MAAO,SAAQ,IAAI,KAAK,aAAG,GAAG,CAAC,GAAG,eAAK,EAAE;AAAA,EAC3D;AACF;AAEO,SAAS,SAAS,SAAwB;AAC/C,UACG,QAAQ,qBAAqB,EAC7B,YAAY,4CAA4C,EACxD,OAAO,kBAAkB,sCAAsC,MAAM,EACrE,OAAO,mBAAmB,4BAA4B,EACtD,OAAO,oBAAoB,wBAAwB,EACnD,OAAO,mBAAmB,kBAAkB,EAC5C,OAAO,iBAAiB,6BAA6B,EACrD,OAAO,UAAU,gBAAgB,EACjC,OAAO,OAAO,aAAiC,SAG1C;AACJ,UAAM,aAAa,MAAM,QAAQ,QAAQ,EAAE,KAAK,MAAM,QAAQ,gBAAgB,CAAC,GAAG;AAClF,UAAM,SAAS,KAAK,QAAQ,eAAe,QAAQ,IAAI,CAAC;AACxD,UAAM,KAAK,MAAM,UAAU,KAAK,MAAM;AACtC,UAAM,OAAO,GAAG,SAAS;AAAA,MACvB,YAAY,KAAK;AAAA,MACjB,SAAS,KAAK;AAAA,MACd,UAAU,KAAK,WAAW,SAAS,KAAK,QAAQ,IAAI;AAAA,IACtD,CAAC;AAED,QAAI,KAAK,MAAM;AAAE,cAAQ,IAAI,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;AAAG,SAAG,MAAM;AAAG;AAAA,IAAQ;AAEjF,QAAI,KAAK,WAAW,QAAQ;AAC1B,gBAAU,MAAM,KAAK,QAAQ;AAAA,IAC/B,WAAW,KAAK,WAAW,WAAW;AACpC,mBAAa,MAAM,KAAK,QAAQ;AAAA,IAClC,OAAO;AACL,gBAAU,MAAM,IAAI,KAAK,QAAQ;AAAA,IACnC;AACA,OAAG,MAAM;AAAA,EACX,CAAC;AACL;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../../../src/bin/commands/help.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGpC,wBAAgB,gBAAgB,IAAI,IAAI,CA0EvC;AAED,wBAAgB,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAS/C"}
|