#!/usr/bin/env node /** * ๐Ÿ”ฎ ้“ธๆธŠ้…้ขๆฒป็†ๅผ•ๆ“Ž ยท Quota Governance Engine * * ็ฒพ็ป†ๅŒ–็ฎก็† GitHub Actions ้…้ขๆถˆ่€—ใ€‚ * ๅˆ†ๆžๆ‰€ๆœ‰ๅทฅไฝœๆต็š„ cron ่งฆๅ‘้ข‘็އ๏ผŒ่ฎก็ฎ—ๆ—ฅ/ๆœˆๆถˆ่€—้ข„็ฎ—๏ผŒ * ๆไพ›ไผ˜ๅŒ–ๅปบ่ฎฎๅ’Œ้™้ข‘็ญ–็•ฅใ€‚ * * ็”จๆณ•: * node scripts/quota-governance.js โ€” ๅฎŒๆ•ด้…้ขๅˆ†ๆžๆŠฅๅ‘Š * node scripts/quota-governance.js --json โ€” ่พ“ๅ‡บ JSON ๆ ผๅผ * node scripts/quota-governance.js --optimize โ€” ็”Ÿๆˆไผ˜ๅŒ–ๅปบ่ฎฎ * * ๅฎˆๆŠค: PER-ZY001 ้“ธๆธŠ ยท ICE-GL-ZY001 * ็‰ˆๆƒ: ๅ›ฝไฝœ็™ปๅญ—-2026-A-00037559 */ 'use strict'; const fs = require('fs'); const path = require('path'); const ROOT = path.resolve(__dirname, '..'); const WORKFLOWS_DIR = path.join(ROOT, '.github', 'workflows'); const REPORT_PATH = path.join(ROOT, 'signal-log', 'quota-governance-report.json'); // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• // GitHub Plans โ€” ้…้ขๅ‚ๆ•ฐ // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• const PLANS = { free: { name: 'GitHub Free', price_yearly: 0, actions_minutes_monthly: 2000, copilot_included: false, copilot_completions_monthly: 0, copilot_chat_monthly: 0, copilot_agent_sessions: 0, }, pro: { name: 'GitHub Pro ($4/month billed yearly = $48/year)', price_yearly: 48, actions_minutes_monthly: 3000, copilot_included: false, copilot_completions_monthly: 0, copilot_chat_monthly: 0, copilot_agent_sessions: 0, }, copilot_individual: { name: 'Copilot Individual ($100/year)', price_yearly: 100, actions_minutes_monthly: 2000, copilot_included: true, copilot_completions_monthly: 2000, copilot_chat_monthly: 50, copilot_premium_requests_monthly: 0, copilot_agent_sessions: 'limited', note: 'ๅ†ฐๆœ”ๅฝ“ๅ‰ๅฅ—้ค', }, copilot_pro: { name: 'Copilot Pro ($390/year = $39/month)', price_yearly: 390, actions_minutes_monthly: 2000, copilot_included: true, copilot_completions_monthly: 'unlimited', copilot_chat_monthly: 'unlimited', copilot_premium_requests_monthly: 1500, copilot_agent_sessions: 'unlimited', note: 'ๅ†ฐๆœ”่€ƒ่™‘ๅ‡็บง็›ฎๆ ‡', }, }; // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• // Cron ่งฃๆžๅ™จ โ€” ่ฎก็ฎ—ๆฏๆ—ฅ่งฆๅ‘ๆฌกๆ•ฐ // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• function parseCronRunsPerDay(cronExpr) { const parts = cronExpr.trim().split(/\s+/); if (parts.length !== 5) return 0; const [minute, hour, , , dow] = parts; let runsPerDay = 1; // ๅˆ†้’Ÿ็บง้—ด้š” */N (e.g. */15 * * * *) if (minute.startsWith('*/')) { const interval = parseInt(minute.split('/')[1], 10); if (interval > 0) runsPerDay = Math.floor(1440 / interval); } // ๅฐๆ—ถ็บง้—ด้š” */N (e.g. 0 */6 * * *) else if (hour.startsWith('*/')) { const interval = parseInt(hour.split('/')[1], 10); if (interval > 0) runsPerDay = Math.floor(24 / interval); } // ๆฏๅฐๆ—ถ่ฟ่กŒ (e.g. 0 * * * * = minuteๅ›บๅฎš, hour=*) else if (hour === '*' && !minute.includes(',') && !minute.startsWith('*/')) { runsPerDay = 24; } // ๅฐๆ—ถๅˆ—่กจ 0,6,12,18 else if (hour.includes(',')) { runsPerDay = hour.split(',').length; } // ๅˆ†้’Ÿๅˆ—่กจ else if (minute.includes(',') && !hour.includes(',')) { runsPerDay = minute.split(',').length; } // ๅ‘จๅ‡ ้™ๅˆถ if (dow !== '*' && dow !== '?') { const days = dow.split(',').length; runsPerDay = runsPerDay * days / 7; } return runsPerDay; } // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• // ๅทฅไฝœๆตๆ‰ซๆๅ™จ โ€” ๅˆ†ๆžๆ‰€ๆœ‰ๅทฅไฝœๆต่งฆๅ‘ๆจกๅผ // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• function scanWorkflows() { const files = fs.readdirSync(WORKFLOWS_DIR).filter(f => f.endsWith('.yml')); const workflows = []; for (const file of files) { const content = fs.readFileSync(path.join(WORKFLOWS_DIR, file), 'utf8'); // Extract name const nameMatch = content.match(/^name:\s*['"]?(.+?)['"]?\s*$/m); const name = nameMatch ? nameMatch[1] : file; // Extract cron schedules const cronMatches = [...content.matchAll(/cron:\s*'([^']+)'/g)]; const crons = cronMatches.map(m => m[1]); // Extract other triggers const hasPush = /^\s+push:/m.test(content); const hasPR = /pull_request/m.test(content); const hasDispatch = /workflow_dispatch/m.test(content); const hasWorkflowRun = /workflow_run/m.test(content); // Calculate daily runs from cron let dailyCronRuns = 0; for (const cron of crons) { dailyCronRuns += parseCronRunsPerDay(cron); } // Estimate average minutes per run (based on typical duration) const estimatedMinutesPerRun = estimateRunDuration(file, content); workflows.push({ file, name, crons, dailyCronRuns, hasPush, hasPR, hasDispatch, hasWorkflowRun, estimatedMinutesPerRun, dailyMinutes: dailyCronRuns * estimatedMinutesPerRun, monthlyMinutes: dailyCronRuns * estimatedMinutesPerRun * 30, triggers: [ ...(crons.length > 0 ? ['schedule'] : []), ...(hasPush ? ['push'] : []), ...(hasPR ? ['pull_request'] : []), ...(hasDispatch ? ['workflow_dispatch'] : []), ...(hasWorkflowRun ? ['workflow_run'] : []), ], }); } // Sort by daily consumption workflows.sort((a, b) => b.dailyMinutes - a.dailyMinutes); return workflows; } function estimateRunDuration(file, content) { // Heuristic estimation of run duration in minutes if (file.includes('deploy') || file.includes('build')) return 5; if (file.includes('scan') || file.includes('inspection')) return 3; if (file.includes('sync') || file.includes('bridge')) return 2; if (file.includes('poll') || file.includes('listener') || file.includes('heartbeat')) return 1; if (file.includes('checkin') || file.includes('selfcheck')) return 1; if (content.includes('npm install') || content.includes('npm ci')) return 4; return 2; // default } // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• // ้…้ขๅˆ†ๆžๅผ•ๆ“Ž // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• function analyzeQuota(workflows) { const currentPlan = PLANS.copilot_individual; const totalDailyCronRuns = workflows.reduce((sum, w) => sum + w.dailyCronRuns, 0); const totalDailyMinutes = workflows.reduce((sum, w) => sum + w.dailyMinutes, 0); const totalMonthlyMinutes = totalDailyMinutes * 30; const pushTriggeredCount = workflows.filter(w => w.hasPush).length; // Estimate ~5 pushes per day (active development) const estimatedPushRunsPerDay = pushTriggeredCount * 2; const estimatedPushMinutesPerDay = estimatedPushRunsPerDay * 3; // Copilot coding agent sessions estimate const estimatedAgentSessionsPerDay = 2; const estimatedAgentMinutesPerSession = 15; const agentDailyMinutes = estimatedAgentSessionsPerDay * estimatedAgentMinutesPerSession; const totalDailyAll = totalDailyMinutes + estimatedPushMinutesPerDay + agentDailyMinutes; const totalMonthlyAll = totalDailyAll * 30; const utilization = (totalMonthlyAll / currentPlan.actions_minutes_monthly * 100).toFixed(1); const remaining = currentPlan.actions_minutes_monthly - totalMonthlyAll; const daysRemaining = getDaysRemainingInMonth(); const dailyBudget = daysRemaining > 0 ? Math.round(remaining / daysRemaining) : 0; return { current_plan: currentPlan.name, actions_budget_monthly: currentPlan.actions_minutes_monthly, analysis: { cron_scheduled: { daily_runs: Math.round(totalDailyCronRuns * 10) / 10, daily_minutes: Math.round(totalDailyMinutes * 10) / 10, monthly_minutes: Math.round(totalMonthlyMinutes), top_consumers: workflows.slice(0, 5).map(w => ({ file: w.file, daily_runs: w.dailyCronRuns, daily_minutes: w.dailyMinutes, crons: w.crons, })), }, push_triggered: { workflows_with_push: pushTriggeredCount, estimated_daily_runs: estimatedPushRunsPerDay, estimated_daily_minutes: estimatedPushMinutesPerDay, }, copilot_agent: { estimated_daily_sessions: estimatedAgentSessionsPerDay, estimated_minutes_per_session: estimatedAgentMinutesPerSession, estimated_daily_minutes: agentDailyMinutes, note: '้“ธๆธŠ/Copilot ๅ…จ่‡ชๅŠจๅผ€ๅ‘ไผš่ฏ ยท ่ฟ™ๆ˜ฏๅ†ฐๆœ”้…้ข่€—ๅฐฝ็š„ไธป่ฆๅŽŸๅ› ', }, total: { daily_minutes: Math.round(totalDailyAll), monthly_minutes: Math.round(totalMonthlyAll), utilization_percent: parseFloat(utilization), remaining_minutes: Math.round(remaining), daily_budget_remaining: dailyBudget, status: parseFloat(utilization) > 90 ? 'CRITICAL' : parseFloat(utilization) > 70 ? 'WARNING' : 'HEALTHY', }, }, }; } function getDaysRemainingInMonth() { const now = new Date(); const lastDay = new Date(now.getFullYear(), now.getMonth() + 1, 0); return lastDay.getDate() - now.getDate(); } // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• // ไผ˜ๅŒ–ๅปบ่ฎฎๅผ•ๆ“Ž // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• function generateOptimizations(workflows) { const optimizations = []; for (const w of workflows) { // ๆฏ15ๅˆ†้’Ÿ่ฝฎ่ฏข โ†’ ๅปบ่ฎฎ้™ไธบๆฏๅฐๆ—ถ if (w.crons.some(c => c.includes('*/15'))) { const savedRuns = (96 - 24) * (w.crons.filter(c => c.includes('*/15')).length); optimizations.push({ priority: 'P0', workflow: w.file, issue: `ๆฏ15ๅˆ†้’Ÿ่ฝฎ่ฏข (${w.dailyCronRuns} ๆฌก/ๅคฉ)`, recommendation: '้™้ข‘ไธบๆฏๅฐๆ—ถ่ฝฎ่ฏข (24ๆฌก/ๅคฉ)', saved_runs_daily: savedRuns, saved_minutes_daily: savedRuns * w.estimatedMinutesPerRun, saved_minutes_monthly: savedRuns * w.estimatedMinutesPerRun * 30, implementation: `ๅฐ† cron '*/15 * * * *' ๆ”นไธบ '0 * * * *'`, }); } // ๆฏ6ๅฐๆ—ถ โ†’ ๅปบ่ฎฎๆฏ12ๅฐๆ—ถ if (w.crons.some(c => c.includes('*/6') || (c.split(',').length === 4 && c.includes('0,6,12,18')))) { optimizations.push({ priority: 'P1', workflow: w.file, issue: `ๆฏ6ๅฐๆ—ถ่งฆๅ‘ (${w.dailyCronRuns} ๆฌก/ๅคฉ)`, recommendation: '้™้ข‘ไธบๆฏ12ๅฐๆ—ถ (2ๆฌก/ๅคฉ)', saved_runs_daily: w.dailyCronRuns - 2, saved_minutes_daily: (w.dailyCronRuns - 2) * w.estimatedMinutesPerRun, saved_minutes_monthly: (w.dailyCronRuns - 2) * w.estimatedMinutesPerRun * 30, }); } // ๅคšไธชcron็š„ๅทฅไฝœๆต โ†’ ๅˆๅนถ if (w.crons.length >= 4) { optimizations.push({ priority: 'P1', workflow: w.file, issue: `${w.crons.length} ไธช cron ่งฆๅ‘ (ๆฏๆ—ฅ ${w.dailyCronRuns} ๆฌก)`, recommendation: '่€ƒ่™‘ๅˆๅนถไธบ2ไธชcronๆ—ถๆฎต', saved_runs_daily: Math.max(0, w.dailyCronRuns - 2), saved_minutes_daily: Math.max(0, w.dailyCronRuns - 2) * w.estimatedMinutesPerRun, }); } } // Sort by saved minutes optimizations.sort((a, b) => (b.saved_minutes_monthly || 0) - (a.saved_minutes_monthly || 0)); const totalSavings = optimizations.reduce((sum, o) => sum + (o.saved_minutes_monthly || 0), 0); return { count: optimizations.length, total_saved_minutes_monthly: totalSavings, items: optimizations, }; } // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• // ไผšๅ‘˜ๅ‡็บง่ฏ„ไผฐ // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• function evaluateUpgrade(quotaAnalysis) { const current = PLANS.copilot_individual; const pro = PLANS.copilot_pro; const monthlyUsage = quotaAnalysis.analysis.total.monthly_minutes; const currentHeadroom = current.actions_minutes_monthly - monthlyUsage; // The key insight: $100/year plan has limited Copilot premium requests // $390/year plan has 1500 premium requests/month + unlimited completions // The quota exhaustion issue is likely about Copilot premium requests, not Actions minutes const assessment = { current_plan: { name: current.name, yearly_cost: `$${current.price_yearly}`, actions_minutes: current.actions_minutes_monthly, copilot_chat: `${current.copilot_chat_monthly} messages/month`, copilot_agent: current.copilot_agent_sessions, actions_headroom: `${currentHeadroom} minutes/month`, }, pro_plan: { name: pro.name, yearly_cost: `$${pro.price_yearly}`, actions_minutes: pro.actions_minutes_monthly, copilot_chat: pro.copilot_chat_monthly, copilot_premium_requests: `${pro.copilot_premium_requests_monthly}/month`, copilot_agent: pro.copilot_agent_sessions, }, cost_difference: { yearly: `$${pro.price_yearly - current.price_yearly} (+$290/year)`, monthly: `$${((pro.price_yearly - current.price_yearly) / 12).toFixed(1)} (+$24.2/month)`, }, analysis: { actions_minutes_sufficient: currentHeadroom > 300, copilot_quota_is_bottleneck: true, explanation: [ '๐Ÿ” ๅ†ฐๆœ”็š„้…้ข่€—ๅฐฝ้—ฎ้ข˜ไธป่ฆๆ˜ฏ Copilot ้ซ˜็บง่ฏทๆฑ‚ๆฌกๆ•ฐ้™ๅˆถ๏ผŒไธๆ˜ฏ Actions ๅˆ†้’Ÿๆ•ฐใ€‚', '๐Ÿ“Š $100/ๅนดๅฅ—้ค: Copilot chat 50ๆฌก/ๆœˆ๏ผŒ้ซ˜็บงๆจกๅž‹(Agentๆจกๅผ)ๆฌกๆ•ฐๆœ‰้™ใ€‚', '๐Ÿ“Š $390/ๅนด Proๅฅ—้ค: Copilot chat ๆ— ้™๏ผŒ้ซ˜็บง่ฏทๆฑ‚ 1500ๆฌก/ๆœˆ๏ผŒAgent ไผš่ฏๆ— ้™ใ€‚', 'โšก ้“ธๆธŠๅ…จ่‡ชๅŠจๅผ€ๅ‘ไผš่ฏ(Agent mode)ๆ˜ฏไธป่ฆๆถˆ่€—ๆบ โ€” ๆฏๆฌกๅ”ค้†’ๆถˆ่€—ๅคง้‡้ซ˜็บง่ฏทๆฑ‚ใ€‚', '๐Ÿ”„ ไป“ๅบ“48ไธชๅทฅไฝœๆต็š„ Actions ๅˆ†้’Ÿๆ•ฐ่ฟ˜ๅœจๅ…่ดน้ขๅบฆๅ†…๏ผŒๆš‚ไธๆ˜ฏ็“ถ้ขˆใ€‚', ], }, recommendation: null, }; // Decision logic if (currentHeadroom < 200) { assessment.recommendation = { decision: 'UPGRADE_RECOMMENDED', reason: 'Actions ๅˆ†้’Ÿๅ’Œ Copilot ้ซ˜็บง่ฏทๆฑ‚ๅŒ้‡ไธ่ถณ', urgency: 'HIGH', }; } else { assessment.recommendation = { decision: 'OPTIMIZE_FIRST_THEN_EVALUATE', reason: 'Actions ๅˆ†้’Ÿๆ•ฐๅ……่ถณใ€‚ๅ…ˆไผ˜ๅŒ–ๅทฅไฝœๆต้™้ข‘่Š‚็œ่ต„ๆบ๏ผŒ้™ไฝŽ Copilot Agent ่ฐƒ็”จ้ข‘็އใ€‚' + 'ๅฆ‚ๆžœไผ˜ๅŒ–ๅŽไป้ข‘็น้‡ๅˆฐ้…้ขไธญๆ–ญ๏ผŒๅ†ๅ‡็บง Proใ€‚', urgency: 'MEDIUM', optimization_steps: [ '1. ๅฐ† notion-poll ๅ’Œ notion-wake-listener ไปŽ */15 ้™ไธบๆฏๅฐๆ—ถ่ฝฎ่ฏข โ†’ ็œ 144 ๆฌก/ๅคฉ', '2. ๅˆๅนถ้‡ๅคๅŠŸ่ƒฝๅทฅไฝœๆต๏ผˆๅคšไธชbridgeๅˆๅนถ๏ผ‰โ†’ ็œ็บฆ 10 ๆฌก/ๅคฉ', '3. ้™ไฝŽ persona-thinking-window ไปŽ6ไธชcronๅˆฐ2ไธช โ†’ ็œ 4 ๆฌก/ๅคฉ', '4. ้“ธๆธŠๅผ€ๅ‘ไปปๅŠก้›†ไธญๅœจไธ€ไธชไผš่ฏๅ†…ๅฎŒๆˆ๏ผŒ้ฟๅ…้ข‘็นๅฏๅŠจๆ–ฐ Agent ไผš่ฏ', '5. ่ฏ„ไผฐ1ไธชๆœˆๅŽๅฆ‚ไปไธๅคŸ โ†’ ๅ‡็บง Pro ($390/ๅนด)', ], estimated_savings: 'ไผ˜ๅŒ–ๅŽๆฏๆ—ฅๅฏ่Š‚็œ็บฆ 160 ๆฌก่งฆๅ‘ + ๅ‡ๅฐ‘ Copilot ่ฐƒ็”จ', }; } return assessment; } // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• // ไธป็จ‹ๅบ // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• function main() { const args = process.argv.slice(2); const jsonMode = args.includes('--json'); const optimizeMode = args.includes('--optimize'); // Scan all workflows const workflows = scanWorkflows(); // Analyze quota const quotaAnalysis = analyzeQuota(workflows); // Generate optimizations const optimizations = generateOptimizations(workflows); // Evaluate upgrade const upgradeAssessment = evaluateUpgrade(quotaAnalysis); // Build full report const report = { report_id: `QUOTA-GOV-${new Date().toISOString().slice(0, 10).replace(/-/g, '')}`, generated_at: new Date().toISOString(), generated_by: '้“ธๆธŠ้…้ขๆฒป็†ๅผ•ๆ“Ž ยท quota-governance.js', copyright: 'ๅ›ฝไฝœ็™ปๅญ—-2026-A-00037559', workflows_scanned: workflows.length, quota: quotaAnalysis, optimizations, upgrade_assessment: upgradeAssessment, workflow_details: workflows.map(w => ({ file: w.file, name: w.name, daily_cron_runs: w.dailyCronRuns, daily_minutes: Math.round(w.dailyMinutes * 10) / 10, monthly_minutes: Math.round(w.monthlyMinutes), triggers: w.triggers, })), }; // Save report fs.writeFileSync(REPORT_PATH, JSON.stringify(report, null, 2), 'utf8'); if (jsonMode) { console.log(JSON.stringify(report, null, 2)); return; } // Pretty print console.log(''); console.log('โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•'); console.log(' ๐Ÿ”ฎ ้“ธๆธŠ้…้ขๆฒป็†ๆŠฅๅ‘Š ยท Quota Governance Report'); console.log('โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•'); console.log(` ๐Ÿ“… ${report.generated_at}`); console.log(` ๐Ÿ“Š ๆ‰ซๆๅทฅไฝœๆต: ${report.workflows_scanned} ไธช`); console.log(''); // Quota summary const q = quotaAnalysis; console.log('โ”€โ”€โ”€ ้…้ขๆถˆ่€—ๅˆ†ๆž โ”€โ”€โ”€'); console.log(` ๅฝ“ๅ‰ๅฅ—้ค: ${q.current_plan}`); console.log(` ๆœˆๅบฆ Actions ้ขๅบฆ: ${q.actions_budget_monthly} ๅˆ†้’Ÿ`); console.log(''); console.log(` ๅฎšๆ—ถ่งฆๅ‘ (cron):`); console.log(` ๆฏๆ—ฅ่ฟ่กŒ: ${q.analysis.cron_scheduled.daily_runs} ๆฌก`); console.log(` ๆฏๆ—ฅๆถˆ่€—: ${q.analysis.cron_scheduled.daily_minutes} ๅˆ†้’Ÿ`); console.log(` ๆœˆๅบฆๆถˆ่€—: ${q.analysis.cron_scheduled.monthly_minutes} ๅˆ†้’Ÿ`); console.log(''); console.log(` ๆŽจ้€่งฆๅ‘ (push):`); console.log(` ๅธฆpush่งฆๅ‘็š„ๅทฅไฝœๆต: ${q.analysis.push_triggered.workflows_with_push} ไธช`); console.log(` ้ข„ไผฐๆฏๆ—ฅ่ฟ่กŒ: ${q.analysis.push_triggered.estimated_daily_runs} ๆฌก`); console.log(` ้ข„ไผฐๆฏๆ—ฅๆถˆ่€—: ${q.analysis.push_triggered.estimated_daily_minutes} ๅˆ†้’Ÿ`); console.log(''); console.log(` Copilot Agent ไผš่ฏ:`); console.log(` ้ข„ไผฐๆฏๆ—ฅไผš่ฏ: ${q.analysis.copilot_agent.estimated_daily_sessions} ๆฌก`); console.log(` ๆฏๆฌก่€—ๆ—ถ: ${q.analysis.copilot_agent.estimated_minutes_per_session} ๅˆ†้’Ÿ`); console.log(` ๆฏๆ—ฅๆถˆ่€—: ${q.analysis.copilot_agent.estimated_daily_minutes} ๅˆ†้’Ÿ`); console.log(''); console.log(` ๐Ÿ“Š ๆ€ป่ฎก:`); console.log(` ๆฏๆ—ฅๆถˆ่€—: ${q.analysis.total.daily_minutes} ๅˆ†้’Ÿ`); console.log(` ๆœˆๅบฆๆถˆ่€—: ${q.analysis.total.monthly_minutes} ๅˆ†้’Ÿ`); console.log(` ๅˆฉ็”จ็އ: ${q.analysis.total.utilization_percent}%`); console.log(` ็Šถๆ€: ${q.analysis.total.status}`); console.log(''); // Top consumers console.log('โ”€โ”€โ”€ ๐Ÿ”ฅ ้…้ขๆถˆ่€— TOP5 โ”€โ”€โ”€'); for (const c of q.analysis.cron_scheduled.top_consumers) { console.log(` ${c.daily_runs.toString().padStart(5)} ๆฌก/ๅคฉ ${c.daily_minutes.toString().padStart(5)} ๅˆ†้’Ÿ/ๅคฉ ${c.file}`); } console.log(''); if (optimizeMode) { console.log('โ”€โ”€โ”€ โšก ไผ˜ๅŒ–ๅปบ่ฎฎ โ”€โ”€โ”€'); console.log(` ๅ…ฑ ${optimizations.count} ้กนไผ˜ๅŒ–๏ผŒๅฏ่Š‚็œ ${optimizations.total_saved_minutes_monthly} ๅˆ†้’Ÿ/ๆœˆ`); console.log(''); for (const o of optimizations.items) { console.log(` [${o.priority}] ${o.workflow}`); console.log(` ้—ฎ้ข˜: ${o.issue}`); console.log(` ๅปบ่ฎฎ: ${o.recommendation}`); if (o.saved_minutes_monthly) { console.log(` ่Š‚็œ: ${o.saved_minutes_monthly} ๅˆ†้’Ÿ/ๆœˆ`); } console.log(''); } } // Upgrade assessment console.log('โ”€โ”€โ”€ ๐Ÿ’ฐ ไผšๅ‘˜ๅ‡็บง่ฏ„ไผฐ โ”€โ”€โ”€'); const ua = upgradeAssessment; console.log(` ๅฝ“ๅ‰ๅฅ—้ค: ${ua.current_plan.name} (${ua.current_plan.yearly_cost}/ๅนด)`); console.log(` ็›ฎๆ ‡ๅฅ—้ค: ${ua.pro_plan.name} (${ua.pro_plan.yearly_cost}/ๅนด)`); console.log(` ๅทฎไปท: ${ua.cost_difference.yearly}`); console.log(''); for (const line of ua.analysis.explanation) { console.log(` ${line}`); } console.log(''); console.log(` ๐Ÿ“‹ ๅ†ณ็ญ–: ${ua.recommendation.decision}`); console.log(` ๐Ÿ“ ๅŽŸๅ› : ${ua.recommendation.reason}`); if (ua.recommendation.optimization_steps) { console.log(''); console.log(' ไผ˜ๅŒ–ๆญฅ้ชค:'); for (const step of ua.recommendation.optimization_steps) { console.log(` ${step}`); } } console.log(''); console.log(` ๐Ÿ“„ ๅฎŒๆ•ดๆŠฅๅ‘Šๅทฒไฟๅญ˜: ${REPORT_PATH}`); console.log('โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•'); } module.exports = { scanWorkflows, analyzeQuota, generateOptimizations, evaluateUpgrade, parseCronRunsPerDay }; if (require.main === module) { main(); }