thumbgate 1.16.22 → 1.18.0
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 +1 -1
- package/.well-known/mcp/server-card.json +1 -1
- package/README.md +11 -5
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/mcp/server-stdio.js +1 -1
- package/adapters/opencode/opencode.json +1 -1
- package/config/github-about.json +1 -1
- package/package.json +10 -5
- package/public/blog.html +18 -19
- package/public/compare.html +2 -2
- package/public/guide.html +1 -1
- package/public/index.html +166 -419
- package/public/numbers.html +2 -2
- package/scripts/auto-promote-gates.js +4 -1
- package/scripts/billing.js +62 -3
- package/scripts/feedback-to-rules.js +11 -1
- package/scripts/feedback_quality_eval.py +725 -0
- package/scripts/rate-limiter.js +15 -15
- package/src/api/server.js +91 -19
package/public/numbers.html
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"alternateName": "thumbgate",
|
|
26
26
|
"applicationCategory": "DeveloperApplication",
|
|
27
27
|
"operatingSystem": "Cross-platform, Node.js >=18.18.0",
|
|
28
|
-
"softwareVersion": "1.
|
|
28
|
+
"softwareVersion": "1.18.0",
|
|
29
29
|
"url": "https://thumbgate-production.up.railway.app/numbers",
|
|
30
30
|
"dateModified": "2026-05-07",
|
|
31
31
|
"creator": {
|
|
@@ -202,7 +202,7 @@
|
|
|
202
202
|
<main class="container">
|
|
203
203
|
<h1>The Numbers</h1>
|
|
204
204
|
<p class="subtitle">Generated first-party operational snapshot from the ThumbGate runtime. This is not customer traction, install volume, revenue, or proof that a configured gate has fired.</p>
|
|
205
|
-
<div class="freshness">Updated: 2026-05-07 · Version 1.
|
|
205
|
+
<div class="freshness">Updated: 2026-05-07 · Version 1.18.0</div>
|
|
206
206
|
<div class="truth-note"><strong>Read this first:</strong> configured checks are inventory. Recorded blocks and warnings are usage evidence. This snapshot currently reports 0 recorded hard-block event(s) and 0 recorded warning event(s).</div>
|
|
207
207
|
|
|
208
208
|
<h2>Gate enforcement</h2>
|
|
@@ -6,7 +6,10 @@ const path = require('path');
|
|
|
6
6
|
const { resolveFeedbackDir } = require('./feedback-paths');
|
|
7
7
|
|
|
8
8
|
const MAX_AUTO_GATES = 10;
|
|
9
|
-
|
|
9
|
+
// 1+ failure auto-promotes to a warning gate. Cold buyers expect "one 👎 → blocked next time"
|
|
10
|
+
// — a 2-capture threshold made first-capture invisible and broke the activation loop. Block
|
|
11
|
+
// escalation still requires 3 captures (BLOCK_THRESHOLD) so noise doesn't auto-hard-block.
|
|
12
|
+
const WARN_THRESHOLD = 1;
|
|
10
13
|
const BLOCK_THRESHOLD = 3; // 3+ repeated failures hard-block the action
|
|
11
14
|
const WINDOW_DAYS = 30;
|
|
12
15
|
|
package/scripts/billing.js
CHANGED
|
@@ -1149,7 +1149,8 @@ function loadResolvedRevenueEvents(options = {}) {
|
|
|
1149
1149
|
const derived = deriveRevenueEventFromPaidProviderEvent(entry);
|
|
1150
1150
|
if (!derived) continue;
|
|
1151
1151
|
if (hasRevenueEventMatch(resolved, derived)) continue;
|
|
1152
|
-
|
|
1152
|
+
const priced = resolveGithubMarketplaceRevenueEntry(derived, { annotate: false }).entry;
|
|
1153
|
+
resolved.push(priced);
|
|
1153
1154
|
}
|
|
1154
1155
|
|
|
1155
1156
|
return mergeRevenueEvents(resolved, extraRevenueEvents);
|
|
@@ -1161,6 +1162,9 @@ function repairGithubMarketplaceRevenueLedger(options = {}) {
|
|
|
1161
1162
|
const rows = loadJsonlFile(ledgerPath);
|
|
1162
1163
|
const resolvedAt = new Date().toISOString();
|
|
1163
1164
|
const repairs = [];
|
|
1165
|
+
|
|
1166
|
+
// Pass 1: in-place repair of rows already in revenue-events.jsonl that have
|
|
1167
|
+
// unknown amounts but resolvable plan metadata.
|
|
1164
1168
|
const updatedRows = rows.map((entry) => {
|
|
1165
1169
|
const result = resolveGithubMarketplaceRevenueEntry(entry, {
|
|
1166
1170
|
annotate: true,
|
|
@@ -1178,21 +1182,76 @@ function repairGithubMarketplaceRevenueLedger(options = {}) {
|
|
|
1178
1182
|
currency: normalizeCurrency(result.entry.currency),
|
|
1179
1183
|
recurringInterval: normalizeText(result.entry.recurringInterval),
|
|
1180
1184
|
pricingSource: normalizeText(metadata.githubMarketplaceAmountSource),
|
|
1185
|
+
source: 'in_place',
|
|
1181
1186
|
});
|
|
1182
1187
|
return result.entry;
|
|
1183
1188
|
});
|
|
1184
1189
|
|
|
1190
|
+
// Pass 2: append rows for funnel-derived paid github_marketplace events that
|
|
1191
|
+
// never landed in the revenue ledger. The webhook handler at handleGithubWebhook
|
|
1192
|
+
// skips appendRevenueEvent when hasRevenueEventMatch is true, so duplicates from
|
|
1193
|
+
// a re-run are already prevented; the only way an order gets here is if the
|
|
1194
|
+
// revenue write was skipped at webhook time (e.g. funnel pre-existed, planPricing
|
|
1195
|
+
// had unknown amount at the time, or the row was created via a different path).
|
|
1196
|
+
const funnelRecords = loadFunnelLedger().filter(
|
|
1197
|
+
(e) =>
|
|
1198
|
+
e &&
|
|
1199
|
+
e.stage === 'paid' &&
|
|
1200
|
+
normalizeText((e.metadata && e.metadata.provider) || e.provider) === 'github_marketplace'
|
|
1201
|
+
);
|
|
1202
|
+
|
|
1203
|
+
for (const funnelEntry of funnelRecords) {
|
|
1204
|
+
const derived = deriveRevenueEventFromPaidProviderEvent(funnelEntry);
|
|
1205
|
+
if (!derived) continue;
|
|
1206
|
+
if (hasRevenueEventMatch(updatedRows, derived)) continue;
|
|
1207
|
+
|
|
1208
|
+
const resolved = resolveGithubMarketplaceRevenueEntry(derived, {
|
|
1209
|
+
annotate: true,
|
|
1210
|
+
resolvedAt,
|
|
1211
|
+
});
|
|
1212
|
+
// Skip funnel-derived rows that still have unknown amounts after resolving —
|
|
1213
|
+
// we don't want to permanently bake amountKnown:false rows when there's no
|
|
1214
|
+
// pricing data to attach. Better to leave them off-disk and keep the
|
|
1215
|
+
// read-time merge in loadResolvedRevenueEvents covering them.
|
|
1216
|
+
if (!resolved.changed || !resolved.entry.amountKnown) continue;
|
|
1217
|
+
|
|
1218
|
+
const persisted = {
|
|
1219
|
+
...resolved.entry,
|
|
1220
|
+
metadata: {
|
|
1221
|
+
...sanitizeMetadata(resolved.entry.metadata),
|
|
1222
|
+
recoveredFromFunnelLedger: true,
|
|
1223
|
+
funnelRecordedAt: normalizeText(funnelEntry.timestamp) || null,
|
|
1224
|
+
},
|
|
1225
|
+
};
|
|
1226
|
+
updatedRows.push(persisted);
|
|
1227
|
+
|
|
1228
|
+
const metadata = sanitizeMetadata(persisted.metadata);
|
|
1229
|
+
repairs.push({
|
|
1230
|
+
orderId: normalizeText(persisted.orderId),
|
|
1231
|
+
customerId: normalizeText(persisted.customerId),
|
|
1232
|
+
planId: normalizeText(metadata.planId ?? persisted.planId),
|
|
1233
|
+
amountCents: normalizeInteger(persisted.amountCents),
|
|
1234
|
+
currency: normalizeCurrency(persisted.currency),
|
|
1235
|
+
recurringInterval: normalizeText(persisted.recurringInterval),
|
|
1236
|
+
pricingSource: normalizeText(metadata.githubMarketplaceAmountSource),
|
|
1237
|
+
source: 'funnel_derived',
|
|
1238
|
+
});
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1185
1241
|
const writeResult = write && repairs.length > 0
|
|
1186
1242
|
? writeJsonlRecords(ledgerPath, updatedRows)
|
|
1187
|
-
: { written: false, rowCount:
|
|
1243
|
+
: { written: false, rowCount: updatedRows.length };
|
|
1188
1244
|
|
|
1189
1245
|
return {
|
|
1190
1246
|
ledgerPath,
|
|
1191
1247
|
write,
|
|
1192
1248
|
wrote: Boolean(writeResult.written),
|
|
1193
1249
|
scanned: rows.length,
|
|
1250
|
+
funnelScanned: funnelRecords.length,
|
|
1194
1251
|
repaired: repairs.length,
|
|
1195
|
-
|
|
1252
|
+
repairedInPlace: repairs.filter((r) => r.source === 'in_place').length,
|
|
1253
|
+
repairedFromFunnel: repairs.filter((r) => r.source === 'funnel_derived').length,
|
|
1254
|
+
unchanged: rows.length - repairs.filter((r) => r.source === 'in_place').length,
|
|
1196
1255
|
repairs,
|
|
1197
1256
|
writeResult,
|
|
1198
1257
|
};
|
|
@@ -32,7 +32,17 @@ function normalize(ctx) {
|
|
|
32
32
|
return (ctx || '').replace(/\/Users\/[^\s/]+/g, '~').replace(/:[0-9]+/g, '').toLowerCase().trim();
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
// HIGH_RISK_TAGS triggers single-capture promotion (count >= 1 && hasHighRisk).
|
|
36
|
+
// Tags here MUST overlap with what inferSemanticTags() actually emits (see scripts/feedback-loop.js)
|
|
37
|
+
// — otherwise cold buyers' first 👎 stays a lesson and never becomes a gate.
|
|
38
|
+
const HIGH_RISK_TAGS = new Set([
|
|
39
|
+
// Original semantic-category labels
|
|
40
|
+
'git-workflow', 'scope-control', 'trust-breach', 'execution-gap', 'regression', 'security',
|
|
41
|
+
// Tags inferSemanticTags() emits for destructive / irreversible operations
|
|
42
|
+
'destructive', 'force-push', 'delete', 'drop', 'force-overwrite',
|
|
43
|
+
'production', 'database', 'payment', 'credentials', 'secrets',
|
|
44
|
+
'rm-rf', 'reset-hard', 'truncate', 'data-loss',
|
|
45
|
+
]);
|
|
36
46
|
function analyze(entries) {
|
|
37
47
|
let positiveCount = 0, negativeCount = 0;
|
|
38
48
|
const categories = {};
|