@@ -53,10 +53,27 @@ async def analyze_github_profile(username: str | None) -> dict[str, Any]:
5353
5454 try :
5555 async with httpx .AsyncClient (timeout = 10.0 ) as client :
56+ headers = _get_github_headers ()
57+ is_authenticated = "Authorization" in headers
58+
59+ # 認証されている場合、認証ユーザー情報を取得
60+ authenticated_username = None
61+ if is_authenticated :
62+ try :
63+ auth_user_response = await client .get (
64+ "https://api.github.com/user" ,
65+ headers = headers ,
66+ )
67+ if auth_user_response .status_code == 200 :
68+ authenticated_username = auth_user_response .json ().get ("login" )
69+ logger .info (f"Authenticated as: { authenticated_username } " )
70+ except Exception as e :
71+ logger .warning (f"Failed to get authenticated user info: { e } " )
72+
5673 # ユーザー情報取得
5774 user_response = await client .get (
5875 f"https://api.github.com/users/{ username } " ,
59- headers = _get_github_headers () ,
76+ headers = headers ,
6077 )
6178
6279 if user_response .status_code == 404 :
@@ -70,26 +87,57 @@ async def analyze_github_profile(username: str | None) -> dict[str, Any]:
7087 user_response .raise_for_status ()
7188 user_data = user_response .json ()
7289
73- # リポジトリ一覧取得(最大100件)
74- repos_response = await client .get (
75- f"https://api.github.com/users/{ username } /repos" ,
76- params = {"sort" : "updated" , "per_page" : 100 },
77- headers = _get_github_headers (),
78- )
90+ # リポジトリ一覧取得
91+ # 認証ユーザーと一致する場合はプライベートリポジトリも取得
92+ if (
93+ is_authenticated
94+ and authenticated_username
95+ and authenticated_username .lower () == username .lower ()
96+ ):
97+ logger .info (
98+ f"Fetching private repos for authenticated user: { username } "
99+ )
100+ repos_response = await client .get (
101+ "https://api.github.com/user/repos" ,
102+ params = {
103+ "affiliation" : "owner" ,
104+ "visibility" : "all" ,
105+ "sort" : "updated" ,
106+ "per_page" : 100 ,
107+ },
108+ headers = headers ,
109+ )
110+ else :
111+ logger .info (f"Fetching public repos only for user: { username } " )
112+ repos_response = await client .get (
113+ f"https://api.github.com/users/{ username } /repos" ,
114+ params = {"sort" : "updated" , "per_page" : 100 },
115+ headers = headers ,
116+ )
117+
79118 repos_response .raise_for_status ()
80119 repos = repos_response .json ()
81120
121+ logger .info (f"Fetched { len (repos )} repositories for { username } " )
122+
82123 # 言語分析
83124 languages = _analyze_languages (repos )
125+ logger .info (f"Detected languages: { languages } " )
84126
85127 # 技術スタック検出
86128 tech_stack = await _detect_tech_stack (client , username , repos )
129+ logger .info (f"Detected tech stack: { tech_stack } " )
87130
88131 # 最近の活動(簡易版)
89132 recent_activity = _analyze_recent_activity (user_data , repos )
90133
91134 # スキル完了シグナル
92- completion_signals = _generate_completion_signals (languages , tech_stack )
135+ completion_signals = _generate_completion_signals (
136+ languages , tech_stack , repos
137+ )
138+ logger .info (
139+ f"Generated { len (completion_signals )} completion signals: { list (completion_signals .keys ())} "
140+ )
93141
94142 return {
95143 "languages" : languages ,
@@ -160,33 +208,61 @@ async def _detect_tech_stack(
160208 """
161209 tech_stack = set ()
162210
163- # 主要リポジトリ(スター数順、最大10件 )から検出
164- sorted_repos = sorted (
165- repos , key = lambda r : r . get ( "stargazers_count" , 0 ), reverse = True
166- )[: 10 ]
211+ # 主要リポジトリ(更新日時順、最大20件 )から検出
212+ sorted_repos = sorted (repos , key = lambda r : r . get ( "updated_at" , "" ), reverse = True )[
213+ : 20
214+ ]
167215
168216 for repo in sorted_repos :
169217 repo_name = repo .get ("name" , "" )
170218 description = repo .get ("description" , "" ) or ""
219+ language = repo .get ("language" , "" ) or ""
171220
172- # リポジトリ名・説明文から技術スタックを推定
221+ # リポジトリ名・説明文・言語から技術スタックを推定
173222 tech_keywords = {
174- "FastAPI" : ["fastapi" ],
223+ # Web Backend
224+ "FastAPI" : ["fastapi" , "fast-api" ],
175225 "Django" : ["django" ],
176226 "Flask" : ["flask" ],
227+ "Express" : ["express" , "expressjs" ],
228+ "NestJS" : ["nestjs" ],
229+ # Web Frontend
177230 "React" : ["react" , "reactjs" ],
178- "Next.js" : ["nextjs" , "next.js" ],
231+ "Next.js" : ["nextjs" , "next.js" , "next-js" ],
179232 "Vue" : ["vue" , "vuejs" ],
233+ "Nuxt" : ["nuxt" , "nuxtjs" ],
180234 "Angular" : ["angular" ],
181- "TypeScript" : ["typescript" , "ts" ],
182- "Docker" : ["docker" , "dockerfile" ],
235+ "Svelte" : ["svelte" ],
236+ # CSS/UI
237+ "Tailwind" : ["tailwind" ],
238+ "Bootstrap" : ["bootstrap" ],
239+ "Material-UI" : ["material-ui" , "mui" ],
240+ # TypeScript
241+ "TypeScript" : ["typescript" ],
242+ # Infrastructure
243+ "Docker" : ["docker" , "dockerfile" , "container" ],
183244 "Kubernetes" : ["kubernetes" , "k8s" ],
245+ "AWS" : ["aws" , "lambda" , "ec2" , "s3" ],
246+ "GCP" : ["gcp" , "google cloud" ],
247+ "Terraform" : ["terraform" ],
248+ # Database
249+ "PostgreSQL" : ["postgres" , "postgresql" ],
250+ "MySQL" : ["mysql" ],
251+ "MongoDB" : ["mongodb" , "mongo" ],
252+ "Redis" : ["redis" ],
253+ # AI/ML
184254 "TensorFlow" : ["tensorflow" ],
185255 "PyTorch" : ["pytorch" ],
186256 "Scikit-learn" : ["scikit" , "sklearn" ],
257+ "Keras" : ["keras" ],
258+ "OpenAI" : ["openai" , "gpt" ],
259+ # Testing
260+ "Jest" : ["jest" ],
261+ "Pytest" : ["pytest" ],
262+ "Vitest" : ["vitest" ],
187263 }
188264
189- combined_text = f"{ repo_name } { description } " .lower ()
265+ combined_text = f"{ repo_name } { description } { language } " .lower ()
190266
191267 for tech , keywords in tech_keywords .items ():
192268 if any (keyword in combined_text for keyword in keywords ):
@@ -235,59 +311,155 @@ def _analyze_recent_activity(
235311
236312
237313def _generate_completion_signals (
238- languages : list [str ], tech_stack : list [str ]
314+ languages : list [str ], tech_stack : list [str ], repos : list [ dict [ str , Any ]]
239315) -> dict [str , bool ]:
240316 """
241317 習得済みスキルのシグナルを生成
242318
243319 Args:
244320 languages: 使用言語リスト
245321 tech_stack: 技術スタックリスト
322+ repos: リポジトリリスト
246323
247324 Returns:
248325 スキルID: 完了フラグのマッピング
249326 """
250327 signals = {}
251328
252- # 言語ベースのシグナル
329+ # 言語ベースのシグナル(大幅に拡張)
253330 language_mapping = {
254- "HTML" : ["web_html_css" ],
255- "CSS" : ["web_html_css" ],
256- "JavaScript" : ["web_js_basics" ],
257- "TypeScript" : ["web_typescript" ],
258- "Python" : ["ai_python_basics" , "infrastructure_linux_basics" ],
259- "Java" : ["infrastructure_container" ],
260- "Go" : ["infrastructure_container" ],
261- "Rust" : ["infrastructure_container" ],
262- "C" : ["game_math" ],
263- "C++" : ["game_math" , "game_engine" ],
264- "C#" : ["game_engine" ],
331+ "HTML" : ["web_html_css" , "web_a11y_basics" ],
332+ "CSS" : ["web_html_css" , "web_css_fw" ],
333+ "JavaScript" : ["web_js_basics" , "web_js_advanced" ],
334+ "TypeScript" : ["web_typescript" , "web_js_advanced" ],
335+ "Python" : ["ai_python_basics" , "ai_data_processing" , "infra_shell_scripting" ],
336+ "Java" : ["infra_virt_basics" ],
337+ "Go" : ["infra_docker" , "infra_kubernetes" ],
338+ "Rust" : ["infra_linux_admin" ],
339+ "C" : ["game_math_basics" , "game_physics_basics" ],
340+ "C++" : ["game_math_basics" , "game_engine_basics" ],
341+ "C#" : ["game_engine_basics" , "game_scripting" ],
342+ "Ruby" : ["web_backend_api" ],
343+ "PHP" : ["web_backend_api" ],
344+ "Shell" : ["infra_shell_scripting" , "infra_linux_admin" ],
345+ "Dockerfile" : ["infra_docker" ],
265346 }
266347
267348 for lang in languages :
268349 if lang in language_mapping :
269350 for skill_id in language_mapping [lang ]:
270351 signals [skill_id ] = True
271352
272- # 技術スタックベースのシグナル
353+ # 技術スタックベースのシグナル(大幅に拡張)
273354 tech_mapping = {
274- "React" : ["web_react" ],
275- "Next.js" : ["web_nextjs" ],
276- "Vue" : ["web_vue" ],
277- "Angular" : ["web_angular" ],
278- "FastAPI" : ["web_api_design" ],
279- "Django" : ["web_api_design" ],
280- "Flask" : ["web_api_design" ],
281- "Docker" : ["infrastructure_docker" ],
282- "Kubernetes" : ["infrastructure_kubernetes" ],
283- "TensorFlow" : ["ai_deep_learning" ],
284- "PyTorch" : ["ai_deep_learning" ],
285- "Scikit-learn" : ["ai_ml" ],
355+ # Web Frontend
356+ "React" : ["web_spa_fw" , "web_js_advanced" ],
357+ "Next.js" : ["web_ssr_ssg" , "web_spa_fw" , "web_build_tools" ],
358+ "Vue" : ["web_spa_fw" ],
359+ "Nuxt" : ["web_ssr_ssg" , "web_spa_fw" ],
360+ "Angular" : ["web_spa_fw" ],
361+ "Svelte" : ["web_spa_fw" ],
362+ # Web Backend
363+ "FastAPI" : ["web_backend_api" , "web_http_basics" ],
364+ "Django" : ["web_backend_api" , "web_db_orm" ],
365+ "Flask" : ["web_backend_api" ],
366+ "Express" : ["web_backend_api" ],
367+ "NestJS" : ["web_backend_api" ],
368+ # CSS/UI
369+ "Tailwind" : ["web_css_fw" ],
370+ "Bootstrap" : ["web_css_fw" ],
371+ "Material-UI" : ["web_css_fw" ],
372+ # Infrastructure
373+ "Docker" : ["infra_docker" , "infra_virt_basics" ],
374+ "Kubernetes" : ["infra_kubernetes" , "infra_docker" ],
375+ "AWS" : ["infra_cloud" , "infra_cicd" ],
376+ "GCP" : ["infra_cloud" ],
377+ "Terraform" : ["infra_iac" , "infra_cloud" ],
378+ # Database
379+ "PostgreSQL" : ["web_db_orm" ],
380+ "MySQL" : ["web_db_orm" ],
381+ "MongoDB" : ["web_db_orm" ],
382+ "Redis" : ["web_cache" ],
383+ # AI/ML
384+ "TensorFlow" : ["ai_deep_learning" , "ai_ml_basics" ],
385+ "PyTorch" : ["ai_deep_learning" , "ai_ml_basics" ],
386+ "Scikit-learn" : ["ai_ml_basics" , "ai_statistics" ],
387+ "Keras" : ["ai_deep_learning" ],
388+ "OpenAI" : ["ai_llm" , "ai_ml_basics" ],
389+ # Testing
390+ "Jest" : ["web_testing" ],
391+ "Pytest" : ["ai_python_basics" ],
392+ "Vitest" : ["web_testing" ],
286393 }
287394
288395 for tech in tech_stack :
289396 if tech in tech_mapping :
290397 for skill_id in tech_mapping [tech ]:
291398 signals [skill_id ] = True
292399
400+ # リポジトリ名・説明からセキュリティ関連を検出
401+ security_keywords = [
402+ "security" ,
403+ "vulnerability" ,
404+ "pentest" ,
405+ "penetration" ,
406+ "ctf" ,
407+ "exploit" ,
408+ "hacking" ,
409+ "cryptography" ,
410+ "crypto" ,
411+ "ssl" ,
412+ "tls" ,
413+ "authentication" ,
414+ "authorization" ,
415+ "oauth" ,
416+ "jwt" ,
417+ "xss" ,
418+ "csrf" ,
419+ "sql injection" ,
420+ "injection" ,
421+ "firewall" ,
422+ "ids" ,
423+ "ips" ,
424+ "siem" ,
425+ "malware" ,
426+ "reverse" ,
427+ "forensics" ,
428+ "audit" ,
429+ "compliance" ,
430+ ]
431+
432+ has_security_work = False
433+ for repo in repos :
434+ repo_name = repo .get ("name" , "" ).lower ()
435+ description = (repo .get ("description" ) or "" ).lower ()
436+ combined = f"{ repo_name } { description } "
437+
438+ if any (keyword in combined for keyword in security_keywords ):
439+ has_security_work = True
440+ break
441+
442+ if has_security_work :
443+ signals ["sec_net_os_basics" ] = True
444+ signals ["sec_web_vuln_basics" ] = True
445+ logger .info ("Security-related work detected in repositories" )
446+
447+ # HTTPサーバー・API開発の形跡があればHTTP基礎はクリア
448+ if any (tech in tech_stack for tech in ["FastAPI" , "Django" , "Flask" ]):
449+ signals ["web_http_basics" ] = True
450+
451+ # フロントエンド + バックエンドの両方があれば全体的な理解があると判断
452+ has_frontend = any (
453+ tech in tech_stack for tech in ["React" , "Next.js" , "Vue" , "Angular" ]
454+ )
455+ has_backend = any (tech in tech_stack for tech in ["FastAPI" , "Django" , "Flask" ])
456+
457+ if has_frontend and has_backend :
458+ signals ["web_build_tools" ] = True
459+
460+ # Dockerがあればインフラ基礎もクリア
461+ if "Docker" in tech_stack :
462+ signals ["infra_linux_admin" ] = True
463+ signals ["infra_net_routing" ] = True
464+
293465 return signals
0 commit comments