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.
@@ -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 {