thumbgate 1.4.3 → 1.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.well-known/llms.txt +12 -8
- package/.well-known/mcp/server-card.json +1 -1
- package/README.md +18 -8
- package/adapters/README.md +1 -1
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/codex/config.toml +2 -2
- package/adapters/mcp/server-stdio.js +1 -1
- package/adapters/opencode/opencode.json +1 -1
- package/config/github-about.json +2 -2
- package/package.json +158 -10
- package/scripts/billing.js +5 -2
- package/scripts/statusline.sh +1 -0
- package/src/api/server.js +113 -16
- package/src/index.js +3 -0
- package/.claude-plugin/bundle/icon.png +0 -0
- package/.claude-plugin/bundle/icon.svg +0 -18
- package/.claude-plugin/bundle/server/index.js +0 -24
- package/adapters/chatgpt/INSTALL.md +0 -158
- package/adapters/perplexity/.mcp.json +0 -36
- package/adapters/perplexity/config.toml +0 -16
- package/adapters/perplexity/opencode.json +0 -29
- package/bin/memory.sh +0 -64
- package/bin/obsidian-sync.sh +0 -20
- package/plugins/amp-skill/INSTALL.md +0 -52
- package/plugins/amp-skill/SKILL.md +0 -64
- package/plugins/claude-codex-bridge/.claude-plugin/plugin.json +0 -22
- package/plugins/claude-codex-bridge/.mcp.json +0 -14
- package/plugins/claude-codex-bridge/INSTALL.md +0 -43
- package/plugins/claude-codex-bridge/README.md +0 -46
- package/plugins/claude-codex-bridge/scripts/codex-bridge.js +0 -286
- package/plugins/claude-codex-bridge/skills/adversarial-review/SKILL.md +0 -24
- package/plugins/claude-codex-bridge/skills/result/SKILL.md +0 -22
- package/plugins/claude-codex-bridge/skills/review/SKILL.md +0 -28
- package/plugins/claude-codex-bridge/skills/second-pass/SKILL.md +0 -27
- package/plugins/claude-codex-bridge/skills/setup/SKILL.md +0 -21
- package/plugins/claude-codex-bridge/skills/status/SKILL.md +0 -19
- package/plugins/claude-skill/INSTALL.md +0 -55
- package/plugins/claude-skill/SKILL.md +0 -46
- package/plugins/codex-profile/.codex-plugin/plugin.json +0 -43
- package/plugins/codex-profile/.mcp.json +0 -14
- package/plugins/codex-profile/AGENTS.md +0 -20
- package/plugins/codex-profile/INSTALL.md +0 -89
- package/plugins/codex-profile/README.md +0 -61
- package/plugins/cursor-marketplace/.cursor-plugin/plugin.json +0 -23
- package/plugins/cursor-marketplace/CHANGELOG.md +0 -30
- package/plugins/cursor-marketplace/LICENSE +0 -21
- package/plugins/cursor-marketplace/README.md +0 -124
- package/plugins/cursor-marketplace/agents/reliability-reviewer.md +0 -31
- package/plugins/cursor-marketplace/assets/logo-400x400.png +0 -0
- package/plugins/cursor-marketplace/commands/capture-feedback.md +0 -33
- package/plugins/cursor-marketplace/commands/check-gates.md +0 -25
- package/plugins/cursor-marketplace/commands/show-lessons.md +0 -27
- package/plugins/cursor-marketplace/hooks/hooks.json +0 -10
- package/plugins/cursor-marketplace/mcp.json +0 -14
- package/plugins/cursor-marketplace/rules/feedback-capture.mdc +0 -34
- package/plugins/cursor-marketplace/rules/pre-action-gates.mdc +0 -30
- package/plugins/cursor-marketplace/rules/session-continuity.mdc +0 -28
- package/plugins/cursor-marketplace/scripts/gate-check.sh +0 -21
- package/plugins/cursor-marketplace/skills/capture-feedback/SKILL.md +0 -48
- package/plugins/cursor-marketplace/skills/prevention-rules/SKILL.md +0 -31
- package/plugins/cursor-marketplace/skills/recall-context/SKILL.md +0 -30
- package/plugins/cursor-marketplace/skills/search-lessons/SKILL.md +0 -33
- package/plugins/gemini-extension/INSTALL.md +0 -92
- package/plugins/gemini-extension/gemini_prompt.txt +0 -14
- package/plugins/gemini-extension/tool_contract.json +0 -45
- package/plugins/opencode-profile/INSTALL.md +0 -57
- package/public/assets/instagram-card.png +0 -0
- package/public/assets/tiktok-agent-memory.mp4 +0 -0
- package/public/blog.html +0 -474
- package/public/compare/mem0.html +0 -189
- package/public/compare/speclock.html +0 -180
- package/public/compare.html +0 -310
- package/public/dashboard.html +0 -1100
- package/public/guide.html +0 -317
- package/public/guides/claude-code-prevent-repeated-mistakes.html +0 -161
- package/public/guides/codex-cli-guardrails.html +0 -158
- package/public/guides/cursor-prevent-repeated-mistakes.html +0 -161
- package/public/guides/pre-action-gates.html +0 -162
- package/public/guides/stop-repeated-ai-agent-mistakes.html +0 -159
- package/public/index.html +0 -1225
- package/public/js/buyer-intent.js +0 -252
- package/public/learn/agent-harness-pattern.html +0 -180
- package/public/learn/ai-agent-persistent-memory.html +0 -203
- package/public/learn/learn.css +0 -45
- package/public/learn/mcp-pre-action-gates-explained.html +0 -172
- package/public/learn/stop-ai-agent-force-push.html +0 -134
- package/public/learn/vibe-coding-safety-net.html +0 -142
- package/public/learn.html +0 -274
- package/public/lessons.html +0 -967
- package/public/llm-context.md +0 -156
- package/public/pro.html +0 -1087
- package/public/vercel.json +0 -8
- package/scripts/a2ui-engine.js +0 -73
- package/scripts/adk-consolidator.js +0 -274
- package/scripts/agent-security-hardening.js +0 -225
- package/scripts/ai-search-visibility.js +0 -116
- package/scripts/autonomous-sales-agent.js +0 -39
- package/scripts/autoresearch-runner.js +0 -216
- package/scripts/background-agent-governance.js +0 -229
- package/scripts/behavioral-extraction.js +0 -93
- package/scripts/budget-enforcer.js +0 -173
- package/scripts/budget-guard.js +0 -173
- package/scripts/build-claude-mcpb.js +0 -255
- package/scripts/build-codex-plugin.js +0 -152
- package/scripts/capture-railway-diagnostics.sh +0 -97
- package/scripts/changeset-check.js +0 -372
- package/scripts/check-congruence.js +0 -443
- package/scripts/computer-use-firewall.js +0 -280
- package/scripts/content-engine/linkedin-content-generator.js +0 -154
- package/scripts/content-engine/output/linkedin-memento-validation.md +0 -17
- package/scripts/content-engine/output/linkedin-posts-2026-04-09.md +0 -175
- package/scripts/content-engine/reddit-thread-finder.js +0 -154
- package/scripts/context-engine.js +0 -710
- package/scripts/daily-digest.js +0 -11
- package/scripts/data-governance.js +0 -173
- package/scripts/deploy-gcp.sh +0 -44
- package/scripts/deploy-policy.js +0 -249
- package/scripts/disagreement-mining.js +0 -315
- package/scripts/dpo-optimizer.js +0 -206
- package/scripts/ensure-repo-bootstrap.js +0 -130
- package/scripts/ephemeral-agent-store.js +0 -212
- package/scripts/eval-harness.js +0 -56
- package/scripts/export-kto-pairs.js +0 -309
- package/scripts/export-training.js +0 -446
- package/scripts/feedback-fallback.js +0 -111
- package/scripts/feedback-inbox-read.js +0 -162
- package/scripts/feedback-root-consolidator.js +0 -233
- package/scripts/feedback-to-memory.js +0 -185
- package/scripts/gate-satisfy.js +0 -42
- package/scripts/generate-paperbanana-diagrams.sh +0 -99
- package/scripts/generate-pretool-hook.sh +0 -40
- package/scripts/github-about.js +0 -430
- package/scripts/github-outreach.js +0 -65
- package/scripts/gtm-revenue-loop.js +0 -535
- package/scripts/hallucination-detector.js +0 -226
- package/scripts/hf-papers.js +0 -317
- package/scripts/hook-auto-capture.sh +0 -100
- package/scripts/hook-stop-pr-thread-check.sh +0 -68
- package/scripts/hook-stop-self-score.sh +0 -51
- package/scripts/hook-stop-verify-deploy.sh +0 -31
- package/scripts/hook-verify-before-done.sh +0 -20
- package/scripts/managed-dpo-export.js +0 -91
- package/scripts/markdown-escape.js +0 -12
- package/scripts/marketing-experiment.js +0 -657
- package/scripts/memalign-recall.js +0 -111
- package/scripts/memory-migration.js +0 -296
- package/scripts/meta-policy.js +0 -190
- package/scripts/metered-billing.js +0 -16
- package/scripts/model-tier-router.js +0 -310
- package/scripts/money-watcher.js +0 -218
- package/scripts/multi-hop-recall.js +0 -240
- package/scripts/per-step-scoring.js +0 -163
- package/scripts/perplexity-command-center.js +0 -644
- package/scripts/perplexity-marketing.js +0 -454
- package/scripts/pii-scanner.js +0 -153
- package/scripts/plan-gate.js +0 -154
- package/scripts/post-everywhere.js +0 -341
- package/scripts/post-to-x-retry.sh +0 -22
- package/scripts/post-to-x.js +0 -369
- package/scripts/pr-manager.js +0 -421
- package/scripts/principle-extractor.js +0 -162
- package/scripts/pro-features.js +0 -41
- package/scripts/prompt-dlp.js +0 -222
- package/scripts/prove-adapters.js +0 -860
- package/scripts/prove-attribution.js +0 -361
- package/scripts/prove-automation.js +0 -651
- package/scripts/prove-autoresearch.js +0 -304
- package/scripts/prove-claim-verification.js +0 -277
- package/scripts/prove-cloudflare-sandbox.js +0 -161
- package/scripts/prove-data-pipeline.js +0 -408
- package/scripts/prove-data-quality.js +0 -227
- package/scripts/prove-evolution.js +0 -352
- package/scripts/prove-harnesses.js +0 -287
- package/scripts/prove-intelligence.js +0 -257
- package/scripts/prove-lancedb.js +0 -425
- package/scripts/prove-local-intelligence.js +0 -340
- package/scripts/prove-loop-closure.js +0 -263
- package/scripts/prove-packaged-runtime.js +0 -327
- package/scripts/prove-predictive-insights.js +0 -355
- package/scripts/prove-runtime.js +0 -363
- package/scripts/prove-seo-gsd.js +0 -234
- package/scripts/prove-settings.js +0 -279
- package/scripts/prove-subway-upgrades.js +0 -277
- package/scripts/prove-tessl.js +0 -229
- package/scripts/prove-training-export.js +0 -325
- package/scripts/prove-workflow-contract.js +0 -112
- package/scripts/prove-xmemory.js +0 -332
- package/scripts/publish-decision.js +0 -159
- package/scripts/ralph-loop.js +0 -376
- package/scripts/ralph-mode-ci.js +0 -434
- package/scripts/reddit-dm-outreach.js +0 -192
- package/scripts/reddit-monitor-cron.sh +0 -26
- package/scripts/reminder-engine.js +0 -132
- package/scripts/revenue-status.js +0 -472
- package/scripts/rotate-stripe-webhook-secret.js +0 -314
- package/scripts/schedule-manager.js +0 -249
- package/scripts/self-healing-check.js +0 -193
- package/scripts/session-analyzer.js +0 -533
- package/scripts/shieldcortex-memory-firewall-runner.mjs +0 -53
- package/scripts/skill-exporter.js +0 -260
- package/scripts/skill-materializer.js +0 -134
- package/scripts/skill-packs.js +0 -136
- package/scripts/skill-proposer.js +0 -99
- package/scripts/skill-quality-tracker.js +0 -282
- package/scripts/slow-loop.js +0 -72
- package/scripts/social-analytics/db/marketing-db.js +0 -179
- package/scripts/social-analytics/db/schema.sql +0 -55
- package/scripts/social-analytics/digest.js +0 -256
- package/scripts/social-analytics/engagement-audit.js +0 -185
- package/scripts/social-analytics/generate-instagram-card.js +0 -123
- package/scripts/social-analytics/generate-slides.js +0 -268
- package/scripts/social-analytics/instagram-thumbgate-post.js +0 -111
- package/scripts/social-analytics/install-growth-automation.js +0 -114
- package/scripts/social-analytics/load-env.js +0 -77
- package/scripts/social-analytics/mcp-server.js +0 -289
- package/scripts/social-analytics/normalizer.js +0 -580
- package/scripts/social-analytics/notify.js +0 -162
- package/scripts/social-analytics/poll-all.js +0 -107
- package/scripts/social-analytics/pollers/github.js +0 -195
- package/scripts/social-analytics/pollers/instagram.js +0 -253
- package/scripts/social-analytics/pollers/linkedin.js +0 -340
- package/scripts/social-analytics/pollers/plausible.js +0 -245
- package/scripts/social-analytics/pollers/reddit.js +0 -306
- package/scripts/social-analytics/pollers/threads.js +0 -233
- package/scripts/social-analytics/pollers/tiktok.js +0 -203
- package/scripts/social-analytics/pollers/x.js +0 -227
- package/scripts/social-analytics/pollers/youtube.js +0 -304
- package/scripts/social-analytics/pollers/zernio.js +0 -183
- package/scripts/social-analytics/post-video.js +0 -316
- package/scripts/social-analytics/publish-instagram-thumbgate.js +0 -104
- package/scripts/social-analytics/publish-thumbgate-launch.js +0 -322
- package/scripts/social-analytics/publishers/devto.js +0 -122
- package/scripts/social-analytics/publishers/instagram.js +0 -317
- package/scripts/social-analytics/publishers/linkedin.js +0 -294
- package/scripts/social-analytics/publishers/reddit.js +0 -385
- package/scripts/social-analytics/publishers/threads.js +0 -275
- package/scripts/social-analytics/publishers/tiktok.js +0 -217
- package/scripts/social-analytics/publishers/x.js +0 -259
- package/scripts/social-analytics/publishers/youtube.js +0 -223
- package/scripts/social-analytics/publishers/zernio.js +0 -568
- package/scripts/social-analytics/reconcile-thumbgate-campaign.js +0 -165
- package/scripts/social-analytics/run-digest.js +0 -34
- package/scripts/social-analytics/schedule-thumbgate-campaign.js +0 -275
- package/scripts/social-analytics/store.js +0 -455
- package/scripts/social-analytics/sync-launch-assets.js +0 -185
- package/scripts/social-analytics/utm.js +0 -143
- package/scripts/social-pipeline.js +0 -2626
- package/scripts/social-post-hourly.js +0 -228
- package/scripts/social-quality-gate.js +0 -134
- package/scripts/social-reply-monitor.js +0 -592
- package/scripts/status-dashboard.js +0 -155
- package/scripts/stripe-live-status.js +0 -115
- package/scripts/subagent-profiles.js +0 -79
- package/scripts/sync-branch-protection.js +0 -340
- package/scripts/sync-gh-secrets-from-env.sh +0 -70
- package/scripts/sync-github-about.js +0 -55
- package/scripts/sync-version.js +0 -479
- package/scripts/synthetic-dpo.js +0 -234
- package/scripts/tessl-export.js +0 -369
- package/scripts/test-coverage.js +0 -128
- package/scripts/thumbgate-bench.js +0 -494
- package/scripts/thumbgate_session_start.sh +0 -32
- package/scripts/train_from_feedback.py +0 -929
- package/scripts/validate-feedback.js +0 -581
- package/scripts/verify-obsidian-setup.sh +0 -269
- package/scripts/verify-run.js +0 -269
- package/scripts/weekly-auto-post.js +0 -124
- package/scripts/x-autonomous-marketing.js +0 -139
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Status Dashboard
|
|
4
|
-
*
|
|
5
|
-
* CLI dashboard that reads feedback data and outputs a learning curve summary.
|
|
6
|
-
*
|
|
7
|
-
* Usage:
|
|
8
|
-
* node scripts/status-dashboard.js
|
|
9
|
-
*
|
|
10
|
-
* Exports:
|
|
11
|
-
* generateStatus(feedbackDir) — returns status object with approval rates,
|
|
12
|
-
* trends, failure domains, memory count, prevention rule count, and learning curve.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
const fs = require('fs');
|
|
16
|
-
const path = require('path');
|
|
17
|
-
const { getFeedbackPaths, readJSONL } = require('./feedback-loop');
|
|
18
|
-
|
|
19
|
-
function generateStatus(feedbackDir) {
|
|
20
|
-
const feedbackLogPath = path.join(feedbackDir, 'feedback-log.jsonl');
|
|
21
|
-
const memoryLogPath = path.join(feedbackDir, 'memory-log.jsonl');
|
|
22
|
-
const preventionRulesPath = path.join(feedbackDir, 'prevention-rules.md');
|
|
23
|
-
|
|
24
|
-
const entries = readJSONL(feedbackLogPath);
|
|
25
|
-
const totalSignals = entries.length;
|
|
26
|
-
const positive = entries.filter((e) => e.signal === 'positive').length;
|
|
27
|
-
const negative = entries.filter((e) => e.signal === 'negative').length;
|
|
28
|
-
const approvalRate = totalSignals > 0 ? Math.round((positive / totalSignals) * 100) : 0;
|
|
29
|
-
|
|
30
|
-
// Recent approval rate (last 20)
|
|
31
|
-
const recentWindow = 20;
|
|
32
|
-
const recent = entries.slice(-recentWindow);
|
|
33
|
-
const recentPositive = recent.filter((e) => e.signal === 'positive').length;
|
|
34
|
-
const recentApprovalRate = recent.length > 0 ? Math.round((recentPositive / recent.length) * 100) : 0;
|
|
35
|
-
|
|
36
|
-
// Trend
|
|
37
|
-
let trend = 'stable';
|
|
38
|
-
if (totalSignals >= recentWindow) {
|
|
39
|
-
const diff = recentApprovalRate - approvalRate;
|
|
40
|
-
if (diff > 5) trend = 'improving';
|
|
41
|
-
else if (diff < -5) trend = 'declining';
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Top failure domains
|
|
45
|
-
const domainCounts = {};
|
|
46
|
-
entries
|
|
47
|
-
.filter((e) => e.signal === 'negative')
|
|
48
|
-
.forEach((e) => {
|
|
49
|
-
const domain = (e.richContext && e.richContext.domain) || inferDomainFromTags(e.tags);
|
|
50
|
-
domainCounts[domain] = (domainCounts[domain] || 0) + 1;
|
|
51
|
-
});
|
|
52
|
-
const topFailureDomains = Object.entries(domainCounts)
|
|
53
|
-
.map(([domain, count]) => ({ domain, count }))
|
|
54
|
-
.sort((a, b) => b.count - a.count);
|
|
55
|
-
|
|
56
|
-
// Memory count
|
|
57
|
-
const memoryEntries = readJSONL(memoryLogPath);
|
|
58
|
-
const memoryCount = memoryEntries.length;
|
|
59
|
-
|
|
60
|
-
// Prevention rule count
|
|
61
|
-
let preventionRuleCount = 0;
|
|
62
|
-
if (fs.existsSync(preventionRulesPath)) {
|
|
63
|
-
const rulesContent = fs.readFileSync(preventionRulesPath, 'utf-8');
|
|
64
|
-
const ruleHeaders = rulesContent.match(/^## /gm);
|
|
65
|
-
preventionRuleCount = ruleHeaders ? ruleHeaders.length : 0;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Learning curve — sliding windows of 10
|
|
69
|
-
const learningCurve = [];
|
|
70
|
-
const windowSize = 10;
|
|
71
|
-
for (let i = 0; i + windowSize <= entries.length; i++) {
|
|
72
|
-
const window = entries.slice(i, i + windowSize);
|
|
73
|
-
const windowPositive = window.filter((e) => e.signal === 'positive').length;
|
|
74
|
-
const windowRate = Math.round((windowPositive / windowSize) * 100);
|
|
75
|
-
learningCurve.push({ window: i, approvalRate: windowRate });
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return {
|
|
79
|
-
totalSignals,
|
|
80
|
-
positive,
|
|
81
|
-
negative,
|
|
82
|
-
approvalRate,
|
|
83
|
-
recentApprovalRate,
|
|
84
|
-
trend,
|
|
85
|
-
topFailureDomains,
|
|
86
|
-
memoryCount,
|
|
87
|
-
preventionRuleCount,
|
|
88
|
-
learningCurve,
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function inferDomainFromTags(tags) {
|
|
93
|
-
if (!Array.isArray(tags) || tags.length === 0) return 'general';
|
|
94
|
-
const tagSet = new Set(tags.map((t) => t.toLowerCase()));
|
|
95
|
-
if (tagSet.has('testing') || tagSet.has('test')) return 'testing';
|
|
96
|
-
if (tagSet.has('security')) return 'security';
|
|
97
|
-
if (tagSet.has('performance') || tagSet.has('perf')) return 'performance';
|
|
98
|
-
if (tagSet.has('ui') || tagSet.has('component')) return 'ui-components';
|
|
99
|
-
if (tagSet.has('api') || tagSet.has('endpoint')) return 'api-integration';
|
|
100
|
-
if (tagSet.has('git') || tagSet.has('commit')) return 'git-workflow';
|
|
101
|
-
if (tagSet.has('doc') || tagSet.has('readme')) return 'documentation';
|
|
102
|
-
if (tagSet.has('debug') || tagSet.has('debugging')) return 'debugging';
|
|
103
|
-
if (tagSet.has('arch') || tagSet.has('architecture')) return 'architecture';
|
|
104
|
-
if (tagSet.has('data') || tagSet.has('schema')) return 'data-modeling';
|
|
105
|
-
return 'general';
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function printDashboard(status) {
|
|
109
|
-
console.log('╔══════════════════════════════════════════╗');
|
|
110
|
-
console.log('║ Feedback Tracking Dashboard ║');
|
|
111
|
-
console.log('╠══════════════════════════════════════════╣');
|
|
112
|
-
console.log(`║ Total Signals: ${String(status.totalSignals).padStart(6)} ║`);
|
|
113
|
-
console.log(`║ Positive: ${String(status.positive).padStart(6)} ║`);
|
|
114
|
-
console.log(`║ Negative: ${String(status.negative).padStart(6)} ║`);
|
|
115
|
-
console.log(`║ Approval Rate: ${String(status.approvalRate + '%').padStart(6)} ║`);
|
|
116
|
-
console.log(`║ Recent (last 20): ${String(status.recentApprovalRate + '%').padStart(6)} ║`);
|
|
117
|
-
console.log(`║ Trend: ${status.trend.padStart(6)} ║`);
|
|
118
|
-
console.log(`║ Memories: ${String(status.memoryCount).padStart(6)} ║`);
|
|
119
|
-
console.log(`║ Prevention Rules: ${String(status.preventionRuleCount).padStart(6)} ║`);
|
|
120
|
-
console.log('╠══════════════════════════════════════════╣');
|
|
121
|
-
|
|
122
|
-
if (status.topFailureDomains.length > 0) {
|
|
123
|
-
console.log('║ Top Failure Domains: ║');
|
|
124
|
-
status.topFailureDomains.slice(0, 5).forEach((d) => {
|
|
125
|
-
const line = ` ${d.domain}: ${d.count}`;
|
|
126
|
-
console.log(`║ ${line.padEnd(38)}║`);
|
|
127
|
-
});
|
|
128
|
-
} else {
|
|
129
|
-
console.log('║ No failure domains recorded ║');
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
console.log('╠══════════════════════════════════════════╣');
|
|
133
|
-
console.log('║ Feedback Trend: ║');
|
|
134
|
-
if (status.learningCurve.length > 0) {
|
|
135
|
-
const step = Math.max(1, Math.floor(status.learningCurve.length / 5));
|
|
136
|
-
for (let i = 0; i < status.learningCurve.length; i += step) {
|
|
137
|
-
const point = status.learningCurve[i];
|
|
138
|
-
const bar = '█'.repeat(Math.floor(point.approvalRate / 5));
|
|
139
|
-
const line = ` w${String(point.window).padStart(3)}: ${bar} ${point.approvalRate}%`;
|
|
140
|
-
console.log(`║ ${line.padEnd(38)}║`);
|
|
141
|
-
}
|
|
142
|
-
} else {
|
|
143
|
-
console.log('║ Not enough data for feedback trend ║');
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
console.log('╚══════════════════════════════════════════╝');
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
if (require.main === module) {
|
|
150
|
-
const { FEEDBACK_DIR } = getFeedbackPaths();
|
|
151
|
-
const status = generateStatus(FEEDBACK_DIR);
|
|
152
|
-
printDashboard(status);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
module.exports = { generateStatus, printDashboard };
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* stripe-live-status.js — Pull live financial data from Stripe API.
|
|
4
|
-
* Shows real revenue, not local ledger approximations.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
'use strict';
|
|
8
|
-
|
|
9
|
-
const Stripe = require('stripe');
|
|
10
|
-
|
|
11
|
-
const STRIPE_SECRET_KEY = process.env.STRIPE_SECRET_KEY;
|
|
12
|
-
|
|
13
|
-
if (!STRIPE_SECRET_KEY) {
|
|
14
|
-
console.error('STRIPE_SECRET_KEY not set');
|
|
15
|
-
process.exit(1);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const stripe = new Stripe(STRIPE_SECRET_KEY);
|
|
19
|
-
|
|
20
|
-
async function getLiveStatus() {
|
|
21
|
-
const [balance, charges, subscriptions, products, prices, sessions] = await Promise.all([
|
|
22
|
-
stripe.balance.retrieve(),
|
|
23
|
-
stripe.charges.list({ limit: 100 }),
|
|
24
|
-
stripe.subscriptions.list({ limit: 100, status: 'all' }),
|
|
25
|
-
stripe.products.list({ limit: 20, active: true }),
|
|
26
|
-
stripe.prices.list({ limit: 20, active: true }),
|
|
27
|
-
stripe.checkout.sessions.list({ limit: 50 }),
|
|
28
|
-
]);
|
|
29
|
-
|
|
30
|
-
const availableBalance = balance.available.reduce((sum, b) => sum + b.amount, 0);
|
|
31
|
-
const pendingBalance = balance.pending.reduce((sum, b) => sum + b.amount, 0);
|
|
32
|
-
|
|
33
|
-
const paidCharges = charges.data.filter(c => c.paid && !c.refunded);
|
|
34
|
-
const refundedCharges = charges.data.filter(c => c.refunded);
|
|
35
|
-
const failedCharges = charges.data.filter(c => c.status === 'failed');
|
|
36
|
-
|
|
37
|
-
const grossRevenue = paidCharges.reduce((sum, c) => sum + c.amount, 0);
|
|
38
|
-
const refundedAmount = refundedCharges.reduce((sum, c) => sum + c.amount_refunded, 0);
|
|
39
|
-
|
|
40
|
-
const activeSubs = subscriptions.data.filter(s => s.status === 'active');
|
|
41
|
-
const cancelledSubs = subscriptions.data.filter(s => s.status === 'canceled');
|
|
42
|
-
|
|
43
|
-
const completedSessions = sessions.data.filter(s => s.payment_status === 'paid');
|
|
44
|
-
const expiredSessions = sessions.data.filter(s => s.status === 'expired');
|
|
45
|
-
|
|
46
|
-
const todayStart = new Date();
|
|
47
|
-
todayStart.setHours(0, 0, 0, 0);
|
|
48
|
-
const todayCharges = paidCharges.filter(c => c.created * 1000 >= todayStart.getTime());
|
|
49
|
-
const todayRevenue = todayCharges.reduce((sum, c) => sum + c.amount, 0);
|
|
50
|
-
|
|
51
|
-
const report = {
|
|
52
|
-
generatedAt: new Date().toISOString(),
|
|
53
|
-
source: 'stripe_live_api',
|
|
54
|
-
balance: {
|
|
55
|
-
available: availableBalance / 100,
|
|
56
|
-
pending: pendingBalance / 100,
|
|
57
|
-
currency: 'USD',
|
|
58
|
-
},
|
|
59
|
-
revenue: {
|
|
60
|
-
grossLifetime: grossRevenue / 100,
|
|
61
|
-
refundedLifetime: refundedAmount / 100,
|
|
62
|
-
netLifetime: (grossRevenue - refundedAmount) / 100,
|
|
63
|
-
today: todayRevenue / 100,
|
|
64
|
-
todayChargeCount: todayCharges.length,
|
|
65
|
-
},
|
|
66
|
-
charges: {
|
|
67
|
-
total: charges.data.length,
|
|
68
|
-
paid: paidCharges.length,
|
|
69
|
-
refunded: refundedCharges.length,
|
|
70
|
-
failed: failedCharges.length,
|
|
71
|
-
},
|
|
72
|
-
subscriptions: {
|
|
73
|
-
active: activeSubs.length,
|
|
74
|
-
cancelled: cancelledSubs.length,
|
|
75
|
-
total: subscriptions.data.length,
|
|
76
|
-
mrr: activeSubs.reduce((sum, s) => sum + (s.plan?.amount || 0), 0) / 100,
|
|
77
|
-
},
|
|
78
|
-
checkout: {
|
|
79
|
-
completed: completedSessions.length,
|
|
80
|
-
expired: expiredSessions.length,
|
|
81
|
-
total: sessions.data.length,
|
|
82
|
-
conversionRate: sessions.data.length > 0
|
|
83
|
-
? (completedSessions.length / sessions.data.length * 100).toFixed(1) + '%'
|
|
84
|
-
: '0%',
|
|
85
|
-
},
|
|
86
|
-
products: products.data.map(p => ({
|
|
87
|
-
id: p.id,
|
|
88
|
-
name: p.name,
|
|
89
|
-
defaultPrice: p.default_price,
|
|
90
|
-
})),
|
|
91
|
-
activePrices: prices.data.map(p => ({
|
|
92
|
-
id: p.id,
|
|
93
|
-
amount: p.unit_amount / 100,
|
|
94
|
-
type: p.type,
|
|
95
|
-
interval: p.recurring?.interval || 'one_time',
|
|
96
|
-
product: p.product,
|
|
97
|
-
})),
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
return report;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
async function main() {
|
|
104
|
-
const report = await getLiveStatus();
|
|
105
|
-
console.log(JSON.stringify(report, null, 2));
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (require.main === module) {
|
|
109
|
-
main().catch(err => {
|
|
110
|
-
console.error('Stripe live status failed:', err.message);
|
|
111
|
-
process.exit(1);
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
module.exports = { getLiveStatus };
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
const fs = require('fs');
|
|
3
|
-
const path = require('path');
|
|
4
|
-
const { loadMcpPolicy } = require('./mcp-policy');
|
|
5
|
-
|
|
6
|
-
const PROJECT_ROOT = path.join(__dirname, '..');
|
|
7
|
-
const DEFAULT_SUBAGENT_PROFILE_PATH = path.join(PROJECT_ROOT, 'config', 'subagent-profiles.json');
|
|
8
|
-
|
|
9
|
-
function getSubagentProfilePath() {
|
|
10
|
-
return process.env.THUMBGATE_SUBAGENT_PROFILE_PATH || DEFAULT_SUBAGENT_PROFILE_PATH;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function loadSubagentProfiles() {
|
|
14
|
-
const raw = fs.readFileSync(getSubagentProfilePath(), 'utf-8');
|
|
15
|
-
const parsed = JSON.parse(raw);
|
|
16
|
-
if (!parsed.profiles || typeof parsed.profiles !== 'object') {
|
|
17
|
-
throw new Error('Invalid subagent profile config: missing profiles object');
|
|
18
|
-
}
|
|
19
|
-
return parsed;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function listSubagentProfiles() {
|
|
23
|
-
const parsed = loadSubagentProfiles();
|
|
24
|
-
return Object.keys(parsed.profiles);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function getSubagentProfile(name) {
|
|
28
|
-
const parsed = loadSubagentProfiles();
|
|
29
|
-
const profile = parsed.profiles[name];
|
|
30
|
-
if (!profile) {
|
|
31
|
-
throw new Error(`Unknown subagent profile: ${name}`);
|
|
32
|
-
}
|
|
33
|
-
return profile;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function validateSubagentProfiles() {
|
|
37
|
-
const parsed = loadSubagentProfiles();
|
|
38
|
-
const policy = loadMcpPolicy();
|
|
39
|
-
const issues = [];
|
|
40
|
-
|
|
41
|
-
for (const [name, profile] of Object.entries(parsed.profiles)) {
|
|
42
|
-
if (!profile.mcpProfile) {
|
|
43
|
-
issues.push(`${name}: missing mcpProfile`);
|
|
44
|
-
} else if (!policy.profiles[profile.mcpProfile]) {
|
|
45
|
-
issues.push(`${name}: unknown mcpProfile '${profile.mcpProfile}'`);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (!profile.context || typeof profile.context !== 'object') {
|
|
49
|
-
issues.push(`${name}: missing context settings`);
|
|
50
|
-
} else {
|
|
51
|
-
if (!Number.isFinite(profile.context.maxItems) || profile.context.maxItems <= 0) {
|
|
52
|
-
issues.push(`${name}: invalid context.maxItems`);
|
|
53
|
-
}
|
|
54
|
-
if (!Number.isFinite(profile.context.maxChars) || profile.context.maxChars <= 0) {
|
|
55
|
-
issues.push(`${name}: invalid context.maxChars`);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return {
|
|
61
|
-
valid: issues.length === 0,
|
|
62
|
-
issues,
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
module.exports = {
|
|
67
|
-
DEFAULT_SUBAGENT_PROFILE_PATH,
|
|
68
|
-
getSubagentProfilePath,
|
|
69
|
-
loadSubagentProfiles,
|
|
70
|
-
listSubagentProfiles,
|
|
71
|
-
getSubagentProfile,
|
|
72
|
-
validateSubagentProfiles,
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
if (require.main === module) {
|
|
76
|
-
const result = validateSubagentProfiles();
|
|
77
|
-
console.log(JSON.stringify({ profiles: listSubagentProfiles(), ...result }, null, 2));
|
|
78
|
-
process.exit(result.valid ? 0 : 1);
|
|
79
|
-
}
|
|
@@ -1,340 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
const fs = require('node:fs');
|
|
5
|
-
const path = require('node:path');
|
|
6
|
-
const { spawnSync } = require('node:child_process');
|
|
7
|
-
const MERGE_QUALITY_CHECKS = require('../config/merge-quality-checks.json');
|
|
8
|
-
|
|
9
|
-
const DEFAULT_REPO = process.env.GITHUB_REPOSITORY || 'IgorGanapolsky/ThumbGate';
|
|
10
|
-
const DEFAULT_BRANCH = process.env.DEFAULT_BRANCH || 'main';
|
|
11
|
-
const FIXED_GH_BINARIES = [
|
|
12
|
-
'/usr/bin/gh',
|
|
13
|
-
'/usr/local/bin/gh',
|
|
14
|
-
'/opt/homebrew/bin/gh',
|
|
15
|
-
];
|
|
16
|
-
|
|
17
|
-
function assertSafeGhArgs(args) {
|
|
18
|
-
if (!Array.isArray(args) || args.length === 0) {
|
|
19
|
-
throw new Error('GH CLI args must be a non-empty array.');
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
return args.map((arg) => {
|
|
23
|
-
const normalized = String(arg ?? '');
|
|
24
|
-
if (!normalized || /\0/.test(normalized)) {
|
|
25
|
-
throw new Error(`Unsafe GH CLI arg: ${arg}`);
|
|
26
|
-
}
|
|
27
|
-
return normalized;
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function resolveGhBinary(options = {}) {
|
|
32
|
-
const accessSync = options.accessSync || fs.accessSync;
|
|
33
|
-
const candidates = [];
|
|
34
|
-
const configuredBinary = String(process.env.THUMBGATE_GH_BIN || '').trim();
|
|
35
|
-
|
|
36
|
-
if (configuredBinary) {
|
|
37
|
-
if (!path.isAbsolute(configuredBinary)) {
|
|
38
|
-
throw new Error(`Unsafe GH binary path: ${configuredBinary}`);
|
|
39
|
-
}
|
|
40
|
-
candidates.push(configuredBinary);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
candidates.push(...FIXED_GH_BINARIES);
|
|
44
|
-
|
|
45
|
-
for (const candidate of candidates) {
|
|
46
|
-
try {
|
|
47
|
-
accessSync(candidate, fs.constants.X_OK);
|
|
48
|
-
return candidate;
|
|
49
|
-
} catch {
|
|
50
|
-
continue;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
throw new Error(`Unable to locate GH CLI in fixed paths: ${candidates.join(', ')}`);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function runGh(args, options = {}) {
|
|
58
|
-
const env = { ...process.env };
|
|
59
|
-
if (!env.GITHUB_ACTIONS && env.GITHUB_TOKEN && !env.GH_TOKEN) {
|
|
60
|
-
delete env.GITHUB_TOKEN;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return spawnSync(resolveGhBinary(options), assertSafeGhArgs(args), {
|
|
64
|
-
encoding: 'utf8',
|
|
65
|
-
env,
|
|
66
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function formatGhError(result) {
|
|
71
|
-
return (result.stderr || result.stdout || 'Unknown GH CLI failure').trim();
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function parseArgs(argv = process.argv.slice(2)) {
|
|
75
|
-
const options = {
|
|
76
|
-
check: false,
|
|
77
|
-
json: false,
|
|
78
|
-
repo: DEFAULT_REPO,
|
|
79
|
-
branch: DEFAULT_BRANCH,
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
for (let index = 0; index < argv.length; index += 1) {
|
|
83
|
-
const arg = argv[index];
|
|
84
|
-
if (arg === '--check') {
|
|
85
|
-
options.check = true;
|
|
86
|
-
} else if (arg === '--json') {
|
|
87
|
-
options.json = true;
|
|
88
|
-
} else if (arg === '--repo' && argv[index + 1]) {
|
|
89
|
-
options.repo = argv[index + 1];
|
|
90
|
-
index += 1;
|
|
91
|
-
} else if (arg === '--branch' && argv[index + 1]) {
|
|
92
|
-
options.branch = argv[index + 1];
|
|
93
|
-
index += 1;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return options;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function assertSafeRepoSegment(value, label) {
|
|
101
|
-
const normalized = String(value || '').trim();
|
|
102
|
-
if (!/^[A-Za-z0-9_.-]+$/.test(normalized)) {
|
|
103
|
-
throw new Error(`Unsafe repository ${label}: ${value}`);
|
|
104
|
-
}
|
|
105
|
-
return normalized;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function splitRepo(repo) {
|
|
109
|
-
const [owner, name] = String(repo || '').trim().split('/');
|
|
110
|
-
if (!owner || !name) {
|
|
111
|
-
throw new Error(`Invalid repository "${repo}". Expected owner/name.`);
|
|
112
|
-
}
|
|
113
|
-
return {
|
|
114
|
-
owner: assertSafeRepoSegment(owner, 'owner'),
|
|
115
|
-
name: assertSafeRepoSegment(name, 'name'),
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function assertSafeBranchPattern(branch) {
|
|
120
|
-
const normalized = String(branch || '').trim();
|
|
121
|
-
if (!normalized) {
|
|
122
|
-
throw new Error('Branch pattern is required.');
|
|
123
|
-
}
|
|
124
|
-
if (normalized.startsWith('-') || normalized.includes('..') || normalized.includes('//') || normalized.includes('@{')) {
|
|
125
|
-
throw new Error(`Unsafe branch pattern: ${branch}`);
|
|
126
|
-
}
|
|
127
|
-
if (normalized.endsWith('.') || normalized.endsWith('/')) {
|
|
128
|
-
throw new Error(`Unsafe branch pattern: ${branch}`);
|
|
129
|
-
}
|
|
130
|
-
if (!/^[A-Za-z0-9._/-]+$/.test(normalized)) {
|
|
131
|
-
throw new Error(`Unsafe branch pattern: ${branch}`);
|
|
132
|
-
}
|
|
133
|
-
return normalized;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function assertSafeRuleId(ruleId) {
|
|
137
|
-
const normalized = String(ruleId || '').trim();
|
|
138
|
-
if (!/^[A-Za-z0-9_=-]+$/.test(normalized)) {
|
|
139
|
-
throw new Error(`Unsafe branch protection rule id: ${ruleId}`);
|
|
140
|
-
}
|
|
141
|
-
return normalized;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function assertSafeStatusContext(context) {
|
|
145
|
-
const normalized = String(context || '').trim();
|
|
146
|
-
if (!normalized || /[\0\r\n]/.test(normalized)) {
|
|
147
|
-
throw new Error(`Unsafe status check context: ${context}`);
|
|
148
|
-
}
|
|
149
|
-
return normalized;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function normalizeContexts(contexts = []) {
|
|
153
|
-
return [...new Set((Array.isArray(contexts) ? contexts : []).map((value) => {
|
|
154
|
-
const normalized = String(value || '').trim();
|
|
155
|
-
return normalized ? assertSafeStatusContext(normalized) : '';
|
|
156
|
-
}).filter(Boolean))].sort((left, right) => left.localeCompare(right));
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function loadBranchProtectionRule(repo, runner = runGh) {
|
|
160
|
-
const { owner, name } = splitRepo(repo);
|
|
161
|
-
const query = `
|
|
162
|
-
query BranchProtectionRules($owner: String!, $name: String!) {
|
|
163
|
-
repository(owner: $owner, name: $name) {
|
|
164
|
-
branchProtectionRules(first: 50) {
|
|
165
|
-
nodes {
|
|
166
|
-
id
|
|
167
|
-
pattern
|
|
168
|
-
requiresStatusChecks
|
|
169
|
-
requiredStatusCheckContexts
|
|
170
|
-
requiresApprovingReviews
|
|
171
|
-
requiresConversationResolution
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
`;
|
|
177
|
-
|
|
178
|
-
const result = runner([
|
|
179
|
-
'api',
|
|
180
|
-
'graphql',
|
|
181
|
-
'-f',
|
|
182
|
-
`query=${query}`,
|
|
183
|
-
'-f',
|
|
184
|
-
`owner=${owner}`,
|
|
185
|
-
'-f',
|
|
186
|
-
`name=${name}`,
|
|
187
|
-
]);
|
|
188
|
-
|
|
189
|
-
if (result.status !== 0) {
|
|
190
|
-
throw new Error(`Failed to load branch protection: ${formatGhError(result)}`);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const payload = JSON.parse(result.stdout || '{}');
|
|
194
|
-
return payload.data?.repository?.branchProtectionRules?.nodes || [];
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
function findBranchProtectionRule(rules, branch) {
|
|
198
|
-
return (Array.isArray(rules) ? rules : []).find((rule) => rule.pattern === branch) || null;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
function diffContexts(actual, expected) {
|
|
202
|
-
const actualSet = new Set(normalizeContexts(actual));
|
|
203
|
-
const expectedSet = new Set(normalizeContexts(expected));
|
|
204
|
-
|
|
205
|
-
return {
|
|
206
|
-
missing: [...expectedSet].filter((value) => !actualSet.has(value)),
|
|
207
|
-
unexpected: [...actualSet].filter((value) => !expectedSet.has(value)),
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
function updateBranchProtectionRule(ruleId, requiredStatusCheckContexts, runner = runGh) {
|
|
212
|
-
const safeRuleId = assertSafeRuleId(ruleId);
|
|
213
|
-
const contexts = normalizeContexts(requiredStatusCheckContexts);
|
|
214
|
-
const mutation = `
|
|
215
|
-
mutation UpdateBranchProtectionRule($ruleId: ID!, $contexts: [String!]) {
|
|
216
|
-
updateBranchProtectionRule(input: {
|
|
217
|
-
branchProtectionRuleId: $ruleId
|
|
218
|
-
requiresStatusChecks: true
|
|
219
|
-
requiredStatusCheckContexts: $contexts
|
|
220
|
-
}) {
|
|
221
|
-
branchProtectionRule {
|
|
222
|
-
id
|
|
223
|
-
pattern
|
|
224
|
-
requiresStatusChecks
|
|
225
|
-
requiredStatusCheckContexts
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
`;
|
|
230
|
-
|
|
231
|
-
const args = [
|
|
232
|
-
'api',
|
|
233
|
-
'graphql',
|
|
234
|
-
'-f',
|
|
235
|
-
`query=${mutation}`,
|
|
236
|
-
'-f',
|
|
237
|
-
`ruleId=${safeRuleId}`,
|
|
238
|
-
];
|
|
239
|
-
for (const context of contexts) {
|
|
240
|
-
args.push('-F', `contexts[]=${context}`);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
const result = runner(args);
|
|
244
|
-
|
|
245
|
-
if (result.status !== 0) {
|
|
246
|
-
throw new Error(`Failed to update branch protection: ${formatGhError(result)}`);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
return JSON.parse(result.stdout || '{}').data?.updateBranchProtectionRule?.branchProtectionRule || null;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
function syncBranchProtection(options = {}, runner = runGh) {
|
|
253
|
-
const repo = options.repo || DEFAULT_REPO;
|
|
254
|
-
const branch = assertSafeBranchPattern(options.branch || DEFAULT_BRANCH);
|
|
255
|
-
const expectedContexts = normalizeContexts(MERGE_QUALITY_CHECKS.requiredStatusCheckContexts);
|
|
256
|
-
const rules = loadBranchProtectionRule(repo, runner);
|
|
257
|
-
const rule = findBranchProtectionRule(rules, branch);
|
|
258
|
-
|
|
259
|
-
if (!rule) {
|
|
260
|
-
throw new Error(`No branch protection rule found for ${repo}#${branch}.`);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
const actualContexts = normalizeContexts(rule.requiredStatusCheckContexts);
|
|
264
|
-
const diff = diffContexts(actualContexts, expectedContexts);
|
|
265
|
-
const inSync = diff.missing.length === 0 && diff.unexpected.length === 0 && rule.requiresStatusChecks === true;
|
|
266
|
-
|
|
267
|
-
if (options.check) {
|
|
268
|
-
return {
|
|
269
|
-
ok: inSync,
|
|
270
|
-
repo,
|
|
271
|
-
branch,
|
|
272
|
-
ruleId: rule.id,
|
|
273
|
-
actualContexts,
|
|
274
|
-
expectedContexts,
|
|
275
|
-
diff,
|
|
276
|
-
};
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
const updatedRule = inSync
|
|
280
|
-
? rule
|
|
281
|
-
: updateBranchProtectionRule(rule.id, expectedContexts, runner);
|
|
282
|
-
const finalContexts = normalizeContexts(updatedRule.requiredStatusCheckContexts);
|
|
283
|
-
const finalDiff = diffContexts(finalContexts, expectedContexts);
|
|
284
|
-
|
|
285
|
-
return {
|
|
286
|
-
ok: true,
|
|
287
|
-
repo,
|
|
288
|
-
branch,
|
|
289
|
-
ruleId: rule.id,
|
|
290
|
-
actualContexts: finalContexts,
|
|
291
|
-
expectedContexts,
|
|
292
|
-
diff: finalDiff,
|
|
293
|
-
updated: !inSync,
|
|
294
|
-
};
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
function runCli(argv = process.argv.slice(2), runner = runGh) {
|
|
298
|
-
const options = parseArgs(argv);
|
|
299
|
-
const result = syncBranchProtection(options, runner);
|
|
300
|
-
|
|
301
|
-
if (options.json) {
|
|
302
|
-
console.log(JSON.stringify(result, null, 2));
|
|
303
|
-
} else if (options.check) {
|
|
304
|
-
const status = result.ok ? 'ok' : 'drift';
|
|
305
|
-
console.log(`Branch protection ${status}: ${result.repo} ${result.branch}`);
|
|
306
|
-
if (!result.ok) {
|
|
307
|
-
if (result.diff.missing.length > 0) {
|
|
308
|
-
console.log(`Missing contexts: ${result.diff.missing.join(', ')}`);
|
|
309
|
-
}
|
|
310
|
-
if (result.diff.unexpected.length > 0) {
|
|
311
|
-
console.log(`Unexpected contexts: ${result.diff.unexpected.join(', ')}`);
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
} else {
|
|
315
|
-
console.log(`Branch protection synced: ${result.repo} ${result.branch}`);
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
return options.check ? (result.ok ? 0 : 1) : 0;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
if (process.argv[1] && path.resolve(process.argv[1]) === __filename) {
|
|
322
|
-
process.exitCode = runCli();
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
module.exports = {
|
|
326
|
-
assertSafeBranchPattern,
|
|
327
|
-
assertSafeGhArgs,
|
|
328
|
-
assertSafeRuleId,
|
|
329
|
-
assertSafeStatusContext,
|
|
330
|
-
diffContexts,
|
|
331
|
-
findBranchProtectionRule,
|
|
332
|
-
loadBranchProtectionRule,
|
|
333
|
-
normalizeContexts,
|
|
334
|
-
parseArgs,
|
|
335
|
-
resolveGhBinary,
|
|
336
|
-
runCli,
|
|
337
|
-
splitRepo,
|
|
338
|
-
syncBranchProtection,
|
|
339
|
-
updateBranchProtectionRule,
|
|
340
|
-
};
|