pixelcrew 0.1.0 → 0.1.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.
- package/README.md +12 -12
- package/package.json +1 -1
- package/src/dashboard/app.js +428 -14
- package/src/dashboard/index.html +46 -1
- package/src/dashboard/styles.css +453 -0
- package/src/orchestrator/engine.js +234 -33
- package/src/server/server.js +31 -3
|
@@ -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 {
|