@@ -13,7 +13,7 @@ import {
1313 analyzeKnowledge ,
1414 analyzeImpact ,
1515 analyzeRot ,
16- createAIClient ,
16+ getAIProvider ,
1717 generateSummary ,
1818 type AnalysisResult
1919} from "@grotto/core" ;
@@ -82,67 +82,82 @@ export const analyzeCommand = new Command("analyze")
8282 const cachedResult = latestCommit ? getCachedResult ( cache , repoRoot , latestCommit ) : null ;
8383
8484 if ( cachedResult ) {
85- spinner . succeed ( chalk . green ( `Loaded from cache for ${ latestCommit . slice ( 0 , 7 ) } .` ) ) ;
86- if ( options . output ) {
87- // Proceed to save output if requested
88- await handleReportExport ( cachedResult , repoPath , repoRoot , options , spinner ) ;
89- } else {
90- printConsoleReport ( cachedResult , options . detailLevel ) ;
85+ if ( ! options . ai || ( options . ai && cachedResult . aiSummary ) ) {
86+ spinner . succeed ( chalk . green ( `Loaded from cache for ${ latestCommit . slice ( 0 , 7 ) } .` ) ) ;
87+ if ( options . output ) {
88+ await handleReportExport ( cachedResult , repoPath , repoRoot , options , spinner ) ;
89+ } else {
90+ printConsoleReport ( cachedResult , options . detailLevel , ! ! options . ai ) ;
91+ }
92+ return ;
9193 }
92- return ;
94+ spinner . info ( chalk . blue ( `Found cached analysis for ${ latestCommit . slice ( 0 , 7 ) } , but AI summary is missing. Generating now...` ) ) ;
9395 }
9496
95- const commits = await getCommits ( git , {
96- branch : options . branch ,
97- maxCount : parseInt ( options . maxCommits , 10 )
98- } ) ;
97+ const commits = cachedResult
98+ ? [ ] // We won't re-fetch commits if we have a cached result and just need AI
99+ : await getCommits ( git , {
100+ branch : options . branch ,
101+ maxCount : parseInt ( options . maxCommits , 10 )
102+ } ) ;
99103
100- if ( commits . length === 0 ) {
104+ if ( ! cachedResult && commits . length === 0 ) {
101105 spinner . fail ( chalk . red ( "No commits found in the specified window/branch." ) ) ;
102106 return ;
103107 }
104108
105- spinner . text = `Analyzing ${ commits . length } commits...` ;
106-
107- const hotspots = analyzeHotspots ( commits , options . window as any ) ;
108- const riskScores = computeRiskScores ( hotspots ) ;
109- const churn = analyzeChurn ( commits , options . window as any ) ;
110- const contributors = analyzeContributors ( commits ) ;
111- const burnout = analyzeBurnout ( commits ) ;
112- const coupling = analyzeCoupling ( commits ) ;
113- const knowledge = analyzeKnowledge ( commits ) ;
114- const impact = analyzeImpact ( commits ) ;
115- const rot = analyzeRot ( commits ) ;
116-
117- const result : AnalysisResult = {
109+ const result : AnalysisResult = cachedResult || {
118110 meta : {
119111 repoPath,
120112 branch : options . branch ,
121113 window : options . window ,
122114 commitCount : commits . length ,
123115 generatedAt : new Date ( ) ,
124116 } ,
125- hotspots,
126- riskScores,
127- churn,
128- contributors,
129- burnout,
130- coupling,
131- knowledge,
132- impact,
133- rot
117+ hotspots : analyzeHotspots ( commits , options . window as any ) ,
118+ riskScores : computeRiskScores ( analyzeHotspots ( commits , options . window as any ) ) , // Simplified for rebuild
119+ churn : analyzeChurn ( commits , options . window as any ) ,
120+ contributors : analyzeContributors ( commits ) ,
121+ burnout : analyzeBurnout ( commits ) ,
122+ coupling : analyzeCoupling ( commits ) ,
123+ knowledge : analyzeKnowledge ( commits ) ,
124+ impact : analyzeImpact ( commits ) ,
125+ rot : analyzeRot ( commits )
134126 } ;
135127
128+ // Re-calculate hotspots/risk if we don't have cached result (above logic is a bit messy, let's fix)
129+ if ( ! cachedResult ) {
130+ spinner . text = `Analyzing ${ commits . length } commits...` ;
131+ const h = analyzeHotspots ( commits , options . window as any ) ;
132+ result . hotspots = h ;
133+ result . riskScores = computeRiskScores ( h ) ;
134+ result . churn = analyzeChurn ( commits , options . window as any ) ;
135+ result . contributors = analyzeContributors ( commits ) ;
136+ result . burnout = analyzeBurnout ( commits ) ;
137+ result . coupling = analyzeCoupling ( commits ) ;
138+ result . knowledge = analyzeKnowledge ( commits ) ;
139+ result . impact = analyzeImpact ( commits ) ;
140+ result . rot = analyzeRot ( commits ) ;
141+ }
142+
136143 if ( options . ai ) {
137144 spinner . text = "Generating AI insights..." ;
138- const apiKey = process . env [ ENV_VARS . ANTHROPIC_API_KEY ] || ( config . get ( CONFIG_KEYS . AI_KEY ) as string ) ;
139145
146+ // Resolve provider and key
147+ let providerType = ( config . get ( CONFIG_KEYS . AI_PROVIDER ) as string ) || "anthropic" ;
148+ let apiKey = process . env [ ENV_VARS . ANTHROPIC_API_KEY ] ;
149+
150+ if ( providerType === "openai" ) apiKey = process . env [ ENV_VARS . OPENAI_API_KEY ] || ( config . get ( "ai.openaiKey" ) as string ) ;
151+ else if ( providerType === "gemini" ) apiKey = process . env [ ENV_VARS . GEMINI_API_KEY ] || ( config . get ( "ai.geminiKey" ) as string ) ;
152+ else apiKey = apiKey || ( config . get ( "ai.anthropicKey" ) as string ) || ( config . get ( CONFIG_KEYS . AI_KEY ) as string ) ;
153+
140154 if ( ! apiKey ) {
141- spinner . warn ( chalk . yellow ( "AI summary requested but no API key found. Skipping AI layer." ) ) ;
155+ spinner . warn ( chalk . yellow ( `AI summary requested but no API key found for ${ providerType } . Skipping AI layer.` ) ) ;
156+ spinner . info ( chalk . blue ( `Run 'grotto config set-ai' to configure your preferred provider.` ) ) ;
142157 } else {
143158 try {
144- const aiClient = createAIClient ( apiKey ) ;
145- result . aiSummary = await generateSummary ( aiClient , result ) ;
159+ const aiProvider = getAIProvider ( providerType as any , apiKey ) ;
160+ result . aiSummary = await generateSummary ( aiProvider , result ) ;
146161 } catch ( aiErr ) {
147162 spinner . warn ( chalk . yellow ( "AI summary failed: " + ( aiErr as Error ) . message ) ) ;
148163 }
@@ -160,7 +175,7 @@ export const analyzeCommand = new Command("analyze")
160175 if ( options . output ) {
161176 await handleReportExport ( result , repoPath , repoRoot , options , spinner ) ;
162177 } else {
163- printConsoleReport ( result , options . detailLevel ) ;
178+ printConsoleReport ( result , options . detailLevel , ! ! options . ai ) ;
164179 }
165180
166181 } catch ( err ) {
0 commit comments