branch-finalization

安装量: 37
排名: #18831

安装

npx skills add https://github.com/qodex-ai/ai-agent-skills --skill branch-finalization

Finishing a Development Branch Overview

Guide completion of development work by presenting clear options and handling chosen workflow.

Core principle: Verify tests → Present options → Execute choice → Clean up.

Announce at start: "I'm using the finishing-a-development-branch skill to complete this work."

The Process Step 1: Verify Tests

Before presenting options, verify tests pass:

Run project's test suite

npm test / cargo test / pytest / go test ./...

If tests fail:

Tests failing ( failures). Must fix before completing:

[Show failures]

Cannot proceed with merge/PR until tests pass.

Stop. Don't proceed to Step 2.

If tests pass: Continue to Step 2.

Step 2: Determine Base Branch

Try common base branches

git merge-base HEAD main 2>/dev/null || git merge-base HEAD master 2>/dev/null

Or ask: "This branch split from main - is that correct?"

Step 3: Present Options

Present exactly these 4 options:

Implementation complete. What would you like to do?

  1. Merge back to locally
  2. Push and create a Pull Request
  3. Keep the branch as-is (I'll handle it later)
  4. Discard this work

Which option?

Don't add explanation - keep options concise.

Step 4: Execute Choice Option 1: Merge Locally

Switch to base branch

git checkout

Pull latest

git pull

Merge feature branch

git merge

Verify tests on merged result

If tests pass

git branch -d

Then: Cleanup worktree (Step 5)

Option 2: Push and Create PR

Push branch

git push -u origin

Create PR

gh pr create --title "" --body "$(cat <<'EOF'</p> <h2 id="summary">Summary</h2> <p><2-3 bullets of what changed></p> <h2 id="test-plan">Test Plan</h2> <ul> <li>[ ] <verification steps> EOF )"</li> </ul> <p>Then: Cleanup worktree (Step 5)</p> <p>Option 3: Keep As-Is</p> <p>Report: "Keeping branch . Worktree preserved at ."</p> <p>Don't cleanup worktree.</p> <p>Option 4: Discard</p> <p>Confirm first:</p> <p>This will permanently delete: - Branch <name> - All commits: <commit-list> - Worktree at <path></p> <p>Type 'discard' to confirm.</p> <p>Wait for exact confirmation.</p> <p>If confirmed:</p> <p>git checkout <base-branch> git branch -D <feature-branch></p> <p>Then: Cleanup worktree (Step 5)</p> <p>Step 5: Cleanup Worktree</p> <p>For Options 1, 2, 4:</p> <p>Check if in worktree:</p> <p>git worktree list | grep $(git branch --show-current)</p> <p>If yes:</p> <p>git worktree remove <worktree-path></p> <p>For Option 3: Keep worktree.</p> <p>Quick Reference Option Merge Push Keep Worktree Cleanup Branch 1. Merge locally ✓ - - ✓ 2. Create PR - ✓ ✓ - 3. Keep as-is - - ✓ - 4. Discard - - - ✓ (force) Common Mistakes</p> <p>Skipping test verification</p> <p>Problem: Merge broken code, create failing PR Fix: Always verify tests before offering options</p> <p>Open-ended questions</p> <p>Problem: "What should I do next?" → ambiguous Fix: Present exactly 4 structured options</p> <p>Automatic worktree cleanup</p> <p>Problem: Remove worktree when might need it (Option 2, 3) Fix: Only cleanup for Options 1 and 4</p> <p>No confirmation for discard</p> <p>Problem: Accidentally delete work Fix: Require typed "discard" confirmation Red Flags</p> <p>Never:</p> <p>Proceed with failing tests Merge without verifying tests on result Delete work without confirmation Force-push without explicit request</p> <p>Always:</p> <p>Verify tests before offering options Present exactly 4 options Get typed confirmation for Option 4 Clean up worktree for Options 1 & 4 only Integration</p> <p>Called by:</p> <p>subagent-driven-development (Step 7) - After all tasks complete executing-plans (Step 5) - After all batches complete</p> <p>Pairs with:</p> <p>using-git-worktrees - Cleans up worktree created by that skill</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 = 'qodex-ai'; const skillName = 'branch-finalization'; const currentLang = 'ko'; 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>