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 +12 -12
- package/package.json +1 -1
- package/src/dashboard/app.js +339 -14
- package/src/dashboard/index.html +46 -1
- package/src/dashboard/styles.css +326 -0
- package/src/orchestrator/engine.js +234 -33
- package/src/server/server.js +31 -3
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
|
|
66
|
+
npx pixelcrew init
|
|
67
67
|
|
|
68
68
|
# 2. Launch the orchestrator daemon and real-time visual office
|
|
69
|
-
npx
|
|
69
|
+
npx pixelcrew start
|
|
70
70
|
|
|
71
71
|
# 3. (Optional) Run an instant multi-agent simulated sprint demo
|
|
72
|
-
npx
|
|
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
|
|
244
|
-
| `npx
|
|
245
|
-
| `npx
|
|
246
|
-
| `npx
|
|
247
|
-
| `npx
|
|
248
|
-
| `npx
|
|
249
|
-
| `npx
|
|
250
|
-
| `npx
|
|
251
|
-
| `npx
|
|
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
package/src/dashboard/app.js
CHANGED
|
@@ -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
|
-
//
|
|
654
|
-
document.getElementById('
|
|
655
|
-
|
|
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('
|
|
659
|
-
|
|
660
|
-
|
|
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, '&')
|
|
988
|
+
.replace(/</g, '<')
|
|
989
|
+
.replace(/>/g, '>')
|
|
990
|
+
.replace(/"/g, '"')
|
|
991
|
+
.replace(/'/g, ''');
|
|
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')
|
|
764
|
-
|
|
765
|
-
else if (event.type === '
|
|
766
|
-
|
|
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 = () => {
|
package/src/dashboard/index.html
CHANGED
|
@@ -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]
|
|
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>
|
package/src/dashboard/styles.css
CHANGED
|
@@ -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
|
|
576
|
-
await this.saveAuditReport(
|
|
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: {
|
|
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
|
|
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
|
|
607
|
+
* Compiles structured audit report with both user-friendly JSON and Markdown formats
|
|
604
608
|
*/
|
|
605
609
|
compileAuditReport(taskPrompt, targetAgents, findings) {
|
|
606
|
-
const timestamp =
|
|
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
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
╚═══════════════════════════════════════════════════════════════════╝
|
|
627
|
+
const sections = [];
|
|
628
|
+
const actionItems = [];
|
|
629
|
+
let totalFindings = 0;
|
|
613
630
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
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
|
-
|
|
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
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
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
|
-
|
|
633
|
-
|
|
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
|
|
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(
|
|
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
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
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
|
|
package/src/server/server.js
CHANGED
|
@@ -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
|
-
//
|
|
191
|
-
let filePath = path.join(
|
|
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(
|
|
223
|
+
filePath = path.join(dashboardDir, filename);
|
|
196
224
|
try {
|
|
197
225
|
await fs.access(filePath);
|
|
198
226
|
} catch {
|