thumbgate 1.29.2 → 1.30.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/plugin.json +1 -1
- package/.well-known/mcp/server-card.json +1 -1
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/forge/forge.yaml +3 -3
- package/adapters/mcp/server-stdio.js +78 -7
- package/adapters/opencode/opencode.json +1 -1
- package/bin/cli.js +7 -5
- package/config/mcp-allowlists.json +26 -2
- package/config/post-deploy-marketing-pages.json +26 -1
- package/package.json +38 -7
- package/public/architecture.html +130 -0
- package/public/assets/diagrams/agent-integration.png +0 -0
- package/public/assets/diagrams/before-after.svg +21 -0
- package/public/assets/diagrams/decision.svg +36 -0
- package/public/assets/diagrams/feedback-pipeline.png +0 -0
- package/public/assets/diagrams/loop.svg +34 -0
- package/public/assets/diagrams/plugin-topology.png +0 -0
- package/public/assets/diagrams/pre-action-gate-loop.svg +59 -0
- package/public/assets/diagrams/stack.svg +18 -0
- package/public/assets/diagrams/thumbgate-architecture.png +0 -0
- package/public/case-studies.html +151 -0
- package/public/eval-scorecard.html +195 -0
- package/public/eval-scorecard.json +18 -0
- package/public/evaluations.html +168 -0
- package/public/index.html +4 -3
- package/public/numbers.html +2 -2
- package/public/whitepaper.html +189 -0
- package/scripts/activation-quickstart.js +1 -0
- package/scripts/agent-outcome-monitor.js +71 -1
- package/scripts/billing.js +3 -1
- package/scripts/claude-feedback-sync.js +3 -2
- package/scripts/cli-feedback.js +13 -7
- package/scripts/cross-encoder-reranker.js +3 -0
- package/scripts/feedback-aggregate.js +5 -2
- package/scripts/feedback-loop.js +244 -182
- package/scripts/gates-engine.js +81 -4
- package/scripts/generate-case-study-outreach.js +253 -0
- package/scripts/generate-eval-scorecard.js +276 -0
- package/scripts/growth-campaigns.js +183 -0
- package/scripts/jsonl-watcher.js +1 -0
- package/scripts/lesson-inference.js +23 -4
- package/scripts/lesson-retrieval.js +71 -4
- package/scripts/lesson-search.js +26 -3
- package/scripts/mcp-config.js +26 -5
- package/scripts/mcp-oauth.js +37 -2
- package/scripts/model-eval.js +308 -0
- package/scripts/parallel-workflow-orchestrator.js +86 -22
- package/scripts/published-cli.js +11 -1
- package/scripts/refresh-proof-pack.js +261 -0
- package/scripts/risk-scorer.js +144 -15
- package/scripts/statusline-local-stats.js +1 -1
- package/scripts/thumbgate-bench.js +13 -0
- package/scripts/tool-kpi-tracker.js +124 -0
- package/scripts/tool-registry.js +49 -1
- package/src/api/server.js +230 -86
|
@@ -93,6 +93,7 @@ async function runActivationFlow({ ask, out, isTTY, deps = {} }) {
|
|
|
93
93
|
whatToChange: `Block this action: ${mistake}`,
|
|
94
94
|
tags: 'quickstart,activation,first-rule',
|
|
95
95
|
gateAction: 'block',
|
|
96
|
+
reviewOrigin: 'human',
|
|
96
97
|
});
|
|
97
98
|
} catch {
|
|
98
99
|
// Capture failure should not abort the activation aha.
|
|
@@ -5,11 +5,13 @@ const fs = require('node:fs');
|
|
|
5
5
|
const os = require('node:os');
|
|
6
6
|
const path = require('node:path');
|
|
7
7
|
const { calculateTaskOutcomeMetrics, readTaskOutcomes } = require('./task-outcomes');
|
|
8
|
+
const { computeToolKpis } = require('./tool-kpi-tracker');
|
|
8
9
|
|
|
9
10
|
const DEFAULT_THRESHOLDS = path.join(__dirname, '..', 'config', 'agent-outcome-monitor-thresholds.json');
|
|
10
11
|
const DEFAULT_HOSTED_ORIGIN = 'https://thumbgate-production.up.railway.app';
|
|
11
12
|
const DEFAULT_MONITOR_PATH = '/v1/task-outcomes/monitor';
|
|
12
13
|
const DEFAULT_SCHEDULE_ID = 'thumbgate-agent-outcome-monitor';
|
|
14
|
+
const DEFAULT_MINIMUM_TOOL_CALLS = 20;
|
|
13
15
|
|
|
14
16
|
function monitorTaskOutcomes(outcomes = [], options = {}) {
|
|
15
17
|
const metrics = calculateTaskOutcomeMetrics(outcomes);
|
|
@@ -72,6 +74,71 @@ function monitorTaskOutcomes(outcomes = [], options = {}) {
|
|
|
72
74
|
};
|
|
73
75
|
}
|
|
74
76
|
|
|
77
|
+
function monitorProductionSignals(outcomes = [], toolKpis = {}, options = {}) {
|
|
78
|
+
const taskReport = monitorTaskOutcomes(outcomes, options);
|
|
79
|
+
const requestedMinimumToolCalls = Number(options.minimumToolCalls);
|
|
80
|
+
const minimumToolCalls = Number.isFinite(requestedMinimumToolCalls) && requestedMinimumToolCalls >= 1
|
|
81
|
+
? Math.floor(requestedMinimumToolCalls)
|
|
82
|
+
: DEFAULT_MINIMUM_TOOL_CALLS;
|
|
83
|
+
const measuredToolCalls = Number(toolKpis.totalCalls);
|
|
84
|
+
const totalToolCalls = Number.isFinite(measuredToolCalls) && measuredToolCalls >= 0
|
|
85
|
+
? Math.floor(measuredToolCalls)
|
|
86
|
+
: 0;
|
|
87
|
+
const toolAlerts = [];
|
|
88
|
+
|
|
89
|
+
if (totalToolCalls < minimumToolCalls) {
|
|
90
|
+
toolAlerts.push({
|
|
91
|
+
id: 'minimum-tool-calls',
|
|
92
|
+
severity: 'block',
|
|
93
|
+
message: `Need ${minimumToolCalls} observed tool calls; observed ${totalToolCalls}.`,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
for (const tool of (toolKpis.tools || [])) {
|
|
97
|
+
if (tool.requestCount < 3) continue;
|
|
98
|
+
if (tool.successRate < 90) {
|
|
99
|
+
toolAlerts.push({
|
|
100
|
+
id: `tool-success-${tool.toolName}`,
|
|
101
|
+
severity: 'block',
|
|
102
|
+
actual: tool.successRate,
|
|
103
|
+
expected: 'gte 90',
|
|
104
|
+
message: `${tool.toolName} success rate ${tool.successRate}% is below 90%.`,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
if (tool.p95 > 500) {
|
|
108
|
+
toolAlerts.push({
|
|
109
|
+
id: `tool-latency-${tool.toolName}`,
|
|
110
|
+
severity: 'warn',
|
|
111
|
+
actual: tool.p95,
|
|
112
|
+
expected: 'lte 500',
|
|
113
|
+
message: `${tool.toolName} p95 latency ${tool.p95}ms is above 500ms.`,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const alerts = [...taskReport.alerts, ...toolAlerts];
|
|
119
|
+
let verdict = taskReport.verdict;
|
|
120
|
+
if (taskReport.verdict !== 'blocked' && toolAlerts.some((alert) => alert.id === 'minimum-tool-calls')) {
|
|
121
|
+
verdict = 'insufficient_evidence';
|
|
122
|
+
} else if (toolAlerts.some((alert) => alert.severity === 'block')) {
|
|
123
|
+
verdict = 'blocked';
|
|
124
|
+
} else if (verdict === 'healthy' && toolAlerts.length > 0) {
|
|
125
|
+
verdict = 'watch';
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
...taskReport,
|
|
130
|
+
verdict,
|
|
131
|
+
alerts,
|
|
132
|
+
observability: {
|
|
133
|
+
minimumToolCalls,
|
|
134
|
+
totalToolCalls,
|
|
135
|
+
evidenceStatus: totalToolCalls >= minimumToolCalls ? 'measured' : 'insufficient_evidence',
|
|
136
|
+
tools: toolKpis.tools || [],
|
|
137
|
+
servers: toolKpis.servers || [],
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
75
142
|
function flattenMetricValues(metrics) {
|
|
76
143
|
return {
|
|
77
144
|
workingRate: metrics.task.workingRate,
|
|
@@ -228,8 +295,9 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
228
295
|
|
|
229
296
|
const report = options.hosted
|
|
230
297
|
? await fetchHostedMonitor(options)
|
|
231
|
-
:
|
|
298
|
+
: monitorProductionSignals(
|
|
232
299
|
readTaskOutcomes({ inputPath: options.inputPath }),
|
|
300
|
+
computeToolKpis(),
|
|
233
301
|
{ thresholdsPath: options.thresholdsPath },
|
|
234
302
|
);
|
|
235
303
|
if (options.outputPath) {
|
|
@@ -255,7 +323,9 @@ module.exports = {
|
|
|
255
323
|
flattenMetricValues,
|
|
256
324
|
installAgentOutcomeMonitorSchedule,
|
|
257
325
|
main,
|
|
326
|
+
monitorProductionSignals,
|
|
258
327
|
monitorTaskOutcomes,
|
|
259
328
|
parseArgs,
|
|
260
329
|
passesRule,
|
|
330
|
+
DEFAULT_MINIMUM_TOOL_CALLS,
|
|
261
331
|
};
|
package/scripts/billing.js
CHANGED
|
@@ -30,6 +30,7 @@ const {
|
|
|
30
30
|
resolveFallbackArtifactPath,
|
|
31
31
|
} = require('./feedback-paths');
|
|
32
32
|
const { getTelemetryAnalytics, getTelemetrySourceDiagnostics } = require('./telemetry-analytics');
|
|
33
|
+
const { normalizeCampaignId } = require('./growth-campaigns');
|
|
33
34
|
const {
|
|
34
35
|
PRO_MONTHLY_PRICE_ID,
|
|
35
36
|
PRO_ANNUAL_PRICE_ID,
|
|
@@ -1154,7 +1155,7 @@ function extractAttribution(metadata = {}) {
|
|
|
1154
1155
|
return {
|
|
1155
1156
|
source: normalizeText(safe.utmSource || safe.source),
|
|
1156
1157
|
medium: normalizeText(safe.utmMedium || safe.medium),
|
|
1157
|
-
campaign:
|
|
1158
|
+
campaign: normalizeCampaignId(safe.utmCampaign || safe.campaign),
|
|
1158
1159
|
content: normalizeText(safe.utmContent || safe.content),
|
|
1159
1160
|
term: normalizeText(safe.utmTerm || safe.term),
|
|
1160
1161
|
creator: normalizeText(safe.creator || safe.creatorHandle || safe.creator_handle),
|
|
@@ -4237,6 +4238,7 @@ module.exports = {
|
|
|
4237
4238
|
_TRIAL_EMAIL_LEDGER_PATH: () => CONFIG.TRIAL_EMAIL_LEDGER_PATH,
|
|
4238
4239
|
_ORDER_EMAIL_LEDGER_PATH: () => CONFIG.ORDER_EMAIL_LEDGER_PATH,
|
|
4239
4240
|
_LOCAL_MODE: () => LOCAL_MODE(),
|
|
4241
|
+
_extractAttribution: extractAttribution,
|
|
4240
4242
|
_withTimeout: withTimeout,
|
|
4241
4243
|
_mailer: mailer,
|
|
4242
4244
|
};
|
|
@@ -8,7 +8,6 @@ const {
|
|
|
8
8
|
buildFeedbackSourceIdentity,
|
|
9
9
|
getFeedbackPaths,
|
|
10
10
|
readJSONL,
|
|
11
|
-
analyzeFeedback,
|
|
12
11
|
} = require('./feedback-loop');
|
|
13
12
|
const { detectFeedbackSignal, normalizeFeedbackText } = require('./feedback-quality');
|
|
14
13
|
const {
|
|
@@ -279,6 +278,7 @@ function syncClaudeHistoryFeedback(options = {}) {
|
|
|
279
278
|
whatWorked: candidate.signal === 'up' ? candidate.promptText : undefined,
|
|
280
279
|
tags: ['claude-history-sync', 'auto-capture-fallback'],
|
|
281
280
|
sourceEvent,
|
|
281
|
+
reviewOrigin: 'human',
|
|
282
282
|
});
|
|
283
283
|
|
|
284
284
|
if (captureResult?.duplicate) {
|
|
@@ -300,7 +300,8 @@ function syncClaudeHistoryFeedback(options = {}) {
|
|
|
300
300
|
}, { feedbackDir });
|
|
301
301
|
|
|
302
302
|
if (importedCount > 0) {
|
|
303
|
-
|
|
303
|
+
const { analyzeFeedback } = require('./feedback-loop');
|
|
304
|
+
refreshStatuslineCache(analyzeFeedback(path.join(feedbackDir, 'feedback-log.jsonl'), { humanOnly: true }), path.join(feedbackDir, 'statusline_cache.json'));
|
|
304
305
|
}
|
|
305
306
|
|
|
306
307
|
return {
|
package/scripts/cli-feedback.js
CHANGED
|
@@ -13,13 +13,9 @@
|
|
|
13
13
|
* node scripts/cli-feedback.js down "what went wrong"
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
const { captureFeedback } = require('./feedback-loop');
|
|
16
|
+
const { captureFeedback, analyzeFeedback } = require('./feedback-loop');
|
|
17
17
|
const { loadOptionalModule } = require('./private-core-boundary');
|
|
18
|
-
//
|
|
19
|
-
// in ThumbGate-Core, but intentionally excluded from the public npm tarball.
|
|
20
|
-
// The hard `require('./history-distiller')` form crashed `hook-auto-capture`
|
|
21
|
-
// in published 1.19.0 with MODULE_NOT_FOUND. Public-shell fallback returns
|
|
22
|
-
// null distillation; caller already handles a null distillResult.
|
|
18
|
+
// Keep the optional distiller from breaking the public package.
|
|
23
19
|
const { distillFromHistory } = loadOptionalModule('./history-distiller', () => ({
|
|
24
20
|
distillFromHistory: () => null,
|
|
25
21
|
}));
|
|
@@ -60,6 +56,7 @@ function processInlineFeedback({ signal, context, chatHistory, whatWentWrong, wh
|
|
|
60
56
|
whatWorked: whatWorked || undefined,
|
|
61
57
|
chatHistory,
|
|
62
58
|
sourceEvent,
|
|
59
|
+
reviewOrigin: 'human',
|
|
63
60
|
});
|
|
64
61
|
} catch (err) {
|
|
65
62
|
feedbackResult = { accepted: false, reason: err.message };
|
|
@@ -75,7 +72,16 @@ function processInlineFeedback({ signal, context, chatHistory, whatWentWrong, wh
|
|
|
75
72
|
|
|
76
73
|
// 3. Get the most recent lesson and stats
|
|
77
74
|
const recentLesson = getRecentLesson();
|
|
78
|
-
const
|
|
75
|
+
const lessonStats = getLessonStats();
|
|
76
|
+
const feedbackStats = analyzeFeedback(undefined, { humanOnly: true });
|
|
77
|
+
const stats = {
|
|
78
|
+
...lessonStats,
|
|
79
|
+
total: feedbackStats.total,
|
|
80
|
+
positive: feedbackStats.totalPositive,
|
|
81
|
+
negative: feedbackStats.totalNegative,
|
|
82
|
+
rawTotal: feedbackStats.rawTotal,
|
|
83
|
+
excludedTotal: feedbackStats.excludedTotal,
|
|
84
|
+
};
|
|
79
85
|
|
|
80
86
|
// 4. If the user wrote an explicit "never …" / "always …" directive, surface
|
|
81
87
|
// an OFFER (never auto-act): "never" on a thumbs-down → offer force-gate now.
|
|
@@ -133,6 +133,9 @@ async function retrieveWithReranking(toolName, actionContext, options = {}) {
|
|
|
133
133
|
const candidates = retrieveRelevantLessons(toolName, actionContext, {
|
|
134
134
|
maxResults: candidateCount,
|
|
135
135
|
feedbackDir,
|
|
136
|
+
scope: options.scope,
|
|
137
|
+
requireScope: options.requireScope,
|
|
138
|
+
includeShared: options.includeShared,
|
|
136
139
|
});
|
|
137
140
|
|
|
138
141
|
if (candidates.length === 0) return [];
|
|
@@ -239,11 +239,12 @@ function trendFromRateWindows(windows) {
|
|
|
239
239
|
function computeAggregateFeedbackStats(options = {}) {
|
|
240
240
|
const { entries, stores } = collectAggregateLogEntries(FEEDBACK_LOG, options);
|
|
241
241
|
const memory = collectAggregateLogEntries(MEMORY_LOG, options);
|
|
242
|
-
const
|
|
242
|
+
const humanReviewedEntries = entries.filter((entry) => entry.reviewOrigin === 'human');
|
|
243
|
+
const { totalPositive, totalNegative, rubricSamples } = summarizeFeedbackEntries(humanReviewedEntries);
|
|
243
244
|
const total = totalPositive + totalNegative;
|
|
244
245
|
const approvalRate = total > 0 ? Math.round((totalPositive / total) * 1000) / 1000 : 0;
|
|
245
246
|
const windows = createRateWindows(total, totalPositive, approvalRate);
|
|
246
|
-
updateRateWindows(windows,
|
|
247
|
+
updateRateWindows(windows, humanReviewedEntries);
|
|
247
248
|
finalizeRateWindows(windows);
|
|
248
249
|
const trend = trendFromRateWindows(windows);
|
|
249
250
|
|
|
@@ -255,6 +256,8 @@ function computeAggregateFeedbackStats(options = {}) {
|
|
|
255
256
|
recentRate: windows['7d'].rate || approvalRate,
|
|
256
257
|
trend,
|
|
257
258
|
windows,
|
|
259
|
+
rawTotal: entries.length,
|
|
260
|
+
excludedTotal: entries.length - humanReviewedEntries.length,
|
|
258
261
|
rubric: { samples: rubricSamples || memory.entries.length, blockedPromotions: 0, failingCriteria: {} },
|
|
259
262
|
aggregate: {
|
|
260
263
|
enabled: true,
|