thumbgate 1.27.18 → 1.27.19
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 +6 -6
- package/.claude-plugin/plugin.json +4 -3
- package/.well-known/agentic-verify.txt +1 -0
- package/.well-known/llms.txt +33 -12
- package/.well-known/mcp/server-card.json +8 -8
- package/README.md +249 -30
- package/adapters/chatgpt/openapi.yaml +12 -0
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/codex/config.toml +2 -2
- package/adapters/gemini/function-declarations.json +1 -0
- package/adapters/mcp/server-stdio.js +263 -11
- package/adapters/opencode/opencode.json +1 -1
- package/bench/thumbgate-bench.json +2 -2
- package/bin/cli.js +1429 -121
- package/bin/postinstall.js +1 -8
- package/config/gate-classifier-routing.json +98 -0
- package/config/gate-templates.json +216 -0
- package/config/gates/claim-verification.json +12 -0
- package/config/gates/default.json +31 -2
- package/config/github-about.json +2 -2
- package/config/mcp-allowlists.json +23 -13
- package/config/merge-quality-checks.json +0 -1
- package/config/model-candidates.json +121 -6
- package/config/post-deploy-marketing-pages.json +80 -0
- package/config/tessl-tiles.json +1 -3
- package/openapi/openapi.yaml +12 -0
- package/package.json +1 -1
- package/public/blog.html +4 -4
- package/public/codex-plugin.html +72 -20
- package/public/compare.html +31 -8
- package/public/dashboard.html +930 -166
- package/public/federal.html +2 -2
- package/public/guide.html +33 -13
- package/public/index.html +469 -111
- package/public/learn.html +183 -18
- package/public/lessons.html +168 -10
- package/public/numbers.html +7 -7
- package/public/pro.html +34 -11
- package/scripts/agent-memory-lifecycle.js +211 -0
- package/scripts/agent-readiness.js +20 -3
- package/scripts/agent-reward-model.js +53 -1
- package/scripts/auto-promote-gates.js +82 -10
- package/scripts/auto-wire-hooks.js +14 -0
- package/scripts/billing.js +93 -1
- package/scripts/bot-detection.js +61 -3
- package/scripts/build-metadata.js +50 -10
- package/scripts/cli-feedback.js +4 -2
- package/scripts/cli-schema.js +97 -0
- package/scripts/cli-telemetry.js +6 -1
- package/scripts/commercial-offer.js +82 -2
- package/scripts/context-manager.js +74 -6
- package/scripts/dashboard.js +68 -2
- package/scripts/export-databricks-bundle.js +5 -1
- package/scripts/export-dpo-pairs.js +7 -2
- package/scripts/feedback-loop.js +123 -1
- package/scripts/feedback-quality.js +87 -0
- package/scripts/filesystem-search.js +35 -10
- package/scripts/gate-stats.js +89 -0
- package/scripts/gates-engine.js +1176 -85
- package/scripts/gemini-embedding-policy.js +2 -1
- package/scripts/hook-runtime.js +20 -14
- package/scripts/hook-thumbgate-cache-updater.js +18 -2
- package/scripts/hybrid-feedback-context.js +142 -7
- package/scripts/lesson-inference.js +8 -3
- package/scripts/lesson-search.js +17 -1
- package/scripts/license.js +10 -10
- package/scripts/llm-client.js +169 -4
- package/scripts/local-model-profile.js +15 -8
- package/scripts/mcp-config.js +7 -1
- package/scripts/memory-scope-readiness.js +159 -0
- package/scripts/meta-agent-loop.js +36 -0
- package/scripts/operational-integrity.js +39 -5
- package/scripts/oss-pr-opportunity-scout.js +35 -5
- package/scripts/plausible-server-events.js +9 -6
- package/scripts/pro-local-dashboard.js +4 -4
- package/scripts/proxy-pointer-rag-guardrails.js +42 -1
- package/scripts/published-cli.js +0 -8
- package/scripts/rate-limiter.js +64 -13
- package/scripts/secret-scanner.js +44 -5
- package/scripts/security-scanner.js +260 -10
- package/scripts/self-distill-agent.js +3 -1
- package/scripts/seo-gsd.js +916 -7
- package/scripts/statusline-cache-path.js +17 -2
- package/scripts/statusline-local-stats.js +9 -1
- package/scripts/statusline-meta.js +28 -2
- package/scripts/statusline.sh +20 -4
- package/scripts/telemetry-analytics.js +357 -0
- package/scripts/thompson-sampling.js +31 -10
- package/scripts/thumbgate-bench.js +16 -1
- package/scripts/thumbgate-search.js +85 -19
- package/scripts/tool-registry.js +169 -1
- package/scripts/vector-store.js +45 -0
- package/scripts/workflow-sentinel.js +286 -53
- package/scripts/workspace-evolver.js +62 -2
- package/src/api/server.js +2683 -319
- package/scripts/bot-detector.js +0 -50
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
4
7
|
/**
|
|
5
8
|
* Context Manager — Unified Context-Augmented Generation (CAG) Orchestrator
|
|
6
9
|
*
|
|
@@ -27,8 +30,9 @@ const {
|
|
|
27
30
|
recordProvenance,
|
|
28
31
|
} = require('./contextfs');
|
|
29
32
|
const { loadOptionalModule } = require('./private-core-boundary');
|
|
30
|
-
const { retrieveRelevantLessons } = loadOptionalModule('./lesson-retrieval', () => ({
|
|
33
|
+
const { retrieveRelevantLessons, calculateRetrievalEntropy } = loadOptionalModule('./lesson-retrieval', () => ({
|
|
31
34
|
retrieveRelevantLessons: () => [],
|
|
35
|
+
calculateRetrievalEntropy: () => 0,
|
|
32
36
|
}));
|
|
33
37
|
const { evaluatePretool } = require('./hybrid-feedback-context');
|
|
34
38
|
const { loadProfile } = require('./user-profile');
|
|
@@ -93,13 +97,21 @@ function assembleSession() {
|
|
|
93
97
|
|
|
94
98
|
function assembleLessons(query, agentProfile, options = {}) {
|
|
95
99
|
try {
|
|
96
|
-
|
|
100
|
+
const lessons = retrieveRelevantLessons(
|
|
97
101
|
options.toolName || '',
|
|
98
102
|
query,
|
|
99
103
|
{ maxResults: agentProfile.maxLessons, feedbackDir: options.feedbackDir },
|
|
100
104
|
);
|
|
105
|
+
|
|
106
|
+
const entropy = calculateRetrievalEntropy(lessons);
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
items: lessons,
|
|
110
|
+
entropy,
|
|
111
|
+
highConflict: entropy > 0.7
|
|
112
|
+
};
|
|
101
113
|
} catch {
|
|
102
|
-
return [];
|
|
114
|
+
return { items: [], entropy: 0, highConflict: false };
|
|
103
115
|
}
|
|
104
116
|
}
|
|
105
117
|
|
|
@@ -111,13 +123,41 @@ function assembleGuards(toolName, toolInput) {
|
|
|
111
123
|
}
|
|
112
124
|
}
|
|
113
125
|
|
|
114
|
-
function assembleContextPack(query, agentProfile) {
|
|
126
|
+
function assembleContextPack(query, agentProfile, options = {}) {
|
|
127
|
+
const { guards } = options;
|
|
115
128
|
try {
|
|
116
129
|
ensureContextFs();
|
|
130
|
+
|
|
131
|
+
// 1. Proactive Governance: Filter what the agent sees based on prevention rules
|
|
132
|
+
let structuredQuery = query;
|
|
133
|
+
if (guards && guards.mode === 'block') {
|
|
134
|
+
structuredQuery = `${query} (Active Block Policy: ${guards.reason})`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// 2. Elevate Thompson Sampling to Architecture Level
|
|
138
|
+
let strategy = null;
|
|
139
|
+
try {
|
|
140
|
+
const ts = require('./thompson-sampling');
|
|
141
|
+
const model = ts.loadModel();
|
|
142
|
+
const bestCategory = ts.argmaxPosteriors(model);
|
|
143
|
+
|
|
144
|
+
// Route between context-building strategies based on TS posterior mean
|
|
145
|
+
if (bestCategory === 'architecture' || bestCategory === 'infra') {
|
|
146
|
+
strategy = 'hierarchical';
|
|
147
|
+
} else if (bestCategory === 'observability' || bestCategory === 'debugging') {
|
|
148
|
+
strategy = 'summarize-then-expand';
|
|
149
|
+
} else {
|
|
150
|
+
strategy = 'semantic';
|
|
151
|
+
}
|
|
152
|
+
} catch (e) {
|
|
153
|
+
// Fallback to default routing
|
|
154
|
+
}
|
|
155
|
+
|
|
117
156
|
return constructContextPack({
|
|
118
|
-
query,
|
|
157
|
+
query: structuredQuery,
|
|
119
158
|
maxItems: Math.min(8, Math.ceil(agentProfile.contextBudget / 1000)),
|
|
120
159
|
maxChars: agentProfile.contextBudget,
|
|
160
|
+
strategy
|
|
121
161
|
});
|
|
122
162
|
} catch {
|
|
123
163
|
return null;
|
|
@@ -196,7 +236,8 @@ function assembleUnifiedContext(params = {}) {
|
|
|
196
236
|
// Assemble all components — each is fault-tolerant
|
|
197
237
|
const session = assembleSession();
|
|
198
238
|
const userProfile = assembleUserProfile();
|
|
199
|
-
const
|
|
239
|
+
const lessonData = assembleLessons(query, agentProfile, { toolName, feedbackDir });
|
|
240
|
+
const lessons = lessonData.items;
|
|
200
241
|
const guards = assembleGuards(toolName, toolInput);
|
|
201
242
|
const contextPack = assembleContextPack(query, agentProfile);
|
|
202
243
|
const codeGraph = assembleCodeGraph(query, repoPath, agentProfile);
|
|
@@ -204,9 +245,24 @@ function assembleUnifiedContext(params = {}) {
|
|
|
204
245
|
const components = { session, userProfile, lessons, guards, contextPack, codeGraph };
|
|
205
246
|
const tier = classifyTier(components);
|
|
206
247
|
|
|
248
|
+
// v4.2: Entropy-Aware Reliability Directive
|
|
249
|
+
let reliabilityDirective = null;
|
|
250
|
+
if (lessonData.highConflict) {
|
|
251
|
+
reliabilityDirective = 'CAUTION: Conflicting past patterns detected for this action. Prioritize absolute ground truth verification over rapid completion.';
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// v1.26.0: CodeRabbit Planning Directive
|
|
255
|
+
const planPath = path.join(repoPath || process.cwd(), 'PLAN.md');
|
|
256
|
+
if (!fs.existsSync(planPath) && ['Bash', 'Write', 'Edit', 'Deploy'].includes(toolName)) {
|
|
257
|
+
const planReminder = 'ORCHESTRATION: High-risk action detected without a PLAN.md. Please document your intent, assumptions, and verification steps before proceeding.';
|
|
258
|
+
reliabilityDirective = reliabilityDirective ? `${reliabilityDirective}\n\n${planReminder}` : planReminder;
|
|
259
|
+
}
|
|
260
|
+
|
|
207
261
|
const result = {
|
|
208
262
|
tier,
|
|
209
263
|
agentType: agentType || 'default',
|
|
264
|
+
reliabilityDirective,
|
|
265
|
+
entropy: lessonData.entropy,
|
|
210
266
|
agentProfile: {
|
|
211
267
|
maxLessons: agentProfile.maxLessons,
|
|
212
268
|
contextBudget: agentProfile.contextBudget,
|
|
@@ -228,6 +284,12 @@ function assembleUnifiedContext(params = {}) {
|
|
|
228
284
|
})),
|
|
229
285
|
visibility: contextPack.visibility || null,
|
|
230
286
|
cached: !!(contextPack.cache && contextPack.cache.hit),
|
|
287
|
+
layers: {
|
|
288
|
+
localState: session || null,
|
|
289
|
+
graphState: codeGraph || null,
|
|
290
|
+
policyState: guards || null,
|
|
291
|
+
sessionState: contextPack.items ? contextPack.items.filter(i => i.namespace === 'session') : []
|
|
292
|
+
}
|
|
231
293
|
} : null,
|
|
232
294
|
codeGraph: codeGraph || null,
|
|
233
295
|
assembledAt: new Date().toISOString(),
|
|
@@ -306,6 +368,12 @@ function formatUnifiedContext(ctx) {
|
|
|
306
368
|
|
|
307
369
|
// Context pack
|
|
308
370
|
if (ctx.contextPack) {
|
|
371
|
+
lines.push(`### Context Architecture Layers`);
|
|
372
|
+
lines.push(`- Local State: ${ctx.contextPack.layers.localState ? 'Active' : 'Empty'}`);
|
|
373
|
+
lines.push(`- Graph State: ${ctx.contextPack.layers.graphState ? 'Active' : 'Empty'}`);
|
|
374
|
+
lines.push(`- Policy State: ${ctx.contextPack.layers.policyState ? ctx.contextPack.layers.policyState.mode : 'Empty'}`);
|
|
375
|
+
lines.push(`- Session State: ${ctx.contextPack.layers.sessionState.length} items`);
|
|
376
|
+
lines.push('');
|
|
309
377
|
lines.push(`### Context Pack (${ctx.contextPack.itemCount} items)`);
|
|
310
378
|
ctx.contextPack.items.forEach((item) => {
|
|
311
379
|
lines.push(`- [${item.namespace}] ${item.title} (score: ${item.score})`);
|
package/scripts/dashboard.js
CHANGED
|
@@ -17,6 +17,7 @@ const { filterEntriesForWindow, resolveAnalyticsWindow } = require('./analytics-
|
|
|
17
17
|
const { resolveHostedBillingConfig } = require('./hosted-config');
|
|
18
18
|
const { generateAgentReadinessReport } = require('./agent-readiness');
|
|
19
19
|
const { summarizeGateTemplates } = require('./gate-templates');
|
|
20
|
+
const { mergeRepeatMetricIntoGateStats } = require('./repeat-metric');
|
|
20
21
|
const { buildPredictiveInsights } = loadOptionalModule('./predictive-insights', () => ({
|
|
21
22
|
buildPredictiveInsights: () => ({
|
|
22
23
|
upgradePropensity: {
|
|
@@ -48,6 +49,10 @@ const {
|
|
|
48
49
|
readDecisionLog,
|
|
49
50
|
} = require('./decision-journal');
|
|
50
51
|
const { analyzeFeedback } = require('./feedback-loop');
|
|
52
|
+
const {
|
|
53
|
+
collectAggregateLogEntries,
|
|
54
|
+
shouldAggregateFeedback,
|
|
55
|
+
} = require('./feedback-aggregate');
|
|
51
56
|
|
|
52
57
|
const PROJECT_ROOT = path.join(__dirname, '..');
|
|
53
58
|
const DEFAULT_GATES_PATH = path.join(PROJECT_ROOT, 'config', 'gates', 'default.json');
|
|
@@ -967,6 +972,20 @@ function computeAnalyticsSummary(feedbackDir, options = {}) {
|
|
|
967
972
|
return {
|
|
968
973
|
window: telemetry.window || analyticsWindow,
|
|
969
974
|
telemetry,
|
|
975
|
+
firstPartyTrafficQuality: telemetry.trafficQuality || {
|
|
976
|
+
rawEvents: telemetry.totalEvents || 0,
|
|
977
|
+
externalEvents: 0,
|
|
978
|
+
excludedEvents: 0,
|
|
979
|
+
exclusionRate: 0,
|
|
980
|
+
byAudience: {},
|
|
981
|
+
byExclusionReason: {},
|
|
982
|
+
external: {
|
|
983
|
+
uniqueVisitors: 0,
|
|
984
|
+
pageViews: 0,
|
|
985
|
+
checkoutStarts: 0,
|
|
986
|
+
},
|
|
987
|
+
verdict: 'missing',
|
|
988
|
+
},
|
|
970
989
|
funnel: {
|
|
971
990
|
visitors: uniqueVisitors,
|
|
972
991
|
sessions: telemetry.visitors ? telemetry.visitors.uniqueSessions || 0 : 0,
|
|
@@ -1149,16 +1168,42 @@ function computeInstrumentationReadiness(analytics, billing) {
|
|
|
1149
1168
|
const coverage = billing && billing.coverage ? billing.coverage : {};
|
|
1150
1169
|
const telemetry = analytics.telemetry || {};
|
|
1151
1170
|
const visitors = telemetry.visitors || {};
|
|
1171
|
+
const quality = telemetry.trafficQuality || analytics.firstPartyTrafficQuality || {};
|
|
1172
|
+
const external = quality.external || {};
|
|
1152
1173
|
const cli = telemetry.cli || {};
|
|
1174
|
+
const plausibleExportConfigured = Boolean(
|
|
1175
|
+
process.env.PLAUSIBLE_API_KEY && (process.env.PLAUSIBLE_SITE_ID || process.env.PLAUSIBLE_DOMAIN)
|
|
1176
|
+
);
|
|
1177
|
+
const posthogExportConfigured = Boolean(
|
|
1178
|
+
(process.env.POSTHOG_PERSONAL_API_KEY || process.env.POSTHOG_API_KEY) && process.env.POSTHOG_PROJECT_ID
|
|
1179
|
+
);
|
|
1180
|
+
const ga4ExportConfigured = Boolean(
|
|
1181
|
+
process.env.GA4_PROPERTY_ID && (process.env.GOOGLE_APPLICATION_CREDENTIALS || process.env.GOOGLE_CLIENT_EMAIL)
|
|
1182
|
+
);
|
|
1183
|
+
const dashboardGradeExportReady = plausibleExportConfigured || posthogExportConfigured || ga4ExportConfigured;
|
|
1153
1184
|
|
|
1154
1185
|
return {
|
|
1155
|
-
plausibleConfigured: /plausible\.io\/js\/script
|
|
1186
|
+
plausibleConfigured: /plausible\.io\/js\/script(?:\.tagged-events)?\.js|\/js\/analytics\.js/.test(landingPage),
|
|
1156
1187
|
ga4Configured: Boolean(runtimeConfig.gaMeasurementId),
|
|
1157
1188
|
googleSearchConsoleConfigured: Boolean(runtimeConfig.googleSiteVerification),
|
|
1158
1189
|
softwareApplicationSchemaPresent: /"@type": "SoftwareApplication"/.test(landingPage),
|
|
1159
1190
|
faqSchemaPresent: /"@type": "FAQPage"/.test(landingPage),
|
|
1160
1191
|
telemetryEventsPresent: (telemetry.totalEvents || 0) > 0,
|
|
1161
1192
|
uniqueVisitorsTracked: visitors.uniqueVisitors || 0,
|
|
1193
|
+
rawTelemetryEvents: quality.rawEvents || telemetry.totalEvents || 0,
|
|
1194
|
+
externalTelemetryEvents: quality.externalEvents || 0,
|
|
1195
|
+
excludedTelemetryEvents: quality.excludedEvents || 0,
|
|
1196
|
+
externalVisitorsTracked: external.uniqueVisitors || 0,
|
|
1197
|
+
externalPageViewsTracked: external.pageViews || 0,
|
|
1198
|
+
externalCheckoutStartsTracked: external.checkoutStarts || 0,
|
|
1199
|
+
externalVisitorPathsTracked: Array.isArray(external.visitorPaths) ? external.visitorPaths.length : 0,
|
|
1200
|
+
internalTestPollutionRate: quality.exclusionRate || 0,
|
|
1201
|
+
trafficQualityVerdict: quality.verdict || 'missing',
|
|
1202
|
+
topExcludedTrafficReason: quality.topExclusionReason || null,
|
|
1203
|
+
plausibleExportConfigured,
|
|
1204
|
+
posthogExportConfigured,
|
|
1205
|
+
ga4ExportConfigured,
|
|
1206
|
+
dashboardGradeExportReady,
|
|
1162
1207
|
cliInstallsTracked: cli.uniqueInstalls || 0,
|
|
1163
1208
|
funnelEventsPresent: (analytics.reconciliation.telemetryCheckoutStarts || 0) > 0,
|
|
1164
1209
|
seoSignalsPresent: (analytics.seo.landingViews || 0) > 0,
|
|
@@ -1504,6 +1549,10 @@ function resolveTeamWindowHours(analyticsWindow) {
|
|
|
1504
1549
|
// ---------------------------------------------------------------------------
|
|
1505
1550
|
|
|
1506
1551
|
function collectAllFeedbackEntries(feedbackDir) {
|
|
1552
|
+
if (shouldAggregateFeedback()) {
|
|
1553
|
+
return collectAggregateLogEntries('feedback-log.jsonl', { feedbackDir }).entries;
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1507
1556
|
const entries = [];
|
|
1508
1557
|
const seen = new Set();
|
|
1509
1558
|
|
|
@@ -1573,7 +1622,11 @@ function generateDashboard(feedbackDir, options = {}) {
|
|
|
1573
1622
|
const billingSummary = options.billingSummary || getBillingSummary(analyticsWindow);
|
|
1574
1623
|
|
|
1575
1624
|
const approval = computeApprovalStats(entries);
|
|
1576
|
-
|
|
1625
|
+
// Surface the "repeat-attempts blocked before execution" metric on the
|
|
1626
|
+
// dashboard JSON and the /v1/dashboard HTTP route. Use the non-mutating
|
|
1627
|
+
// helper (mirrors server-stdio.js) instead of mutating computeGateStats()'s
|
|
1628
|
+
// return value. (mergeRepeatMetricIntoGateStats is imported at top of file.)
|
|
1629
|
+
const gateStats = mergeRepeatMetricIntoGateStats(computeGateStats());
|
|
1577
1630
|
const prevention = computePreventionImpact(feedbackDir, gateStats);
|
|
1578
1631
|
const trend = computeSessionTrend(entries, 10);
|
|
1579
1632
|
const health = computeSystemHealth(feedbackDir, gateStats);
|
|
@@ -1865,8 +1918,10 @@ function printDashboard(data) {
|
|
|
1865
1918
|
console.log('');
|
|
1866
1919
|
console.log('\uD83D\uDCBC Growth Analytics');
|
|
1867
1920
|
console.log(` Unique Visitors : ${analytics.trafficMetrics.visitors}`);
|
|
1921
|
+
console.log(` External Visitors: ${instrumentation.externalVisitorsTracked}`);
|
|
1868
1922
|
console.log(` Sessions : ${analytics.trafficMetrics.sessions}`);
|
|
1869
1923
|
console.log(` Page Views : ${analytics.trafficMetrics.pageViews}`);
|
|
1924
|
+
console.log(` External Views : ${instrumentation.externalPageViewsTracked}`);
|
|
1870
1925
|
console.log(` CTA Clicks : ${analytics.trafficMetrics.ctaClicks}`);
|
|
1871
1926
|
console.log(` Leads : ${analytics.funnel.acquisitionLeads}`);
|
|
1872
1927
|
console.log(` Sprint Leads : ${analytics.pipeline.workflowSprintLeads.total}`);
|
|
@@ -1877,6 +1932,7 @@ function printDashboard(data) {
|
|
|
1877
1932
|
console.log(` Booked Revenue : $${(analytics.revenue.bookedRevenueCents / 100).toFixed(2)}`);
|
|
1878
1933
|
console.log(` Matched Journeys : ${analytics.reconciliation.matchedPaidOrders}/${analytics.reconciliation.telemetryCheckoutStarts}`);
|
|
1879
1934
|
console.log(` Buyer Loss : ${analytics.buyerLoss.totalSignals}`);
|
|
1935
|
+
console.log(` Data Quality : ${analytics.firstPartyTrafficQuality.verdict} (${instrumentation.excludedTelemetryEvents}/${instrumentation.rawTelemetryEvents} excluded)`);
|
|
1880
1936
|
if (analytics.telemetry.visitors.topSource) {
|
|
1881
1937
|
console.log(` Top Source : ${analytics.telemetry.visitors.topSource.key} (${analytics.telemetry.visitors.topSource.count}\u00D7)`);
|
|
1882
1938
|
}
|
|
@@ -1896,6 +1952,15 @@ function printDashboard(data) {
|
|
|
1896
1952
|
console.log(` GA4 : ${instrumentation.ga4Configured ? 'configured' : 'missing'}`);
|
|
1897
1953
|
console.log(` Search Console : ${instrumentation.googleSearchConsoleConfigured ? 'configured' : 'missing'}`);
|
|
1898
1954
|
console.log(` Telemetry Events : ${instrumentation.telemetryEventsPresent ? instrumentation.uniqueVisitorsTracked : 0} visitors`);
|
|
1955
|
+
console.log(` Clean Visitors : ${instrumentation.externalVisitorsTracked} external (${Math.round((instrumentation.internalTestPollutionRate || 0) * 100)}% internal/test/bot events)`);
|
|
1956
|
+
console.log(` Clean Paths : ${instrumentation.externalVisitorPathsTracked} first-party paths`);
|
|
1957
|
+
if (instrumentation.topExcludedTrafficReason) {
|
|
1958
|
+
console.log(` Top Exclusion : ${instrumentation.topExcludedTrafficReason.key} (${instrumentation.topExcludedTrafficReason.count}\u00D7)`);
|
|
1959
|
+
}
|
|
1960
|
+
console.log(` Plausible Export : ${instrumentation.plausibleExportConfigured ? 'configured' : 'missing API credentials'}`);
|
|
1961
|
+
console.log(` PostHog Export : ${instrumentation.posthogExportConfigured ? 'configured' : 'missing API credentials'}`);
|
|
1962
|
+
console.log(` GA4 Export : ${instrumentation.ga4ExportConfigured ? 'configured' : 'missing API credentials'}`);
|
|
1963
|
+
console.log(` Visitor Paths : ${instrumentation.dashboardGradeExportReady ? 'export-ready' : 'not dashboard-grade in repo'}`);
|
|
1899
1964
|
console.log(` SEO Signals : ${instrumentation.seoSignalsPresent ? analytics.seo.landingViews : 0}`);
|
|
1900
1965
|
console.log(` Buyer Loss : ${instrumentation.buyerLossSignalsPresent ? analytics.buyerLoss.totalSignals : 0}`);
|
|
1901
1966
|
console.log(` Attribution : ${Math.round((instrumentation.trafficAttributionCoverage || 0) * 100)}% page-view coverage`);
|
|
@@ -2025,6 +2090,7 @@ module.exports = {
|
|
|
2025
2090
|
computeObservabilityStats,
|
|
2026
2091
|
readJSONL,
|
|
2027
2092
|
readJsonFile,
|
|
2093
|
+
collectAllFeedbackEntries,
|
|
2028
2094
|
};
|
|
2029
2095
|
|
|
2030
2096
|
if (require.main === module) {
|
|
@@ -6,6 +6,7 @@ const path = require('path');
|
|
|
6
6
|
|
|
7
7
|
const { getFeedbackPaths } = require('./feedback-loop');
|
|
8
8
|
const { ensureDir } = require('./fs-utils');
|
|
9
|
+
const { redactSecretsDeep } = require('./secret-redaction');
|
|
9
10
|
|
|
10
11
|
const PROJECT_ROOT = path.join(__dirname, '..');
|
|
11
12
|
const DEFAULT_PROOF_DIR = process.env.THUMBGATE_PROOF_DIR
|
|
@@ -47,7 +48,10 @@ function readJSON(filePath) {
|
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
function writeJSONL(filePath, rows) {
|
|
50
|
-
|
|
51
|
+
// Redact secrets from every bundle row — this is the single choke point for all bundle tables
|
|
52
|
+
// (feedback_events, memory_records, sequences, attributions, proof_reports). A shared/published
|
|
53
|
+
// dataset must never ship a captured credential. See scripts/secret-redaction.js.
|
|
54
|
+
const content = rows.map((row) => JSON.stringify(redactSecretsDeep(row))).join('\n');
|
|
51
55
|
fs.writeFileSync(filePath, content ? `${content}\n` : '');
|
|
52
56
|
}
|
|
53
57
|
|
|
@@ -9,6 +9,7 @@ const fs = require('fs');
|
|
|
9
9
|
const path = require('path');
|
|
10
10
|
const { traceForDpoPair, aggregateTraces } = require('./code-reasoning');
|
|
11
11
|
const { resolveFeedbackDir } = require('./feedback-paths');
|
|
12
|
+
const { redactSecretsDeep } = require('./secret-redaction');
|
|
12
13
|
|
|
13
14
|
const DEFAULT_LOCAL_MEMORY_LOG = path.join(resolveFeedbackDir(), 'memory-log.jsonl');
|
|
14
15
|
|
|
@@ -201,14 +202,18 @@ function exportDpoFromMemories(memories) {
|
|
|
201
202
|
},
|
|
202
203
|
}));
|
|
203
204
|
|
|
205
|
+
// Redact secrets before the pairs leave this module — they are derived from memory content and
|
|
206
|
+
// are shipped to disk here AND consumed by export-hf-dataset.js. See scripts/secret-redaction.js.
|
|
207
|
+
const redactedPairs = pairsWithTraces.map((pair) => redactSecretsDeep(pair));
|
|
208
|
+
|
|
204
209
|
return {
|
|
205
|
-
pairs:
|
|
210
|
+
pairs: redactedPairs,
|
|
206
211
|
unpairedErrors: result.unpairedErrors,
|
|
207
212
|
unpairedLearnings: result.unpairedLearnings,
|
|
208
213
|
errors,
|
|
209
214
|
learnings,
|
|
210
215
|
reasoning,
|
|
211
|
-
jsonl: toJSONL(
|
|
216
|
+
jsonl: toJSONL(redactedPairs),
|
|
212
217
|
};
|
|
213
218
|
}
|
|
214
219
|
|
package/scripts/feedback-loop.js
CHANGED
|
@@ -56,6 +56,90 @@ const {
|
|
|
56
56
|
|
|
57
57
|
const AUDIT_TRAIL_TAG = 'audit-trail';
|
|
58
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Anonymous fire-and-forget CLI feedback telemetry.
|
|
61
|
+
*
|
|
62
|
+
* Pings the hosted /v1/telemetry/ping endpoint exactly once per successful
|
|
63
|
+
* local feedback capture so the dashboard can measure CLI-side lesson volume.
|
|
64
|
+
*
|
|
65
|
+
* Hard contract (do NOT widen without explicit approval):
|
|
66
|
+
* - ONE event type: `feedback_captured`
|
|
67
|
+
* - Payload: { installId, signal: 'up'|'down', tier, ts } only.
|
|
68
|
+
* No context strings, tags, file paths, or content of any kind.
|
|
69
|
+
* - Opt-out: THUMBGATE_DISABLE_TELEMETRY=1 (or 'true') short-circuits
|
|
70
|
+
* immediately. Legacy THUMBGATE_NO_TELEMETRY=1 / DO_NOT_TRACK=1 are
|
|
71
|
+
* also honored for parity with cli-telemetry.js.
|
|
72
|
+
* - Fire-and-forget: NEVER await this call. Errors are swallowed.
|
|
73
|
+
* - 2-second timeout via AbortSignal.timeout.
|
|
74
|
+
*/
|
|
75
|
+
function emitAnonymousFeedbackPing(signal) {
|
|
76
|
+
try {
|
|
77
|
+
const env = process.env || {};
|
|
78
|
+
if (
|
|
79
|
+
env.THUMBGATE_DISABLE_TELEMETRY === '1' ||
|
|
80
|
+
env.THUMBGATE_DISABLE_TELEMETRY === 'true' ||
|
|
81
|
+
env.THUMBGATE_NO_TELEMETRY === '1' ||
|
|
82
|
+
env.DO_NOT_TRACK === '1'
|
|
83
|
+
) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const normalizedSignal = signal === 'positive' ? 'up' : signal === 'negative' ? 'down' : null;
|
|
88
|
+
if (!normalizedSignal) return;
|
|
89
|
+
|
|
90
|
+
// Reuse the canonical installId from cli-telemetry.js (persisted at
|
|
91
|
+
// ~/.thumbgate/install-id). Falls back to a fresh UUID if that module
|
|
92
|
+
// is unavailable — better to ship an event we can dedup on the server
|
|
93
|
+
// than to drop the ping entirely.
|
|
94
|
+
let installId = null;
|
|
95
|
+
try {
|
|
96
|
+
const { getInstallId } = require('./cli-telemetry');
|
|
97
|
+
installId = getInstallId();
|
|
98
|
+
} catch (_) { /* fall through */ }
|
|
99
|
+
if (!installId) {
|
|
100
|
+
try {
|
|
101
|
+
installId = require('crypto').randomUUID();
|
|
102
|
+
} catch (_) {
|
|
103
|
+
return; // no crypto, no install id → drop silently
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
let tier = 'free';
|
|
108
|
+
try {
|
|
109
|
+
const { getStatuslineMeta } = require('./statusline-meta');
|
|
110
|
+
const meta = getStatuslineMeta({ env });
|
|
111
|
+
const rawTier = String(meta && meta.tier ? meta.tier : 'free').toLowerCase();
|
|
112
|
+
if (rawTier === 'pro' || rawTier === 'enterprise' || rawTier === 'free') {
|
|
113
|
+
tier = rawTier;
|
|
114
|
+
}
|
|
115
|
+
} catch (_) { /* default to 'free' */ }
|
|
116
|
+
|
|
117
|
+
const base = env.THUMBGATE_PUBLIC_APP_ORIGIN
|
|
118
|
+
|| env.THUMBGATE_API_URL
|
|
119
|
+
|| 'https://thumbgate-production.up.railway.app';
|
|
120
|
+
|
|
121
|
+
const body = JSON.stringify({
|
|
122
|
+
eventType: 'feedback_captured',
|
|
123
|
+
clientType: 'cli',
|
|
124
|
+
installId,
|
|
125
|
+
signal: normalizedSignal,
|
|
126
|
+
tier,
|
|
127
|
+
ts: new Date().toISOString(),
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Fire-and-forget. No await. AbortSignal.timeout enforces the 2s cap.
|
|
131
|
+
if (typeof fetch !== 'function' || typeof AbortSignal === 'undefined' || typeof AbortSignal.timeout !== 'function') {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
fetch(`${base.replace(/\/+$/, '')}/v1/telemetry/ping`, {
|
|
135
|
+
method: 'POST',
|
|
136
|
+
headers: { 'Content-Type': 'application/json' },
|
|
137
|
+
body,
|
|
138
|
+
signal: AbortSignal.timeout(2000),
|
|
139
|
+
}).catch(() => { /* fire-and-forget */ });
|
|
140
|
+
} catch (_) { /* telemetry must never disrupt CLI */ }
|
|
141
|
+
}
|
|
142
|
+
|
|
59
143
|
function isAuditTrailEntry(entry = {}) {
|
|
60
144
|
return Array.isArray(entry.tags) && entry.tags.includes(AUDIT_TRAIL_TAG);
|
|
61
145
|
}
|
|
@@ -1059,6 +1143,7 @@ function captureFeedback(params) {
|
|
|
1059
1143
|
: null),
|
|
1060
1144
|
structuredRule: structuredRule || null,
|
|
1061
1145
|
...(reflection && { reflection }),
|
|
1146
|
+
gateAction: params.gateAction || null,
|
|
1062
1147
|
timestamp: now,
|
|
1063
1148
|
};
|
|
1064
1149
|
|
|
@@ -1112,6 +1197,7 @@ function captureFeedback(params) {
|
|
|
1112
1197
|
summary.lastUpdated = now;
|
|
1113
1198
|
saveSummary(summary);
|
|
1114
1199
|
appendJSONL(FEEDBACK_LOG_PATH, feedbackEvent);
|
|
1200
|
+
emitAnonymousFeedbackPing(signal);
|
|
1115
1201
|
try { appendRejectionLedger(feedbackEvent, action.reason); } catch { /* non-critical */ }
|
|
1116
1202
|
try {
|
|
1117
1203
|
appendSequence(historyEntries, feedbackEvent, getFeedbackPaths(), { accepted: false });
|
|
@@ -1153,6 +1239,7 @@ function captureFeedback(params) {
|
|
|
1153
1239
|
...feedbackEvent,
|
|
1154
1240
|
validationIssues: prepared.issues,
|
|
1155
1241
|
});
|
|
1242
|
+
emitAnonymousFeedbackPing(signal);
|
|
1156
1243
|
try { appendRejectionLedger(feedbackEvent, `Schema validation failed: ${prepared.issues.join('; ')}`); } catch { /* non-critical */ }
|
|
1157
1244
|
try {
|
|
1158
1245
|
appendSequence(historyEntries, feedbackEvent, getFeedbackPaths(), { accepted: false });
|
|
@@ -1227,6 +1314,7 @@ function captureFeedback(params) {
|
|
|
1227
1314
|
}
|
|
1228
1315
|
|
|
1229
1316
|
appendJSONL(FEEDBACK_LOG_PATH, feedbackEvent);
|
|
1317
|
+
emitAnonymousFeedbackPing(signal);
|
|
1230
1318
|
|
|
1231
1319
|
// Synthesis: merge similar lessons instead of creating duplicates
|
|
1232
1320
|
let synthesisResult = null;
|
|
@@ -1398,7 +1486,7 @@ function captureFeedback(params) {
|
|
|
1398
1486
|
if (feedbackEvent.signal === 'negative') {
|
|
1399
1487
|
try {
|
|
1400
1488
|
const autoPromote = require('./auto-promote-gates');
|
|
1401
|
-
const promoteResult = autoPromote.promote(FEEDBACK_LOG_PATH);
|
|
1489
|
+
const promoteResult = autoPromote.promote(FEEDBACK_LOG_PATH, { gateAction: feedbackEvent.gateAction });
|
|
1402
1490
|
// First-rule activation telemetry: anonymous ping the first time
|
|
1403
1491
|
// a prevention rule auto-promotes for this install. Idempotent —
|
|
1404
1492
|
// see scripts/activation-tracker.js. Critical for activation funnel
|
|
@@ -1411,10 +1499,44 @@ function captureFeedback(params) {
|
|
|
1411
1499
|
totalGates: promoteResult.totalGates,
|
|
1412
1500
|
});
|
|
1413
1501
|
} catch { /* activation telemetry is non-critical */ }
|
|
1502
|
+
|
|
1503
|
+
// Trigger Self-Harness Optimizer to propagate the new rules to prompt files & validate
|
|
1504
|
+
try {
|
|
1505
|
+
const { fork } = require('child_process');
|
|
1506
|
+
const localOptimizerPath = path.join(process.cwd(), 'scripts', 'self-harness-optimizer.js');
|
|
1507
|
+
const packageOptimizerPath = path.join(__dirname, 'self-harness-optimizer.js');
|
|
1508
|
+
|
|
1509
|
+
if (fs.existsSync(localOptimizerPath)) {
|
|
1510
|
+
fork(localOptimizerPath, [], { stdio: 'ignore', detached: true }).unref();
|
|
1511
|
+
} else if (fs.existsSync(packageOptimizerPath)) {
|
|
1512
|
+
fork(packageOptimizerPath, [], { stdio: 'ignore', detached: true }).unref();
|
|
1513
|
+
}
|
|
1514
|
+
} catch (err) {
|
|
1515
|
+
console.error('Failed to trigger self-harness optimizer:', err);
|
|
1516
|
+
}
|
|
1414
1517
|
}
|
|
1415
1518
|
} catch { /* Gate promotion is non-critical */ }
|
|
1416
1519
|
}
|
|
1417
1520
|
|
|
1521
|
+
// Auto-export to Obsidian if configured (deferred but tracked)
|
|
1522
|
+
if (process.env.THUMBGATE_OBSIDIAN_VAULT_PATH) {
|
|
1523
|
+
const exportPromise = new Promise((resolve) => {
|
|
1524
|
+
setImmediate(() => {
|
|
1525
|
+
try {
|
|
1526
|
+
const { exportAll } = require('./obsidian-export');
|
|
1527
|
+
exportAll({
|
|
1528
|
+
feedbackDir: FEEDBACK_DIR,
|
|
1529
|
+
outputDir: process.env.THUMBGATE_OBSIDIAN_VAULT_PATH,
|
|
1530
|
+
});
|
|
1531
|
+
} catch (_err) {
|
|
1532
|
+
// Non-critical, do not crash feedback loop
|
|
1533
|
+
}
|
|
1534
|
+
resolve();
|
|
1535
|
+
});
|
|
1536
|
+
});
|
|
1537
|
+
trackBackgroundSideEffect(exportPromise);
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1418
1540
|
// --- Deferred side-effects (contextFs, RLAIF — non-critical, potentially slow) ---
|
|
1419
1541
|
setImmediate(() => {
|
|
1420
1542
|
try {
|
|
@@ -62,6 +62,92 @@ function normalizeFeedbackText(value) {
|
|
|
62
62
|
.trim();
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
function editDistance(a, b) {
|
|
66
|
+
const left = String(a || '');
|
|
67
|
+
const right = String(b || '');
|
|
68
|
+
const dp = Array.from({ length: left.length + 1 }, () => Array(right.length + 1).fill(0));
|
|
69
|
+
for (let i = 0; i <= left.length; i++) dp[i][0] = i;
|
|
70
|
+
for (let j = 0; j <= right.length; j++) dp[0][j] = j;
|
|
71
|
+
for (let i = 1; i <= left.length; i++) {
|
|
72
|
+
for (let j = 1; j <= right.length; j++) {
|
|
73
|
+
const cost = left[i - 1] === right[j - 1] ? 0 : 1;
|
|
74
|
+
dp[i][j] = Math.min(
|
|
75
|
+
dp[i - 1][j] + 1,
|
|
76
|
+
dp[i][j - 1] + 1,
|
|
77
|
+
dp[i - 1][j - 1] + cost,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return dp[left.length][right.length];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function isNearThumbToken(token) {
|
|
85
|
+
const value = String(token || '');
|
|
86
|
+
if (value.length < 4) return false;
|
|
87
|
+
return editDistance(value, 'thumb') <= 1 || editDistance(value, 'thumbs') <= 2;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function isNearUpToken(token) {
|
|
91
|
+
const value = String(token || '');
|
|
92
|
+
return value === 'up' || editDistance(value, 'up') <= 1;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function isNearDownToken(token) {
|
|
96
|
+
const value = String(token || '');
|
|
97
|
+
if (value.length < 2) return false;
|
|
98
|
+
return editDistance(value, 'down') <= 1;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function detectFeedbackSignal(value) {
|
|
102
|
+
const raw = String(value || '');
|
|
103
|
+
if (/[👎👎🏻👎🏼👎🏽👎🏾👎🏿]/u.test(raw)) return { signal: 'down', confidence: 'emoji', match: '👎' };
|
|
104
|
+
if (/[👍👍🏻👍🏼👍🏽👍🏾👍🏿]/u.test(raw)) return { signal: 'up', confidence: 'emoji', match: '👍' };
|
|
105
|
+
|
|
106
|
+
const normalized = normalizeFeedbackText(raw);
|
|
107
|
+
if (!normalized) return null;
|
|
108
|
+
|
|
109
|
+
const exactDown = [
|
|
110
|
+
/\bthumbs?\s*down\b/,
|
|
111
|
+
/\bthumbs?down\b/,
|
|
112
|
+
/\bthat failed\b/,
|
|
113
|
+
/\bit failed\b/,
|
|
114
|
+
/\bthat was wrong\b/,
|
|
115
|
+
/\bfix this\b/,
|
|
116
|
+
];
|
|
117
|
+
if (exactDown.some((pattern) => pattern.test(normalized))) {
|
|
118
|
+
return { signal: 'down', confidence: 'exact', match: normalized };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const exactUp = [
|
|
122
|
+
/\bthumbs?\s*up\b/,
|
|
123
|
+
/\bthumbs?up\b/,
|
|
124
|
+
/\bthat worked\b/,
|
|
125
|
+
/\bit worked\b/,
|
|
126
|
+
/\blooks good\b/,
|
|
127
|
+
/\bgood job\b/,
|
|
128
|
+
/\bgood work\b/,
|
|
129
|
+
/\bnice work\b/,
|
|
130
|
+
/\bperfect\b/,
|
|
131
|
+
/\blgtm\b/,
|
|
132
|
+
];
|
|
133
|
+
if (exactUp.some((pattern) => pattern.test(normalized))) {
|
|
134
|
+
return { signal: 'up', confidence: 'exact', match: normalized };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const words = normalized.split(/\s+/).filter(Boolean);
|
|
138
|
+
for (let i = 0; i < words.length - 1; i++) {
|
|
139
|
+
if (!isNearThumbToken(words[i])) continue;
|
|
140
|
+
if (isNearDownToken(words[i + 1])) {
|
|
141
|
+
return { signal: 'down', confidence: 'fuzzy', match: `${words[i]} ${words[i + 1]}` };
|
|
142
|
+
}
|
|
143
|
+
if (isNearUpToken(words[i + 1])) {
|
|
144
|
+
return { signal: 'up', confidence: 'fuzzy', match: `${words[i]} ${words[i + 1]}` };
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
|
|
65
151
|
function isGenericFeedbackText(value, signal) {
|
|
66
152
|
const normalized = normalizeFeedbackText(value);
|
|
67
153
|
if (!normalized) return false;
|
|
@@ -131,6 +217,7 @@ function buildClarificationMessage(params = {}) {
|
|
|
131
217
|
|
|
132
218
|
module.exports = {
|
|
133
219
|
GENERIC_PHRASE_RULES,
|
|
220
|
+
detectFeedbackSignal,
|
|
134
221
|
normalizeFeedbackSignal,
|
|
135
222
|
normalizeFeedbackText,
|
|
136
223
|
isGenericFeedbackText,
|