gitnexus-pr-review

安装量: 50
排名: #14778

安装

npx skills add https://github.com/abhigyanpatwari/gitnexus --skill gitnexus-pr-review

PR Review with GitNexus When to Use "Review this PR" "What does PR #42 change?" "Is this safe to merge?" "What's the blast radius of this PR?" "Are there missing tests for this PR?" Reviewing someone else's code changes before merge Workflow 1. gh pr diff → Get the raw diff 2. gitnexus_detect_changes({scope: "compare", base_ref: "main"}) → Map diff to affected flows 3. For each changed symbol: gitnexus_impact({target: "", direction: "upstream"}) → Blast radius per change 4. gitnexus_context({name: ""}) → Understand callers/callees 5. READ gitnexus://repo/{name}/processes → Check affected execution flows 6. Summarize findings with risk assessment If "Index is stale" → run npx gitnexus analyze in terminal before reviewing. Checklist - [ ] Fetch PR diff (gh pr diff or git diff base...head) - [ ] gitnexus_detect_changes to map changes to affected execution flows - [ ] gitnexus_impact on each non-trivial changed symbol - [ ] Review d=1 items (WILL BREAK) — are callers updated? - [ ] gitnexus_context on key changed symbols to understand full picture - [ ] Check if affected processes have test coverage - [ ] Assess overall risk level - [ ] Write review summary with findings Review Dimensions Dimension How GitNexus Helps Correctness context shows callers — are they all compatible with the change? Blast radius impact shows d=1/d=2/d=3 dependents — anything missed? Completeness detect_changes shows all affected flows — are they all handled? Test coverage impact({includeTests: true}) shows which tests touch changed code Breaking changes d=1 upstream items that aren't updated in the PR = potential breakage Risk Assessment Signal Risk Changes touch <3 symbols, 0-1 processes LOW Changes touch 3-10 symbols, 2-5 processes MEDIUM Changes touch >10 symbols or many processes HIGH Changes touch auth, payments, or data integrity code CRITICAL d=1 callers exist outside the PR diff Potential breakage — flag it Tools gitnexus_detect_changes — map PR diff to affected execution flows: gitnexus_detect_changes({scope: "compare", base_ref: "main"}) → Changed: 8 symbols in 4 files → Affected processes: CheckoutFlow, RefundFlow, WebhookHandler → Risk: MEDIUM gitnexus_impact — blast radius per changed symbol: gitnexus_impact({target: "validatePayment", direction: "upstream"}) → d=1 (WILL BREAK): - processCheckout (src/checkout.ts:42) [CALLS, 100%] - webhookHandler (src/webhooks.ts:15) [CALLS, 100%] → d=2 (LIKELY AFFECTED): - checkoutRouter (src/routes/checkout.ts:22) [CALLS, 95%] gitnexus_impact with tests — check test coverage: gitnexus_impact({target: "validatePayment", direction: "upstream", includeTests: true}) → Tests that cover this symbol: - validatePayment.test.ts [direct] - checkout.integration.test.ts [via processCheckout] gitnexus_context — understand a changed symbol's role: gitnexus_context({name: "validatePayment"}) → Incoming calls: processCheckout, webhookHandler → Outgoing calls: verifyCard, fetchRates → Processes: CheckoutFlow (step 3/7), RefundFlow (step 1/5) Example: "Review PR #42" 1. gh pr diff 42 > /tmp/pr42.diff → 4 files changed: payments.ts, checkout.ts, types.ts, utils.ts 2. gitnexus_detect_changes({scope: "compare", base_ref: "main"}) → Changed symbols: validatePayment, PaymentInput, formatAmount → Affected processes: CheckoutFlow, RefundFlow → Risk: MEDIUM 3. gitnexus_impact({target: "validatePayment", direction: "upstream"}) → d=1: processCheckout, webhookHandler (WILL BREAK) → webhookHandler is NOT in the PR diff — potential breakage! 4. gitnexus_impact({target: "PaymentInput", direction: "upstream"}) → d=1: validatePayment (in PR), createPayment (NOT in PR) → createPayment uses the old PaymentInput shape — breaking change! 5. gitnexus_context({name: "formatAmount"}) → Called by 12 functions — but change is backwards-compatible (added optional param) 6. Review summary: - MEDIUM risk — 3 changed symbols affect 2 execution flows - BUG: webhookHandler calls validatePayment but isn't updated for new signature - BUG: createPayment depends on PaymentInput type which changed - OK: formatAmount change is backwards-compatible - Tests: checkout.test.ts covers processCheckout path, but no webhook test Review Output Format Structure your review as:

