55"""
66import os
77from pathlib import Path
8- from typing import Any , Dict , Optional
8+ from typing import Any , Dict , List , Optional
99
1010from buddy_renderer import (
1111 ANSI_COLORS ,
@@ -164,6 +164,275 @@ def get_tour_skip_message(lang: str) -> str:
164164_STEP_NUMBERS = {1 : "\u2460 " , 2 : "\u2461 " , 3 : "\u2462 " }
165165
166166
167+ # ── Suggestion Templates (i18n) ───────────────────────────────────
168+
169+ _SUGGESTION_TEMPLATES : Dict [str , Dict [str , Dict [str , str ]]] = {
170+ "low_coverage" : {
171+ "en" : {
172+ "mode" : "AUTO" ,
173+ "prompt" : "AUTO improve test coverage" ,
174+ "reason" : "Test coverage is {coverage}%" ,
175+ },
176+ "ko" : {
177+ "mode" : "AUTO" ,
178+ "prompt" : "AUTO 테스트 커버리지 개선" ,
179+ "reason" : "테스트 커버리지가 {coverage}%입니다" ,
180+ },
181+ "ja" : {
182+ "mode" : "AUTO" ,
183+ "prompt" : "AUTO テストカバレッジを改善" ,
184+ "reason" : "テストカバレッジは{coverage}%です" ,
185+ },
186+ "zh" : {
187+ "mode" : "AUTO" ,
188+ "prompt" : "AUTO 提高测试覆盖率" ,
189+ "reason" : "测试覆盖率为{coverage}%" ,
190+ },
191+ "es" : {
192+ "mode" : "AUTO" ,
193+ "prompt" : "AUTO mejorar cobertura de tests" ,
194+ "reason" : "Cobertura de tests es {coverage}%" ,
195+ },
196+ },
197+ "no_coverage_with_files" : {
198+ "en" : {
199+ "mode" : "PLAN" ,
200+ "prompt" : "PLAN add test coverage for the project" ,
201+ "reason" : "{file_count} source files with no test coverage data" ,
202+ },
203+ "ko" : {
204+ "mode" : "PLAN" ,
205+ "prompt" : "PLAN 프로젝트 테스트 커버리지 추가" ,
206+ "reason" : "{file_count}개 소스 파일에 테스트 커버리지 데이터 없음" ,
207+ },
208+ "ja" : {
209+ "mode" : "PLAN" ,
210+ "prompt" : "PLAN プロジェクトのテストカバレッジを追加" ,
211+ "reason" : "{file_count}個のソースファイルにテストカバレッジデータなし" ,
212+ },
213+ "zh" : {
214+ "mode" : "PLAN" ,
215+ "prompt" : "PLAN 添加项目测试覆盖率" ,
216+ "reason" : "{file_count}个源文件没有测试覆盖率数据" ,
217+ },
218+ "es" : {
219+ "mode" : "PLAN" ,
220+ "prompt" : "PLAN agregar cobertura de tests al proyecto" ,
221+ "reason" : "{file_count} archivos fuente sin datos de cobertura" ,
222+ },
223+ },
224+ "api_endpoints" : {
225+ "en" : {
226+ "mode" : "EVAL" ,
227+ "prompt" : "EVAL review API security" ,
228+ "reason" : "{api_endpoints} API endpoint(s) to review" ,
229+ },
230+ "ko" : {
231+ "mode" : "EVAL" ,
232+ "prompt" : "EVAL API 보안 검토" ,
233+ "reason" : "{api_endpoints}개 API 엔드포인트 검토 필요" ,
234+ },
235+ "ja" : {
236+ "mode" : "EVAL" ,
237+ "prompt" : "EVAL APIセキュリティをレビュー" ,
238+ "reason" : "{api_endpoints}個のAPIエンドポイントをレビュー" ,
239+ },
240+ "zh" : {
241+ "mode" : "EVAL" ,
242+ "prompt" : "EVAL 审查API安全" ,
243+ "reason" : "{api_endpoints}个API端点需要审查" ,
244+ },
245+ "es" : {
246+ "mode" : "EVAL" ,
247+ "prompt" : "EVAL revisar seguridad de API" ,
248+ "reason" : "{api_endpoints} endpoint(s) de API para revisar" ,
249+ },
250+ },
251+ }
252+
253+ # Framework-specific suggestion templates
254+ _FRAMEWORK_SUGGESTIONS : Dict [str , Dict [str , Dict [str , str ]]] = {
255+ "Next.js" : {
256+ "en" : {
257+ "prompt" : "PLAN add Server Components optimization" ,
258+ "reason" : "Next.js project detected" ,
259+ },
260+ "ko" : {
261+ "prompt" : "PLAN Server Components 최적화 추가" ,
262+ "reason" : "Next.js 프로젝트 감지됨" ,
263+ },
264+ "ja" : {
265+ "prompt" : "PLAN Server Components最適化を追加" ,
266+ "reason" : "Next.jsプロジェクトを検出" ,
267+ },
268+ "zh" : {
269+ "prompt" : "PLAN 添加Server Components优化" ,
270+ "reason" : "检测到Next.js项目" ,
271+ },
272+ "es" : {
273+ "prompt" : "PLAN agregar optimizacion de Server Components" ,
274+ "reason" : "Proyecto Next.js detectado" ,
275+ },
276+ },
277+ "NestJS" : {
278+ "en" : {
279+ "prompt" : "PLAN add API validation with class-validator" ,
280+ "reason" : "NestJS project detected" ,
281+ },
282+ "ko" : {
283+ "prompt" : "PLAN class-validator로 API 유효성 검증 추가" ,
284+ "reason" : "NestJS 프로젝트 감지됨" ,
285+ },
286+ "ja" : {
287+ "prompt" : "PLAN class-validatorでAPIバリデーションを追加" ,
288+ "reason" : "NestJSプロジェクトを検出" ,
289+ },
290+ "zh" : {
291+ "prompt" : "PLAN 使用class-validator添加API验证" ,
292+ "reason" : "检测到NestJS项目" ,
293+ },
294+ "es" : {
295+ "prompt" : "PLAN agregar validacion de API con class-validator" ,
296+ "reason" : "Proyecto NestJS detectado" ,
297+ },
298+ },
299+ "Vue" : {
300+ "en" : {
301+ "prompt" : "PLAN add Composition API refactoring" ,
302+ "reason" : "Vue project detected" ,
303+ },
304+ "ko" : {
305+ "prompt" : "PLAN Composition API 리팩토링 추가" ,
306+ "reason" : "Vue 프로젝트 감지됨" ,
307+ },
308+ "ja" : {
309+ "prompt" : "PLAN Composition APIリファクタリングを追加" ,
310+ "reason" : "Vueプロジェクトを検出" ,
311+ },
312+ "zh" : {
313+ "prompt" : "PLAN 添加Composition API重构" ,
314+ "reason" : "检测到Vue项目" ,
315+ },
316+ "es" : {
317+ "prompt" : "PLAN agregar refactorizacion de Composition API" ,
318+ "reason" : "Proyecto Vue detectado" ,
319+ },
320+ },
321+ "React" : {
322+ "en" : {
323+ "prompt" : "PLAN optimize React component performance" ,
324+ "reason" : "React project detected" ,
325+ },
326+ "ko" : {
327+ "prompt" : "PLAN React 컴포넌트 성능 최적화" ,
328+ "reason" : "React 프로젝트 감지됨" ,
329+ },
330+ "ja" : {
331+ "prompt" : "PLAN Reactコンポーネントパフォーマンスを最適化" ,
332+ "reason" : "Reactプロジェクトを検出" ,
333+ },
334+ "zh" : {
335+ "prompt" : "PLAN 优化React组件性能" ,
336+ "reason" : "检测到React项目" ,
337+ },
338+ "es" : {
339+ "prompt" : "PLAN optimizar rendimiento de componentes React" ,
340+ "reason" : "Proyecto React detectado" ,
341+ },
342+ },
343+ }
344+
345+ _GENERIC_FALLBACK : Dict [str , List [Dict [str , str ]]] = {
346+ "en" : [
347+ {"mode" : "PLAN" , "prompt" : "PLAN add user authentication" , "reason" : "Common starting point for new projects" },
348+ ],
349+ "ko" : [
350+ {"mode" : "PLAN" , "prompt" : "PLAN 사용자 인증 추가" , "reason" : "새 프로젝트의 일반적인 시작점" },
351+ ],
352+ "ja" : [
353+ {"mode" : "PLAN" , "prompt" : "PLAN ユーザー認証を追加" , "reason" : "新規プロジェクトの一般的な出発点" },
354+ ],
355+ "zh" : [
356+ {"mode" : "PLAN" , "prompt" : "PLAN 添加用户认证" , "reason" : "新项目的常见起点" },
357+ ],
358+ "es" : [
359+ {"mode" : "PLAN" , "prompt" : "PLAN agregar autenticacion de usuario" , "reason" : "Punto de partida comun para nuevos proyectos" },
360+ ],
361+ }
362+
363+
364+ def generate_suggestions (
365+ scan_result : Dict [str , Any ],
366+ language : str = "en" ,
367+ ) -> List [Dict [str , str ]]:
368+ """Generate project-specific prompt suggestions from scan data.
369+
370+ Maps scanner findings (coverage, framework, endpoints, file count)
371+ to mode-specific prompt templates. Falls back to generic suggestions
372+ when scan data is insufficient.
373+
374+ Args:
375+ scan_result: Output from project_scanner.scan_project().
376+ language: Language code (en, ko, ja, zh, es).
377+
378+ Returns:
379+ List of suggestion dicts, each with keys: mode, prompt, reason.
380+ """
381+ suggestions : List [Dict [str , str ]] = []
382+ lang = language if language in ("en" , "ko" , "ja" , "zh" , "es" ) else "en"
383+
384+ coverage = scan_result .get ("coverage" )
385+ framework = scan_result .get ("framework" , "" )
386+ api_endpoints = scan_result .get ("api_endpoints" , 0 )
387+ file_count = scan_result .get ("file_count" , 0 )
388+
389+ # Low coverage → AUTO improve
390+ if coverage is not None and coverage < 80 :
391+ tpl = _SUGGESTION_TEMPLATES ["low_coverage" ].get (lang , _SUGGESTION_TEMPLATES ["low_coverage" ]["en" ])
392+ suggestions .append ({
393+ "mode" : tpl ["mode" ],
394+ "prompt" : tpl ["prompt" ],
395+ "reason" : tpl ["reason" ].format (coverage = coverage ),
396+ })
397+
398+ # Files exist but no coverage data → suggest adding tests
399+ if coverage is None and file_count > 0 :
400+ tpl = _SUGGESTION_TEMPLATES ["no_coverage_with_files" ].get (lang , _SUGGESTION_TEMPLATES ["no_coverage_with_files" ]["en" ])
401+ suggestions .append ({
402+ "mode" : tpl ["mode" ],
403+ "prompt" : tpl ["prompt" ],
404+ "reason" : tpl ["reason" ].format (file_count = file_count ),
405+ })
406+
407+ # Framework detected → PLAN framework-specific feature
408+ if framework :
409+ for fw_key , fw_tpl in _FRAMEWORK_SUGGESTIONS .items ():
410+ if fw_key in framework :
411+ tpl = fw_tpl .get (lang , fw_tpl ["en" ])
412+ suggestions .append ({
413+ "mode" : "PLAN" ,
414+ "prompt" : tpl ["prompt" ],
415+ "reason" : tpl ["reason" ],
416+ })
417+ break
418+
419+ # API endpoints → EVAL security review
420+ if api_endpoints > 0 :
421+ tpl = _SUGGESTION_TEMPLATES ["api_endpoints" ].get (lang , _SUGGESTION_TEMPLATES ["api_endpoints" ]["en" ])
422+ suggestions .append ({
423+ "mode" : tpl ["mode" ],
424+ "prompt" : tpl ["prompt" ],
425+ "reason" : tpl ["reason" ].format (api_endpoints = api_endpoints ),
426+ })
427+
428+ # Fallback to generic if no project-specific suggestions
429+ if not suggestions :
430+ fallback = _GENERIC_FALLBACK .get (lang , _GENERIC_FALLBACK ["en" ])
431+ suggestions .extend (fallback )
432+
433+ return suggestions
434+
435+
167436def _get_text (mapping : Dict [str , str ], language : str ) -> str :
168437 """Get localized text with English fallback."""
169438 return mapping .get (language , mapping .get ("en" , "" ))
@@ -178,12 +447,17 @@ def _get_step(step_num: int, language: str) -> Dict[str, str]:
178447def render_onboarding_tour (
179448 language : str = "en" ,
180449 buddy_config : Optional [Dict [str , str ]] = None ,
450+ scan_result : Optional [Dict [str , Any ]] = None ,
181451) -> str :
182452 """Render the complete onboarding tour output.
183453
454+ When scan_result is provided, step examples are replaced with
455+ project-specific prompt suggestions from generate_suggestions().
456+
184457 Args:
185458 language: Language code (en, ko, ja, zh, es).
186459 buddy_config: Optional buddy customization from get_buddy_config().
460+ scan_result: Optional project scan data for context-aware suggestions.
187461
188462 Returns:
189463 Formatted onboarding tour string.
@@ -198,6 +472,9 @@ def render_onboarding_tour(
198472 magenta = ANSI_COLORS ["magenta" ]
199473 reset = ANSI_COLORS ["reset" ]
200474
475+ # Generate project-specific suggestions if scan data available
476+ suggestions = generate_suggestions (scan_result , language ) if scan_result else []
477+
201478 lines = [
202479 * render_face_banner (face , f"{ cyan } { welcome } { reset } " ),
203480 "" ,
@@ -213,6 +490,14 @@ def render_onboarding_tour(
213490 body = step .get ("body" , "" )
214491 example = step .get ("example" , "" )
215492
493+ # Replace example with project-specific suggestion if available
494+ suggestion_idx = step_num - 1
495+ if suggestions and suggestion_idx < len (suggestions ):
496+ s = suggestions [suggestion_idx ]
497+ example = s ["prompt" ]
498+ body_suffix = f" ({ s ['reason' ]} )"
499+ body = body + body_suffix
500+
216501 lines .append (f"" )
217502 lines .append (f" { yellow } { circled } { reset } { green } { title } { reset } " )
218503 lines .append (f" { body } " )
0 commit comments