claude-code-workflow 6.0.0 → 6.0.2

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.
@@ -121,6 +121,9 @@ function renderReviewSessionDetailPage(session) {
121
121
 
122
122
  </div>
123
123
 
124
+ <!-- Fix Progress Section (dynamically populated) -->
125
+ <div id="fixProgressSection" class="fix-progress-section-container"></div>
126
+
124
127
  <!-- Enhanced Findings Section -->
125
128
  <div class="review-enhanced-container">
126
129
  <!-- Header with Stats & Controls -->
@@ -697,6 +700,11 @@ function initReviewSessionPage(session) {
697
700
  // Reset state when page loads
698
701
  reviewSessionState.session = session;
699
702
  // Event handlers are inline onclick - no additional setup needed
703
+
704
+ // Start fix progress polling if in server mode
705
+ if (window.SERVER_MODE && session?.session_id) {
706
+ startFixProgressPolling(session.session_id);
707
+ }
700
708
  }
701
709
 
702
710
  // Legacy filter function for compatibility
@@ -709,3 +717,314 @@ function filterReviewFindings(severity) {
709
717
  }
710
718
  applyReviewSessionFilters();
711
719
  }
720
+
721
+ // ==========================================
722
+ // FIX PROGRESS TRACKING
723
+ // ==========================================
724
+
725
+ // Fix progress state
726
+ let fixProgressState = {
727
+ fixPlan: null,
728
+ progressData: null,
729
+ pollInterval: null,
730
+ currentSlide: 0
731
+ };
732
+
733
+ /**
734
+ * Discover and load fix-plan.json for the current review session
735
+ * Searches in: .review/fixes/{fix-session-id}/fix-plan.json
736
+ */
737
+ async function loadFixProgress(sessionId) {
738
+ if (!window.SERVER_MODE) {
739
+ return null;
740
+ }
741
+
742
+ try {
743
+ // First, discover active fix session
744
+ const activeFixResponse = await fetch(`/api/file?path=${encodeURIComponent(projectPath + '/.review/fixes/active-fix-session.json')}`);
745
+ if (!activeFixResponse.ok) {
746
+ return null;
747
+ }
748
+ const activeFixSession = await activeFixResponse.json();
749
+ const fixSessionId = activeFixSession.fix_session_id;
750
+
751
+ // Load fix-plan.json
752
+ const planPath = `${projectPath}/.review/fixes/${fixSessionId}/fix-plan.json`;
753
+ const planResponse = await fetch(`/api/file?path=${encodeURIComponent(planPath)}`);
754
+ if (!planResponse.ok) {
755
+ return null;
756
+ }
757
+ const fixPlan = await planResponse.json();
758
+
759
+ // Load progress files for each group
760
+ const progressPromises = (fixPlan.groups || []).map(async (group) => {
761
+ const progressPath = `${projectPath}/.review/fixes/${fixSessionId}/${group.progress_file}`;
762
+ try {
763
+ const response = await fetch(`/api/file?path=${encodeURIComponent(progressPath)}`);
764
+ return response.ok ? await response.json() : null;
765
+ } catch {
766
+ return null;
767
+ }
768
+ });
769
+ const progressDataArray = await Promise.all(progressPromises);
770
+
771
+ // Aggregate progress data
772
+ const aggregated = aggregateFixProgress(fixPlan, progressDataArray.filter(d => d !== null));
773
+
774
+ fixProgressState.fixPlan = fixPlan;
775
+ fixProgressState.progressData = aggregated;
776
+
777
+ return aggregated;
778
+ } catch (err) {
779
+ console.error('Failed to load fix progress:', err);
780
+ return null;
781
+ }
782
+ }
783
+
784
+ /**
785
+ * Aggregate progress from multiple group progress files
786
+ */
787
+ function aggregateFixProgress(fixPlan, progressDataArray) {
788
+ let totalFindings = 0;
789
+ let fixedCount = 0;
790
+ let failedCount = 0;
791
+ let inProgressCount = 0;
792
+ let pendingCount = 0;
793
+ const activeAgents = [];
794
+
795
+ progressDataArray.forEach(progress => {
796
+ if (progress.findings) {
797
+ progress.findings.forEach(f => {
798
+ totalFindings++;
799
+ if (f.result === 'fixed') fixedCount++;
800
+ else if (f.result === 'failed') failedCount++;
801
+ else if (f.status === 'in-progress') inProgressCount++;
802
+ else pendingCount++;
803
+ });
804
+ }
805
+ if (progress.assigned_agent && progress.status === 'in-progress') {
806
+ activeAgents.push({
807
+ agent_id: progress.assigned_agent,
808
+ group_id: progress.group_id,
809
+ current_finding: progress.current_finding
810
+ });
811
+ }
812
+ });
813
+
814
+ // Determine phase
815
+ let phase = 'planning';
816
+ if (fixPlan.metadata?.status === 'executing' || inProgressCount > 0 || fixedCount > 0 || failedCount > 0) {
817
+ phase = 'execution';
818
+ }
819
+ if (totalFindings > 0 && pendingCount === 0 && inProgressCount === 0) {
820
+ phase = 'completion';
821
+ }
822
+
823
+ // Calculate stage progress
824
+ const stages = (fixPlan.timeline?.stages || []).map(stage => {
825
+ const groupStatuses = stage.groups.map(groupId => {
826
+ const progress = progressDataArray.find(p => p.group_id === groupId);
827
+ return progress ? progress.status : 'pending';
828
+ });
829
+ let status = 'pending';
830
+ if (groupStatuses.every(s => s === 'completed' || s === 'failed')) status = 'completed';
831
+ else if (groupStatuses.some(s => s === 'in-progress')) status = 'in-progress';
832
+ return { stage: stage.stage, status, groups: stage.groups };
833
+ });
834
+
835
+ const currentStage = stages.findIndex(s => s.status === 'in-progress' || s.status === 'pending') + 1 || stages.length;
836
+ const percentComplete = totalFindings > 0 ? ((fixedCount + failedCount) / totalFindings) * 100 : 0;
837
+
838
+ return {
839
+ fix_session_id: fixPlan.metadata?.fix_session_id,
840
+ phase,
841
+ total_findings: totalFindings,
842
+ fixed_count: fixedCount,
843
+ failed_count: failedCount,
844
+ in_progress_count: inProgressCount,
845
+ pending_count: pendingCount,
846
+ percent_complete: percentComplete,
847
+ current_stage: currentStage,
848
+ total_stages: stages.length,
849
+ stages,
850
+ active_agents: activeAgents
851
+ };
852
+ }
853
+
854
+ /**
855
+ * Render fix progress tracking card (carousel style)
856
+ */
857
+ function renderFixProgressCard(progressData) {
858
+ if (!progressData) {
859
+ return '';
860
+ }
861
+
862
+ const { phase, total_findings, fixed_count, failed_count, in_progress_count, pending_count, percent_complete, current_stage, total_stages, stages, active_agents, fix_session_id } = progressData;
863
+
864
+ // Phase badge class
865
+ const phaseClass = phase === 'planning' ? 'phase-planning' : phase === 'execution' ? 'phase-execution' : 'phase-completion';
866
+ const phaseIcon = phase === 'planning' ? '📝' : phase === 'execution' ? '⚡' : '✅';
867
+
868
+ // Build stage dots
869
+ const stageDots = stages.map((s, i) => {
870
+ const dotClass = s.status === 'completed' ? 'completed' : s.status === 'in-progress' ? 'active' : '';
871
+ return `<span class="fix-stage-dot ${dotClass}" title="Stage ${i + 1}: ${s.status}"></span>`;
872
+ }).join('');
873
+
874
+ // Build carousel slides
875
+ const slides = [];
876
+
877
+ // Slide 1: Overview
878
+ slides.push(`
879
+ <div class="fix-carousel-slide">
880
+ <div class="fix-slide-header">
881
+ <span class="fix-phase-badge ${phaseClass}">${phaseIcon} ${phase.toUpperCase()}</span>
882
+ <span class="fix-session-id">${fix_session_id || 'Fix Session'}</span>
883
+ </div>
884
+ <div class="fix-progress-bar-mini">
885
+ <div class="fix-progress-fill" style="width: ${percent_complete}%"></div>
886
+ </div>
887
+ <div class="fix-progress-text">${percent_complete.toFixed(0)}% Complete · Stage ${current_stage}/${total_stages}</div>
888
+ </div>
889
+ `);
890
+
891
+ // Slide 2: Stats
892
+ slides.push(`
893
+ <div class="fix-carousel-slide">
894
+ <div class="fix-stats-row">
895
+ <div class="fix-stat">
896
+ <span class="fix-stat-value">${total_findings}</span>
897
+ <span class="fix-stat-label">Total</span>
898
+ </div>
899
+ <div class="fix-stat fixed">
900
+ <span class="fix-stat-value">${fixed_count}</span>
901
+ <span class="fix-stat-label">Fixed</span>
902
+ </div>
903
+ <div class="fix-stat failed">
904
+ <span class="fix-stat-value">${failed_count}</span>
905
+ <span class="fix-stat-label">Failed</span>
906
+ </div>
907
+ <div class="fix-stat pending">
908
+ <span class="fix-stat-value">${pending_count + in_progress_count}</span>
909
+ <span class="fix-stat-label">Pending</span>
910
+ </div>
911
+ </div>
912
+ </div>
913
+ `);
914
+
915
+ // Slide 3: Active agents (if any)
916
+ if (active_agents.length > 0) {
917
+ const agentItems = active_agents.slice(0, 2).map(a => `
918
+ <div class="fix-agent-item">
919
+ <span class="fix-agent-icon">🤖</span>
920
+ <span class="fix-agent-info">${a.current_finding?.finding_title || 'Working...'}</span>
921
+ </div>
922
+ `).join('');
923
+ slides.push(`
924
+ <div class="fix-carousel-slide">
925
+ <div class="fix-agents-header">${active_agents.length} Active Agent${active_agents.length > 1 ? 's' : ''}</div>
926
+ ${agentItems}
927
+ </div>
928
+ `);
929
+ }
930
+
931
+ // Build carousel navigation
932
+ const navDots = slides.map((_, i) => `
933
+ <span class="fix-nav-dot ${i === 0 ? 'active' : ''}" onclick="navigateFixCarousel(${i})"></span>
934
+ `).join('');
935
+
936
+ return `
937
+ <div class="fix-progress-card" id="fixProgressCard">
938
+ <div class="fix-card-header">
939
+ <span class="fix-card-title">🔧 Fix Progress</span>
940
+ <div class="fix-stage-dots">${stageDots}</div>
941
+ </div>
942
+ <div class="fix-carousel-container">
943
+ <div class="fix-carousel-track" id="fixCarouselTrack">
944
+ ${slides.join('')}
945
+ </div>
946
+ </div>
947
+ <div class="fix-carousel-nav">
948
+ <button class="fix-nav-btn prev" onclick="navigateFixCarousel('prev')">‹</button>
949
+ <div class="fix-nav-dots">${navDots}</div>
950
+ <button class="fix-nav-btn next" onclick="navigateFixCarousel('next')">›</button>
951
+ </div>
952
+ </div>
953
+ `;
954
+ }
955
+
956
+ /**
957
+ * Navigate fix progress carousel
958
+ */
959
+ function navigateFixCarousel(direction) {
960
+ const track = document.getElementById('fixCarouselTrack');
961
+ if (!track) return;
962
+
963
+ const slides = track.querySelectorAll('.fix-carousel-slide');
964
+ const totalSlides = slides.length;
965
+
966
+ if (typeof direction === 'number') {
967
+ fixProgressState.currentSlide = direction;
968
+ } else if (direction === 'next') {
969
+ fixProgressState.currentSlide = (fixProgressState.currentSlide + 1) % totalSlides;
970
+ } else if (direction === 'prev') {
971
+ fixProgressState.currentSlide = (fixProgressState.currentSlide - 1 + totalSlides) % totalSlides;
972
+ }
973
+
974
+ track.style.transform = `translateX(-${fixProgressState.currentSlide * 100}%)`;
975
+
976
+ // Update nav dots
977
+ document.querySelectorAll('.fix-nav-dot').forEach((dot, i) => {
978
+ dot.classList.toggle('active', i === fixProgressState.currentSlide);
979
+ });
980
+ }
981
+
982
+ /**
983
+ * Start polling for fix progress updates
984
+ */
985
+ function startFixProgressPolling(sessionId) {
986
+ if (fixProgressState.pollInterval) {
987
+ clearInterval(fixProgressState.pollInterval);
988
+ }
989
+
990
+ // Initial load
991
+ loadFixProgress(sessionId).then(data => {
992
+ if (data) {
993
+ updateFixProgressUI(data);
994
+ }
995
+ });
996
+
997
+ // Poll every 5 seconds
998
+ fixProgressState.pollInterval = setInterval(async () => {
999
+ const data = await loadFixProgress(sessionId);
1000
+ if (data) {
1001
+ updateFixProgressUI(data);
1002
+ // Stop polling if completed
1003
+ if (data.phase === 'completion') {
1004
+ clearInterval(fixProgressState.pollInterval);
1005
+ fixProgressState.pollInterval = null;
1006
+ }
1007
+ }
1008
+ }, 5000);
1009
+ }
1010
+
1011
+ /**
1012
+ * Update fix progress UI
1013
+ */
1014
+ function updateFixProgressUI(progressData) {
1015
+ const container = document.getElementById('fixProgressSection');
1016
+ if (!container) return;
1017
+
1018
+ container.innerHTML = renderFixProgressCard(progressData);
1019
+ fixProgressState.currentSlide = 0;
1020
+ }
1021
+
1022
+ /**
1023
+ * Stop fix progress polling
1024
+ */
1025
+ function stopFixProgressPolling() {
1026
+ if (fixProgressState.pollInterval) {
1027
+ clearInterval(fixProgressState.pollInterval);
1028
+ fixProgressState.pollInterval = null;
1029
+ }
1030
+ }
@@ -7658,3 +7658,274 @@ code.ctx-meta-chip-value {
7658
7658
  font-size: 0.7rem;
7659
7659
  color: hsl(var(--muted-foreground));
7660
7660
  }
