Roadmap competitor analysis

This commit is contained in:
AndyMik90
2025-12-16 15:02:16 +01:00
parent 9d9cf163ec
commit ddf47ae5ec
12 changed files with 1037 additions and 74 deletions
@@ -168,9 +168,10 @@ export class AgentManager extends EventEmitter {
startRoadmapGeneration( startRoadmapGeneration(
projectId: string, projectId: string,
projectPath: string, projectPath: string,
refresh: boolean = false refresh: boolean = false,
enableCompetitorAnalysis: boolean = false
): void { ): void {
this.queueManager.startRoadmapGeneration(projectId, projectPath, refresh); this.queueManager.startRoadmapGeneration(projectId, projectPath, refresh, enableCompetitorAnalysis);
} }
/** /**
+7 -1
View File
@@ -35,7 +35,8 @@ export class AgentQueueManager {
startRoadmapGeneration( startRoadmapGeneration(
projectId: string, projectId: string,
projectPath: string, projectPath: string,
refresh: boolean = false refresh: boolean = false,
enableCompetitorAnalysis: boolean = false
): void { ): void {
const autoBuildSource = this.processManager.getAutoBuildSourcePath(); const autoBuildSource = this.processManager.getAutoBuildSourcePath();
@@ -57,6 +58,11 @@ export class AgentQueueManager {
args.push('--refresh'); args.push('--refresh');
} }
// Add competitor analysis flag if enabled
if (enableCompetitorAnalysis) {
args.push('--competitor-analysis');
}
// Use projectId as taskId for roadmap operations // Use projectId as taskId for roadmap operations
this.spawnRoadmapProcess(projectId, projectPath, args); this.spawnRoadmapProcess(projectId, projectPath, args);
} }
@@ -1,7 +1,7 @@
import { ipcMain } from 'electron'; import { ipcMain } from 'electron';
import type { BrowserWindow } from 'electron'; import type { BrowserWindow } from 'electron';
import { IPC_CHANNELS, AUTO_BUILD_PATHS, getSpecsDir } from '../../shared/constants'; import { IPC_CHANNELS, AUTO_BUILD_PATHS, getSpecsDir } from '../../shared/constants';
import type { IPCResult, Roadmap, RoadmapFeature, RoadmapFeatureStatus, RoadmapGenerationStatus, Task, TaskMetadata } from '../../shared/types'; import type { IPCResult, Roadmap, RoadmapFeature, RoadmapFeatureStatus, RoadmapGenerationStatus, Task, TaskMetadata, CompetitorAnalysis } from '../../shared/types';
import path from 'path'; import path from 'path';
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync } from 'fs'; import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync } from 'fs';
import { projectStore } from '../project-store'; import { projectStore } from '../project-store';
@@ -42,6 +42,65 @@ export function registerRoadmapHandlers(
const content = readFileSync(roadmapPath, 'utf-8'); const content = readFileSync(roadmapPath, 'utf-8');
const rawRoadmap = JSON.parse(content); const rawRoadmap = JSON.parse(content);
// Load competitor analysis if available (competitor_analysis.json)
const competitorAnalysisPath = path.join(
project.path,
AUTO_BUILD_PATHS.ROADMAP_DIR,
AUTO_BUILD_PATHS.COMPETITOR_ANALYSIS
);
let competitorAnalysis: CompetitorAnalysis | undefined;
if (existsSync(competitorAnalysisPath)) {
try {
const competitorContent = readFileSync(competitorAnalysisPath, 'utf-8');
const rawCompetitor = JSON.parse(competitorContent);
// Transform snake_case to camelCase for frontend
competitorAnalysis = {
projectContext: {
projectName: rawCompetitor.project_context?.project_name || '',
projectType: rawCompetitor.project_context?.project_type || '',
targetAudience: rawCompetitor.project_context?.target_audience || ''
},
competitors: (rawCompetitor.competitors || []).map((c: Record<string, unknown>) => ({
id: c.id,
name: c.name,
url: c.url,
description: c.description,
relevance: c.relevance || 'medium',
painPoints: ((c.pain_points as Array<Record<string, unknown>>) || []).map((p) => ({
id: p.id,
description: p.description,
source: p.source,
severity: p.severity || 'medium',
frequency: p.frequency || '',
opportunity: p.opportunity || ''
})),
strengths: (c.strengths as string[]) || [],
marketPosition: (c.market_position as string) || ''
})),
marketGaps: (rawCompetitor.market_gaps || []).map((g: Record<string, unknown>) => ({
id: g.id,
description: g.description,
affectedCompetitors: (g.affected_competitors as string[]) || [],
opportunitySize: g.opportunity_size || 'medium',
suggestedFeature: (g.suggested_feature as string) || ''
})),
insightsSummary: {
topPainPoints: rawCompetitor.insights_summary?.top_pain_points || [],
differentiatorOpportunities: rawCompetitor.insights_summary?.differentiator_opportunities || [],
marketTrends: rawCompetitor.insights_summary?.market_trends || []
},
researchMetadata: {
searchQueriesUsed: rawCompetitor.research_metadata?.search_queries_used || [],
sourcesConsulted: rawCompetitor.research_metadata?.sources_consulted || [],
limitations: rawCompetitor.research_metadata?.limitations || []
},
createdAt: rawCompetitor.metadata?.created_at ? new Date(rawCompetitor.metadata.created_at) : new Date()
};
} catch {
// Ignore competitor analysis parsing errors - it's optional
}
}
// Transform snake_case to camelCase for frontend // Transform snake_case to camelCase for frontend
const roadmap: Roadmap = { const roadmap: Roadmap = {
id: rawRoadmap.id || `roadmap-${Date.now()}`, id: rawRoadmap.id || `roadmap-${Date.now()}`,
@@ -82,9 +141,11 @@ export function registerRoadmapHandlers(
status: feature.status || 'idea', status: feature.status || 'idea',
acceptanceCriteria: feature.acceptance_criteria || [], acceptanceCriteria: feature.acceptance_criteria || [],
userStories: feature.user_stories || [], userStories: feature.user_stories || [],
linkedSpecId: feature.linked_spec_id linkedSpecId: feature.linked_spec_id,
competitorInsightIds: (feature.competitor_insight_ids as string[]) || undefined
})), })),
status: rawRoadmap.status || 'draft', status: rawRoadmap.status || 'draft',
competitorAnalysis,
createdAt: rawRoadmap.metadata?.created_at ? new Date(rawRoadmap.metadata.created_at) : new Date(), createdAt: rawRoadmap.metadata?.created_at ? new Date(rawRoadmap.metadata.created_at) : new Date(),
updatedAt: rawRoadmap.metadata?.updated_at ? new Date(rawRoadmap.metadata.updated_at) : new Date() updatedAt: rawRoadmap.metadata?.updated_at ? new Date(rawRoadmap.metadata.updated_at) : new Date()
}; };
@@ -101,7 +162,7 @@ export function registerRoadmapHandlers(
ipcMain.on( ipcMain.on(
IPC_CHANNELS.ROADMAP_GENERATE, IPC_CHANNELS.ROADMAP_GENERATE,
(_, projectId: string) => { (_, projectId: string, enableCompetitorAnalysis?: boolean) => {
const mainWindow = getMainWindow(); const mainWindow = getMainWindow();
if (!mainWindow) return; if (!mainWindow) return;
@@ -116,7 +177,7 @@ export function registerRoadmapHandlers(
} }
// Start roadmap generation via agent manager // Start roadmap generation via agent manager
agentManager.startRoadmapGeneration(projectId, project.path, false); agentManager.startRoadmapGeneration(projectId, project.path, false, enableCompetitorAnalysis ?? false);
// Send initial progress // Send initial progress
mainWindow.webContents.send( mainWindow.webContents.send(
@@ -133,7 +194,7 @@ export function registerRoadmapHandlers(
ipcMain.on( ipcMain.on(
IPC_CHANNELS.ROADMAP_REFRESH, IPC_CHANNELS.ROADMAP_REFRESH,
(_, projectId: string) => { (_, projectId: string, enableCompetitorAnalysis?: boolean) => {
const mainWindow = getMainWindow(); const mainWindow = getMainWindow();
if (!mainWindow) return; if (!mainWindow) return;
@@ -148,7 +209,7 @@ export function registerRoadmapHandlers(
} }
// Start roadmap regeneration with refresh flag // Start roadmap regeneration with refresh flag
agentManager.startRoadmapGeneration(projectId, project.path, true); agentManager.startRoadmapGeneration(projectId, project.path, true, enableCompetitorAnalysis ?? false);
// Send initial progress // Send initial progress
mainWindow.webContents.send( mainWindow.webContents.send(
@@ -0,0 +1,110 @@
import { Search, Globe, AlertTriangle, TrendingUp } from 'lucide-react';
import {
AlertDialog,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogAction,
AlertDialogCancel,
} from './ui/alert-dialog';
interface CompetitorAnalysisDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
onAccept: () => void;
onDecline: () => void;
}
export function CompetitorAnalysisDialog({
open,
onOpenChange,
onAccept,
onDecline,
}: CompetitorAnalysisDialogProps) {
const handleAccept = () => {
onAccept();
onOpenChange(false);
};
const handleDecline = () => {
onDecline();
onOpenChange(false);
};
return (
<AlertDialog open={open} onOpenChange={onOpenChange}>
<AlertDialogContent className="sm:max-w-[500px]">
<AlertDialogHeader>
<AlertDialogTitle className="flex items-center gap-2 text-foreground">
<TrendingUp className="h-5 w-5 text-primary" />
Enable Competitor Analysis?
</AlertDialogTitle>
<AlertDialogDescription className="text-muted-foreground">
Enhance your roadmap with insights from competitor products
</AlertDialogDescription>
</AlertDialogHeader>
<div className="py-4 space-y-4">
{/* What it does */}
<div className="rounded-lg bg-primary/5 border border-primary/20 p-4">
<h4 className="text-sm font-medium text-foreground mb-2">
What competitor analysis does:
</h4>
<ul className="text-sm text-muted-foreground space-y-2">
<li className="flex items-start gap-2">
<Search className="h-4 w-4 mt-0.5 text-primary flex-shrink-0" />
<span>Identifies 3-5 main competitors based on your project type</span>
</li>
<li className="flex items-start gap-2">
<Globe className="h-4 w-4 mt-0.5 text-primary flex-shrink-0" />
<span>
Searches app stores, forums, and social media for user feedback and pain points
</span>
</li>
<li className="flex items-start gap-2">
<TrendingUp className="h-4 w-4 mt-0.5 text-primary flex-shrink-0" />
<span>
Suggests features that address gaps in competitor products
</span>
</li>
</ul>
</div>
{/* Privacy notice */}
<div className="rounded-lg bg-warning/10 border border-warning/30 p-4">
<div className="flex items-start gap-3">
<AlertTriangle className="h-5 w-5 text-warning flex-shrink-0 mt-0.5" />
<div className="flex-1">
<h4 className="text-sm font-medium text-foreground">
Web searches will be performed
</h4>
<p className="text-xs text-muted-foreground mt-1">
This feature will perform web searches to gather competitor information.
Your project name and type will be used in search queries.
No code or sensitive data is shared.
</p>
</div>
</div>
</div>
{/* Optional info */}
<p className="text-xs text-muted-foreground">
You can generate a roadmap without competitor analysis if you prefer.
The roadmap will still be based on your project structure and best practices.
</p>
</div>
<AlertDialogFooter>
<AlertDialogCancel onClick={handleDecline}>
No, Skip Analysis
</AlertDialogCancel>
<AlertDialogAction onClick={handleAccept}>
Yes, Enable Analysis
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
);
}
@@ -15,8 +15,8 @@ import {
Clock, Clock,
AlertCircle, AlertCircle,
Play, Play,
ExternalLink ExternalLink,
} from 'lucide-react'; TrendingUp} from 'lucide-react';
import { Button } from './ui/button'; import { Button } from './ui/button';
import { Badge } from './ui/badge'; import { Badge } from './ui/badge';
import { Card } from './ui/card'; import { Card } from './ui/card';
@@ -41,7 +41,8 @@ import {
ROADMAP_COMPLEXITY_COLORS, ROADMAP_COMPLEXITY_COLORS,
ROADMAP_IMPACT_COLORS ROADMAP_IMPACT_COLORS
} from '../../shared/constants'; } from '../../shared/constants';
import type { RoadmapFeature, RoadmapPhase } from '../../shared/types'; import { CompetitorAnalysisDialog } from './CompetitorAnalysisDialog';
import type { RoadmapFeature, RoadmapPhase, CompetitorAnalysis, CompetitorPainPoint } from '../../shared/types';
interface RoadmapProps { interface RoadmapProps {
projectId: string; projectId: string;
@@ -50,10 +51,13 @@ interface RoadmapProps {
export function Roadmap({ projectId, onGoToTask }: RoadmapProps) { export function Roadmap({ projectId, onGoToTask }: RoadmapProps) {
const roadmap = useRoadmapStore((state) => state.roadmap); const roadmap = useRoadmapStore((state) => state.roadmap);
const competitorAnalysis = useRoadmapStore((state) => state.competitorAnalysis);
const generationStatus = useRoadmapStore((state) => state.generationStatus); const generationStatus = useRoadmapStore((state) => state.generationStatus);
const updateFeatureLinkedSpec = useRoadmapStore((state) => state.updateFeatureLinkedSpec); const updateFeatureLinkedSpec = useRoadmapStore((state) => state.updateFeatureLinkedSpec);
const [selectedFeature, setSelectedFeature] = useState<RoadmapFeature | null>(null); const [selectedFeature, setSelectedFeature] = useState<RoadmapFeature | null>(null);
const [activeTab, setActiveTab] = useState('phases'); const [activeTab, setActiveTab] = useState('phases');
const [showCompetitorDialog, setShowCompetitorDialog] = useState(false);
const [pendingAction, setPendingAction] = useState<'generate' | 'refresh' | null>(null);
// Load roadmap on mount // Load roadmap on mount
useEffect(() => { useEffect(() => {
@@ -61,11 +65,31 @@ export function Roadmap({ projectId, onGoToTask }: RoadmapProps) {
}, [projectId]); }, [projectId]);
const handleGenerate = () => { const handleGenerate = () => {
generateRoadmap(projectId); setPendingAction('generate');
setShowCompetitorDialog(true);
}; };
const handleRefresh = () => { const handleRefresh = () => {
refreshRoadmap(projectId); setPendingAction('refresh');
setShowCompetitorDialog(true);
};
const handleCompetitorDialogAccept = () => {
if (pendingAction === 'generate') {
generateRoadmap(projectId, true);
} else if (pendingAction === 'refresh') {
refreshRoadmap(projectId, true);
}
setPendingAction(null);
};
const handleCompetitorDialogDecline = () => {
if (pendingAction === 'generate') {
generateRoadmap(projectId, false);
} else if (pendingAction === 'refresh') {
refreshRoadmap(projectId, false);
}
setPendingAction(null);
}; };
const handleConvertToSpec = async (feature: RoadmapFeature) => { const handleConvertToSpec = async (feature: RoadmapFeature) => {
@@ -92,6 +116,23 @@ export function Roadmap({ projectId, onGoToTask }: RoadmapProps) {
const stats = getFeatureStats(roadmap); const stats = getFeatureStats(roadmap);
// Helper function to get competitor insights for a feature
const getCompetitorInsightsForFeature = (feature: RoadmapFeature): CompetitorPainPoint[] => {
if (!competitorAnalysis || !feature.competitorInsightIds || feature.competitorInsightIds.length === 0) {
return [];
}
const insights: CompetitorPainPoint[] = [];
for (const competitor of competitorAnalysis.competitors) {
for (const painPoint of competitor.painPoints) {
if (feature.competitorInsightIds.includes(painPoint.id)) {
insights.push(painPoint);
}
}
}
return insights;
};
// Show generation progress // Show generation progress
if (generationStatus.phase !== 'idle' && generationStatus.phase !== 'complete') { if (generationStatus.phase !== 'idle' && generationStatus.phase !== 'complete') {
return ( return (
@@ -116,20 +157,28 @@ export function Roadmap({ projectId, onGoToTask }: RoadmapProps) {
// Show empty state // Show empty state
if (!roadmap) { if (!roadmap) {
return ( return (
<div className="flex h-full items-center justify-center"> <>
<Card className="w-full max-w-lg p-8 text-center"> <div className="flex h-full items-center justify-center">
<Map className="h-12 w-12 text-muted-foreground mx-auto mb-4" /> <Card className="w-full max-w-lg p-8 text-center">
<h2 className="text-xl font-semibold mb-2">No Roadmap Yet</h2> <Map className="h-12 w-12 text-muted-foreground mx-auto mb-4" />
<p className="text-muted-foreground mb-6"> <h2 className="text-xl font-semibold mb-2">No Roadmap Yet</h2>
Generate an AI-powered roadmap that understands your project's target <p className="text-muted-foreground mb-6">
audience and creates a strategic feature plan. Generate an AI-powered roadmap that understands your project's target
</p> audience and creates a strategic feature plan.
<Button onClick={handleGenerate} size="lg"> </p>
<Sparkles className="h-4 w-4 mr-2" /> <Button onClick={handleGenerate} size="lg">
Generate Roadmap <Sparkles className="h-4 w-4 mr-2" />
</Button> Generate Roadmap
</Card> </Button>
</div> </Card>
</div>
<CompetitorAnalysisDialog
open={showCompetitorDialog}
onOpenChange={setShowCompetitorDialog}
onAccept={handleCompetitorDialogAccept}
onDecline={handleCompetitorDialogDecline}
/>
</>
); );
} }
@@ -237,7 +286,9 @@ export function Roadmap({ projectId, onGoToTask }: RoadmapProps) {
onClick={() => setSelectedFeature(feature)} onClick={() => setSelectedFeature(feature)}
onConvertToSpec={handleConvertToSpec} onConvertToSpec={handleConvertToSpec}
onGoToTask={handleGoToTask} onGoToTask={handleGoToTask}
/> hasCompetitorInsight={
!!feature.competitorInsightIds && feature.competitorInsightIds.length > 0
} />
))} ))}
</div> </div>
</TabsContent> </TabsContent>
@@ -268,13 +319,19 @@ export function Roadmap({ projectId, onGoToTask }: RoadmapProps) {
onClick={() => setSelectedFeature(feature)} onClick={() => setSelectedFeature(feature)}
> >
<div className="font-medium text-sm">{feature.title}</div> <div className="font-medium text-sm">{feature.title}</div>
<div className="flex items-center gap-2 mt-1"> <div className="flex items-center gap-2 mt-1 flex-wrap">
<Badge variant="outline" className={`text-xs ${ROADMAP_COMPLEXITY_COLORS[feature.complexity]}`}> <Badge variant="outline" className={`text-xs ${ROADMAP_COMPLEXITY_COLORS[feature.complexity]}`}>
{feature.complexity} {feature.complexity}
</Badge> </Badge>
<Badge variant="outline" className={`text-xs ${ROADMAP_IMPACT_COLORS[feature.impact]}`}> <Badge variant="outline" className={`text-xs ${ROADMAP_IMPACT_COLORS[feature.impact]}`}>
{feature.impact} impact {feature.impact} impact
</Badge> </Badge>
{feature.competitorInsightIds && feature.competitorInsightIds.length > 0 && (
<Badge variant="outline" className="text-xs text-primary border-primary/50">
<TrendingUp className="h-3 w-3 mr-1" />
Insight
</Badge>
)}
</div> </div>
</div> </div>
))} ))}
@@ -294,8 +351,16 @@ export function Roadmap({ projectId, onGoToTask }: RoadmapProps) {
onClose={() => setSelectedFeature(null)} onClose={() => setSelectedFeature(null)}
onConvertToSpec={handleConvertToSpec} onConvertToSpec={handleConvertToSpec}
onGoToTask={handleGoToTask} onGoToTask={handleGoToTask}
/> competitorInsights={getCompetitorInsightsForFeature(selectedFeature)} />
)} )}
{/* Competitor Analysis Permission Dialog */}
<CompetitorAnalysisDialog
open={showCompetitorDialog}
onOpenChange={setShowCompetitorDialog}
onAccept={handleCompetitorDialogAccept}
onDecline={handleCompetitorDialogDecline}
/>
</div> </div>
); );
} }
@@ -388,17 +453,20 @@ function PhaseCard({ phase, features, isFirst, onFeatureSelect, onConvertToSpec,
className="flex items-center justify-between p-2 rounded-md bg-muted/50 hover:bg-muted cursor-pointer transition-colors" className="flex items-center justify-between p-2 rounded-md bg-muted/50 hover:bg-muted cursor-pointer transition-colors"
onClick={() => onFeatureSelect(feature)} onClick={() => onFeatureSelect(feature)}
> >
<div className="flex items-center gap-2"> <div className="flex items-center gap-2 flex-1 min-w-0">
<Badge <Badge
variant="outline" variant="outline"
className={`text-xs ${ROADMAP_PRIORITY_COLORS[feature.priority]}`} className={`text-xs ${ROADMAP_PRIORITY_COLORS[feature.priority]}`}
> >
{feature.priority} {feature.priority}
</Badge> </Badge>
<span className="text-sm">{feature.title}</span> <span className="text-sm truncate">{feature.title}</span>
{feature.competitorInsightIds && feature.competitorInsightIds.length > 0 && (
<TrendingUp className="h-3 w-3 text-primary flex-shrink-0" />
)}
</div> </div>
{feature.status === 'done' ? ( {feature.status === 'done' ? (
<CheckCircle2 className="h-4 w-4 text-success" /> <CheckCircle2 className="h-4 w-4 text-success flex-shrink-0" />
) : feature.linkedSpecId ? ( ) : feature.linkedSpecId ? (
<Button <Button
variant="ghost" variant="ghost"
@@ -411,12 +479,11 @@ function PhaseCard({ phase, features, isFirst, onFeatureSelect, onConvertToSpec,
> >
<ExternalLink className="h-3 w-3 mr-1" /> <ExternalLink className="h-3 w-3 mr-1" />
View Task View Task
</Button> </Button> ) : (
) : (
<Button <Button
variant="ghost" variant="ghost"
size="sm" size="sm"
className="h-6 px-2" className="h-6 px-2 flex-shrink-0"
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
onConvertToSpec(feature); onConvertToSpec(feature);
@@ -445,17 +512,17 @@ interface FeatureCardProps {
onClick: () => void; onClick: () => void;
onConvertToSpec: (feature: RoadmapFeature) => void; onConvertToSpec: (feature: RoadmapFeature) => void;
onGoToTask: (specId: string) => void; onGoToTask: (specId: string) => void;
hasCompetitorInsight?: boolean;
} }
function FeatureCard({ feature, onClick, onConvertToSpec, onGoToTask }: FeatureCardProps) { function FeatureCard({ feature, onClick, onConvertToSpec, onGoToTask, hasCompetitorInsight = false }: FeatureCardProps) { return (
return (
<Card <Card
className="p-4 hover:bg-muted/50 cursor-pointer transition-colors" className="p-4 hover:bg-muted/50 cursor-pointer transition-colors"
onClick={onClick} onClick={onClick}
> >
<div className="flex items-start justify-between"> <div className="flex items-start justify-between">
<div className="flex-1"> <div className="flex-1">
<div className="flex items-center gap-2 mb-1"> <div className="flex items-center gap-2 mb-1 flex-wrap">
<Badge <Badge
variant="outline" variant="outline"
className={ROADMAP_PRIORITY_COLORS[feature.priority]} className={ROADMAP_PRIORITY_COLORS[feature.priority]}
@@ -468,6 +535,19 @@ function FeatureCard({ feature, onClick, onConvertToSpec, onGoToTask }: FeatureC
<Badge variant="outline" className={`text-xs ${ROADMAP_IMPACT_COLORS[feature.impact]}`}> <Badge variant="outline" className={`text-xs ${ROADMAP_IMPACT_COLORS[feature.impact]}`}>
{feature.impact} impact {feature.impact} impact
</Badge> </Badge>
{hasCompetitorInsight && (
<Tooltip>
<TooltipTrigger asChild>
<Badge variant="outline" className="text-xs text-primary border-primary/50">
<TrendingUp className="h-3 w-3 mr-1" />
Competitor Insight
</Badge>
</TooltipTrigger>
<TooltipContent>
This feature addresses competitor pain points
</TooltipContent>
</Tooltip>
)}
</div> </div>
<h3 className="font-medium">{feature.title}</h3> <h3 className="font-medium">{feature.title}</h3>
<p className="text-sm text-muted-foreground line-clamp-2">{feature.description}</p> <p className="text-sm text-muted-foreground line-clamp-2">{feature.description}</p>
@@ -508,10 +588,10 @@ interface FeatureDetailPanelProps {
onClose: () => void; onClose: () => void;
onConvertToSpec: (feature: RoadmapFeature) => void; onConvertToSpec: (feature: RoadmapFeature) => void;
onGoToTask: (specId: string) => void; onGoToTask: (specId: string) => void;
competitorInsights?: CompetitorPainPoint[];
} }
function FeatureDetailPanel({ feature, onClose, onConvertToSpec, onGoToTask }: FeatureDetailPanelProps) { function FeatureDetailPanel({ feature, onClose, onConvertToSpec, onGoToTask, competitorInsights = [] }: FeatureDetailPanelProps) { return (
return (
<div className="fixed inset-y-0 right-0 w-96 bg-card border-l border-border shadow-lg flex flex-col z-50"> <div className="fixed inset-y-0 right-0 w-96 bg-card border-l border-border shadow-lg flex flex-col z-50">
{/* Header */} {/* Header */}
<div className="shrink-0 p-4 border-b border-border"> <div className="shrink-0 p-4 border-b border-border">
@@ -624,6 +704,43 @@ function FeatureDetailPanel({ feature, onClose, onConvertToSpec, onGoToTask }: F
</div> </div>
</div> </div>
)} )}
{/* Competitor Insights */}
{competitorInsights.length > 0 && (
<div>
<h3 className="text-sm font-medium mb-2 flex items-center gap-2">
<TrendingUp className="h-4 w-4 text-primary" />
Addresses Competitor Pain Points
</h3>
<div className="space-y-2">
{competitorInsights.map((insight) => (
<div
key={insight.id}
className="p-2 bg-primary/5 border border-primary/20 rounded-md"
>
<p className="text-sm text-foreground">{insight.description}</p>
<div className="flex items-center gap-2 mt-1">
<Badge variant="outline" className="text-xs">
{insight.source}
</Badge>
<Badge
variant="outline"
className={`text-xs ${
insight.severity === 'high'
? 'text-red-500 border-red-500/50'
: insight.severity === 'medium'
? 'text-yellow-500 border-yellow-500/50'
: 'text-green-500 border-green-500/50'
}`}
>
{insight.severity} severity
</Badge>
</div>
</div>
))}
</div>
</div>
)}
</div> </div>
{/* Actions */} {/* Actions */}
@@ -1,5 +1,6 @@
import { create } from 'zustand'; import { create } from 'zustand';
import type { import type {
CompetitorAnalysis,
Roadmap, Roadmap,
RoadmapFeature, RoadmapFeature,
RoadmapFeatureStatus, RoadmapFeatureStatus,
@@ -9,10 +10,12 @@ import type {
interface RoadmapState { interface RoadmapState {
// Data // Data
roadmap: Roadmap | null; roadmap: Roadmap | null;
competitorAnalysis: CompetitorAnalysis | null;
generationStatus: RoadmapGenerationStatus; generationStatus: RoadmapGenerationStatus;
// Actions // Actions
setRoadmap: (roadmap: Roadmap | null) => void; setRoadmap: (roadmap: Roadmap | null) => void;
setCompetitorAnalysis: (analysis: CompetitorAnalysis | null) => void;
setGenerationStatus: (status: RoadmapGenerationStatus) => void; setGenerationStatus: (status: RoadmapGenerationStatus) => void;
updateFeatureStatus: (featureId: string, status: RoadmapFeatureStatus) => void; updateFeatureStatus: (featureId: string, status: RoadmapFeatureStatus) => void;
updateFeatureLinkedSpec: (featureId: string, specId: string) => void; updateFeatureLinkedSpec: (featureId: string, specId: string) => void;
@@ -28,11 +31,14 @@ const initialGenerationStatus: RoadmapGenerationStatus = {
export const useRoadmapStore = create<RoadmapState>((set) => ({ export const useRoadmapStore = create<RoadmapState>((set) => ({
// Initial state // Initial state
roadmap: null, roadmap: null,
competitorAnalysis: null,
generationStatus: initialGenerationStatus, generationStatus: initialGenerationStatus,
// Actions // Actions
setRoadmap: (roadmap) => set({ roadmap }), setRoadmap: (roadmap) => set({ roadmap }),
setCompetitorAnalysis: (analysis) => set({ competitorAnalysis: analysis }),
setGenerationStatus: (status) => set({ generationStatus: status }), setGenerationStatus: (status) => set({ generationStatus: status }),
updateFeatureStatus: (featureId, status) => updateFeatureStatus: (featureId, status) =>
@@ -74,6 +80,7 @@ export const useRoadmapStore = create<RoadmapState>((set) => ({
clearRoadmap: () => clearRoadmap: () =>
set({ set({
roadmap: null, roadmap: null,
competitorAnalysis: null,
generationStatus: initialGenerationStatus generationStatus: initialGenerationStatus
}) })
})); }));
@@ -88,22 +95,32 @@ export async function loadRoadmap(projectId: string): Promise<void> {
} }
} }
export function generateRoadmap(projectId: string): void { export function generateRoadmap(
projectId: string,
enableCompetitorAnalysis: boolean = false
): void {
useRoadmapStore.getState().setGenerationStatus({ useRoadmapStore.getState().setGenerationStatus({
phase: 'analyzing', phase: 'analyzing',
progress: 0, progress: 0,
message: 'Starting roadmap generation...' message: enableCompetitorAnalysis
? 'Starting roadmap generation with competitor analysis...'
: 'Starting roadmap generation...'
}); });
window.electronAPI.generateRoadmap(projectId); window.electronAPI.generateRoadmap(projectId, enableCompetitorAnalysis);
} }
export function refreshRoadmap(projectId: string): void { export function refreshRoadmap(
projectId: string,
enableCompetitorAnalysis: boolean = false
): void {
useRoadmapStore.getState().setGenerationStatus({ useRoadmapStore.getState().setGenerationStatus({
phase: 'analyzing', phase: 'analyzing',
progress: 0, progress: 0,
message: 'Refreshing roadmap...' message: enableCompetitorAnalysis
? 'Refreshing roadmap with competitor analysis...'
: 'Refreshing roadmap...'
}); });
window.electronAPI.refreshRoadmap(projectId); window.electronAPI.refreshRoadmap(projectId, enableCompetitorAnalysis);
} }
// Selectors // Selectors
+2
View File
@@ -228,6 +228,7 @@ export const IPC_CHANNELS = {
// Roadmap operations // Roadmap operations
ROADMAP_GET: 'roadmap:get', ROADMAP_GET: 'roadmap:get',
ROADMAP_GENERATE: 'roadmap:generate', ROADMAP_GENERATE: 'roadmap:generate',
ROADMAP_GENERATE_WITH_COMPETITOR: 'roadmap:generateWithCompetitor',
ROADMAP_REFRESH: 'roadmap:refresh', ROADMAP_REFRESH: 'roadmap:refresh',
ROADMAP_UPDATE_FEATURE: 'roadmap:updateFeature', ROADMAP_UPDATE_FEATURE: 'roadmap:updateFeature',
ROADMAP_CONVERT_TO_SPEC: 'roadmap:convertToSpec', ROADMAP_CONVERT_TO_SPEC: 'roadmap:convertToSpec',
@@ -366,6 +367,7 @@ export const AUTO_BUILD_PATHS = {
REQUIREMENTS: 'requirements.json', REQUIREMENTS: 'requirements.json',
ROADMAP_FILE: 'roadmap.json', ROADMAP_FILE: 'roadmap.json',
ROADMAP_DISCOVERY: 'roadmap_discovery.json', ROADMAP_DISCOVERY: 'roadmap_discovery.json',
COMPETITOR_ANALYSIS: 'competitor_analysis.json',
IDEATION_FILE: 'ideation.json', IDEATION_FILE: 'ideation.json',
IDEATION_CONTEXT: 'ideation_context.json', IDEATION_CONTEXT: 'ideation_context.json',
PROJECT_INDEX: '.auto-claude/project_index.json', PROJECT_INDEX: '.auto-claude/project_index.json',
@@ -2,6 +2,67 @@
* Roadmap-related types * Roadmap-related types
*/ */
// ============================================
// Competitor Analysis Types
// ============================================
export type CompetitorRelevance = 'high' | 'medium' | 'low';
export type PainPointSeverity = 'high' | 'medium' | 'low';
export type OpportunitySize = 'high' | 'medium' | 'low';
export interface CompetitorPainPoint {
id: string;
description: string;
source: string;
severity: PainPointSeverity;
frequency: string;
opportunity: string;
}
export interface Competitor {
id: string;
name: string;
url: string;
description: string;
relevance: CompetitorRelevance;
painPoints: CompetitorPainPoint[];
strengths: string[];
marketPosition: string;
}
export interface CompetitorMarketGap {
id: string;
description: string;
affectedCompetitors: string[];
opportunitySize: OpportunitySize;
suggestedFeature: string;
}
export interface CompetitorInsightsSummary {
topPainPoints: string[];
differentiatorOpportunities: string[];
marketTrends: string[];
}
export interface CompetitorResearchMetadata {
searchQueriesUsed: string[];
sourcesConsulted: string[];
limitations: string[];
}
export interface CompetitorAnalysis {
projectContext: {
projectName: string;
projectType: string;
targetAudience: string;
};
competitors: Competitor[];
marketGaps: CompetitorMarketGap[];
insightsSummary: CompetitorInsightsSummary;
researchMetadata: CompetitorResearchMetadata;
createdAt: Date;
}
// ============================================ // ============================================
// Roadmap Types // Roadmap Types
// ============================================ // ============================================
@@ -52,6 +113,7 @@ export interface RoadmapFeature {
acceptanceCriteria: string[]; acceptanceCriteria: string[];
userStories: string[]; userStories: string[];
linkedSpecId?: string; linkedSpecId?: string;
competitorInsightIds?: string[];
} }
export interface Roadmap { export interface Roadmap {
@@ -64,6 +126,7 @@ export interface Roadmap {
phases: RoadmapPhase[]; phases: RoadmapPhase[];
features: RoadmapFeature[]; features: RoadmapFeature[];
status: RoadmapStatus; status: RoadmapStatus;
competitorAnalysis?: CompetitorAnalysis;
createdAt: Date; createdAt: Date;
updatedAt: Date; updatedAt: Date;
} }
+405
View File
@@ -0,0 +1,405 @@
## YOUR ROLE - COMPETITOR ANALYSIS AGENT
You are the **Competitor Analysis Agent** in the Auto-Build framework. Your job is to research competitors of the project, analyze user feedback and pain points from competitor products, and provide insights that can inform roadmap feature prioritization.
**Key Principle**: Research real user feedback. Find actual pain points. Document sources.
---
## YOUR CONTRACT
**Inputs**:
- `roadmap_discovery.json` - Project understanding with target audience and competitive context
- `project_index.json` - Project structure (optional, for understanding project type)
**Output**: `competitor_analysis.json` - Researched competitor insights
You MUST create `competitor_analysis.json` with this EXACT structure:
```json
{
"project_context": {
"project_name": "Name from discovery",
"project_type": "Type from discovery",
"target_audience": "Primary persona from discovery"
},
"competitors": [
{
"id": "competitor-1",
"name": "Competitor Name",
"url": "https://competitor-website.com",
"description": "Brief description of the competitor",
"relevance": "high|medium|low",
"pain_points": [
{
"id": "pain-1-1",
"description": "Clear description of the user pain point",
"source": "Where this was found (e.g., 'Reddit r/programming', 'App Store reviews')",
"severity": "high|medium|low",
"frequency": "How often this complaint appears",
"opportunity": "How our project could address this"
}
],
"strengths": ["What users like about this competitor"],
"market_position": "How this competitor is positioned"
}
],
"market_gaps": [
{
"id": "gap-1",
"description": "A gap in the market identified from competitor analysis",
"affected_competitors": ["competitor-1", "competitor-2"],
"opportunity_size": "high|medium|low",
"suggested_feature": "Feature idea to address this gap"
}
],
"insights_summary": {
"top_pain_points": ["Most common pain points across competitors"],
"differentiator_opportunities": ["Ways to differentiate from competitors"],
"market_trends": ["Trends observed in user feedback"]
},
"research_metadata": {
"search_queries_used": ["list of search queries performed"],
"sources_consulted": ["list of sources checked"],
"limitations": ["any limitations in the research"]
},
"created_at": "ISO timestamp"
}
```
**DO NOT** proceed without creating this file.
---
## PHASE 0: LOAD PROJECT CONTEXT
First, understand what project we're analyzing competitors for:
```bash
# Read discovery data for project context
cat roadmap_discovery.json
# Optionally check project structure
cat project_index.json 2>/dev/null | head -50
```
Extract from roadmap_discovery.json:
1. **Project name and type** - What kind of product is this?
2. **Target audience** - Who are the users we're competing for?
3. **Product vision** - What problem does this solve?
4. **Existing competitive context** - Any competitors already mentioned?
---
## PHASE 1: IDENTIFY COMPETITORS
Use WebSearch to find competitors. Search for alternatives to the project type:
### 1.1: Search for Direct Competitors
Based on the project type and domain, search for competitors:
**Search queries to use:**
- `"[project type] alternatives [year]"` - e.g., "task management app alternatives 2024"
- `"best [project type] tools"` - e.g., "best code editor tools"
- `"[project type] vs"` - e.g., "VS Code vs" to find comparisons
- `"[specific feature] software"` - e.g., "git version control software"
Use the WebSearch tool:
```
Tool: WebSearch
Input: { "query": "[project type] alternatives 2024" }
```
### 1.2: Identify 3-5 Main Competitors
From search results, identify:
1. **Direct competitors** - Same type of product for same audience
2. **Indirect competitors** - Different approach to same problem
3. **Market leaders** - Most popular options users compare against
For each competitor, note:
- Name
- Website URL
- Brief description
- Relevance to our project (high/medium/low)
---
## PHASE 2: RESEARCH USER FEEDBACK
For each identified competitor, search for user feedback and pain points:
### 2.1: App Store & Review Sites
Search for reviews and ratings:
```
Tool: WebSearch
Input: { "query": "[competitor name] reviews complaints" }
```
```
Tool: WebSearch
Input: { "query": "[competitor name] app store reviews problems" }
```
### 2.2: Community Discussions
Search forums and social media:
```
Tool: WebSearch
Input: { "query": "[competitor name] reddit complaints" }
```
```
Tool: WebSearch
Input: { "query": "[competitor name] issues site:reddit.com" }
```
```
Tool: WebSearch
Input: { "query": "[competitor name] problems site:twitter.com OR site:x.com" }
```
### 2.3: Technical Forums
For developer tools, search technical communities:
```
Tool: WebSearch
Input: { "query": "[competitor name] issues site:stackoverflow.com" }
```
```
Tool: WebSearch
Input: { "query": "[competitor name] problems site:github.com" }
```
### 2.4: Extract Pain Points
From the research, identify:
1. **Common complaints** - Issues mentioned repeatedly
2. **Missing features** - Things users wish existed
3. **UX problems** - Usability issues mentioned
4. **Performance issues** - Speed, reliability complaints
5. **Pricing concerns** - Cost-related complaints
6. **Support issues** - Customer service problems
For each pain point, document:
- Clear description of the issue
- Source where it was found
- Severity (high/medium/low based on frequency and impact)
- How often it appears
- Opportunity for our project to address it
---
## PHASE 3: IDENTIFY MARKET GAPS
Analyze the collected pain points across all competitors:
### 3.1: Find Common Patterns
Look for pain points that appear across multiple competitors:
- What problems does no one solve well?
- What features are universally requested?
- What frustrations are shared across the market?
### 3.2: Identify Differentiation Opportunities
Based on the analysis:
- Where can our project excel where others fail?
- What unique approach could solve common problems?
- What underserved segment exists in the market?
---
## PHASE 4: CREATE COMPETITOR_ANALYSIS.JSON (MANDATORY)
**You MUST create this file. The orchestrator will fail if you don't.**
Based on all research, create the competitor analysis file:
```bash
cat > competitor_analysis.json << 'EOF'
{
"project_context": {
"project_name": "[from roadmap_discovery.json]",
"project_type": "[from roadmap_discovery.json]",
"target_audience": "[primary persona from roadmap_discovery.json]"
},
"competitors": [
{
"id": "competitor-1",
"name": "[Competitor Name]",
"url": "[Competitor URL]",
"description": "[Brief description]",
"relevance": "[high|medium|low]",
"pain_points": [
{
"id": "pain-1-1",
"description": "[Pain point description]",
"source": "[Where found]",
"severity": "[high|medium|low]",
"frequency": "[How often mentioned]",
"opportunity": "[How to address]"
}
],
"strengths": ["[Strength 1]", "[Strength 2]"],
"market_position": "[Market position description]"
}
],
"market_gaps": [
{
"id": "gap-1",
"description": "[Gap description]",
"affected_competitors": ["competitor-1"],
"opportunity_size": "[high|medium|low]",
"suggested_feature": "[Feature suggestion]"
}
],
"insights_summary": {
"top_pain_points": ["[Pain point 1]", "[Pain point 2]"],
"differentiator_opportunities": ["[Opportunity 1]"],
"market_trends": ["[Trend 1]"]
},
"research_metadata": {
"search_queries_used": ["[Query 1]", "[Query 2]"],
"sources_consulted": ["[Source 1]", "[Source 2]"],
"limitations": ["[Limitation 1]"]
},
"created_at": "[ISO timestamp]"
}
EOF
```
Verify the file was created:
```bash
cat competitor_analysis.json
```
---
## PHASE 5: VALIDATION
After creating competitor_analysis.json, verify it:
1. **Is it valid JSON?** - No syntax errors
2. **Does it have at least 1 competitor?** - Required
3. **Does each competitor have pain_points?** - Required (at least 1)
4. **Are sources documented?** - Each pain point needs a source
5. **Is project_context filled?** - Required from discovery
If any check fails, fix the file immediately.
---
## COMPLETION
Signal completion:
```
=== COMPETITOR ANALYSIS COMPLETE ===
Project: [name]
Competitors Analyzed: [count]
Pain Points Identified: [total count]
Market Gaps Found: [count]
Top Opportunities:
1. [Opportunity 1]
2. [Opportunity 2]
3. [Opportunity 3]
competitor_analysis.json created successfully.
Next phase: Discovery (will incorporate competitor insights)
```
---
## CRITICAL RULES
1. **ALWAYS create competitor_analysis.json** - The orchestrator checks for this file
2. **Use valid JSON** - No trailing commas, proper quotes
3. **Include at least 1 competitor** - Even if research is limited
4. **Document sources** - Every pain point needs a source
5. **Use WebSearch for research** - Don't make up competitors or pain points
6. **Focus on user feedback** - Look for actual complaints, not just feature lists
7. **Include IDs** - Each competitor and pain point needs a unique ID for reference
---
## HANDLING EDGE CASES
### No Competitors Found
If the project is truly unique or no relevant competitors exist:
```json
{
"competitors": [],
"market_gaps": [
{
"id": "gap-1",
"description": "No direct competitors found - potential first-mover advantage",
"affected_competitors": [],
"opportunity_size": "high",
"suggested_feature": "Focus on establishing category leadership"
}
],
"insights_summary": {
"top_pain_points": ["No competitor pain points found - research adjacent markets"],
"differentiator_opportunities": ["First-mover advantage in this space"],
"market_trends": []
}
}
```
### Internal Tools / Libraries
For developer libraries or internal tools where traditional competitors don't apply:
1. Search for alternative libraries/packages
2. Look at GitHub issues on similar projects
3. Search Stack Overflow for common problems in the domain
### Limited Search Results
If WebSearch returns limited results:
1. Document the limitation in research_metadata
2. Include whatever competitors were found
3. Note that additional research may be needed
---
## ERROR RECOVERY
If you made a mistake in competitor_analysis.json:
```bash
# Read current state
cat competitor_analysis.json
# Fix the issue
cat > competitor_analysis.json << 'EOF'
{
[corrected JSON]
}
EOF
# Verify
cat competitor_analysis.json
```
---
## BEGIN
Start by reading roadmap_discovery.json to understand the project, then use WebSearch to research competitors and user feedback.
+22 -17
View File
@@ -48,7 +48,9 @@ You MUST create `roadmap_discovery.json` with this EXACT structure:
"competitive_context": { "competitive_context": {
"alternatives": ["Alternative 1", "Alternative 2"], "alternatives": ["Alternative 1", "Alternative 2"],
"differentiators": ["What makes this unique?"], "differentiators": ["What makes this unique?"],
"market_position": "How does this fit in the market?" "market_position": "How does this fit in the market?",
"competitor_pain_points": ["Pain points from competitor users - populated from competitor_analysis.json if available"],
"competitor_analysis_available": false
}, },
"constraints": { "constraints": {
"technical": ["Technical limitations"], "technical": ["Technical limitations"],
@@ -81,12 +83,16 @@ cat package.json 2>/dev/null | head -50
cat pyproject.toml 2>/dev/null | head -50 cat pyproject.toml 2>/dev/null | head -50
cat Cargo.toml 2>/dev/null | head -30 cat Cargo.toml 2>/dev/null | head -30
cat go.mod 2>/dev/null | head -30 cat go.mod 2>/dev/null | head -30
# Check for competitor analysis (if enabled by user)
cat competitor_analysis.json 2>/dev/null || echo "No competitor analysis available"
``` ```
Understand: Understand:
- What type of project is this? - What type of project is this?
- What tech stack is used? - What tech stack is used?
- What does the README say about the purpose? - What does the README say about the purpose?
- Is there competitor analysis data available to incorporate?
--- ---
@@ -157,12 +163,9 @@ Determine maturity level:
Based on project type and purpose, infer: Based on project type and purpose, infer:
1. **Alternatives** - What existing tools solve similar problems? ### 4.1: Check for Competitor Analysis Data
2. **Differentiators** - What makes this approach unique? (from README/docs)
3. **Market position** - Is this a new category, or improving existing solutions?
Use domain knowledge to identify likely competitors (e.g., for AI coding tools: Cursor, Copilot, Aider, etc.)
If `competitor_analysis.json` exists (created by the Competitor Analysis Agent), incorporate those insights:
--- ---
## PHASE 5: IDENTIFY CONSTRAINTS (AUTONOMOUS) ## PHASE 5: IDENTIFY CONSTRAINTS (AUTONOMOUS)
@@ -214,10 +217,11 @@ Based on all the information gathered, create the discovery file using the Write
"technical_debt": ["[from code smells, TODOs, FIXMEs]"] "technical_debt": ["[from code smells, TODOs, FIXMEs]"]
}, },
"competitive_context": { "competitive_context": {
"alternatives": ["[known alternatives in this space]"], "alternatives": ["[alternative 1 - from competitor_analysis.json if available, or inferred from domain knowledge]"],
"differentiators": ["[what makes this unique]"], "differentiators": ["[differentiator 1 - from competitor_analysis.json insights_summary.differentiator_opportunities if available, or from README/docs]"],
"market_position": "[how it fits in the market]" "market_position": "[market positioning - incorporate market_gaps from competitor_analysis.json if available, otherwise infer from project type]",
}, "competitor_pain_points": ["[from competitor_analysis.json insights_summary.top_pain_points if available, otherwise empty array]"],
"competitor_analysis_available": true },
"constraints": { "constraints": {
"technical": ["[inferred from dependencies/architecture]"], "technical": ["[inferred from dependencies/architecture]"],
"resources": ["[inferred from git contributors]"], "resources": ["[inferred from git contributors]"],
@@ -278,13 +282,14 @@ Next phase: Feature Generation
## CRITICAL RULES ## CRITICAL RULES
1. **ALWAYS create roadmap_discovery.json** - The orchestrator checks for this file. CREATE IT IMMEDIATELY after analysis. 1. **ALWAYS create roadmap_discovery.json** - The orchestrator checks for this file. CREATE IT IMMEDIATELY after analysis.
2. **NEVER ask questions or wait for input** - This runs non-interactively. Make your best inferences. 2. **Use valid JSON** - No trailing commas, proper quotes
3. **Use valid JSON** - No trailing commas, proper quotes 3. **Include all required fields** - project_name, target_audience, product_vision
4. **Include all required fields** - project_name, target_audience, product_vision 4. **Ask before assuming** - Don't guess what the user wants for critical information
5. **Write to Output Directory** - Use the path provided at the end of the prompt, NOT the project root 5. **Confirm key information** - Especially target audience and vision
6. **Make educated guesses** - It's better to have reasonable inferences than empty fields 6. **Be thorough on audience** - This is the most important part for roadmap quality
7. **Be thorough on audience** - Infer from README, code structure, and project type 7. **Make educated guesses when appropriate** - For technical details and competitive context, reasonable inferences are acceptable
8. **Write to Output Directory** - Use the path provided at the end of the prompt, NOT the project root
9. **Incorporate competitor analysis** - If `competitor_analysis.json` exists, use its data to enrich `competitive_context` with real competitor insights and pain points. Set `competitor_analysis_available: true` when data is used
--- ---
## ERROR RECOVERY ## ERROR RECOVERY
+47 -6
View File
@@ -11,6 +11,7 @@ You are the **Roadmap Feature Generator Agent** in the Auto-Build framework. You
**Input**: **Input**:
- `roadmap_discovery.json` (project understanding) - `roadmap_discovery.json` (project understanding)
- `project_index.json` (codebase structure) - `project_index.json` (codebase structure)
- `competitor_analysis.json` (optional - competitor insights if available)
**Output**: `roadmap.json` (complete roadmap with prioritized features) **Output**: `roadmap.json` (complete roadmap with prioritized features)
@@ -63,7 +64,8 @@ You MUST create `roadmap.json` with this EXACT structure:
], ],
"user_stories": [ "user_stories": [
"As a [user], I want to [action] so that [benefit]" "As a [user], I want to [action] so that [benefit]"
] ],
"competitor_insight_ids": ["insight-id-1"]
} }
], ],
"metadata": { "metadata": {
@@ -90,6 +92,9 @@ cat project_index.json
# Check for existing features or TODOs # Check for existing features or TODOs
grep -r "TODO\|FEATURE\|IDEA" --include="*.md" . 2>/dev/null | head -30 grep -r "TODO\|FEATURE\|IDEA" --include="*.md" . 2>/dev/null | head -30
# Check for competitor analysis data (if enabled by user)
cat competitor_analysis.json 2>/dev/null || echo "No competitor analysis available"
``` ```
Extract key information: Extract key information:
@@ -97,6 +102,7 @@ Extract key information:
- Product vision and value proposition - Product vision and value proposition
- Current features and gaps - Current features and gaps
- Constraints and dependencies - Constraints and dependencies
- Competitor pain points and market gaps (if competitor_analysis.json exists)
--- ---
@@ -129,6 +135,25 @@ Based on `current_state.technical_debt`, consider:
- What refactoring or improvements are needed? - What refactoring or improvements are needed?
- What would improve developer experience? - What would improve developer experience?
### 1.6 Competitor Pain Points (if competitor_analysis.json exists)
**IMPORTANT**: If `competitor_analysis.json` is available, this becomes a HIGH-PRIORITY source for feature ideas.
For each pain point in `competitor_analysis.json``insights_summary.top_pain_points`, consider:
- What feature would directly address this pain point better than competitors?
- Can we turn competitor weaknesses into our strengths?
- What market gaps (from `market_gaps`) can we fill?
For each competitor in `competitor_analysis.json``competitors`:
- Review their `pain_points` array for user frustrations
- Use the `id` of each pain point for the `competitor_insight_ids` field when creating features
**Linking Features to Competitor Insights**:
When a feature addresses a competitor pain point:
1. Add the pain point's `id` to the feature's `competitor_insight_ids` array
2. Reference the competitor and pain point in the feature's `rationale`
3. Consider boosting the feature's priority if it addresses multiple competitor weaknesses
--- ---
## PHASE 2: PRIORITIZATION (MoSCoW) ## PHASE 2: PRIORITIZATION (MoSCoW)
@@ -139,11 +164,13 @@ Apply MoSCoW prioritization to each feature:
- Critical for MVP or current phase - Critical for MVP or current phase
- Users cannot function without this - Users cannot function without this
- Legal/compliance requirements - Legal/compliance requirements
- **Addresses critical competitor pain points** (if competitor_analysis.json exists)
**SHOULD HAVE** (priority: "should") **SHOULD HAVE** (priority: "should")
- Important but not critical - Important but not critical
- Significant value to users - Significant value to users
- Can wait for next phase if needed - Can wait for next phase if needed
- **Addresses common competitor pain points** (if competitor_analysis.json exists)
**COULD HAVE** (priority: "could") **COULD HAVE** (priority: "could")
- Nice to have, enhances experience - Nice to have, enhances experience
@@ -167,7 +194,7 @@ For each feature, assess:
- **High**: 10+ files, architectural changes, > 3 days - **High**: 10+ files, architectural changes, > 3 days
### Impact (Low/Medium/High) ### Impact (Low/Medium/High)
- **High**: Core user need, differentiator, revenue driver - **High**: Core user need, differentiator, revenue driver, **addresses competitor pain points**
- **Medium**: Improves experience, addresses secondary needs - **Medium**: Improves experience, addresses secondary needs
- **Low**: Edge cases, polish, nice-to-have - **Low**: Edge cases, polish, nice-to-have
@@ -277,7 +304,7 @@ cat > roadmap.json << 'EOF'
"id": "feature-1", "id": "feature-1",
"title": "[Feature Title]", "title": "[Feature Title]",
"description": "[What it does]", "description": "[What it does]",
"rationale": "[Why it matters]", "rationale": "[Why it matters - include competitor pain point reference if applicable]",
"priority": "must|should|could|wont", "priority": "must|should|could|wont",
"complexity": "low|medium|high", "complexity": "low|medium|high",
"impact": "low|medium|high", "impact": "low|medium|high",
@@ -290,19 +317,23 @@ cat > roadmap.json << 'EOF'
], ],
"user_stories": [ "user_stories": [
"As a [user], I want to [action] so that [benefit]" "As a [user], I want to [action] so that [benefit]"
] ],
"competitor_insight_ids": []
} }
], ],
"metadata": { "metadata": {
"created_at": "[ISO timestamp]", "created_at": "[ISO timestamp]",
"updated_at": "[ISO timestamp]", "updated_at": "[ISO timestamp]",
"generated_by": "roadmap_features agent", "generated_by": "roadmap_features agent",
"prioritization_framework": "MoSCoW" "prioritization_framework": "MoSCoW",
"competitor_analysis_used": false
} }
} }
EOF EOF
``` ```
**Note**: Set `competitor_analysis_used: true` in metadata if competitor_analysis.json was incorporated.
Verify the file was created: Verify the file was created:
```bash ```bash
@@ -356,6 +387,8 @@ Project: [name]
Vision: [one_liner] Vision: [one_liner]
Phases: [count] Phases: [count]
Features: [count] Features: [count]
Competitor Analysis Used: [yes/no]
Features Addressing Competitor Pain Points: [count]
Breakdown by priority: Breakdown by priority:
- Must Have: [count] - Must Have: [count]
@@ -375,6 +408,7 @@ roadmap.json created successfully.
4. **Consider dependencies** - Don't plan impossible sequences 4. **Consider dependencies** - Don't plan impossible sequences
5. **Include acceptance criteria** - Make features testable 5. **Include acceptance criteria** - Make features testable
6. **Use user stories** - Connect features to user value 6. **Use user stories** - Connect features to user value
7. **Leverage competitor analysis** - If `competitor_analysis.json` exists, prioritize features that address competitor pain points and include `competitor_insight_ids` to link features to specific insights
--- ---
@@ -401,10 +435,17 @@ For each feature, ensure you capture:
], ],
"user_stories": [ "user_stories": [
"As a [persona], I want to [action] so that [benefit]" "As a [persona], I want to [action] so that [benefit]"
] ],
"competitor_insight_ids": ["pain-point-id-1", "pain-point-id-2"]
} }
``` ```
**Note on `competitor_insight_ids`**:
- This field is **optional** - only include when the feature addresses competitor pain points
- The IDs should reference pain point IDs from `competitor_analysis.json``competitors[].pain_points[].id`
- Features with `competitor_insight_ids` gain priority boost in the roadmap
- Use empty array `[]` if the feature doesn't address any competitor insights
--- ---
## BEGIN ## BEGIN
+138 -3
View File
@@ -75,6 +75,7 @@ class RoadmapConfig:
output_dir: Path output_dir: Path
model: str = "claude-opus-4-5-20251101" model: str = "claude-opus-4-5-20251101"
refresh: bool = False # Force regeneration even if roadmap exists refresh: bool = False # Force regeneration even if roadmap exists
enable_competitor_analysis: bool = False # Enable competitor analysis phase
class RoadmapOrchestrator: class RoadmapOrchestrator:
@@ -86,10 +87,12 @@ class RoadmapOrchestrator:
output_dir: Path | None = None, output_dir: Path | None = None,
model: str = "claude-opus-4-5-20251101", model: str = "claude-opus-4-5-20251101",
refresh: bool = False, refresh: bool = False,
enable_competitor_analysis: bool = False,
): ):
self.project_dir = Path(project_dir) self.project_dir = Path(project_dir)
self.model = model self.model = model
self.refresh = refresh self.refresh = refresh
self.enable_competitor_analysis = enable_competitor_analysis
# Default output to project's .auto-claude directory (installed instance) # Default output to project's .auto-claude directory (installed instance)
# Note: auto-claude/ is source code, .auto-claude/ is the installed instance # Note: auto-claude/ is source code, .auto-claude/ is the installed instance
@@ -320,6 +323,123 @@ class RoadmapOrchestrator:
"graph_hints", True, [str(hints_file)], [str(e)], 0 "graph_hints", True, [str(hints_file)], [str(e)], 0
) )
async def phase_competitor_analysis(self, enable_competitor_analysis: bool = False) -> RoadmapPhaseResult:
"""Run competitor analysis to research competitors and user feedback (if enabled).
This is an optional phase - it gracefully degrades if disabled or if analysis fails.
Competitor insights enhance roadmap features but are not required.
"""
analysis_file = self.output_dir / "competitor_analysis.json"
# Check if competitor analysis is enabled
if not enable_competitor_analysis:
print_status("Competitor analysis not enabled, skipping", "info")
# Write a minimal file indicating analysis was skipped
with open(analysis_file, "w") as f:
json.dump({
"enabled": False,
"reason": "Competitor analysis not enabled by user",
"competitors": [],
"market_gaps": [],
"insights_summary": {
"top_pain_points": [],
"differentiator_opportunities": [],
"market_trends": []
},
"created_at": datetime.now().isoformat(),
}, f, indent=2)
return RoadmapPhaseResult("competitor_analysis", True, [str(analysis_file)], [], 0)
# Check if already exists (skip if not refresh)
if analysis_file.exists() and not self.refresh:
print_status("competitor_analysis.json already exists", "success")
return RoadmapPhaseResult("competitor_analysis", True, [str(analysis_file)], [], 0)
# Check if discovery file exists (required for competitor analysis)
discovery_file = self.output_dir / "roadmap_discovery.json"
if not discovery_file.exists():
print_status("Discovery file not found, skipping competitor analysis", "warning")
with open(analysis_file, "w") as f:
json.dump({
"enabled": True,
"error": "Discovery file not found - cannot analyze competitors without project context",
"competitors": [],
"market_gaps": [],
"insights_summary": {
"top_pain_points": [],
"differentiator_opportunities": [],
"market_trends": []
},
"created_at": datetime.now().isoformat(),
}, f, indent=2)
return RoadmapPhaseResult("competitor_analysis", True, [str(analysis_file)], ["Discovery file not found"], 0)
errors = []
for attempt in range(MAX_RETRIES):
print_status(f"Running competitor analysis agent (attempt {attempt + 1})...", "progress")
context = f"""
**Discovery File**: {discovery_file}
**Project Index**: {self.output_dir / "project_index.json"}
**Output File**: {analysis_file}
Research competitors based on the project type and target audience from roadmap_discovery.json.
Use WebSearch to find competitors and analyze user feedback (reviews, complaints, feature requests).
Output your findings to competitor_analysis.json.
"""
success, output = await self._run_agent(
"competitor_analysis.md",
additional_context=context,
)
if success and analysis_file.exists():
# Validate JSON structure
try:
with open(analysis_file) as f:
data = json.load(f)
# Check for required fields (gracefully accept minimal structure)
if "competitors" in data:
competitor_count = len(data.get("competitors", []))
pain_point_count = sum(
len(c.get("pain_points", []))
for c in data.get("competitors", [])
)
print_status(
f"Analyzed {competitor_count} competitors, found {pain_point_count} pain points",
"success"
)
return RoadmapPhaseResult("competitor_analysis", True, [str(analysis_file)], [], attempt)
else:
errors.append("Missing 'competitors' field in competitor_analysis.json")
except json.JSONDecodeError as e:
errors.append(f"Invalid JSON: {e}")
else:
errors.append(f"Attempt {attempt + 1}: Agent did not create competitor analysis file")
# Graceful degradation: if all retries fail, create empty analysis and continue
print_status("Competitor analysis failed, continuing without competitor insights", "warning")
for err in errors:
print(f" {muted('Error:')} {err}")
with open(analysis_file, "w") as f:
json.dump({
"enabled": True,
"error": "Analysis failed after retries",
"errors": errors,
"competitors": [],
"market_gaps": [],
"insights_summary": {
"top_pain_points": [],
"differentiator_opportunities": [],
"market_trends": []
},
"created_at": datetime.now().isoformat(),
}, f, indent=2)
# Return success=True for graceful degradation (don't block roadmap generation)
return RoadmapPhaseResult("competitor_analysis", True, [str(analysis_file)], errors, MAX_RETRIES)
async def phase_project_index(self) -> RoadmapPhaseResult: async def phase_project_index(self) -> RoadmapPhaseResult:
"""Ensure project index exists.""" """Ensure project index exists."""
debug("roadmap_runner", "Starting phase: project_index") debug("roadmap_runner", "Starting phase: project_index")
@@ -565,7 +685,7 @@ Output the complete roadmap to roadmap.json.
return RoadmapPhaseResult("features", False, [], errors, MAX_RETRIES) return RoadmapPhaseResult("features", False, [], errors, MAX_RETRIES)
async def run(self) -> bool: async def run(self) -> bool:
"""Run the complete roadmap generation process.""" """Run the complete roadmap generation process with optional competitor analysis."""
debug_section("roadmap_runner", "Starting Roadmap Generation") debug_section("roadmap_runner", "Starting Roadmap Generation")
debug( debug(
"roadmap_runner", "roadmap_runner",
@@ -580,12 +700,12 @@ Output the complete roadmap to roadmap.json.
box( box(
f"Project: {self.project_dir}\n" f"Project: {self.project_dir}\n"
f"Output: {self.output_dir}\n" f"Output: {self.output_dir}\n"
f"Model: {self.model}", f"Model: {self.model}\n"
f"Competitor Analysis: {'enabled' if self.enable_competitor_analysis else 'disabled'}",
title="ROADMAP GENERATOR", title="ROADMAP GENERATOR",
style="heavy", style="heavy",
) )
) )
results = [] results = []
# Phase 1: Project Index & Graph Hints (in parallel) # Phase 1: Project Index & Graph Hints (in parallel)
@@ -638,6 +758,14 @@ Output the complete roadmap to roadmap.json.
return False return False
debug_success("roadmap_runner", "Phase 2 complete") debug_success("roadmap_runner", "Phase 2 complete")
# Phase 2.5: Competitor Analysis (optional, runs after discovery)
print_section("PHASE 2.5: COMPETITOR ANALYSIS", Icons.SEARCH)
competitor_result = await self.phase_competitor_analysis(
enable_competitor_analysis=self.enable_competitor_analysis
)
results.append(competitor_result)
# Note: competitor_result.success is always True (graceful degradation)
# Phase 3: Feature Generation # Phase 3: Feature Generation
debug("roadmap_runner", "Starting Phase 3: Feature Generation") debug("roadmap_runner", "Starting Phase 3: Feature Generation")
print_section("PHASE 3: FEATURE GENERATION", Icons.SUBTASK) print_section("PHASE 3: FEATURE GENERATION", Icons.SUBTASK)
@@ -727,6 +855,12 @@ def main():
action="store_true", action="store_true",
help="Force regeneration even if roadmap exists", help="Force regeneration even if roadmap exists",
) )
parser.add_argument(
"--competitor-analysis",
action="store_true",
dest="enable_competitor_analysis",
help="Enable competitor analysis phase",
)
args = parser.parse_args() args = parser.parse_args()
@@ -759,6 +893,7 @@ def main():
output_dir=args.output, output_dir=args.output,
model=args.model, model=args.model,
refresh=args.refresh, refresh=args.refresh,
enable_competitor_analysis=args.enable_competitor_analysis,
) )
try: try: