@plumpslabs/kuma 2.4.10 → 2.4.12
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 +7 -4
- package/dist/index.js +2980 -1766
- package/package.json +3 -3
- package/packages/ide/studio/dist/index.js +209 -4
- package/packages/ide/studio/public/index.html +925 -142
- package/plugin.json +1 -1
- package/skills/kuma/SKILL.md +80 -0
|
@@ -505,32 +505,280 @@ table.clean-table tr:hover td {
|
|
|
505
505
|
#graph-canvas {
|
|
506
506
|
width: 100%;
|
|
507
507
|
height: 100%;
|
|
508
|
-
background:
|
|
508
|
+
background-color: #060911;
|
|
509
|
+
background-image:
|
|
510
|
+
radial-gradient(circle at 50% 50%, rgba(30, 41, 59, 0.45) 0%, transparent 85%),
|
|
511
|
+
radial-gradient(rgba(255, 255, 255, 0.05) 1px, transparent 1px);
|
|
512
|
+
background-size: 100% 100%, 28px 28px;
|
|
509
513
|
}
|
|
510
514
|
.graph-toolbar {
|
|
511
515
|
position: absolute;
|
|
512
516
|
top: 16px;
|
|
513
517
|
left: 16px;
|
|
514
518
|
display: flex;
|
|
519
|
+
align-items: center;
|
|
515
520
|
gap: 8px;
|
|
516
|
-
background: rgba(
|
|
517
|
-
padding: 6px
|
|
518
|
-
border-radius:
|
|
519
|
-
border: 1px solid
|
|
520
|
-
|
|
521
|
+
background: rgba(15, 23, 42, 0.88);
|
|
522
|
+
padding: 6px 12px;
|
|
523
|
+
border-radius: 10px;
|
|
524
|
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
525
|
+
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
|
|
526
|
+
backdrop-filter: blur(12px);
|
|
521
527
|
z-index: 10;
|
|
522
528
|
}
|
|
523
529
|
.graph-toolbar button {
|
|
524
|
-
background:
|
|
525
|
-
border: 1px solid
|
|
526
|
-
color: #
|
|
527
|
-
padding:
|
|
528
|
-
border-radius:
|
|
530
|
+
background: rgba(255, 255, 255, 0.06);
|
|
531
|
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
532
|
+
color: #f1f5f9;
|
|
533
|
+
padding: 5px 10px;
|
|
534
|
+
border-radius: 6px;
|
|
529
535
|
font-size: 11px;
|
|
536
|
+
font-weight: 500;
|
|
530
537
|
cursor: pointer;
|
|
538
|
+
transition: all 0.15s ease;
|
|
539
|
+
display: inline-flex;
|
|
540
|
+
align-items: center;
|
|
541
|
+
gap: 4px;
|
|
542
|
+
}
|
|
543
|
+
.graph-toolbar button:hover {
|
|
544
|
+
background: rgba(255, 255, 255, 0.12);
|
|
545
|
+
border-color: rgba(255, 255, 255, 0.2);
|
|
531
546
|
}
|
|
532
547
|
.graph-toolbar button.active {
|
|
533
|
-
background:
|
|
548
|
+
background: #d97706;
|
|
549
|
+
border-color: #f59e0b;
|
|
550
|
+
color: #fff;
|
|
551
|
+
}
|
|
552
|
+
.graph-toolbar select, .graph-toolbar input {
|
|
553
|
+
background: rgba(11, 15, 25, 0.85);
|
|
554
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
555
|
+
color: #f8fafc;
|
|
556
|
+
padding: 5px 10px;
|
|
557
|
+
border-radius: 6px;
|
|
558
|
+
font-size: 11px;
|
|
559
|
+
}
|
|
560
|
+
.graph-toolbar input:focus, .graph-toolbar select:focus {
|
|
561
|
+
outline: none;
|
|
562
|
+
border-color: var(--amber);
|
|
563
|
+
box-shadow: 0 0 0 2px rgba(245, 158, 11, 0.2);
|
|
564
|
+
}
|
|
565
|
+
.btn-primary {
|
|
566
|
+
background: var(--amber-dark) !important;
|
|
567
|
+
color: #fff !important;
|
|
568
|
+
font-weight: 600 !important;
|
|
569
|
+
}
|
|
570
|
+
.btn-primary:hover {
|
|
571
|
+
background: var(--amber) !important;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/* Slide-over Inspector Sidebar */
|
|
575
|
+
#graph-inspector {
|
|
576
|
+
position: absolute;
|
|
577
|
+
top: 0;
|
|
578
|
+
right: 0;
|
|
579
|
+
width: 420px;
|
|
580
|
+
max-width: 90vw;
|
|
581
|
+
height: 100%;
|
|
582
|
+
background: var(--surface);
|
|
583
|
+
border-left: 1px solid var(--border);
|
|
584
|
+
box-shadow: -8px 0 24px rgba(0,0,0,0.5);
|
|
585
|
+
transform: translateX(100%);
|
|
586
|
+
transition: transform 0.25s cubic-bezier(0.16, 1, 0.3, 1);
|
|
587
|
+
z-index: 25;
|
|
588
|
+
display: flex;
|
|
589
|
+
flex-direction: column;
|
|
590
|
+
}
|
|
591
|
+
#graph-inspector.open {
|
|
592
|
+
transform: translateX(0);
|
|
593
|
+
}
|
|
594
|
+
.inspector-header {
|
|
595
|
+
padding: 14px 18px;
|
|
596
|
+
border-bottom: 1px solid var(--border);
|
|
597
|
+
display: flex;
|
|
598
|
+
align-items: center;
|
|
599
|
+
justify-content: space-between;
|
|
600
|
+
gap: 12px;
|
|
601
|
+
background: var(--card-bg);
|
|
602
|
+
}
|
|
603
|
+
.inspector-title {
|
|
604
|
+
font-size: 15px;
|
|
605
|
+
font-weight: 700;
|
|
606
|
+
color: #fff;
|
|
607
|
+
overflow: hidden;
|
|
608
|
+
text-overflow: ellipsis;
|
|
609
|
+
white-space: nowrap;
|
|
610
|
+
}
|
|
611
|
+
.inspector-close {
|
|
612
|
+
background: transparent;
|
|
613
|
+
border: none;
|
|
614
|
+
color: var(--muted);
|
|
615
|
+
font-size: 20px;
|
|
616
|
+
cursor: pointer;
|
|
617
|
+
line-height: 1;
|
|
618
|
+
padding: 4px;
|
|
619
|
+
border-radius: 4px;
|
|
620
|
+
}
|
|
621
|
+
.inspector-close:hover {
|
|
622
|
+
color: #fff;
|
|
623
|
+
background: var(--surface-hover);
|
|
624
|
+
}
|
|
625
|
+
.inspector-body {
|
|
626
|
+
flex: 1;
|
|
627
|
+
overflow-y: auto;
|
|
628
|
+
padding: 16px 18px;
|
|
629
|
+
display: flex;
|
|
630
|
+
flex-direction: column;
|
|
631
|
+
gap: 16px;
|
|
632
|
+
}
|
|
633
|
+
.inspector-footer {
|
|
634
|
+
padding: 12px 18px;
|
|
635
|
+
border-top: 1px solid var(--border);
|
|
636
|
+
background: var(--card-bg);
|
|
637
|
+
display: flex;
|
|
638
|
+
gap: 8px;
|
|
639
|
+
justify-content: flex-end;
|
|
640
|
+
}
|
|
641
|
+
.inspector-section {
|
|
642
|
+
display: flex;
|
|
643
|
+
flex-direction: column;
|
|
644
|
+
gap: 6px;
|
|
645
|
+
}
|
|
646
|
+
.inspector-label {
|
|
647
|
+
font-size: 11px;
|
|
648
|
+
font-weight: 700;
|
|
649
|
+
text-transform: uppercase;
|
|
650
|
+
letter-spacing: 0.05em;
|
|
651
|
+
color: var(--muted);
|
|
652
|
+
display: flex;
|
|
653
|
+
align-items: center;
|
|
654
|
+
gap: 6px;
|
|
655
|
+
}
|
|
656
|
+
.inspector-code {
|
|
657
|
+
font-family: var(--mono);
|
|
658
|
+
font-size: 12px;
|
|
659
|
+
background: #090d16;
|
|
660
|
+
border: 1px solid var(--border);
|
|
661
|
+
border-radius: 6px;
|
|
662
|
+
padding: 10px 12px;
|
|
663
|
+
color: #38bdf8;
|
|
664
|
+
white-space: pre-wrap;
|
|
665
|
+
word-break: break-all;
|
|
666
|
+
max-height: 160px;
|
|
667
|
+
overflow-y: auto;
|
|
668
|
+
}
|
|
669
|
+
.inspector-desc {
|
|
670
|
+
font-size: 13px;
|
|
671
|
+
line-height: 1.5;
|
|
672
|
+
color: #e2e8f0;
|
|
673
|
+
background: rgba(245, 158, 11, 0.05);
|
|
674
|
+
border-left: 3px solid var(--amber);
|
|
675
|
+
border-radius: 0 6px 6px 0;
|
|
676
|
+
padding: 10px 12px;
|
|
677
|
+
}
|
|
678
|
+
.inspector-chip {
|
|
679
|
+
display: inline-flex;
|
|
680
|
+
align-items: center;
|
|
681
|
+
gap: 6px;
|
|
682
|
+
background: var(--card-bg);
|
|
683
|
+
border: 1px solid var(--border);
|
|
684
|
+
padding: 6px 10px;
|
|
685
|
+
border-radius: 6px;
|
|
686
|
+
font-size: 12px;
|
|
687
|
+
color: #cbd5e1;
|
|
688
|
+
cursor: pointer;
|
|
689
|
+
transition: all 0.15s;
|
|
690
|
+
text-decoration: none;
|
|
691
|
+
}
|
|
692
|
+
.inspector-chip:hover {
|
|
693
|
+
background: var(--surface-hover);
|
|
694
|
+
border-color: var(--amber);
|
|
695
|
+
color: #fff;
|
|
696
|
+
}
|
|
697
|
+
.inspector-chip .chip-badge {
|
|
698
|
+
font-size: 10px;
|
|
699
|
+
font-family: var(--mono);
|
|
700
|
+
background: rgba(255,255,255,0.08);
|
|
701
|
+
padding: 1px 5px;
|
|
702
|
+
border-radius: 4px;
|
|
703
|
+
}
|
|
704
|
+
.chip-list {
|
|
705
|
+
display: flex;
|
|
706
|
+
flex-direction: column;
|
|
707
|
+
gap: 6px;
|
|
708
|
+
max-height: 160px;
|
|
709
|
+
overflow-y: auto;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
/* Modals */
|
|
713
|
+
.kuma-modal-backdrop {
|
|
714
|
+
position: fixed;
|
|
715
|
+
inset: 0;
|
|
716
|
+
background: rgba(0, 0, 0, 0.75);
|
|
717
|
+
backdrop-filter: blur(4px);
|
|
718
|
+
z-index: 100;
|
|
719
|
+
display: none;
|
|
720
|
+
align-items: center;
|
|
721
|
+
justify-content: center;
|
|
722
|
+
padding: 20px;
|
|
723
|
+
}
|
|
724
|
+
.kuma-modal-backdrop.open {
|
|
725
|
+
display: flex;
|
|
726
|
+
}
|
|
727
|
+
.kuma-modal {
|
|
728
|
+
background: var(--surface);
|
|
729
|
+
border: 1px solid var(--border);
|
|
730
|
+
border-radius: 12px;
|
|
731
|
+
width: 100%;
|
|
732
|
+
max-width: 520px;
|
|
733
|
+
box-shadow: 0 20px 40px rgba(0,0,0,0.6);
|
|
734
|
+
display: flex;
|
|
735
|
+
flex-direction: column;
|
|
736
|
+
overflow: hidden;
|
|
737
|
+
}
|
|
738
|
+
.kuma-modal-header {
|
|
739
|
+
padding: 14px 18px;
|
|
740
|
+
background: var(--card-bg);
|
|
741
|
+
border-bottom: 1px solid var(--border);
|
|
742
|
+
display: flex;
|
|
743
|
+
align-items: center;
|
|
744
|
+
justify-content: space-between;
|
|
745
|
+
}
|
|
746
|
+
.kuma-modal-body {
|
|
747
|
+
padding: 18px;
|
|
748
|
+
display: flex;
|
|
749
|
+
flex-direction: column;
|
|
750
|
+
gap: 14px;
|
|
751
|
+
}
|
|
752
|
+
.kuma-modal-footer {
|
|
753
|
+
padding: 14px 18px;
|
|
754
|
+
background: var(--card-bg);
|
|
755
|
+
border-top: 1px solid var(--border);
|
|
756
|
+
display: flex;
|
|
757
|
+
justify-content: flex-end;
|
|
758
|
+
gap: 10px;
|
|
759
|
+
}
|
|
760
|
+
.form-group {
|
|
761
|
+
display: flex;
|
|
762
|
+
flex-direction: column;
|
|
763
|
+
gap: 6px;
|
|
764
|
+
}
|
|
765
|
+
.form-group label {
|
|
766
|
+
font-size: 12px;
|
|
767
|
+
font-weight: 600;
|
|
768
|
+
color: var(--muted);
|
|
769
|
+
}
|
|
770
|
+
.form-control {
|
|
771
|
+
background: var(--bg);
|
|
772
|
+
border: 1px solid var(--border);
|
|
773
|
+
border-radius: 6px;
|
|
774
|
+
padding: 8px 12px;
|
|
775
|
+
color: #fff;
|
|
776
|
+
font-size: 13px;
|
|
777
|
+
font-family: inherit;
|
|
778
|
+
}
|
|
779
|
+
.form-control:focus {
|
|
780
|
+
outline: none;
|
|
781
|
+
border-color: var(--amber);
|
|
534
782
|
}
|
|
535
783
|
|
|
536
784
|
.empty-placeholder {
|
|
@@ -562,27 +810,24 @@ table.clean-table tr:hover td {
|
|
|
562
810
|
</div>
|
|
563
811
|
|
|
564
812
|
<nav class="nav-tabs">
|
|
565
|
-
<button class="nav-tab active" data-view="
|
|
566
|
-
<i data-lucide="
|
|
567
|
-
|
|
568
|
-
<span class="tab-badge" id="badge-
|
|
813
|
+
<button class="nav-tab active" data-view="graph">
|
|
814
|
+
<i data-lucide="network" style="width:14px;height:14px"></i>
|
|
815
|
+
Knowledge Graph
|
|
816
|
+
<span class="tab-badge" id="badge-graph-count">0</span>
|
|
569
817
|
</button>
|
|
570
818
|
<button class="nav-tab" data-view="flows">
|
|
571
819
|
<i data-lucide="git-merge" style="width:14px;height:14px"></i>
|
|
572
|
-
|
|
820
|
+
Architecture & Flows
|
|
573
821
|
<span class="tab-badge" id="badge-flow-count">0</span>
|
|
574
822
|
</button>
|
|
575
|
-
<button class="nav-tab" data-view="
|
|
576
|
-
<i data-lucide="
|
|
577
|
-
|
|
823
|
+
<button class="nav-tab" data-view="gotchas">
|
|
824
|
+
<i data-lucide="shield-alert" style="width:14px;height:14px"></i>
|
|
825
|
+
Known Gotchas
|
|
826
|
+
<span class="tab-badge" id="badge-gotcha-count">0</span>
|
|
578
827
|
</button>
|
|
579
828
|
<button class="nav-tab" data-view="health">
|
|
580
829
|
<i data-lucide="activity" style="width:14px;height:14px"></i>
|
|
581
|
-
|
|
582
|
-
</button>
|
|
583
|
-
<button class="nav-tab" data-view="graph">
|
|
584
|
-
<i data-lucide="network" style="width:14px;height:14px"></i>
|
|
585
|
-
Knowledge Graph
|
|
830
|
+
Health & Audit
|
|
586
831
|
</button>
|
|
587
832
|
</nav>
|
|
588
833
|
|
|
@@ -593,29 +838,51 @@ table.clean-table tr:hover td {
|
|
|
593
838
|
</header>
|
|
594
839
|
|
|
595
840
|
<main id="viewport">
|
|
596
|
-
<!-- VIEW 1:
|
|
597
|
-
<section class="view-panel active" id="view-
|
|
598
|
-
<div
|
|
599
|
-
<div>
|
|
600
|
-
<
|
|
601
|
-
<
|
|
841
|
+
<!-- VIEW 1: KNOWLEDGE GRAPH (PRIMARY HERO) -->
|
|
842
|
+
<section class="view-panel active" id="view-graph" style="padding:0">
|
|
843
|
+
<div id="graph-viewport">
|
|
844
|
+
<div class="graph-toolbar">
|
|
845
|
+
<button onclick="fitGraph()"><i data-lucide="maximize" style="width:11px;height:11px"></i> Fit</button>
|
|
846
|
+
<button id="btn-toggle-physics" onclick="togglePhysics()">Physics: Off</button>
|
|
847
|
+
<select id="graph-filter-type" onchange="onGraphFilterChange(this.value)">
|
|
848
|
+
<option value="all">All Types</option>
|
|
849
|
+
<option value="file">Files</option>
|
|
850
|
+
<option value="function">Functions</option>
|
|
851
|
+
<option value="class">Classes</option>
|
|
852
|
+
<option value="interface">Interfaces</option>
|
|
853
|
+
<option value="feature_domain">Domains & Architecture</option>
|
|
854
|
+
<option value="gotcha">Gotchas</option>
|
|
855
|
+
<option value="decision">Decisions</option>
|
|
856
|
+
</select>
|
|
857
|
+
<input id="graph-search-input" type="text" placeholder="Jump to node..." oninput="onGraphSearch(this.value)" />
|
|
858
|
+
<button class="btn-primary" onclick="openCreateNodeModal()">+ New Node</button>
|
|
859
|
+
<button onclick="openConnectEdgeModal()">+ Connect Edge</button>
|
|
860
|
+
<span style="color:var(--muted);font-size:11px;align-self:center;margin-left:4px" id="graph-stats-label">0 nodes</span>
|
|
602
861
|
</div>
|
|
603
|
-
<
|
|
604
|
-
</div>
|
|
862
|
+
<div id="graph-canvas"></div>
|
|
605
863
|
|
|
606
|
-
|
|
607
|
-
<
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
864
|
+
<!-- Slide-over Inspector Sidebar -->
|
|
865
|
+
<aside id="graph-inspector">
|
|
866
|
+
<div class="inspector-header">
|
|
867
|
+
<div style="display:flex;align-items:center;gap:8px;overflow:hidden">
|
|
868
|
+
<span id="inspector-badge" class="badge-project">NODE</span>
|
|
869
|
+
<span id="inspector-name" class="inspector-title">Node Name</span>
|
|
870
|
+
</div>
|
|
871
|
+
<button class="inspector-close" onclick="closeNodeInspector()">×</button>
|
|
872
|
+
</div>
|
|
873
|
+
<div class="inspector-body" id="inspector-body">
|
|
874
|
+
<div style="color:var(--muted);font-size:12px;text-align:center;padding:20px">Select a node in the graph to inspect details.</div>
|
|
875
|
+
</div>
|
|
876
|
+
<div class="inspector-footer" id="inspector-footer" style="display:none">
|
|
877
|
+
<button class="btn-action" id="inspector-connect-btn">+ Connect</button>
|
|
878
|
+
<button class="btn-action" id="inspector-edit-btn">Edit</button>
|
|
879
|
+
<button class="btn-action" style="color:var(--rose);border-color:rgba(244,63,94,0.3)" id="inspector-delete-btn">Delete</button>
|
|
880
|
+
</div>
|
|
881
|
+
</aside>
|
|
613
882
|
</div>
|
|
614
|
-
|
|
615
|
-
<div class="gotcha-grid" id="gotcha-cards-container"></div>
|
|
616
883
|
</section>
|
|
617
884
|
|
|
618
|
-
<!-- VIEW 2:
|
|
885
|
+
<!-- VIEW 2: ARCHITECTURE & FLOWS -->
|
|
619
886
|
<section class="view-panel" id="view-flows">
|
|
620
887
|
<div class="view-header">
|
|
621
888
|
<div>
|
|
@@ -626,37 +893,34 @@ table.clean-table tr:hover td {
|
|
|
626
893
|
<div class="flow-list" id="flow-cards-container"></div>
|
|
627
894
|
</section>
|
|
628
895
|
|
|
629
|
-
<!-- VIEW 3:
|
|
630
|
-
<section class="view-panel" id="view-
|
|
896
|
+
<!-- VIEW 3: GOTCHA SHIELD -->
|
|
897
|
+
<section class="view-panel" id="view-gotchas">
|
|
631
898
|
<div class="view-header">
|
|
632
899
|
<div>
|
|
633
|
-
<div class="view-title"><i data-lucide="
|
|
634
|
-
<div class="view-desc">
|
|
900
|
+
<div class="view-title"><i data-lucide="shield-alert" style="color:var(--amber)"></i> Known Code Gotchas</div>
|
|
901
|
+
<div class="view-desc">Catalog of fragile legacy contracts, edge cases, and unexpected quirks injected right before edits.</div>
|
|
635
902
|
</div>
|
|
903
|
+
<input type="text" id="gotcha-search" class="search-input" placeholder="Search file, issue, or workaround..." oninput="filterGotchas()" />
|
|
636
904
|
</div>
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
</div>
|
|
642
|
-
<div class="
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
<div style="display:flex;gap:8px;margin-bottom:12px">
|
|
646
|
-
<input type="text" id="blast-input" class="search-input" placeholder="e.g. src/auth.ts or package/core" style="flex:1" />
|
|
647
|
-
<button class="btn-action" onclick="simulateBlastRadius()">Check</button>
|
|
648
|
-
</div>
|
|
649
|
-
<div id="blast-result" style="font-size:12px;color:#cbd5e1;background:var(--bg);padding:12px;border-radius:6px;border:1px solid var(--border)">Enter a target path to inspect blast radius.</div>
|
|
650
|
-
</div>
|
|
905
|
+
|
|
906
|
+
<div class="filter-pills" id="status-pills">
|
|
907
|
+
<div class="pill active" data-status="all" onclick="setStatusFilter('all')">All Statuses</div>
|
|
908
|
+
<div class="pill" data-status="active" onclick="setStatusFilter('active')">Active</div>
|
|
909
|
+
<div class="pill" data-status="verified" onclick="setStatusFilter('verified')">Verified</div>
|
|
910
|
+
<div class="pill" data-status="candidate" onclick="setStatusFilter('candidate')">Candidate</div>
|
|
911
|
+
<div class="pill" data-status="resolved" onclick="setStatusFilter('resolved')">Resolved</div>
|
|
912
|
+
<div class="pill" data-status="deprecated" onclick="setStatusFilter('deprecated')">Deprecated</div>
|
|
651
913
|
</div>
|
|
914
|
+
|
|
915
|
+
<div class="gotcha-grid" id="gotcha-cards-container"></div>
|
|
652
916
|
</section>
|
|
653
917
|
|
|
654
|
-
<!-- VIEW 4: HEALTH &
|
|
918
|
+
<!-- VIEW 4: HEALTH & AUDIT -->
|
|
655
919
|
<section class="view-panel" id="view-health">
|
|
656
920
|
<div class="view-header">
|
|
657
921
|
<div>
|
|
658
922
|
<div class="view-title"><i data-lucide="activity" style="color:var(--emerald)"></i> Efficiency & Health Metrics</div>
|
|
659
|
-
<div class="view-desc">
|
|
923
|
+
<div class="view-desc">Project metrics and stale reference detection ensuring memory reliability.</div>
|
|
660
924
|
</div>
|
|
661
925
|
</div>
|
|
662
926
|
|
|
@@ -684,19 +948,94 @@ table.clean-table tr:hover td {
|
|
|
684
948
|
<div id="staleness-container" style="padding:16px;font-size:12px;color:var(--muted)">Checking staleness...</div>
|
|
685
949
|
</div>
|
|
686
950
|
</section>
|
|
951
|
+
</main>
|
|
687
952
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
953
|
+
<!-- Modal: Create / Edit Node -->
|
|
954
|
+
<div class="kuma-modal-backdrop" id="modal-node">
|
|
955
|
+
<div class="kuma-modal">
|
|
956
|
+
<div class="kuma-modal-header">
|
|
957
|
+
<span style="font-weight:700;font-size:14px" id="modal-node-title">Create Knowledge Node</span>
|
|
958
|
+
<button class="inspector-close" onclick="closeModal('modal-node')">×</button>
|
|
959
|
+
</div>
|
|
960
|
+
<div class="kuma-modal-body">
|
|
961
|
+
<input type="hidden" id="node-form-id" />
|
|
962
|
+
<div class="form-group">
|
|
963
|
+
<label>Type</label>
|
|
964
|
+
<select id="node-form-type" class="form-control">
|
|
965
|
+
<option value="feature_domain">feature_domain (Domain / Architecture Boundary)</option>
|
|
966
|
+
<option value="arch_flow">arch_flow (Architecture Flow / Sequence)</option>
|
|
967
|
+
<option value="decision">decision (Architecture Decision)</option>
|
|
968
|
+
<option value="workflow">workflow (Business / Code Workflow)</option>
|
|
969
|
+
<option value="function">function</option>
|
|
970
|
+
<option value="class">class</option>
|
|
971
|
+
<option value="interface">interface</option>
|
|
972
|
+
<option value="file">file</option>
|
|
973
|
+
<option value="api_route">api_route</option>
|
|
974
|
+
<option value="gotcha">gotcha</option>
|
|
975
|
+
</select>
|
|
976
|
+
</div>
|
|
977
|
+
<div class="form-group">
|
|
978
|
+
<label>Name</label>
|
|
979
|
+
<input type="text" id="node-form-name" class="form-control" placeholder="e.g. AuthEngine, calculateMetrics, UserStore" />
|
|
980
|
+
</div>
|
|
981
|
+
<div class="form-group">
|
|
982
|
+
<label>File Path (optional)</label>
|
|
983
|
+
<input type="text" id="node-form-filepath" class="form-control" placeholder="e.g. src/engine/kumaDb.ts" />
|
|
984
|
+
</div>
|
|
985
|
+
<div class="form-group">
|
|
986
|
+
<label>Purpose / Description (for AI Agents and Developers)</label>
|
|
987
|
+
<textarea id="node-form-desc" class="form-control" rows="3" placeholder="Explain what this component or function does..."></textarea>
|
|
695
988
|
</div>
|
|
696
|
-
<div id="graph-canvas"></div>
|
|
697
989
|
</div>
|
|
698
|
-
|
|
699
|
-
</
|
|
990
|
+
<div class="kuma-modal-footer">
|
|
991
|
+
<button class="btn-action" onclick="closeModal('modal-node')">Cancel</button>
|
|
992
|
+
<button class="btn-action btn-primary" onclick="submitNodeForm()">Save Node</button>
|
|
993
|
+
</div>
|
|
994
|
+
</div>
|
|
995
|
+
</div>
|
|
996
|
+
|
|
997
|
+
<!-- Modal: Connect Edge -->
|
|
998
|
+
<div class="kuma-modal-backdrop" id="modal-edge">
|
|
999
|
+
<div class="kuma-modal">
|
|
1000
|
+
<div class="kuma-modal-header">
|
|
1001
|
+
<span style="font-weight:700;font-size:14px">Connect Nodes (Create Edge)</span>
|
|
1002
|
+
<button class="inspector-close" onclick="closeModal('modal-edge')">×</button>
|
|
1003
|
+
</div>
|
|
1004
|
+
<div class="kuma-modal-body">
|
|
1005
|
+
<div class="form-group">
|
|
1006
|
+
<label>Source Node</label>
|
|
1007
|
+
<select id="edge-form-source" class="form-control"></select>
|
|
1008
|
+
</div>
|
|
1009
|
+
<div class="form-group">
|
|
1010
|
+
<label>Relationship Type</label>
|
|
1011
|
+
<select id="edge-form-type" class="form-control">
|
|
1012
|
+
<option value="calls">calls (Function A calls Function B)</option>
|
|
1013
|
+
<option value="imports">imports (Module A imports Module B)</option>
|
|
1014
|
+
<option value="defines">defines (File defines Function/Class)</option>
|
|
1015
|
+
<option value="tests">tests (Test verifies Code)</option>
|
|
1016
|
+
<option value="depends_on">depends_on (Component depends on Target)</option>
|
|
1017
|
+
<option value="affects">affects (Change here impacts Target)</option>
|
|
1018
|
+
<option value="implements">implements (Class implements Interface)</option>
|
|
1019
|
+
<option value="extends">extends (Class extends Superclass)</option>
|
|
1020
|
+
<option value="flows_through">flows_through (Architecture Flow)</option>
|
|
1021
|
+
<option value="uses">uses (Subsystem uses Component/Service)</option>
|
|
1022
|
+
<option value="produces">produces (Subsystem produces Artifact/Event)</option>
|
|
1023
|
+
<option value="validates">validates (Subsystem validates Contract)</option>
|
|
1024
|
+
<option value="routes_to">routes_to (Router routes to Subsystem)</option>
|
|
1025
|
+
<option value="configures">configures (Subsystem configures Target)</option>
|
|
1026
|
+
</select>
|
|
1027
|
+
</div>
|
|
1028
|
+
<div class="form-group">
|
|
1029
|
+
<label>Target Node</label>
|
|
1030
|
+
<select id="edge-form-target" class="form-control"></select>
|
|
1031
|
+
</div>
|
|
1032
|
+
</div>
|
|
1033
|
+
<div class="kuma-modal-footer">
|
|
1034
|
+
<button class="btn-action" onclick="closeModal('modal-edge')">Cancel</button>
|
|
1035
|
+
<button class="btn-action btn-primary" onclick="submitEdgeForm()">Create Connection</button>
|
|
1036
|
+
</div>
|
|
1037
|
+
</div>
|
|
1038
|
+
</div>
|
|
700
1039
|
|
|
701
1040
|
<script>
|
|
702
1041
|
var API = window.location.origin + "/api";
|
|
@@ -737,10 +1076,17 @@ async function fetchDashboard() {
|
|
|
737
1076
|
|
|
738
1077
|
function renderAll(data) {
|
|
739
1078
|
if (!data) return;
|
|
1079
|
+
if (document.getElementById("badge-graph-count")) {
|
|
1080
|
+
document.getElementById("badge-graph-count").textContent = data.nodes ? data.nodes.length : 0;
|
|
1081
|
+
}
|
|
740
1082
|
try { renderGotchas(data.gotchas || []); } catch (e) { console.error("renderGotchas error:", e); }
|
|
741
1083
|
try { renderFlows(data.flows || []); } catch (e) { console.error("renderFlows error:", e); }
|
|
742
|
-
try { renderWorkspace(data.workspace, data.nodes || []); } catch (e) { console.error("renderWorkspace error:", e); }
|
|
743
1084
|
try { renderHealth(data); } catch (e) { console.error("renderHealth error:", e); }
|
|
1085
|
+
|
|
1086
|
+
var graphTab = document.querySelector('.nav-tab[data-view="graph"]');
|
|
1087
|
+
if (graphTab && graphTab.classList.contains("active")) {
|
|
1088
|
+
setTimeout(initOrRefreshGraph, 20);
|
|
1089
|
+
}
|
|
744
1090
|
if (window.lucide && window.lucide.createIcons) {
|
|
745
1091
|
try { window.lucide.createIcons(); } catch {}
|
|
746
1092
|
}
|
|
@@ -864,53 +1210,6 @@ function renderFlows(flows) {
|
|
|
864
1210
|
if (window.lucide && window.lucide.createIcons) window.lucide.createIcons();
|
|
865
1211
|
}
|
|
866
1212
|
|
|
867
|
-
function renderWorkspace(ws, nodes) {
|
|
868
|
-
var container = document.getElementById("ws-topology-info");
|
|
869
|
-
if (!container) return;
|
|
870
|
-
if (!ws) {
|
|
871
|
-
container.innerHTML = "<div>Single package workspace.</div>";
|
|
872
|
-
return;
|
|
873
|
-
}
|
|
874
|
-
var nodesLen = (nodes && nodes.length) ? nodes.length : 0;
|
|
875
|
-
var pkgHtml = '';
|
|
876
|
-
if (ws.packages && Array.isArray(ws.packages) && ws.packages.length > 0) {
|
|
877
|
-
pkgHtml = `
|
|
878
|
-
<div style="margin-top:10px"><strong>Monorepo Packages (${ws.packages.length}):</strong></div>
|
|
879
|
-
<div style="margin-top:6px;display:flex;flex-wrap:wrap;gap:6px">
|
|
880
|
-
${ws.packages.map(function(p) {
|
|
881
|
-
var name = typeof p === 'string' ? p : (p.name || p.path || '');
|
|
882
|
-
return `<span class="badge-pkg" style="background:#1e293b;border:1px solid #334155;padding:2px 8px;border-radius:4px;font-size:11px">📦 ${escapeHtml(name)}</span>`;
|
|
883
|
-
}).join("")}
|
|
884
|
-
</div>
|
|
885
|
-
`;
|
|
886
|
-
}
|
|
887
|
-
container.innerHTML = `
|
|
888
|
-
<div style="margin-bottom:8px"><strong>Workspace Type:</strong> <span class="badge-project">${escapeHtml(ws.type || "standard")}</span></div>
|
|
889
|
-
<div style="margin-bottom:8px"><strong>Project Root:</strong> <code>${escapeHtml(ws.name || "root")}</code> (v${escapeHtml(ws.version || "1.0.0")})</div>
|
|
890
|
-
${pkgHtml}
|
|
891
|
-
<div style="margin-top:12px;color:var(--muted)">Total indexed knowledge entities: <strong>${nodesLen}</strong></div>
|
|
892
|
-
`;
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
function simulateBlastRadius() {
|
|
896
|
-
var val = (document.getElementById("blast-input").value || "").trim();
|
|
897
|
-
var out = document.getElementById("blast-result");
|
|
898
|
-
if (!val) {
|
|
899
|
-
out.innerHTML = "⚠️ Please enter a target path or symbol.";
|
|
900
|
-
return;
|
|
901
|
-
}
|
|
902
|
-
if (!currentData || !currentData.edges) {
|
|
903
|
-
out.innerHTML = "No graph data available.";
|
|
904
|
-
return;
|
|
905
|
-
}
|
|
906
|
-
var matchingEdges = currentData.edges.filter(e => String(e.source).includes(val) || String(e.target).includes(val));
|
|
907
|
-
out.innerHTML = `
|
|
908
|
-
<div style="font-weight:700;color:var(--amber-light);margin-bottom:6px">Blast Radius for "${escapeHtml(val)}":</div>
|
|
909
|
-
<div>Found <strong>${matchingEdges.length}</strong> directly connected relationship(s).</div>
|
|
910
|
-
<div style="margin-top:8px">${matchingEdges.slice(0, 5).map(e => `• <code>${escapeHtml(e.source)}</code> <strong>${escapeHtml(e.relation)}</strong> <code>${escapeHtml(e.target)}</code>`).join("<br>")}</div>
|
|
911
|
-
`;
|
|
912
|
-
}
|
|
913
|
-
|
|
914
1213
|
function renderHealth(data) {
|
|
915
1214
|
var eff = data.efficiency || {};
|
|
916
1215
|
var kpiContainer = document.getElementById("kpi-container");
|
|
@@ -923,8 +1222,8 @@ function renderHealth(data) {
|
|
|
923
1222
|
<div class="kpi-label">Known Gotchas Tracked</div>
|
|
924
1223
|
</div>
|
|
925
1224
|
<div class="kpi-card">
|
|
926
|
-
<div class="kpi-num" style="color:var(--emerald)">${
|
|
927
|
-
<div class="kpi-label">
|
|
1225
|
+
<div class="kpi-num" style="color:var(--emerald)">${data.nodes ? data.nodes.length : 0}</div>
|
|
1226
|
+
<div class="kpi-label">Topology Nodes Mapped</div>
|
|
928
1227
|
</div>
|
|
929
1228
|
<div class="kpi-card">
|
|
930
1229
|
<div class="kpi-num" style="color:var(--amber)">~${savedMinutes}</div>
|
|
@@ -970,50 +1269,534 @@ function renderHealth(data) {
|
|
|
970
1269
|
}
|
|
971
1270
|
}
|
|
972
1271
|
|
|
1272
|
+
var selectedNodeId = null;
|
|
1273
|
+
var visNodesDataSet = null;
|
|
1274
|
+
var visEdgesDataSet = null;
|
|
1275
|
+
|
|
1276
|
+
var NODE_COLORS = {
|
|
1277
|
+
file: "#06b6d4",
|
|
1278
|
+
function: "#3b82f6",
|
|
1279
|
+
class: "#ec4899",
|
|
1280
|
+
interface: "#a855f7",
|
|
1281
|
+
type: "#8b5cf6",
|
|
1282
|
+
component: "#0ea5e9",
|
|
1283
|
+
api_route: "#14b8a6",
|
|
1284
|
+
test: "#f59e0b",
|
|
1285
|
+
arch_flow: "#6366f1",
|
|
1286
|
+
feature_domain: "#6366f1",
|
|
1287
|
+
feature: "#4f46e5",
|
|
1288
|
+
decision: "#10b981",
|
|
1289
|
+
gotcha: "#f97316",
|
|
1290
|
+
workflow: "#818cf8",
|
|
1291
|
+
cross_service_link: "#818cf8",
|
|
1292
|
+
research: "#22c55e",
|
|
1293
|
+
};
|
|
1294
|
+
|
|
1295
|
+
function getNodeColor(type) {
|
|
1296
|
+
return NODE_COLORS[type] || "#64748b";
|
|
1297
|
+
}
|
|
1298
|
+
|
|
973
1299
|
function initOrRefreshGraph() {
|
|
974
1300
|
if (!currentData || !currentData.nodes) return;
|
|
975
1301
|
var container = document.getElementById("graph-canvas");
|
|
976
1302
|
document.getElementById("graph-stats-label").textContent = `${currentData.nodes.length} nodes, ${currentData.edges ? currentData.edges.length : 0} edges`;
|
|
977
1303
|
|
|
978
|
-
var
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
else
|
|
984
|
-
|
|
1304
|
+
var filterType = document.getElementById("graph-filter-type") ? document.getElementById("graph-filter-type").value : "all";
|
|
1305
|
+
var displayNodes = currentData.nodes;
|
|
1306
|
+
if (filterType !== "all") {
|
|
1307
|
+
if (filterType === "feature_domain") {
|
|
1308
|
+
displayNodes = displayNodes.filter(n => n.type === "feature_domain" || n.type === "arch_flow" || n.type === "decision");
|
|
1309
|
+
} else {
|
|
1310
|
+
displayNodes = displayNodes.filter(n => n.type === filterType);
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
visNodesDataSet = new vis.DataSet(displayNodes.slice(0, 400).map(function(n) {
|
|
1315
|
+
var color = getNodeColor(n.type);
|
|
1316
|
+
var label = n.name || n.id.split("::").pop();
|
|
1317
|
+
|
|
1318
|
+
let meta = {};
|
|
1319
|
+
try { meta = typeof n.metadata === "string" ? JSON.parse(n.metadata) : (n.metadata || {}); } catch {}
|
|
1320
|
+
|
|
1321
|
+
var title = `${label} (${n.type})${n.file_path ? '\nFile: ' + n.file_path : ''}`;
|
|
1322
|
+
if (meta.description) title += `\nPurpose: ${meta.description.split('\n')[0]}`;
|
|
1323
|
+
if (meta.signature) title += `\nSignature: ${meta.signature}`;
|
|
1324
|
+
|
|
1325
|
+
// Size hierarchy for clear visual scanning
|
|
1326
|
+
var size = 11;
|
|
1327
|
+
if (n.type === "file") size = 17;
|
|
1328
|
+
else if (n.type === "feature_domain" || n.type === "arch_flow") size = 15;
|
|
1329
|
+
else if (n.type === "class" || n.type === "interface") size = 13;
|
|
1330
|
+
else if (n.type === "function" || n.type === "component") size = 11;
|
|
1331
|
+
else if (n.type === "decision") size = 12;
|
|
1332
|
+
else if (n.type === "gotcha") size = 12;
|
|
1333
|
+
|
|
985
1334
|
return {
|
|
986
1335
|
id: n.id,
|
|
987
|
-
label:
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
1336
|
+
label: label,
|
|
1337
|
+
title: title,
|
|
1338
|
+
shape: "dot",
|
|
1339
|
+
size: size,
|
|
1340
|
+
color: {
|
|
1341
|
+
background: color,
|
|
1342
|
+
border: "#0f172a",
|
|
1343
|
+
highlight: {
|
|
1344
|
+
background: color,
|
|
1345
|
+
border: "#ffffff",
|
|
1346
|
+
},
|
|
1347
|
+
hover: {
|
|
1348
|
+
background: color,
|
|
1349
|
+
border: "#e2e8f0",
|
|
1350
|
+
},
|
|
1351
|
+
},
|
|
1352
|
+
borderWidth: 2,
|
|
1353
|
+
borderWidthSelected: 3,
|
|
1354
|
+
font: {
|
|
1355
|
+
color: "#cbd5e1",
|
|
1356
|
+
size: 11,
|
|
1357
|
+
face: "Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
|
|
1358
|
+
strokeWidth: 2,
|
|
1359
|
+
strokeColor: "#060911",
|
|
1360
|
+
vadjust: 3, // Pushes title directly underneath the circular node!
|
|
1361
|
+
},
|
|
992
1362
|
};
|
|
993
1363
|
}));
|
|
994
1364
|
|
|
995
|
-
var
|
|
1365
|
+
var displayedNodeIds = new Set(visNodesDataSet.getIds());
|
|
1366
|
+
var validEdges = (currentData.edges || []).filter(e => displayedNodeIds.has(e.source) && displayedNodeIds.has(e.target));
|
|
1367
|
+
|
|
1368
|
+
visEdgesDataSet = new vis.DataSet(validEdges.slice(0, 700).map(function(e) {
|
|
1369
|
+
const isConceptVerb = ["uses", "produces", "validates", "routes_to", "configures", "flows_through"].includes(e.type);
|
|
996
1370
|
return {
|
|
997
1371
|
from: e.source,
|
|
998
1372
|
to: e.target,
|
|
999
|
-
arrows:
|
|
1000
|
-
|
|
1001
|
-
|
|
1373
|
+
arrows: {
|
|
1374
|
+
to: { enabled: true, scaleFactor: 0.5 }
|
|
1375
|
+
},
|
|
1376
|
+
title: e.relation || e.type,
|
|
1377
|
+
label: isConceptVerb ? e.type : undefined,
|
|
1378
|
+
font: isConceptVerb ? { size: 9, color: "#94a3b8", strokeWidth: 2, strokeColor: "#060911", align: "middle" } : undefined,
|
|
1379
|
+
color: {
|
|
1380
|
+
color: isConceptVerb ? "rgba(99, 102, 241, 0.45)" : "rgba(148, 163, 184, 0.22)",
|
|
1381
|
+
highlight: "#f59e0b",
|
|
1382
|
+
hover: "#38bdf8",
|
|
1383
|
+
},
|
|
1384
|
+
smooth: {
|
|
1385
|
+
type: "continuous",
|
|
1386
|
+
roundness: 0.15,
|
|
1387
|
+
},
|
|
1388
|
+
width: isConceptVerb ? 1.8 : 1.2,
|
|
1002
1389
|
};
|
|
1003
1390
|
}));
|
|
1004
1391
|
|
|
1005
1392
|
var options = {
|
|
1006
1393
|
physics: {
|
|
1007
1394
|
enabled: physicsEnabled,
|
|
1008
|
-
barnesHut: {
|
|
1395
|
+
barnesHut: {
|
|
1396
|
+
gravitationalConstant: -2800,
|
|
1397
|
+
centralGravity: 0.2,
|
|
1398
|
+
springLength: 90,
|
|
1399
|
+
springConstant: 0.04,
|
|
1400
|
+
damping: 0.09,
|
|
1401
|
+
},
|
|
1402
|
+
stabilization: {
|
|
1403
|
+
iterations: 150,
|
|
1404
|
+
updateInterval: 25,
|
|
1405
|
+
},
|
|
1406
|
+
},
|
|
1407
|
+
interaction: {
|
|
1408
|
+
hover: true,
|
|
1409
|
+
tooltipDelay: 120,
|
|
1410
|
+
zoomView: true,
|
|
1411
|
+
dragView: true,
|
|
1412
|
+
selectConnectedEdges: true,
|
|
1009
1413
|
},
|
|
1010
|
-
interaction: { hover: true, tooltipDelay: 100 },
|
|
1011
1414
|
};
|
|
1012
1415
|
|
|
1013
1416
|
if (graphNetwork) {
|
|
1014
1417
|
graphNetwork.destroy();
|
|
1015
1418
|
}
|
|
1016
|
-
graphNetwork = new vis.Network(container, { nodes:
|
|
1419
|
+
graphNetwork = new vis.Network(container, { nodes: visNodesDataSet, edges: visEdgesDataSet }, options);
|
|
1420
|
+
|
|
1421
|
+
graphNetwork.on("selectNode", function(params) {
|
|
1422
|
+
if (params.nodes && params.nodes.length > 0) {
|
|
1423
|
+
openNodeInspector(params.nodes[0]);
|
|
1424
|
+
}
|
|
1425
|
+
});
|
|
1426
|
+
|
|
1427
|
+
graphNetwork.on("click", function(params) {
|
|
1428
|
+
if (!params.nodes || params.nodes.length === 0) {
|
|
1429
|
+
// Clicked on empty space
|
|
1430
|
+
}
|
|
1431
|
+
});
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
function onGraphFilterChange(val) {
|
|
1435
|
+
initOrRefreshGraph();
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
function onGraphSearch(query) {
|
|
1439
|
+
if (!query || !graphNetwork || !visNodesDataSet) return;
|
|
1440
|
+
var q = query.trim().toLowerCase();
|
|
1441
|
+
var foundId = null;
|
|
1442
|
+
var allItems = visNodesDataSet.get();
|
|
1443
|
+
for (var i = 0; i < allItems.length; i++) {
|
|
1444
|
+
var item = allItems[i];
|
|
1445
|
+
if (item.label.toLowerCase().includes(q) || item.id.toLowerCase().includes(q)) {
|
|
1446
|
+
foundId = item.id;
|
|
1447
|
+
break;
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
if (foundId) {
|
|
1451
|
+
selectAndFocusNode(foundId);
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
async function openNodeInspector(nodeId) {
|
|
1456
|
+
selectedNodeId = nodeId;
|
|
1457
|
+
var inspector = document.getElementById("graph-inspector");
|
|
1458
|
+
var badge = document.getElementById("inspector-badge");
|
|
1459
|
+
var nameEl = document.getElementById("inspector-name");
|
|
1460
|
+
var body = document.getElementById("inspector-body");
|
|
1461
|
+
var footer = document.getElementById("inspector-footer");
|
|
1462
|
+
|
|
1463
|
+
inspector.classList.add("open");
|
|
1464
|
+
badge.textContent = "...";
|
|
1465
|
+
badge.style.background = "rgba(255,255,255,0.1)";
|
|
1466
|
+
nameEl.textContent = "Loading node details...";
|
|
1467
|
+
body.innerHTML = '<div style="color:var(--muted);text-align:center;padding:30px">Fetching metadata & topology...</div>';
|
|
1468
|
+
footer.style.display = "none";
|
|
1469
|
+
|
|
1470
|
+
try {
|
|
1471
|
+
var res = await fetch(API + "/node/" + encodeURIComponent(nodeId)).then(r => r.json());
|
|
1472
|
+
if (!res || !res.node) {
|
|
1473
|
+
body.innerHTML = '<div style="color:var(--rose);padding:20px">Node not found.</div>';
|
|
1474
|
+
return;
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
var node = res.node;
|
|
1478
|
+
var outgoing = res.outgoing || [];
|
|
1479
|
+
var incoming = res.incoming || [];
|
|
1480
|
+
var gotchas = res.gotchas || [];
|
|
1481
|
+
|
|
1482
|
+
var meta = {};
|
|
1483
|
+
try { meta = typeof node.metadata === "string" ? JSON.parse(node.metadata) : (node.metadata || {}); } catch {}
|
|
1484
|
+
|
|
1485
|
+
var typeColor = getNodeColor(node.type);
|
|
1486
|
+
badge.textContent = node.type;
|
|
1487
|
+
badge.style.background = typeColor;
|
|
1488
|
+
badge.style.color = "#fff";
|
|
1489
|
+
nameEl.textContent = node.name || node.id;
|
|
1490
|
+
nameEl.title = node.name || node.id;
|
|
1491
|
+
|
|
1492
|
+
var html = [];
|
|
1493
|
+
|
|
1494
|
+
// 1. Identity & File Path
|
|
1495
|
+
html.push(`
|
|
1496
|
+
<div class="inspector-section">
|
|
1497
|
+
<div class="inspector-label">Location</div>
|
|
1498
|
+
<div style="display:flex;align-items:center;gap:6px;background:var(--card-bg);padding:8px 10px;border-radius:6px;border:1px solid var(--border)">
|
|
1499
|
+
<span style="font-family:var(--mono);font-size:12px;color:var(--text);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${escapeHtml(node.file_path || 'Virtual / In-memory entity')}</span>
|
|
1500
|
+
${node.file_path ? `<button class="btn-action" style="padding:2px 6px;font-size:10px" onclick="copyText('${escapeAttr(node.file_path)}')">Copy</button>` : ''}
|
|
1501
|
+
</div>
|
|
1502
|
+
</div>
|
|
1503
|
+
`);
|
|
1504
|
+
|
|
1505
|
+
// 1b. Centrality & Blast Radius
|
|
1506
|
+
var centrality = res.centrality || { inDegree: incoming.length, outDegree: outgoing.length, isHub: incoming.length >= 4 };
|
|
1507
|
+
var hubTag = centrality.isHub
|
|
1508
|
+
? '<span style="background:rgba(239,68,68,0.2);color:#f87171;padding:2px 8px;border-radius:4px;font-weight:700;font-size:11px">🔥 ARCHITECTURAL HUB</span>'
|
|
1509
|
+
: '<span style="background:rgba(59,130,246,0.15);color:#60a5fa;padding:2px 8px;border-radius:4px;font-size:11px">Modular Node</span>';
|
|
1510
|
+
html.push(`
|
|
1511
|
+
<div class="inspector-section">
|
|
1512
|
+
<div class="inspector-label">Centrality & Impact Rank</div>
|
|
1513
|
+
<div style="display:flex;align-items:center;justify-content:space-between;background:var(--card-bg);padding:8px 10px;border-radius:6px;border:1px solid var(--border)">
|
|
1514
|
+
<div>${hubTag}</div>
|
|
1515
|
+
<div style="font-size:11px;color:var(--muted)"><b>${centrality.inDegree}</b> incoming · <b>${centrality.outDegree}</b> dependencies</div>
|
|
1516
|
+
</div>
|
|
1517
|
+
</div>
|
|
1518
|
+
`);
|
|
1519
|
+
|
|
1520
|
+
// 2. Description / Purpose
|
|
1521
|
+
if (meta.description) {
|
|
1522
|
+
html.push(`
|
|
1523
|
+
<div class="inspector-section">
|
|
1524
|
+
<div class="inspector-label"><i data-lucide="book-open" style="width:12px;height:12px"></i> Purpose / Explanation</div>
|
|
1525
|
+
<div class="inspector-desc">${escapeHtml(meta.description)}</div>
|
|
1526
|
+
</div>
|
|
1527
|
+
`);
|
|
1528
|
+
} else {
|
|
1529
|
+
html.push(`
|
|
1530
|
+
<div class="inspector-section">
|
|
1531
|
+
<div class="inspector-label"><i data-lucide="book-open" style="width:12px;height:12px"></i> Purpose / Explanation</div>
|
|
1532
|
+
<div style="font-size:12px;color:var(--muted);font-style:italic">No description recorded. Click 'Edit' below to add one.</div>
|
|
1533
|
+
</div>
|
|
1534
|
+
`);
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
// 3. Signature & Parameters
|
|
1538
|
+
if (meta.signature) {
|
|
1539
|
+
html.push(`
|
|
1540
|
+
<div class="inspector-section">
|
|
1541
|
+
<div class="inspector-label"><i data-lucide="code" style="width:12px;height:12px"></i> Type Signature</div>
|
|
1542
|
+
<pre class="inspector-code"><code>${escapeHtml(meta.signature)}</code></pre>
|
|
1543
|
+
</div>
|
|
1544
|
+
`);
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
// 4. Methods / Members
|
|
1548
|
+
if (meta.methods && Array.isArray(meta.methods) && meta.methods.length > 0) {
|
|
1549
|
+
html.push(`
|
|
1550
|
+
<div class="inspector-section">
|
|
1551
|
+
<div class="inspector-label">Public Methods (${meta.methods.length})</div>
|
|
1552
|
+
<div class="chip-list">
|
|
1553
|
+
${meta.methods.map(m => `<div class="inspector-chip" style="cursor:default"><span class="chip-badge">method</span> ${escapeHtml(m)}</div>`).join("")}
|
|
1554
|
+
</div>
|
|
1555
|
+
</div>
|
|
1556
|
+
`);
|
|
1557
|
+
} else if (meta.members && Array.isArray(meta.members) && meta.members.length > 0) {
|
|
1558
|
+
html.push(`
|
|
1559
|
+
<div class="inspector-section">
|
|
1560
|
+
<div class="inspector-label">Members (${meta.members.length})</div>
|
|
1561
|
+
<div class="chip-list">
|
|
1562
|
+
${meta.members.map(m => `<div class="inspector-chip" style="cursor:default"><span class="chip-badge">member</span> ${escapeHtml(m)}</div>`).join("")}
|
|
1563
|
+
</div>
|
|
1564
|
+
</div>
|
|
1565
|
+
`);
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
// 5. Incoming Connections (who references/calls/imports this)
|
|
1569
|
+
html.push(`
|
|
1570
|
+
<div class="inspector-section">
|
|
1571
|
+
<div class="inspector-label">Incoming Connections (${incoming.length})</div>
|
|
1572
|
+
<div class="chip-list">
|
|
1573
|
+
${incoming.length === 0 ? '<div style="color:var(--muted);font-size:12px">None</div>' : ''}
|
|
1574
|
+
${incoming.map(e => `
|
|
1575
|
+
<div class="inspector-chip" onclick="selectAndFocusNode('${escapeAttr(e.source)}')">
|
|
1576
|
+
<span class="chip-badge" style="color:var(--amber-light)">${escapeHtml(e.relation)}</span>
|
|
1577
|
+
<span>${escapeHtml(e.source_name || e.source)}</span>
|
|
1578
|
+
</div>
|
|
1579
|
+
`).join("")}
|
|
1580
|
+
</div>
|
|
1581
|
+
</div>
|
|
1582
|
+
`);
|
|
1583
|
+
|
|
1584
|
+
// 6. Outgoing Connections (what this calls/imports/defines)
|
|
1585
|
+
html.push(`
|
|
1586
|
+
<div class="inspector-section">
|
|
1587
|
+
<div class="inspector-label">Outgoing Connections (${outgoing.length})</div>
|
|
1588
|
+
<div class="chip-list">
|
|
1589
|
+
${outgoing.length === 0 ? '<div style="color:var(--muted);font-size:12px">None</div>' : ''}
|
|
1590
|
+
${outgoing.map(e => `
|
|
1591
|
+
<div class="inspector-chip" onclick="selectAndFocusNode('${escapeAttr(e.target)}')">
|
|
1592
|
+
<span class="chip-badge" style="color:var(--cyan)">${escapeHtml(e.relation)}</span>
|
|
1593
|
+
<span>${escapeHtml(e.target_name || e.target)}</span>
|
|
1594
|
+
</div>
|
|
1595
|
+
`).join("")}
|
|
1596
|
+
</div>
|
|
1597
|
+
</div>
|
|
1598
|
+
`);
|
|
1599
|
+
|
|
1600
|
+
// 7. Associated Gotchas
|
|
1601
|
+
if (gotchas.length > 0) {
|
|
1602
|
+
html.push(`
|
|
1603
|
+
<div class="inspector-section">
|
|
1604
|
+
<div class="inspector-label" style="color:var(--rose)">Associated Gotchas (${gotchas.length})</div>
|
|
1605
|
+
<div style="display:flex;flex-direction:column;gap:8px">
|
|
1606
|
+
${gotchas.map(g => `
|
|
1607
|
+
<div style="background:rgba(244,63,94,0.08);border:1px solid rgba(244,63,94,0.25);border-radius:6px;padding:10px;font-size:12px">
|
|
1608
|
+
<div style="font-weight:700;color:var(--rose);margin-bottom:4px">⚠️ ${escapeHtml(g.description)}</div>
|
|
1609
|
+
${g.workaround ? `<div style="color:var(--muted);font-size:11px">Workaround: ${escapeHtml(g.workaround)}</div>` : ''}
|
|
1610
|
+
</div>
|
|
1611
|
+
`).join("")}
|
|
1612
|
+
</div>
|
|
1613
|
+
</div>
|
|
1614
|
+
`);
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
body.innerHTML = html.join("");
|
|
1618
|
+
footer.style.display = "flex";
|
|
1619
|
+
|
|
1620
|
+
// Setup action buttons
|
|
1621
|
+
document.getElementById("inspector-connect-btn").onclick = function() {
|
|
1622
|
+
openConnectEdgeModal(node.id);
|
|
1623
|
+
};
|
|
1624
|
+
document.getElementById("inspector-edit-btn").onclick = function() {
|
|
1625
|
+
openEditNodeModal(node);
|
|
1626
|
+
};
|
|
1627
|
+
document.getElementById("inspector-delete-btn").onclick = function() {
|
|
1628
|
+
deleteNode(node.id);
|
|
1629
|
+
};
|
|
1630
|
+
|
|
1631
|
+
if (window.lucide && window.lucide.createIcons) {
|
|
1632
|
+
try { window.lucide.createIcons(); } catch {}
|
|
1633
|
+
}
|
|
1634
|
+
} catch (err) {
|
|
1635
|
+
body.innerHTML = `<div style="color:var(--rose);padding:20px">Failed to load node: ${escapeHtml(String(err))}</div>`;
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
function closeNodeInspector() {
|
|
1640
|
+
document.getElementById("graph-inspector").classList.remove("open");
|
|
1641
|
+
selectedNodeId = null;
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
function selectAndFocusNode(nodeId) {
|
|
1645
|
+
if (graphNetwork) {
|
|
1646
|
+
graphNetwork.selectNodes([nodeId]);
|
|
1647
|
+
graphNetwork.focus(nodeId, {
|
|
1648
|
+
scale: 1.2,
|
|
1649
|
+
animation: { duration: 400, easingFunction: "easeInOutQuad" },
|
|
1650
|
+
});
|
|
1651
|
+
}
|
|
1652
|
+
openNodeInspector(nodeId);
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
function closeModal(modalId) {
|
|
1656
|
+
var el = document.getElementById(modalId);
|
|
1657
|
+
if (el) el.classList.remove("open");
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
function openCreateNodeModal() {
|
|
1661
|
+
document.getElementById("modal-node-title").textContent = "Create Knowledge Node";
|
|
1662
|
+
document.getElementById("node-form-id").value = "";
|
|
1663
|
+
document.getElementById("node-form-type").value = "feature_domain";
|
|
1664
|
+
document.getElementById("node-form-name").value = "";
|
|
1665
|
+
document.getElementById("node-form-filepath").value = "";
|
|
1666
|
+
document.getElementById("node-form-desc").value = "";
|
|
1667
|
+
document.getElementById("modal-node").classList.add("open");
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
function openEditNodeModal(node) {
|
|
1671
|
+
document.getElementById("modal-node-title").textContent = "Edit Knowledge Node";
|
|
1672
|
+
document.getElementById("node-form-id").value = node.id;
|
|
1673
|
+
document.getElementById("node-form-type").value = node.type;
|
|
1674
|
+
document.getElementById("node-form-name").value = node.name || "";
|
|
1675
|
+
document.getElementById("node-form-filepath").value = node.file_path || "";
|
|
1676
|
+
var meta = {};
|
|
1677
|
+
try { meta = typeof node.metadata === "string" ? JSON.parse(node.metadata) : (node.metadata || {}); } catch {}
|
|
1678
|
+
document.getElementById("node-form-desc").value = meta.description || "";
|
|
1679
|
+
document.getElementById("modal-node").classList.add("open");
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
async function submitNodeForm() {
|
|
1683
|
+
var id = document.getElementById("node-form-id").value.trim();
|
|
1684
|
+
var type = document.getElementById("node-form-type").value;
|
|
1685
|
+
var name = document.getElementById("node-form-name").value.trim();
|
|
1686
|
+
var filePath = document.getElementById("node-form-filepath").value.trim();
|
|
1687
|
+
var desc = document.getElementById("node-form-desc").value.trim();
|
|
1688
|
+
|
|
1689
|
+
if (!name) {
|
|
1690
|
+
alert("Node name is required.");
|
|
1691
|
+
return;
|
|
1692
|
+
}
|
|
1693
|
+
|
|
1694
|
+
try {
|
|
1695
|
+
var res = await fetch(API + "/node", {
|
|
1696
|
+
method: "POST",
|
|
1697
|
+
headers: { "Content-Type": "application/json" },
|
|
1698
|
+
body: JSON.stringify({
|
|
1699
|
+
id: id || undefined,
|
|
1700
|
+
type: type,
|
|
1701
|
+
name: name,
|
|
1702
|
+
file_path: filePath || undefined,
|
|
1703
|
+
description: desc || undefined,
|
|
1704
|
+
}),
|
|
1705
|
+
}).then(r => r.json());
|
|
1706
|
+
|
|
1707
|
+
if (res.ok) {
|
|
1708
|
+
showToast("Knowledge node saved successfully!");
|
|
1709
|
+
closeModal("modal-node");
|
|
1710
|
+
await fetchDashboard();
|
|
1711
|
+
if (res.node && res.node.id) {
|
|
1712
|
+
selectAndFocusNode(res.node.id);
|
|
1713
|
+
}
|
|
1714
|
+
} else {
|
|
1715
|
+
alert("Failed to save node: " + (res.error || "Unknown error"));
|
|
1716
|
+
}
|
|
1717
|
+
} catch (err) {
|
|
1718
|
+
alert("Error saving node: " + err);
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
function openConnectEdgeModal(prefillSource) {
|
|
1723
|
+
var sourceSelect = document.getElementById("edge-form-source");
|
|
1724
|
+
var targetSelect = document.getElementById("edge-form-target");
|
|
1725
|
+
sourceSelect.innerHTML = "";
|
|
1726
|
+
targetSelect.innerHTML = "";
|
|
1727
|
+
|
|
1728
|
+
if (currentData && currentData.nodes) {
|
|
1729
|
+
currentData.nodes.forEach(function(n) {
|
|
1730
|
+
var opt1 = document.createElement("option");
|
|
1731
|
+
opt1.value = n.id;
|
|
1732
|
+
opt1.textContent = `[${n.type}] ${n.name || n.id}`;
|
|
1733
|
+
sourceSelect.appendChild(opt1);
|
|
1734
|
+
|
|
1735
|
+
var opt2 = document.createElement("option");
|
|
1736
|
+
opt2.value = n.id;
|
|
1737
|
+
opt2.textContent = `[${n.type}] ${n.name || n.id}`;
|
|
1738
|
+
targetSelect.appendChild(opt2);
|
|
1739
|
+
});
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
if (prefillSource) {
|
|
1743
|
+
sourceSelect.value = prefillSource;
|
|
1744
|
+
}
|
|
1745
|
+
document.getElementById("modal-edge").classList.add("open");
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
async function submitEdgeForm() {
|
|
1749
|
+
var source = document.getElementById("edge-form-source").value;
|
|
1750
|
+
var target = document.getElementById("edge-form-target").value;
|
|
1751
|
+
var type = document.getElementById("edge-form-type").value;
|
|
1752
|
+
|
|
1753
|
+
if (!source || !target) {
|
|
1754
|
+
alert("Please select both source and target nodes.");
|
|
1755
|
+
return;
|
|
1756
|
+
}
|
|
1757
|
+
if (source === target) {
|
|
1758
|
+
alert("Source and target cannot be the same node.");
|
|
1759
|
+
return;
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
try {
|
|
1763
|
+
var res = await fetch(API + "/edge", {
|
|
1764
|
+
method: "POST",
|
|
1765
|
+
headers: { "Content-Type": "application/json" },
|
|
1766
|
+
body: JSON.stringify({ source: source, target: target, type: type }),
|
|
1767
|
+
}).then(r => r.json());
|
|
1768
|
+
|
|
1769
|
+
if (res.ok) {
|
|
1770
|
+
showToast("Connection created successfully!");
|
|
1771
|
+
closeModal("modal-edge");
|
|
1772
|
+
await fetchDashboard();
|
|
1773
|
+
selectAndFocusNode(source);
|
|
1774
|
+
} else {
|
|
1775
|
+
alert("Failed to create connection: " + (res.error || "Unknown error"));
|
|
1776
|
+
}
|
|
1777
|
+
} catch (err) {
|
|
1778
|
+
alert("Error creating connection: " + err);
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
async function deleteNode(nodeId) {
|
|
1783
|
+
if (!confirm(`Are you sure you want to delete node "${nodeId}" and its connections?`)) return;
|
|
1784
|
+
|
|
1785
|
+
try {
|
|
1786
|
+
var res = await fetch(API + "/node/" + encodeURIComponent(nodeId), {
|
|
1787
|
+
method: "DELETE",
|
|
1788
|
+
}).then(r => r.json());
|
|
1789
|
+
|
|
1790
|
+
if (res.ok) {
|
|
1791
|
+
showToast("Node deleted successfully!");
|
|
1792
|
+
closeNodeInspector();
|
|
1793
|
+
await fetchDashboard();
|
|
1794
|
+
} else {
|
|
1795
|
+
alert("Failed to delete node: " + (res.error || "Unknown error"));
|
|
1796
|
+
}
|
|
1797
|
+
} catch (err) {
|
|
1798
|
+
alert("Error deleting node: " + err);
|
|
1799
|
+
}
|
|
1017
1800
|
}
|
|
1018
1801
|
|
|
1019
1802
|
function togglePhysics() {
|