pixelcrew 0.1.0 → 0.1.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 CHANGED
@@ -63,13 +63,13 @@ No global installations or complex configuration required. Run directly in your
63
63
 
64
64
  ```bash
65
65
  # 1. Initialize and adapt PixelCrew to your project stack
66
- npx github:hiroqt/PixelCrew init
66
+ npx pixelcrew init
67
67
 
68
68
  # 2. Launch the orchestrator daemon and real-time visual office
69
- npx github:hiroqt/PixelCrew start
69
+ npx pixelcrew start
70
70
 
71
71
  # 3. (Optional) Run an instant multi-agent simulated sprint demo
72
- npx github:hiroqt/PixelCrew demo
72
+ npx pixelcrew demo
73
73
  ```
74
74
 
75
75
  The interactive dashboard opens automatically at:
@@ -240,15 +240,15 @@ Copy the `.agents/skills/` directory into your tool's native skills folder:
240
240
 
241
241
  | Command | Action | Example |
242
242
  | :--- | :--- | :--- |
243
- | `npx github:hiroqt/PixelCrew init` | Scaffolds `.pixel-agents/` and adapts to repository | `npx github:hiroqt/PixelCrew init --yes` |
244
- | `npx github:hiroqt/PixelCrew start` | Launches orchestrator, SSE stream, and web dashboard | `npx github:hiroqt/PixelCrew start --port 4747` |
245
- | `npx github:hiroqt/PixelCrew analyze` | Prints detected frameworks, database, ORM, and test stack | `npx github:hiroqt/PixelCrew analyze` |
246
- | `npx github:hiroqt/PixelCrew demo` | Launches an interactive 6-agent simulated sprint | `npx github:hiroqt/PixelCrew demo` |
247
- | `npx github:hiroqt/PixelCrew task "<text>"` | Dispatches a direct objective to the running agent swarm | `npx github:hiroqt/PixelCrew task "Optimize SQL queries"` |
248
- | `npx github:hiroqt/PixelCrew emit [flags]` | Emits a custom telemetry event into the stream | `npx github:hiroqt/PixelCrew emit --agent db --message "Done"` |
249
- | `npx github:hiroqt/PixelCrew dashboard` | Opens or serves the standalone Floor 42 dashboard UI | `npx github:hiroqt/PixelCrew dashboard` |
250
- | `npx github:hiroqt/PixelCrew status` | Prints ASCII summary of swarm states and active sprints | `npx github:hiroqt/PixelCrew status` |
251
- | `npx github:hiroqt/PixelCrew help` | Displays full CLI manual and flag details | `npx github:hiroqt/PixelCrew help` |
243
+ | `npx pixelcrew init` | Scaffolds `.pixel-agents/` and adapts to repository | `npx pixelcrew init --yes` |
244
+ | `npx pixelcrew start` | Launches orchestrator, SSE stream, and web dashboard | `npx pixelcrew start --port 4747` |
245
+ | `npx pixelcrew analyze` | Prints detected frameworks, database, ORM, and test stack | `npx pixelcrew analyze` |
246
+ | `npx pixelcrew demo` | Launches an interactive 6-agent simulated sprint | `npx pixelcrew demo` |
247
+ | `npx pixelcrew task "<text>"` | Dispatches a direct objective to the running agent swarm | `npx pixelcrew task "Optimize SQL queries"` |
248
+ | `npx pixelcrew emit [flags]` | Emits a custom telemetry event into the stream | `npx pixelcrew emit --agent db --message "Done"` |
249
+ | `npx pixelcrew dashboard` | Opens or serves the standalone Floor 42 dashboard UI | `npx pixelcrew dashboard` |
250
+ | `npx pixelcrew status` | Prints ASCII summary of swarm states and active sprints | `npx pixelcrew status` |
251
+ | `npx pixelcrew help` | Displays full CLI manual and flag details | `npx pixelcrew help` |
252
252
 
253
253
  ### CLI Options & Flags:
254
254
  - `--port <number>`: Dashboard port (default: `4747`)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pixelcrew",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Local multi-agent orchestration framework with an interactive pixel-art tech startup office visual dashboard",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -574,6 +574,7 @@ async function initApp() {
574
574
  appState.state = stRes;
575
575
  renderState();
576
576
  }
577
+ await fetchReports();
577
578
  } catch (e) {
578
579
  // Handled by SSE
579
580
  }
@@ -650,16 +651,59 @@ function setupEventListeners() {
650
651
  });
651
652
  });
652
653
 
653
- // Modal close
654
- document.getElementById('btnCloseModal').addEventListener('click', () => {
655
- document.getElementById('agentModal').classList.remove('open');
656
- });
654
+ // Reports Drawer Buttons & Controls
655
+ const btnReportsToggle = document.getElementById('btnReportsToggle');
656
+ if (btnReportsToggle) {
657
+ btnReportsToggle.addEventListener('click', () => {
658
+ synth.playClick();
659
+ openReportsModal();
660
+ });
661
+ }
657
662
 
658
- document.getElementById('agentModal').addEventListener('click', (e) => {
659
- if (e.target === document.getElementById('agentModal')) {
660
- document.getElementById('agentModal').classList.remove('open');
661
- }
662
- });
663
+ const btnCloseReportsModal = document.getElementById('btnCloseReportsModal');
664
+ if (btnCloseReportsModal) {
665
+ btnCloseReportsModal.addEventListener('click', () => {
666
+ closeReportsModal();
667
+ });
668
+ }
669
+
670
+ const reportsModalEl = document.getElementById('reportsModal');
671
+ if (reportsModalEl) {
672
+ reportsModalEl.addEventListener('click', (e) => {
673
+ if (e.target === reportsModalEl) {
674
+ closeReportsModal();
675
+ }
676
+ });
677
+ }
678
+
679
+ const btnCopyReportMd = document.getElementById('btnCopyReportMd');
680
+ if (btnCopyReportMd) {
681
+ btnCopyReportMd.addEventListener('click', () => {
682
+ copyActiveReportMarkdown();
683
+ });
684
+ }
685
+
686
+ const btnDownloadReport = document.getElementById('btnDownloadReport');
687
+ if (btnDownloadReport) {
688
+ btnDownloadReport.addEventListener('click', () => {
689
+ downloadActiveReport();
690
+ });
691
+ }
692
+
693
+ const btnToggleRawView = document.getElementById('btnToggleRawView');
694
+ if (btnToggleRawView) {
695
+ btnToggleRawView.addEventListener('click', () => {
696
+ toggleRawReportView();
697
+ });
698
+ }
699
+
700
+ const reportsSearchInput = document.getElementById('reportsSearch');
701
+ if (reportsSearchInput) {
702
+ reportsSearchInput.addEventListener('input', (e) => {
703
+ reportsState.searchQuery = e.target.value.toLowerCase().trim();
704
+ renderReportsList();
705
+ });
706
+ }
663
707
 
