Roadmap competitor analysis
This commit is contained in:
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user