PR Review: ** Risk: LOW / MEDIUM / HIGH / CRITICAL **</p> <h3 id="_2"></h3> <h2 id="changes-summary">Changes Summary</h2> <p>< N</p> <blockquote> <p>symbols changed across < M</p> <h2 id="files">files</h2> <p>< P</p> <p>execution flows affected</p> </blockquote> <h3 id="_3"></h3> <p>Findings 1. ** [severity] ** Description of finding - Evidence from GitNexus tools - Affected callers/flows</p> <h3 id="_4"></h3> <h2 id="missing-coverage">Missing Coverage</h2> <h2 id="callers-not-updated-in-pr">Callers not updated in PR: ...</h2> <p>Untested flows: ...</p> <h3 id="_5"></h3> <p>Recommendation APPROVE / REQUEST CHANGES / NEEDS DISCUSSION</p> </article> <a href="/" class="back-link">← <span data-i18n="detail.backToLeaderboard">返回排行榜</span></a> </div> <aside class="sidebar"> <section class="related-skills" id="relatedSkillsSection"> <h2 class="related-title" data-i18n="detail.relatedSkills">相关 Skills</h2> <div class="related-list" id="relatedSkillsList"> <div class="skeleton-card"></div> <div class="skeleton-card"></div> <div class="skeleton-card"></div> </div> </section> </aside> </div> </div> <script src="https://unpkg.com/i18next@23.11.5/i18next.min.js" defer></script> <script src="https://unpkg.com/i18next-browser-languagedetector@7.2.1/i18nextBrowserLanguageDetector.min.js" defer></script> <script defer> // Language resources - same pattern as index page const resources = { 'zh-CN': null, 'en': null, 'ja': null, 'ko': null, 'zh-TW': null, 'es': null, 'fr': null }; // Load language files (only current + fallback for performance) async function loadLanguageResources() { const savedLang = localStorage.getItem('i18nextLng') || 'en'; const langsToLoad = new Set([savedLang, 'en']); // current + fallback await Promise.all([...langsToLoad].map(async (lang) => { try { const response = await fetch(`/locales/${lang}.json`); if (response.ok) { resources[lang] = { translation: await response.json() }; } } catch (error) { console.warn(`Failed to load ${lang} language file:`, error); } })); } // Load a single language on demand (for language switching) async function loadLanguage(lang) { if (resources[lang]) return; try { const response = await fetch(`/locales/${lang}.json`); if (response.ok) { resources[lang] = { translation: await response.json() }; i18next.addResourceBundle(lang, 'translation', resources[lang].translation); } } catch (error) { console.warn(`Failed to load ${lang} language file:`, error); } } // Initialize i18next async function initI18n() { try { await loadLanguageResources(); // Filter out null values from resources const validResources = {}; for (const [lang, data] of Object.entries(resources)) { if (data !== null) { validResources[lang] = data; } } console.log('Loaded languages:', Object.keys(validResources)); console.log('zh-CN resource:', validResources['zh-CN']); console.log('detail.home in resource:', validResources['zh-CN']?.translation?.detail?.home); // 检查是否有保存的语言偏好 const savedLang = localStorage.getItem('i18nextLng'); // 如果没有保存的语言偏好,默认使用英文 const defaultLang = savedLang && ['zh-CN', 'en', 'ja', 'ko', 'zh-TW', 'es', 'fr'].includes(savedLang) ? savedLang : 'en'; await i18next .use(i18nextBrowserLanguageDetector) .init({ lng: defaultLang, // 强制设置初始语言 fallbackLng: 'en', supportedLngs: ['zh-CN', 'en', 'ja', 'ko', 'zh-TW', 'es', 'fr'], resources: validResources, detection: { order: ['localStorage'], // 只使用 localStorage,不检测浏览器语言 caches: ['localStorage'], lookupLocalStorage: 'i18nextLng' }, interpolation: { escapeValue: false } }); console.log('i18next initialized, language:', i18next.language); console.log('Test translation:', i18next.t('detail.home')); // Set initial language in selector const langSwitcher = document.getElementById('langSwitcher'); langSwitcher.value = i18next.language; // Update page language updatePageLanguage(); // Language switch event langSwitcher.addEventListener('change', async (e) => { await loadLanguage(e.target.value); // load on demand i18next.changeLanguage(e.target.value).then(() => { updatePageLanguage(); localStorage.setItem('i18nextLng', e.target.value); }); }); } catch (error) { console.error('i18next init failed:', error); } } // Translation helper function t(key, options = {}) { return i18next.t(key, options); } // Update all translatable elements function updatePageLanguage() { // Update HTML lang attribute document.documentElement.lang = i18next.language; // Update elements with data-i18n attribute document.querySelectorAll('[data-i18n]').forEach(el => { const key = el.getAttribute('data-i18n'); el.textContent = t(key); }); } // Copy command function function copyCommand() { const command = document.getElementById('installCommand').textContent; const btn = document.getElementById('copyBtn'); navigator.clipboard.writeText(command).then(() => { btn.textContent = t('copied'); btn.classList.add('copied'); setTimeout(() => { btn.textContent = t('copy'); btn.classList.remove('copied'); }, 2000); }).catch(() => { // Fallback for non-HTTPS const textArea = document.createElement('textarea'); textArea.value = command; textArea.style.position = 'fixed'; textArea.style.left = '-9999px'; document.body.appendChild(textArea); textArea.select(); document.execCommand('copy'); document.body.removeChild(textArea); btn.textContent = t('copied'); btn.classList.add('copied'); setTimeout(() => { btn.textContent = t('copy'); btn.classList.remove('copied'); }, 2000); }); } // Initialize document.getElementById('copyBtn').addEventListener('click', copyCommand); initI18n(); // 异步加载相关 Skills async function loadRelatedSkills() { const owner = 'abhigyanpatwari'; const skillName = 'gitnexus-pr-review'; const currentLang = 'zh-TW'; const listContainer = document.getElementById('relatedSkillsList'); const section = document.getElementById('relatedSkillsSection'); try { const response = await fetch(`/api/related-skills/${encodeURIComponent(owner)}/${encodeURIComponent(skillName)}?limit=6`); if (!response.ok) { throw new Error('Failed to load'); } const data = await response.json(); const relatedSkills = data.related_skills || []; if (relatedSkills.length === 0) { // 没有相关推荐时隐藏整个区域 section.style.display = 'none'; return; } // 渲染相关 Skills listContainer.innerHTML = relatedSkills.map(skill => { const desc = skill.description || ''; const truncatedDesc = desc.length > 60 ? desc.substring(0, 60) + '...' : desc; return ` <a href="${currentLang === 'en' ? '' : '/' + currentLang}/skill/${skill.owner}/${skill.repo}/${skill.skill_name}" class="related-card"> <div class="related-name">${escapeHtml(skill.skill_name)}</div> <div class="related-meta"> <span class="related-owner">${escapeHtml(skill.owner)}</span> <span class="related-installs">${skill.installs}</span> </div> <div class="related-desc">${escapeHtml(truncatedDesc)}</div> </a> `; }).join(''); } catch (error) { console.error('Failed to load related skills:', error); // 加载失败时显示提示或隐藏 listContainer.innerHTML = '<div class="related-empty">暂无相关推荐</div>'; } } // HTML 转义 function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // 页面加载完成后异步加载相关 Skills if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', loadRelatedSkills); } else { loadRelatedSkills(); } </script> </body> </html>