664
708
  // Global Keyboard Shortcuts
665
709
  window.addEventListener('keydown', (e) => {
@@ -669,6 +713,14 @@ function setupEventListeners() {
669
713
  document.getElementById('btnDemo').click();
670
714
  } else if (e.key === 'Escape') {
671
715
  document.getElementById('agentModal').classList.remove('open');
716
+ closeReportsModal();
717
+ } else if (e.key === 'r' || e.key === 'R') {
718
+ const modal = document.getElementById('reportsModal');
719
+ if (modal && modal.classList.contains('open')) {
720
+ closeReportsModal();
721
+ } else {
722
+ openReportsModal();
723
+ }
672
724
  } else if (['1', '2', '3', '4', '5', '6'].includes(e.key)) {
673
725
  const keys = ['frontend', 'backend', 'database', 'security', 'performance', 'qa'];
674
726
  const agentKey = keys[parseInt(e.key) - 1];
@@ -677,6 +729,268 @@ function setupEventListeners() {
677
729
  });
678
730
  }
679
731
 
732
+ // Reports State & Controller
733
+ const reportsState = {
734
+ reports: [],
735
+ activeReport: null,
736
+ searchQuery: '',
737
+ rawMode: false
738
+ };
739
+
740
+ async function fetchReports() {
741
+ try {
742
+ const res = await fetch('/api/reports');
743
+ if (res.ok) {
744
+ const data = await res.json();
745
+ reportsState.reports = data.reports || [];
746
+ const badge = document.getElementById('reportsCountBadge');
747
+ if (badge) {
748
+ badge.textContent = reportsState.reports.length;
749
+ }
750
+ return reportsState.reports;
751
+ }
752
+ } catch (err) {
753
+ console.error('Error loading reports:', err);
754
+ }
755
+ return [];
756
+ }
757
+
758
+ async function openReportsModal(preferredId = null) {
759
+ const modal = document.getElementById('reportsModal');
760
+ if (!modal) return;
761
+
762
+ await fetchReports();
763
+
764
+ if (preferredId) {
765
+ reportsState.activeReport = reportsState.reports.find(r => r.id === preferredId) || reportsState.reports[0] || null;
766
+ } else if (!reportsState.activeReport && reportsState.reports.length > 0) {
767
+ reportsState.activeReport = reportsState.reports[0];
768
+ }
769
+
770
+ renderReportsList();
771
+ renderActiveReport();
772
+
773
+ modal.classList.add('open');
774
+ synth.playClick();
775
+ }
776
+
777
+ function closeReportsModal() {
778
+ const modal = document.getElementById('reportsModal');
779
+ if (modal) modal.classList.remove('open');
780
+ }
781
+
782
+ function renderReportsList() {
783
+ const container = document.getElementById('reportsHistoryList');
784
+ if (!container) return;
785
+
786
+ container.innerHTML = '';
787
+ const filtered = reportsState.reports.filter(r => {
788
+ if (!reportsState.searchQuery) return true;
789
+ const q = reportsState.searchQuery;
790
+ return (
791
+ (r.objective && r.objective.toLowerCase().includes(q)) ||
792
+ (r.project && r.project.toLowerCase().includes(q)) ||
793
+ (r.dateFormatted && r.dateFormatted.toLowerCase().includes(q))
794
+ );
795
+ });
796
+
797
+ if (filtered.length === 0) {
798
+ container.innerHTML = `
799
+ <div style="padding: 16px; text-align: center; color: var(--text-dim); font-size: 11px;">
800
+ ${reportsState.searchQuery ? 'No matching reports found.' : 'No audit reports recorded yet. Run a sprint demo or task to generate.'}
801
+ </div>
802
+ `;
803
+ return;
804
+ }
805
+
806
+ filtered.forEach(report => {
807
+ const card = document.createElement('div');
808
+ card.className = `report-item-card ${reportsState.activeReport?.id === report.id ? 'active' : ''}`;
809
+
810
+ card.innerHTML = `
811
+ <div class="report-item-top">
812
+ <span class="report-item-date">${report.dateFormatted || new Date(report.timestamp).toLocaleDateString()}</span>
813
+ <span class="report-item-status">${report.status || 'COMPLETED'}</span>
814
+ </div>
815
+ <div class="report-item-title">${report.objective || 'Swarm Audit Sprint'}</div>
816
+ <div class="report-item-meta">
817
+ <span>👥 ${(report.targetAgents || []).length} Squads</span>
818
+ <span>✨ ${report.totalFindings || (report.sections || []).length} Points</span>
819
+ </div>
820
+ `;
821
+
822
+ card.addEventListener('click', () => {
823
+ synth.playClick();
824
+ reportsState.activeReport = report;
825
+ renderReportsList();
826
+ renderActiveReport();
827
+ });
828
+
829
+ container.appendChild(card);
830
+ });
831
+ }
832
+
833
+ function renderActiveReport() {
834
+ const viewer = document.getElementById('reportsViewer');
835
+ if (!viewer) return;
836
+
837
+ const report = reportsState.activeReport;
838
+ if (!report) {
839
+ viewer.innerHTML = `
840
+ <div class="report-empty-state">
841
+ <span style="font-size: 32px;">📋</span>
842
+ <span>SELECT A SPRINT REPORT FROM THE LEFT SIDEBAR</span>
843
+ <span style="color: var(--text-dim); font-size: 10px;">Audit reports are generated automatically after every completed swarm run.</span>
844
+ </div>
845
+ `;
846
+ return;
847
+ }
848
+
849
+ // Raw Markdown Mode
850
+ if (reportsState.rawMode) {
851
+ viewer.innerHTML = `
852
+ <div class="report-hero-card">
853
+ <div class="report-hero-header">
854
+ <div>
855
+ <h3 class="report-hero-title">RAW MARKDOWN SPECIFICATION</h3>
856
+ <div class="report-hero-meta">
857
+ <span>PROJECT: <strong>${report.project || 'Project'}</strong></span>
858
+ <span>DATE: <strong>${report.dateFormatted || 'N/A'}</strong></span>
859
+ </div>
860
+ </div>
861
+ </div>
862
+ </div>
863
+ <pre class="report-raw-pre">${escapeHtml(report.markdown || '# No markdown available')}</pre>
864
+ `;
865
+ return;
866
+ }
867
+
868
+ // Visual Interactive Card Mode
869
+ viewer.innerHTML = `
870
+ <!-- HERO SUMMARY -->
871
+ <div class="report-hero-card">
872
+ <div class="report-hero-header">
873
+ <div>
874
+ <h3 class="report-hero-title">★ ${escapeHtml(report.objective || 'Sprint Audit')}</h3>
875
+ <div class="report-hero-meta">
876
+ <span>PRODUCT: <strong>${escapeHtml(report.project || 'Project')}</strong></span>
877
+ <span>DATE: <strong>${escapeHtml(report.dateFormatted || 'Today')}</strong></span>
878
+ <span>SQUADS: <strong>${(report.targetAgents || []).map(a => a.toUpperCase()).join(', ')}</strong></span>
879
+ </div>
880
+ </div>
881
+ <span class="report-item-status" style="font-size: 10px; padding: 4px 8px;">100% COMPLETE</span>
882
+ </div>
883
+ <div class="report-summary-box">
884
+ <strong>Executive Summary:</strong> The autonomous engineering swarm executed an architectural and quality audit, resolving dependencies and formulating <strong>${report.totalFindings || 0} actionable improvements</strong>.
885
+ </div>
886
+ </div>
887
+
888
+ <!-- METRICS GRID -->
889
+ <div class="report-metrics-grid">
890
+ <div class="report-metric-card">
891
+ <span class="report-metric-label">ENGINEERING SQUADS</span>
892
+ <span class="report-metric-val" style="color: var(--color-cyan);">${(report.targetAgents || []).length}</span>
893
+ </div>
894
+ <div class="report-metric-card">
895
+ <span class="report-metric-label">FINDINGS & ACTIONS</span>
896
+ <span class="report-metric-val" style="color: var(--color-gold);">${report.totalFindings || 0}</span>
897
+ </div>
898
+ <div class="report-metric-card">
899
+ <span class="report-metric-label">QUALITY GATE</span>
900
+ <span class="report-metric-val" style="color: var(--color-green);">PASSED</span>
901
+ </div>
902
+ <div class="report-metric-card">
903
+ <span class="report-metric-label">BLOCKER DEFECTS</span>
904
+ <span class="report-metric-val" style="color: var(--color-green);">0</span>
905
+ </div>
906
+ </div>
907
+
908
+ <!-- SQUAD FINDINGS ACCORDION / CARDS -->
909
+ <div class="report-sections-grid">
910
+ ${(report.sections || []).map(section => `
911
+ <div class="report-squad-card" style="border-left: 4px solid ${section.color || '#00f0ff'};">
912
+ <div class="report-squad-header" style="color: ${section.color || '#00f0ff'};">
913
+ <span>${section.icon || '💼'}</span>
914
+ <span>${escapeHtml(section.name || section.agent.toUpperCase())}</span>
915
+ </div>
916
+ <div class="report-findings-list">
917
+ ${(section.findings || []).map(f => `
918
+ <div class="report-finding-item">
919
+ <span class="finding-pill" style="border-color: ${section.color || '#00f0ff'}; color: ${section.color || '#00f0ff'};">${escapeHtml(f.category || 'Finding')}</span>
920
+ <span class="finding-desc">${escapeHtml(f.description || '')}</span>
921
+ </div>
922
+ `).join('')}
923
+ </div>
924
+ </div>
925
+ `).join('')}
926
+ </div>
927
+
928
+ <!-- ACTION ITEMS CHECKLIST -->
929
+ <div class="report-actions-card">
930
+ <div class="report-actions-title">📋 RECOMMENDED ACTION ITEMS & NEXT STEPS</div>
931
+ <div class="report-checklist">
932
+ ${(report.actionItems || []).map(item => `
933
+ <label class="report-check-item">
934
+ <input type="checkbox" />
935
+ <span>${escapeHtml(item)}</span>
936
+ </label>
937
+ `).join('')}
938
+ </div>
939
+ </div>
940
+ `;
941
+ }
942
+
943
+ function toggleRawReportView() {
944
+ synth.playClick();
945
+ reportsState.rawMode = !reportsState.rawMode;
946
+ const label = document.getElementById('toggleRawLabel');
947
+ if (label) {
948
+ label.textContent = reportsState.rawMode ? 'VISUAL CARDS' : 'RAW MARKDOWN';
949
+ }
950
+ renderActiveReport();
951
+ }
952
+
953
+ function copyActiveReportMarkdown() {
954
+ if (!reportsState.activeReport?.markdown) return;
955
+ navigator.clipboard.writeText(reportsState.activeReport.markdown).then(() => {
956
+ synth.playSkill();
957
+ const btn = document.getElementById('btnCopyReportMd');
958
+ if (btn) {
959
+ const originalText = btn.innerHTML;
960
+ btn.innerHTML = '<span>✓</span> COPIED!';
961
+ setTimeout(() => { btn.innerHTML = originalText; }, 2000);
962
+ }
963
+ }).catch(err => {
964
+ console.error('Failed to copy markdown:', err);
965
+ });
966
+ }
967
+
968
+ function downloadActiveReport() {
969
+ if (!reportsState.activeReport) return;
970
+ const r = reportsState.activeReport;
971
+ const content = r.markdown || JSON.stringify(r, null, 2);
972
+ const blob = new Blob([content], { type: 'text/markdown;charset=utf-8' });
973
+ const url = URL.createObjectURL(blob);
974
+ const a = document.createElement('a');
975
+ a.href = url;
976
+ a.download = `${r.id || 'sprint-report'}.md`;
977
+ document.body.appendChild(a);
978
+ a.click();
979
+ document.body.removeChild(a);
980
+ URL.revokeObjectURL(url);
981
+ synth.playComplete();
982
+ }
983
+
984
+ function escapeHtml(str) {
985
+ if (!str) return '';
986
+ return String(str)
987
+ .replace(/&/g, '&amp;')
988
+ .replace(/</g, '&lt;')
989
+ .replace(/>/g, '&gt;')
990
+ .replace(/"/g, '&quot;')
991
+ .replace(/'/g, '&#039;');
992
+ }
993
+
680
994
  // Submit Task to Server
681
995
  async function submitTask(prompt) {
682
996
  try {
@@ -759,17 +1073,28 @@ function connectSSE() {
759
1073
  renderState();
760
1074
  }
761
1075
 
762
- // Audio cues
763
- if (event.type === 'spawn') synth.playSpawn();
764
- else if (event.type === 'skill') synth.playSkill();
765
- else if (event.type === 'complete') synth.playComplete();
766
- else if (event.type === 'error') synth.playError();
1076
+ // Audio cues & Report Refresh
1077
+ if (event.type === 'spawn') {
1078
+ synth.playSpawn();
1079
+ } else if (event.type === 'skill') {
1080
+ synth.playSkill();
1081
+ } else if (event.type === 'complete') {
1082
+ synth.playComplete();
1083
+ if (event.agent === 'orchestrator' || event.metadata?.report) {
1084
+ fetchReports();
1085
+ }
1086
+ } else if (event.type === 'error') {
1087
+ synth.playError();
1088
+ }
767
1089
  });
768
1090
 
769
1091
  eventSource.addEventListener('state_change', (e) => {
770
1092
  const data = JSON.parse(e.data);
771
1093
  appState.state = data.state;
772
1094
  renderState();
1095
+ if (data.state?.status === 'COMPLETED') {
1096
+ fetchReports();
1097
+ }
773
1098
  });
774
1099
 
775
1100
  eventSource.onerror = () => {
@@ -50,6 +50,9 @@
50
50
  </div>
51
51
 
52
52
  <div class="header-right">
53
+ <button class="pixel-btn btn-sm btn-info" id="btnReportsToggle" title="Open Sprint Audit Reports History [R]">
54
+ <span>📋</span> REPORTS (<span id="reportsCountBadge">0</span>)
55
+ </button>
53
56
  <button class="pixel-btn btn-sm" id="btnAudioToggle" title="Toggle 8-bit Audio SFX">
54
57
  <span id="audioIcon">🔊</span> SFX
55
58
  </button>
@@ -213,10 +216,52 @@
213
216
  </div>
214
217
  </div>
215
218
 
219
+ <!-- SPRINT AUDIT REPORTS & HISTORY MODAL / DRAWER -->
220
+ <div class="pixel-modal-backdrop" id="reportsModal">
221
+ <div class="pixel-modal-card reports-modal-card">
222
+ <div class="modal-header">
223
+ <div class="modal-title-box">
224
+ <span class="modal-agent-icon">📋</span>
225
+ <h2 class="modal-agent-name" style="color: var(--color-cyan);">SPRINT AUDIT REPORTS & HISTORY</h2>
226
+ </div>
227
+ <div class="reports-header-actions">
228
+ <button class="pixel-btn btn-xs" id="btnToggleRawView" title="Toggle between visual card and raw markdown">
229
+ <span>👁️</span> <span id="toggleRawLabel">RAW MARKDOWN</span>
230
+ </button>
231
+ <button class="pixel-btn btn-xs" id="btnCopyReportMd" title="Copy markdown to clipboard">
232
+ <span>📄</span> COPY MD
233
+ </button>
234
+ <button class="pixel-btn btn-xs" id="btnDownloadReport" title="Download report as .md file">
235
+ <span>💾</span> DOWNLOAD
236
+ </button>
237
+ <button class="pixel-btn btn-sm btn-warn" id="btnCloseReportsModal">✕</button>
238
+ </div>
239
+ </div>
240
+ <div class="reports-modal-body">
241
+ <!-- SPRINT HISTORY SIDEBAR -->
242
+ <aside class="reports-sidebar">
243
+ <div class="reports-search-box">
244
+ <input type="text" class="pixel-input reports-search-input" id="reportsSearch" placeholder="Search past sprint reports..." />
245
+ </div>
246
+ <div class="reports-history-list" id="reportsHistoryList">
247
+ <!-- Populated by app.js -->
248
+ </div>
249
+ </aside>
250
+
251
+ <!-- ACTIVE REPORT DETAIL VIEWER -->
252
+ <main class="reports-viewer" id="reportsViewer">
253
+ <!-- Populated dynamically -->
254
+ </main>
255
+ </div>
256
+ </div>
257
+ </div>
258
+
216
259
  <!-- ARCADE / OFFICE FOOTER -->
217
260
  <footer class="arcade-footer">
218
261
  <div class="footer-left">
219
- <span>PRESS [1..6] TO OPEN EMPLOYEE PROFILE</span>
262
+ <span>PRESS [1..6] EMPLOYEE PROFILE</span>
263
+ <span>•</span>
264
+ <span>[R] SPRINT REPORTS</span>
220
265
  <span>•</span>
221
266
  <span>[SPACE] SPRINT DEMO</span>
222
267
  </div>
@@ -842,6 +842,330 @@ body:not(.crt-active) .crt-vignette {
842
842
  font-size: 16px;
843
843
  }
844
844
 
845
+ /* Reports Modal & History Drawer */
846
+ .reports-modal-card {
847
+ max-width: 1100px;
848
+ width: 95vw;
849
+ height: 82vh;
850
+ max-height: 850px;
851
+ display: flex;
852
+ flex-direction: column;
853
+ }
854
+
855
+ .reports-header-actions {
856
+ display: flex;
857
+ align-items: center;
858
+ gap: 8px;
859
+ }
860
+
861
+ .reports-modal-body {
862
+ display: flex;
863
+ flex: 1;
864
+ overflow: hidden;
865
+ background: var(--bg-dark);
866
+ }
867
+
868
+ .reports-sidebar {
869
+ width: 320px;
870
+ min-width: 280px;
871
+ background: var(--bg-panel-dark);
872
+ border-right: 2px solid var(--border-black);
873
+ display: flex;
874
+ flex-direction: column;
875
+ }
876
+
877
+ .reports-search-box {
878
+ padding: 10px;
879
+ border-bottom: 2px solid var(--border-black);
880
+ background: var(--bg-panel);
881
+ }
882
+
883
+ .reports-search-input {
884
+ width: 100%;
885
+ font-size: 11px;
886
+ padding: 6px 8px;
887
+ }
888
+
889
+ .reports-history-list {
890
+ flex: 1;
891
+ overflow-y: auto;
892
+ padding: 8px;
893
+ display: flex;
894
+ flex-direction: column;
895
+ gap: 6px;
896
+ }
897
+
898
+ .report-item-card {
899
+ background: var(--bg-card);
900
+ border: 2px solid var(--border-color);
901
+ padding: 10px;
902
+ cursor: pointer;
903
+ transition: all 0.15s ease;
904
+ display: flex;
905
+ flex-direction: column;
906
+ gap: 6px;
907
+ }
908
+
909
+ .report-item-card:hover {
910
+ background: var(--bg-card-hover);
911
+ border-color: var(--color-cyan);
912
+ transform: translateX(2px);
913
+ }
914
+
915
+ .report-item-card.active {
916
+ background: #17213c;
917
+ border-color: var(--color-cyan);
918
+ box-shadow: inset 3px 0 0 var(--color-cyan);
919
+ }
920
+
921
+ .report-item-top {
922
+ display: flex;
923
+ justify-content: space-between;
924
+ align-items: center;
925
+ }
926
+
927
+ .report-item-date {
928
+ font-size: 9px;
929
+ color: var(--text-dim);
930
+ font-family: var(--font-pixel);
931
+ }
932
+
933
+ .report-item-status {
934
+ font-size: 8px;
935
+ padding: 2px 5px;
936
+ background: rgba(57, 255, 20, 0.15);
937
+ color: var(--color-green);
938
+ border: 1px solid var(--color-green);
939
+ font-family: var(--font-pixel);
940
+ }
941
+
942
+ .report-item-title {
943
+ font-size: 11px;
944
+ color: var(--text-bright);
945
+ font-weight: bold;
946
+ line-height: 1.3;
947
+ display: -webkit-box;
948
+ -webkit-line-clamp: 2;
949
+ -webkit-box-orient: vertical;
950
+ overflow: hidden;
951
+ }
952
+
953
+ .report-item-meta {
954
+ display: flex;
955
+ justify-content: space-between;
956
+ font-size: 10px;
957
+ color: var(--text-dim);
958
+ }
959
+
960
+ /* Reports Viewer (Right Pane) */
961
+ .reports-viewer {
962
+ flex: 1;
963
+ overflow-y: auto;
964
+ padding: 20px;
965
+ display: flex;
966
+ flex-direction: column;
967
+ gap: 16px;
968
+ background: var(--bg-panel);
969
+ }
970
+
971
+ .report-empty-state {
972
+ display: flex;
973
+ flex-direction: column;
974
+ align-items: center;
975
+ justify-content: center;
976
+ height: 100%;
977
+ color: var(--text-dim);
978
+ gap: 12px;
979
+ text-align: center;
980
+ font-family: var(--font-pixel);
981
+ font-size: 11px;
982
+ }
983
+
984
+ .report-hero-card {
985
+ background: linear-gradient(135deg, #182038 0%, #101526 100%);
986
+ border: 2px solid var(--color-cyan);
987
+ padding: 16px;
988
+ display: flex;
989
+ flex-direction: column;
990
+ gap: 10px;
991
+ box-shadow: var(--pixel-shadow);
992
+ }
993
+
994
+ .report-hero-header {
995
+ display: flex;
996
+ justify-content: space-between;
997
+ align-items: flex-start;
998
+ gap: 12px;
999
+ }
1000
+
1001
+ .report-hero-title {
1002
+ font-family: var(--font-pixel);
1003
+ font-size: 13px;
1004
+ color: var(--color-cyan);
1005
+ line-height: 1.4;
1006
+ }
1007
+
1008
+ .report-hero-meta {
1009
+ display: flex;
1010
+ flex-wrap: wrap;
1011
+ gap: 12px;
1012
+ font-size: 10px;
1013
+ color: var(--text-dim);
1014
+ font-family: var(--font-pixel);
1015
+ margin-top: 4px;
1016
+ }
1017
+
1018
+ .report-hero-meta strong {
1019
+ color: var(--text-bright);
1020
+ }
1021
+
1022
+ .report-summary-box {
1023
+ background: rgba(0, 0, 0, 0.4);
1024
+ border-left: 3px solid var(--color-gold);
1025
+ padding: 10px 12px;
1026
+ font-size: 12px;
1027
+ color: var(--text-main);
1028
+ line-height: 1.5;
1029
+ }
1030
+
1031
+ /* Report Metrics Grid */
1032
+ .report-metrics-grid {
1033
+ display: grid;
1034
+ grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
1035
+ gap: 10px;
1036
+ }
1037
+
1038
+ .report-metric-card {
1039
+ background: var(--bg-card);
1040
+ border: 2px solid var(--border-black);
1041
+ padding: 10px;
1042
+ display: flex;
1043
+ flex-direction: column;
1044
+ gap: 4px;
1045
+ align-items: center;
1046
+ text-align: center;
1047
+ }
1048
+
1049
+ .report-metric-label {
1050
+ font-size: 9px;
1051
+ color: var(--text-dim);
1052
+ font-family: var(--font-pixel);
1053
+ }
1054
+
1055
+ .report-metric-val {
1056
+ font-size: 18px;
1057
+ color: var(--color-gold);
1058
+ font-family: var(--font-terminal);
1059
+ font-weight: bold;
1060
+ }
1061
+
1062
+ /* Squad Findings Cards */
1063
+ .report-sections-grid {
1064
+ display: flex;
1065
+ flex-direction: column;
1066
+ gap: 12px;
1067
+ }
1068
+
1069
+ .report-squad-card {
1070
+ background: var(--bg-card);
1071
+ border: 2px solid var(--border-color);
1072
+ padding: 14px;
1073
+ display: flex;
1074
+ flex-direction: column;
1075
+ gap: 10px;
1076
+ }
1077
+
1078
+ .report-squad-header {
1079
+ display: flex;
1080
+ align-items: center;
1081
+ gap: 8px;
1082
+ font-family: var(--font-pixel);
1083
+ font-size: 11px;
1084
+ padding-bottom: 6px;
1085
+ border-bottom: 1px solid var(--border-color);
1086
+ }
1087
+
1088
+ .report-findings-list {
1089
+ display: flex;
1090
+ flex-direction: column;
1091
+ gap: 8px;
1092
+ }
1093
+
1094
+ .report-finding-item {
1095
+ display: flex;
1096
+ align-items: flex-start;
1097
+ gap: 8px;
1098
+ font-size: 12px;
1099
+ line-height: 1.4;
1100
+ }
1101
+
1102
+ .finding-pill {
1103
+ font-size: 9px;
1104
+ padding: 2px 6px;
1105
+ background: #0d1222;
1106
+ border: 1px solid #33406a;
1107
+ color: var(--color-cyan);
1108
+ font-family: var(--font-pixel);
1109
+ white-space: nowrap;
1110
+ }
1111
+
1112
+ .finding-desc {
1113
+ color: var(--text-main);
1114
+ flex: 1;
1115
+ }
1116
+
1117
+ /* Action Items Checklist */
1118
+ .report-actions-card {
1119
+ background: #141a2e;
1120
+ border: 2px solid var(--color-gold);
1121
+ padding: 14px;
1122
+ display: flex;
1123
+ flex-direction: column;
1124
+ gap: 10px;
1125
+ }
1126
+
1127
+ .report-actions-title {
1128
+ font-family: var(--font-pixel);
1129
+ font-size: 11px;
1130
+ color: var(--color-gold);
1131
+ }
1132
+
1133
+ .report-checklist {
1134
+ display: flex;
1135
+ flex-direction: column;
1136
+ gap: 8px;
1137
+ }
1138
+
1139
+ .report-check-item {
1140
+ display: flex;
1141
+ align-items: center;
1142
+ gap: 8px;
1143
+ font-size: 12px;
1144
+ color: var(--text-main);
1145
+ }
1146
+
1147
+ .report-check-item input[type="checkbox"] {
1148
+ accent-color: var(--color-gold);
1149
+ width: 16px;
1150
+ height: 16px;
1151
+ cursor: pointer;
1152
+ }
1153
+
1154
+ /* Raw Markdown Viewer Mode */
1155
+ .report-raw-pre {
1156
+ background: #090c16;
1157
+ border: 2px solid var(--border-black);
1158
+ padding: 16px;
1159
+ color: #a5b4fc;
1160
+ font-family: var(--font-terminal);
1161
+ font-size: 16px;
1162
+ line-height: 1.5;
1163
+ white-space: pre-wrap;
1164
+ word-break: break-word;
1165
+ max-height: 60vh;
1166
+ overflow-y: auto;
1167
+ }
1168
+
845
1169
  /* Arcade Footer */
846
1170
  .arcade-footer {
847
1171
  display: flex;
@@ -889,4 +1213,6 @@ body:not(.crt-active) .crt-vignette {
889
1213
  @media (max-width: 900px) {
890
1214
  .bottom-split-section { grid-template-columns: 1fr; }
891
1215
  .office-legend { display: none; }
1216
+ .reports-modal-body { flex-direction: column; }
1217
+ .reports-sidebar { width: 100%; height: 200px; }
892
1218
  }
@@ -572,17 +572,21 @@ export class OrchestratorEngine extends EventEmitter {
572
572
  await this.persistState();
573
573
 
574
574
  // 5. Generate and Persist Executive Report
575
- const reportMarkdown = this.compileAuditReport(taskPrompt, targetAgents, findings);
576
- await this.saveAuditReport(reportMarkdown);
575
+ const reportData = this.compileAuditReport(taskPrompt, targetAgents, findings);
576
+ await this.saveAuditReport(reportData);
577
577
 
578
578
  await this.emitEvent({
579
579
  agent: 'orchestrator',
580
580
  type: 'complete',
581
- message: `★ SWARM MISSION COMPLETE: Audit report generated with recommendations!`,
582
- metadata: { report: reportMarkdown }
581
+ message: `★ SWARM MISSION COMPLETE: Audit report generated with actionable recommendations!`,
582
+ metadata: {
583
+ report: reportData.markdown,
584
+ reportId: reportData.id,
585
+ reportData
586
+ }
583
587
  });
584
588
 
585
- return reportMarkdown;
589
+ return reportData.markdown;
586
590
 
587
591
  } catch (err) {
588
592
  if (err.message !== 'Task Aborted') {
@@ -600,53 +604,250 @@ export class OrchestratorEngine extends EventEmitter {
600
604
  }
601
605
 
602
606
  /**
603
- * Compiles structured markdown audit report
607
+ * Compiles structured audit report with both user-friendly JSON and Markdown formats
604
608
  */
605
609
  compileAuditReport(taskPrompt, targetAgents, findings) {
606
- const timestamp = new Date().toLocaleTimeString();
610
+ const timestamp = Date.now();
611
+ const dateObj = new Date(timestamp);
612
+ const timeStr = dateObj.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
613
+ const dateStr = dateObj.toLocaleDateString([], { month: 'short', day: 'numeric', year: 'numeric' });
607
614
  const projectName = this.config?.project || path.basename(this.targetDir);
615
+ const reportId = `sprint-${timestamp}`;
616
+
617
+ const agentMeta = {
618
+ frontend: { name: 'Frontend Squad', icon: '🎨', color: '#00f0ff' },
619
+ backend: { name: 'Backend Squad', icon: '⚡', color: '#ff007f' },
620
+ database: { name: 'Database Squad', icon: '🗄️', color: '#ffd700' },
621
+ security: { name: 'Security Squad', icon: '🛡️', color: '#ff3344' },
622
+ performance: { name: 'Performance SRE', icon: '🚀', color: '#39ff14' },
623
+ qa: { name: 'QA Squad', icon: '🧪', color: '#b026ff' },
624
+ orchestrator: { name: 'Lead Orchestrator', icon: '👔', color: '#ffd700' }
625
+ };
608
626
 
609
- let report = `
610
- ╔═══════════════════════════════════════════════════════════════════╗
611
- ║ PIXELCREW SWARM AUDIT REPORT ║
612
- ╚═══════════════════════════════════════════════════════════════════╝
627
+ const sections = [];
628
+ const actionItems = [];
629
+ let totalFindings = 0;
613
630
 
614
- PROJECT: ${projectName}
615
- OBJECTIVE: "${taskPrompt}"
616
- TIMESTAMP: ${timestamp}
617
- TEAM SPRINT: ${targetAgents.map(a => a.toUpperCase()).join(', ')}
618
- STATUS: 100% COMPLETED
631
+ for (const [agent, items] of Object.entries(findings)) {
632
+ if (!items || !items.length) continue;
633
+ totalFindings += items.length;
634
+ const meta = agentMeta[agent] || { name: agent.toUpperCase(), icon: '💼', color: '#00f0ff' };
635
+
636
+ const parsedFindings = items.map((item, idx) => {
637
+ const parts = item.split(':');
638
+ const category = parts.length > 1 ? parts[0].trim() : 'Recommendation';
639
+ const description = parts.length > 1 ? parts.slice(1).join(':').trim() : item;
640
+ actionItems.push(`[${agent.toUpperCase()}] ${description}`);
641
+ return {
642
+ id: `${agent}-${idx + 1}`,
643
+ category,
644
+ description
645
+ };
646
+ });
619
647
 
620
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
621
- EXECUTIVE FINDINGS & ACTIONABLE IMPROVEMENTS
622
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
623
- `;
648
+ sections.push({
649
+ agent,
650
+ name: meta.name,
651
+ icon: meta.icon,
652
+ color: meta.color,
653
+ findings: parsedFindings
654
+ });
655
+ }
624
656
 
625
- for (const [agent, items] of Object.entries(findings)) {
626
- report += `\n[${agent.toUpperCase()} AGENT]:\n`;
627
- for (const item of items) {
628
- report += ` • ${item}\n`;
657
+ // Generate Markdown
658
+ let markdown = `# PixelCrew Swarm Audit Report\n\n`;
659
+ markdown += `**Project:** ${projectName} \n`;
660
+ markdown += `**Objective:** "${taskPrompt}" \n`;
661
+ markdown += `**Date:** ${dateStr} at ${timeStr} \n`;
662
+ markdown += `**Team Roster:** ${targetAgents.map(a => a.toUpperCase()).join(', ')} \n`;
663
+ markdown += `**Status:** 100% Completed \n\n`;
664
+ markdown += `## Executive Summary\n`;
665
+ markdown += `The multi-agent swarm completed the assigned objective with **${totalFindings} key findings & actionable improvements** across ${targetAgents.length} specialized engineering squads.\n\n`;
666
+ markdown += `## Squad Findings & Recommendations\n\n`;
667
+
668
+ for (const section of sections) {
669
+ markdown += `### ${section.icon} ${section.name}\n`;
670
+ for (const finding of section.findings) {
671
+ markdown += `- **${finding.category}:** ${finding.description}\n`;
629
672
  }
673
+ markdown += `\n`;
630
674
  }
631
675
 
632
- report += `\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
633
- report += `NEXT STEPS: Review findings and apply proposed improvements.\n`;
676
+ markdown += `## Action Items & Next Steps\n`;
677
+ for (const action of actionItems) {
678
+ markdown += `- [ ] ${action}\n`;
679
+ }
680
+
681
+ const reportObject = {
682
+ id: reportId,
683
+ timestamp,
684
+ dateFormatted: `${dateStr}, ${timeStr}`,
685
+ project: projectName,
686
+ objective: taskPrompt,
687
+ status: 'COMPLETED',
688
+ targetAgents,
689
+ totalFindings,
690
+ sections,
691
+ actionItems,
692
+ markdown
693
+ };
634
694
 
635
- return report.trim();
695
+ return reportObject;
636
696
  }
637
697
 
638
698
  /**
639
- * Saves audit report to .pixel-agents/reports/
699
+ * Saves audit report to .pixel-agents/reports/ in both JSON and Markdown format
640
700
  */
641
- async saveAuditReport(reportMarkdown) {
701
+ async saveAuditReport(reportData) {
642
702
  const reportsDir = path.join(this.pixelAgentsDir, 'reports');
643
703
  try {
644
704
  await fs.mkdir(reportsDir, { recursive: true });
645
- const filename = `audit-${Date.now()}.md`;
646
- await fs.writeFile(path.join(reportsDir, filename), reportMarkdown + '\n', 'utf-8');
647
- } catch {
648
- // ignore
705
+ const baseFilename = reportData.id || `sprint-${Date.now()}`;
706
+
707
+ // Save JSON metadata
708
+ await fs.writeFile(
709
+ path.join(reportsDir, `${baseFilename}.json`),
710
+ JSON.stringify(reportData, null, 2),
711
+ 'utf-8'
712
+ );
713
+
714
+ // Save readable Markdown
715
+ if (reportData.markdown) {
716
+ await fs.writeFile(
717
+ path.join(reportsDir, `${baseFilename}.md`),
718
+ reportData.markdown.trim() + '\n',
719
+ 'utf-8'
720
+ );
721
+ }
722
+ } catch (err) {
723
+ console.error('Error saving audit report:', err);
649
724
  }
650
725
  }
726
+
727
+ /**
728
+ * Reads and lists all audit reports stored in .pixel-agents/reports/ or reports/
729
+ */
730
+ async getReports() {
731
+ const directories = [
732
+ path.join(this.pixelAgentsDir, 'reports'),
733
+ path.join(this.targetDir || this.rootDir, 'reports')
734
+ ];
735
+
736
+ const reportsMap = new Map();
737
+
738
+ for (const dir of directories) {
739
+ try {
740
+ await fs.mkdir(dir, { recursive: true });
741
+ const files = await fs.readdir(dir);
742
+
743
+ // 1. Process JSON reports first
744
+ const jsonFiles = files.filter(f => f.endsWith('.json'));
745
+ for (const file of jsonFiles) {
746
+ try {
747
+ const raw = await fs.readFile(path.join(dir, file), 'utf-8');
748
+ const data = JSON.parse(raw);
749
+ const id = data.id || file.replace(/\.json$/, '');
750
+ reportsMap.set(id, { ...data, id });
751
+ } catch {}
752
+ }
753
+
754
+ // 2. Process Markdown reports (including standalone .md files)
755
+ const mdFiles = files.filter(f => f.endsWith('.md'));
756
+ for (const file of mdFiles) {
757
+ const id = file.replace(/\.md$/, '');
758
+ if (!reportsMap.has(id)) {
759
+ try {
760
+ const fullPath = path.join(dir, file);
761
+ const raw = await fs.readFile(fullPath, 'utf-8');
762
+ const stats = await fs.stat(fullPath);
763
+ const parsed = this.parseMarkdownReport(id, raw, stats.mtime);
764
+ reportsMap.set(id, parsed);
765
+ } catch {}
766
+ }
767
+ }
768
+ } catch {}
769
+ }
770
+
771
+ const reports = Array.from(reportsMap.values());
772
+ reports.sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0));
773
+ return reports;
774
+ }
775
+
776
+ /**
777
+ * Helper to parse standalone markdown files into structured report objects
778
+ */
779
+ parseMarkdownReport(id, markdown, mtime) {
780
+ const lines = markdown.split('\n');
781
+ let title = id.replace(/[-_]/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
782
+ const findings = [];
783
+ const actionItems = [];
784
+ const detectedAgents = new Set();
785
+
786
+ for (const line of lines) {
787
+ const trimmed = line.trim();
788
+ if (trimmed.startsWith('# ') && (title === id || title.toLowerCase() === id.replace(/[-_]/g, ' '))) {
789
+ title = trimmed.replace(/^#\s+/, '');
790
+ } else if (trimmed.startsWith('- [ ]') || trimmed.startsWith('- [x]')) {
791
+ actionItems.push(trimmed.replace(/^-\s+\[.\]\s*/, ''));
792
+ } else if (trimmed.startsWith('- ') || trimmed.startsWith('* ') || /^\d+\.\s+/.test(trimmed)) {
793
+ findings.push(trimmed.replace(/^[-*]\s+|\d+\.\s+/, ''));
794
+ }
795
+
796
+ // Check agent mentions
797
+ const lower = line.toLowerCase();
798
+ if (lower.includes('frontend') || lower.includes('ui') || lower.includes('react')) detectedAgents.add('frontend');
799
+ if (lower.includes('backend') || lower.includes('api')) detectedAgents.add('backend');
800
+ if (lower.includes('database') || lower.includes('postgres') || lower.includes('prisma') || lower.includes('sql')) detectedAgents.add('database');
801
+ if (lower.includes('security') || lower.includes('auth')) detectedAgents.add('security');
802
+ if (lower.includes('performance') || lower.includes('vital') || lower.includes('speed')) detectedAgents.add('performance');
803
+ if (lower.includes('qa') || lower.includes('test') || lower.includes('playwright')) detectedAgents.add('qa');
804
+ }
805
+
806
+ const targetAgents = detectedAgents.size > 0 ? Array.from(detectedAgents) : ['frontend', 'qa'];
807
+ const timestamp = mtime ? new Date(mtime).getTime() : Date.now();
808
+ const dateObj = new Date(timestamp);
809
+ const timeStr = dateObj.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
810
+ const dateStr = dateObj.toLocaleDateString([], { month: 'short', day: 'numeric', year: 'numeric' });
811
+
812
+ // Group findings into clean sections
813
+ const sections = [
814
+ {
815
+ agent: targetAgents[0] || 'frontend',
816
+ name: `${(targetAgents[0] || 'Team').toUpperCase()} Findings`,
817
+ icon: '📋',
818
+ color: '#00f0ff',
819
+ findings: (findings.length > 0 ? findings : ['Report recorded in markdown']).slice(0, 10).map((f, i) => {
820
+ const parts = f.split(':');
821
+ return {
822
+ id: `item-${i + 1}`,
823
+ category: parts.length > 1 ? parts[0].trim() : 'Observation',
824
+ description: parts.length > 1 ? parts.slice(1).join(':').trim() : f
825
+ };
826
+ })
827
+ }
828
+ ];
829
+
830
+ return {
831
+ id,
832
+ timestamp,
833
+ dateFormatted: `${dateStr}, ${timeStr}`,
834
+ project: this.config?.project || path.basename(this.targetDir || this.rootDir),
835
+ objective: title,
836
+ status: 'COMPLETED',
837
+ targetAgents,
838
+ totalFindings: findings.length || 1,
839
+ sections,
840
+ actionItems: actionItems.length > 0 ? actionItems : (findings.length > 0 ? findings.slice(0, 5) : ['Review report findings']),
841
+ markdown
842
+ };
843
+ }
844
+
845
+ /**
846
+ * Gets a specific report by ID
847
+ */
848
+ async getReportById(reportId) {
849
+ const reports = await this.getReports();
850
+ return reports.find(r => r.id === reportId) || null;
851
+ }
651
852
  }
652
853
 
@@ -169,6 +169,34 @@ export function createServer(engine, options = {}) {
169
169
  return sendJson(200, { status: 'reset' });
170
170
  }
171
171
 
172
+ if (pathname === '/api/reports' && req.method === 'GET') {
173
+ const reports = await engine.getReports();
174
+ return sendJson(200, { reports });
175
+ }
176
+
177
+ if (pathname.startsWith('/api/reports/') && req.method === 'GET') {
178
+ const reportId = pathname.replace('/api/reports/', '');
179
+ const report = await engine.getReportById(reportId);
180
+ if (!report) {
181
+ return sendJson(404, { error: 'Report not found' });
182
+ }
183
+ return sendJson(200, { report });
184
+ }
185
+
186
+ if (pathname === '/api/reports' && req.method === 'POST') {
187
+ const body = await readBody();
188
+ if (!body.objective && !body.markdown) {
189
+ return sendJson(400, { error: 'Missing report objective or markdown' });
190
+ }
191
+ const reportData = engine.compileAuditReport(
192
+ body.objective || 'Manual Audit',
193
+ body.targetAgents || ['frontend', 'backend', 'database'],
194
+ body.findings || {}
195
+ );
196
+ await engine.saveAuditReport(reportData);
197
+ return sendJson(201, { status: 'created', report: reportData });
198
+ }
199
+
172
200
  if (pathname === '/api/demo' && req.method === 'POST') {
173
201
  const demoPrompt = 'Analyze CRM customer search bottlenecks & optimize Postgres Prisma queries with responsive UI';
174
202
  engine.submitTask(demoPrompt).catch(console.error);
@@ -187,12 +215,12 @@ export function createServer(engine, options = {}) {
187
215
  else if (filename.endsWith('.svg')) contentType = 'image/svg+xml';
188
216
  else if (filename.endsWith('.ico')) contentType = 'image/x-icon';
189
217
 
190
- // Check project .pixel-dashboard first, then fallback to built-in
191
- let filePath = path.join(dashboardDir, filename);
218
+ // Serve latest dashboard assets from package with fallback to project directory
219
+ let filePath = path.join(fallbackDashboardDir, filename);
192
220
  try {
193
221
  await fs.access(filePath);
194
222
  } catch {
195
- filePath = path.join(fallbackDashboardDir, filename);
223
+ filePath = path.join(dashboardDir, filename);
196
224
  try {
197
225
  await fs.access(filePath);
198
226
  } catch {