7661
+
7662
+ /* ===================================
7663
+ Fix Progress Tracking Card (Carousel)
7664
+ =================================== */
7665
+
7666
+ .fix-progress-section-container {
7667
+ margin-bottom: 1rem;
7668
+ }
7669
+
7670
+ .fix-progress-card {
7671
+ background: hsl(var(--card));
7672
+ border: 1px solid hsl(var(--border));
7673
+ border-radius: 0.5rem;
7674
+ padding: 1rem;
7675
+ overflow: hidden;
7676
+ }
7677
+
7678
+ .fix-card-header {
7679
+ display: flex;
7680
+ justify-content: space-between;
7681
+ align-items: center;
7682
+ margin-bottom: 0.75rem;
7683
+ }
7684
+
7685
+ .fix-card-title {
7686
+ font-weight: 600;
7687
+ font-size: 0.9rem;
7688
+ color: hsl(var(--foreground));
7689
+ }
7690
+
7691
+ .fix-stage-dots {
7692
+ display: flex;
7693
+ gap: 0.375rem;
7694
+ align-items: center;
7695
+ }
7696
+
7697
+ .fix-stage-dot {
7698
+ width: 8px;
7699
+ height: 8px;
7700
+ border-radius: 50%;
7701
+ background: hsl(var(--muted));
7702
+ transition: all 0.2s ease;
7703
+ }
7704
+
7705
+ .fix-stage-dot.active {
7706
+ background: hsl(var(--primary));
7707
+ animation: pulse-dot 1.5s infinite;
7708
+ }
7709
+
7710
+ .fix-stage-dot.completed {
7711
+ background: hsl(var(--success));
7712
+ }
7713
+
7714
+ @keyframes pulse-dot {
7715
+ 0%, 100% { opacity: 1; transform: scale(1); }
7716
+ 50% { opacity: 0.6; transform: scale(1.2); }
7717
+ }
7718
+
7719
+ /* Carousel Container */
7720
+ .fix-carousel-container {
7721
+ overflow: hidden;
7722
+ margin-bottom: 0.75rem;
7723
+ }
7724
+
7725
+ .fix-carousel-track {
7726
+ display: flex;
7727
+ transition: transform 0.3s ease;
7728
+ }
7729
+
7730
+ .fix-carousel-slide {
7731
+ min-width: 100%;
7732
+ padding: 0.5rem;
7733
+ }
7734
+
7735
+ /* Slide Header */
7736
+ .fix-slide-header {
7737
+ display: flex;
7738
+ justify-content: space-between;
7739
+ align-items: center;
7740
+ margin-bottom: 0.75rem;
7741
+ }
7742
+
7743
+ .fix-phase-badge {
7744
+ display: inline-flex;
7745
+ align-items: center;
7746
+ gap: 0.25rem;
7747
+ padding: 0.25rem 0.75rem;
7748
+ border-radius: 9999px;
7749
+ font-size: 0.7rem;
7750
+ font-weight: 600;
7751
+ text-transform: uppercase;
7752
+ }
7753
+
7754
+ .fix-phase-badge.phase-planning {
7755
+ background: hsl(270 60% 90%);
7756
+ color: hsl(270 60% 40%);
7757
+ }
7758
+
7759
+ .fix-phase-badge.phase-execution {
7760
+ background: hsl(220 80% 90%);
7761
+ color: hsl(220 80% 40%);
7762
+ animation: pulse-badge 2s infinite;
7763
+ }
7764
+
7765
+ .fix-phase-badge.phase-completion {
7766
+ background: hsl(var(--success-light));
7767
+ color: hsl(var(--success));
7768
+ }
7769
+
7770
+ @keyframes pulse-badge {
7771
+ 0%, 100% { opacity: 1; }
7772
+ 50% { opacity: 0.7; }
7773
+ }
7774
+
7775
+ .fix-session-id {
7776
+ font-size: 0.7rem;
7777
+ color: hsl(var(--muted-foreground));
7778
+ font-family: var(--font-mono);
7779
+ }
7780
+
7781
+ /* Progress Bar Mini */
7782
+ .fix-progress-bar-mini {
7783
+ height: 6px;
7784
+ background: hsl(var(--muted));
7785
+ border-radius: 3px;
7786
+ overflow: hidden;
7787
+ margin-bottom: 0.5rem;
7788
+ }
7789
+
7790
+ .fix-progress-fill {
7791
+ height: 100%;
7792
+ background: linear-gradient(90deg, hsl(var(--primary)), hsl(var(--success)));
7793
+ border-radius: 3px;
7794
+ transition: width 0.3s ease;
7795
+ }
7796
+
7797
+ .fix-progress-text {
7798
+ font-size: 0.75rem;
7799
+ color: hsl(var(--muted-foreground));
7800
+ text-align: center;
7801
+ }
7802
+
7803
+ /* Stats Row (Slide 2) */
7804
+ .fix-stats-row {
7805
+ display: flex;
7806
+ justify-content: space-around;
7807
+ padding: 0.5rem 0;
7808
+ }
7809
+
7810
+ .fix-stat {
7811
+ text-align: center;
7812
+ }
7813
+
7814
+ .fix-stat-value {
7815
+ display: block;
7816
+ font-size: 1.25rem;
7817
+ font-weight: 700;
7818
+ color: hsl(var(--foreground));
7819
+ }
7820
+
7821
+ .fix-stat-label {
7822
+ display: block;
7823
+ font-size: 0.65rem;
7824
+ color: hsl(var(--muted-foreground));
7825
+ text-transform: uppercase;
7826
+ }
7827
+
7828
+ .fix-stat.fixed .fix-stat-value {
7829
+ color: hsl(var(--success));
7830
+ }
7831
+
7832
+ .fix-stat.failed .fix-stat-value {
7833
+ color: hsl(var(--destructive));
7834
+ }
7835
+
7836
+ .fix-stat.pending .fix-stat-value {
7837
+ color: hsl(var(--warning));
7838
+ }
7839
+
7840
+ /* Active Agents (Slide 3) */
7841
+ .fix-agents-header {
7842
+ font-size: 0.8rem;
7843
+ font-weight: 600;
7844
+ color: hsl(var(--foreground));
7845
+ margin-bottom: 0.5rem;
7846
+ }
7847
+
7848
+ .fix-agent-item {
7849
+ display: flex;
7850
+ align-items: center;
7851
+ gap: 0.5rem;
7852
+ padding: 0.375rem 0.5rem;
7853
+ background: hsl(var(--muted));
7854
+ border-radius: 0.25rem;
7855
+ margin-bottom: 0.375rem;
7856
+ }
7857
+
7858
+ .fix-agent-item:last-child {
7859
+ margin-bottom: 0;
7860
+ }
7861
+
7862
+ .fix-agent-icon {
7863
+ font-size: 0.875rem;
7864
+ animation: spin-agent 2s linear infinite;
7865
+ }
7866
+
7867
+ @keyframes spin-agent {
7868
+ 0% { transform: rotate(0deg); }
7869
+ 100% { transform: rotate(360deg); }
7870
+ }
7871
+
7872
+ .fix-agent-info {
7873
+ font-size: 0.75rem;
7874
+ color: hsl(var(--foreground));
7875
+ white-space: nowrap;
7876
+ overflow: hidden;
7877
+ text-overflow: ellipsis;
7878
+ }
7879
+
7880
+ /* Carousel Navigation */
7881
+ .fix-carousel-nav {
7882
+ display: flex;
7883
+ justify-content: center;
7884
+ align-items: center;
7885
+ gap: 0.75rem;
7886
+ }
7887
+
7888
+ .fix-nav-btn {
7889
+ width: 24px;
7890
+ height: 24px;
7891
+ border: none;
7892
+ background: hsl(var(--muted));
7893
+ color: hsl(var(--muted-foreground));
7894
+ border-radius: 50%;
7895
+ cursor: pointer;
7896
+ font-size: 1rem;
7897
+ font-weight: bold;
7898
+ display: flex;
7899
+ align-items: center;
7900
+ justify-content: center;
7901
+ transition: all 0.15s;
7902
+ }
7903
+
7904
+ .fix-nav-btn:hover {
7905
+ background: hsl(var(--hover));
7906
+ color: hsl(var(--foreground));
7907
+ }
7908
+
7909
+ .fix-nav-dots {
7910
+ display: flex;
7911
+ gap: 0.375rem;
7912
+ }
7913
+
7914
+ .fix-nav-dot {
7915
+ width: 6px;
7916
+ height: 6px;
7917
+ border-radius: 50%;
7918
+ background: hsl(var(--muted));
7919
+ cursor: pointer;
7920
+ transition: all 0.2s;
7921
+ }
7922
+
7923
+ .fix-nav-dot:hover {
7924
+ background: hsl(var(--muted-foreground));
7925
+ }
7926
+
7927
+ .fix-nav-dot.active {
7928
+ background: hsl(var(--primary));
7929
+ width: 16px;
7930
+ border-radius: 3px;
7931
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-workflow",
3
- "version": "6.0.0",
3
+ "version": "6.0.2",
4
4
  "description": "JSON-driven multi-agent development framework with intelligent CLI orchestration (Gemini/Qwen/Codex), context-first architecture, and automated workflow execution",
5
5
  "type": "module",
6
6
  "main": "ccw/src/index.js",
@@ -28,19 +28,20 @@
28
28
  "node": ">=16.0.0"
29
29
  },
30
30
  "dependencies": {
31
- "commander": "^11.0.0",
32
- "open": "^9.1.0",
31
+ "boxen": "^7.1.0",
33
32
  "chalk": "^5.3.0",
33
+ "commander": "^11.0.0",
34
+ "figlet": "^1.7.0",
34
35
  "glob": "^10.3.0",
36
+ "gradient-string": "^2.0.2",
35
37
  "inquirer": "^9.2.0",
36
- "ora": "^7.0.0",
37
- "figlet": "^1.7.0",
38
- "boxen": "^7.1.0",
39
- "gradient-string": "^2.0.2"
38
+ "open": "^9.1.0",
39
+ "ora": "^7.0.0"
40
40
  },
41
41
  "files": [
42
42
  "ccw/bin/",
43
43
  "ccw/src/",
44
+ "ccw/package.json",
44
45
  ".claude/agents/",
45
46
  ".claude/commands/",
46
47
  ".claude/output-styles/",