thumbgate 1.28.1 โ 1.28.3
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/mcp/server-stdio.js +100 -4
- package/adapters/opencode/opencode.json +1 -1
- package/bin/cli.js +30 -7
- package/config/gates/default.json +2 -1
- package/config/mcp-allowlists.json +5 -0
- package/package.json +9 -4
- package/public/index.html +2 -2
- package/public/numbers.html +2 -2
- package/scripts/auto-wire-hooks.js +58 -2
- package/scripts/claude-feedback-sync.js +1 -1
- package/scripts/cli-feedback.js +5 -0
- package/scripts/cross-encoder-reranker.js +235 -0
- package/scripts/feedback-history-distiller.js +385 -0
- package/scripts/feedback-loop.js +45 -22
- package/scripts/gates-engine.js +6 -1
- package/scripts/lesson-embedding-index.js +203 -0
- package/scripts/lesson-reranker.js +263 -0
- package/scripts/lesson-retrieval.js +485 -0
- package/scripts/llm-client.js +18 -1
- package/scripts/mcp-policy.js +7 -1
- package/scripts/workflow-sentinel.js +34 -6
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thumbgate",
|
|
3
3
|
"description": "One ๐ becomes a hard rule the agent cannot bypass. Captures thumbs-down feedback, distills it into PreToolUse Pre-Action Checks, enforced across every future Claude Code session.",
|
|
4
|
-
"version": "1.28.
|
|
4
|
+
"version": "1.28.3",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Igor Ganapolsky",
|
|
7
7
|
"email": "ig5973700@gmail.com",
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
"mcpServers": {
|
|
3
3
|
"thumbgate": {
|
|
4
4
|
"command": "npx",
|
|
5
|
-
"args": ["--yes", "--package", "thumbgate@1.28.
|
|
5
|
+
"args": ["--yes", "--package", "thumbgate@1.28.3", "thumbgate", "serve"]
|
|
6
6
|
}
|
|
7
7
|
},
|
|
8
8
|
"hooks": {
|
|
9
9
|
"preToolUse": {
|
|
10
10
|
"command": "npx",
|
|
11
|
-
"args": ["--yes", "--package", "thumbgate@1.28.
|
|
11
|
+
"args": ["--yes", "--package", "thumbgate@1.28.3", "thumbgate", "gate-check"]
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
}
|
|
@@ -115,7 +115,11 @@ const {
|
|
|
115
115
|
const {
|
|
116
116
|
retrieveRelevantLessons,
|
|
117
117
|
} = loadOptionalModule(path.join(__dirname, '../../scripts/lesson-retrieval'), () => ({
|
|
118
|
-
retrieveRelevantLessons: () =>
|
|
118
|
+
retrieveRelevantLessons: () => {
|
|
119
|
+
const error = new Error('retrieve_lessons is unavailable because the packaged retrieval modules are missing.');
|
|
120
|
+
error.code = 'THUMBGATE_CAPABILITY_UNAVAILABLE';
|
|
121
|
+
throw error;
|
|
122
|
+
},
|
|
119
123
|
}));
|
|
120
124
|
const {
|
|
121
125
|
searchThumbgate,
|
|
@@ -186,6 +190,64 @@ const PRIVATE_MCP_MODULES = Object.freeze({
|
|
|
186
190
|
lessonInference: path.resolve(__dirname, '../../scripts/lesson-inference.js'),
|
|
187
191
|
lessonSearch: path.resolve(__dirname, '../../scripts/lesson-search.js'),
|
|
188
192
|
});
|
|
193
|
+
const PUBLIC_MCP_MODULES = Object.freeze({
|
|
194
|
+
lessonRetrieval: path.resolve(__dirname, '../../scripts/lesson-retrieval.js'),
|
|
195
|
+
lessonReranker: path.resolve(__dirname, '../../scripts/lesson-reranker.js'),
|
|
196
|
+
crossEncoderReranker: path.resolve(__dirname, '../../scripts/cross-encoder-reranker.js'),
|
|
197
|
+
lessonEmbeddingIndex: path.resolve(__dirname, '../../scripts/lesson-embedding-index.js'),
|
|
198
|
+
});
|
|
199
|
+
const PRIVATE_TOOL_MODULE_KEYS = Object.freeze({
|
|
200
|
+
search_lessons: ['lessonSearch'],
|
|
201
|
+
reflect_on_feedback: ['reflectorAgent'],
|
|
202
|
+
list_intents: ['intentRouter'],
|
|
203
|
+
plan_intent: ['intentRouter'],
|
|
204
|
+
start_handoff: ['intentRouter', 'delegationRuntime'],
|
|
205
|
+
complete_handoff: ['delegationRuntime'],
|
|
206
|
+
distribute_context_to_agents: ['swarmCoordinator'],
|
|
207
|
+
session_report: ['sessionReport'],
|
|
208
|
+
generate_operator_artifact: ['operatorArtifacts'],
|
|
209
|
+
org_dashboard: ['orgDashboard'],
|
|
210
|
+
get_business_metrics: ['semanticLayer'],
|
|
211
|
+
describe_semantic_entity: ['semanticLayer'],
|
|
212
|
+
run_managed_lesson_agent: ['managedLessonAgent'],
|
|
213
|
+
managed_agent_status: ['managedLessonAgent'],
|
|
214
|
+
context_stuff_lessons: ['lessonInference'],
|
|
215
|
+
});
|
|
216
|
+
const PUBLIC_TOOL_MODULE_KEYS = Object.freeze({
|
|
217
|
+
retrieve_lessons: [
|
|
218
|
+
'lessonRetrieval',
|
|
219
|
+
'lessonReranker',
|
|
220
|
+
'crossEncoderReranker',
|
|
221
|
+
'lessonEmbeddingIndex',
|
|
222
|
+
],
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
function getToolCapability(toolName, options = {}) {
|
|
226
|
+
const privateKeys = PRIVATE_TOOL_MODULE_KEYS[toolName] || [];
|
|
227
|
+
const publicKeys = PUBLIC_TOOL_MODULE_KEYS[toolName] || [];
|
|
228
|
+
const privateModuleAvailable = options.existsSync
|
|
229
|
+
? (key) => options.existsSync(PRIVATE_MCP_MODULES[key])
|
|
230
|
+
: (key) => Boolean(loadPrivateMcpModule(key));
|
|
231
|
+
const publicModuleAvailable = options.existsSync
|
|
232
|
+
? (key) => options.existsSync(PUBLIC_MCP_MODULES[key])
|
|
233
|
+
: (key) => fs.existsSync(PUBLIC_MCP_MODULES[key]);
|
|
234
|
+
const missingPrivateModules = privateKeys.filter((key) => !privateModuleAvailable(key));
|
|
235
|
+
const missingPublicModules = publicKeys.filter((key) => !publicModuleAvailable(key));
|
|
236
|
+
if (missingPrivateModules.length > 0) {
|
|
237
|
+
return { available: false, availability: 'private_core', missingModules: missingPrivateModules };
|
|
238
|
+
}
|
|
239
|
+
if (missingPublicModules.length > 0) {
|
|
240
|
+
return { available: false, availability: 'package_incomplete', missingModules: missingPublicModules };
|
|
241
|
+
}
|
|
242
|
+
return { available: true, availability: privateKeys.length > 0 ? 'private_core' : 'public', missingModules: [] };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function getExposedTools(profileName = getActiveMcpProfile(), options = {}) {
|
|
246
|
+
const allowed = new Set(getAllowedTools(profileName));
|
|
247
|
+
return TOOLS.filter((tool) => allowed.has(tool.name) && getToolCapability(tool.name, options).available);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const PRIVATE_MCP_TOOL_REQUIREMENTS = PRIVATE_TOOL_MODULE_KEYS;
|
|
189
251
|
|
|
190
252
|
function loadPrivateMcpModule(key) {
|
|
191
253
|
const modulePath = PRIVATE_MCP_MODULES[key];
|
|
@@ -204,6 +266,18 @@ function loadPrivateMcpModule(key) {
|
|
|
204
266
|
}
|
|
205
267
|
}
|
|
206
268
|
|
|
269
|
+
function isToolAvailable(toolName) {
|
|
270
|
+
try {
|
|
271
|
+
return getToolCapability(toolName).available;
|
|
272
|
+
} catch {
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function listAvailableTools(profileName = getActiveMcpProfile()) {
|
|
278
|
+
return getExposedTools(profileName);
|
|
279
|
+
}
|
|
280
|
+
|
|
207
281
|
function unavailablePrivateMcpFeature(toolName) {
|
|
208
282
|
return toTextResult({
|
|
209
283
|
ok: false,
|
|
@@ -231,7 +305,7 @@ const {
|
|
|
231
305
|
finalizeSession: finalizeFeedbackSession,
|
|
232
306
|
} = require('../../scripts/feedback-session');
|
|
233
307
|
|
|
234
|
-
const SERVER_INFO = { name: 'thumbgate-mcp', version: '1.28.
|
|
308
|
+
const SERVER_INFO = { name: 'thumbgate-mcp', version: '1.28.3' };
|
|
235
309
|
const COMMERCE_CATEGORIES = [
|
|
236
310
|
'product_recommendation',
|
|
237
311
|
'brand_compliance',
|
|
@@ -673,7 +747,21 @@ function buildEstimateUncertaintyResponse(args = {}) {
|
|
|
673
747
|
}
|
|
674
748
|
|
|
675
749
|
async function callTool(name, args = {}) {
|
|
676
|
-
|
|
750
|
+
const activeProfile = getActiveMcpProfile();
|
|
751
|
+
assertToolAllowed(name, activeProfile);
|
|
752
|
+
const capability = getToolCapability(name);
|
|
753
|
+
if (!capability.available) {
|
|
754
|
+
if (capability.availability === 'private_core') {
|
|
755
|
+
return unavailablePrivateMcpFeature(name);
|
|
756
|
+
}
|
|
757
|
+
const error = new Error(
|
|
758
|
+
`Tool '${name}' is unavailable (${capability.availability}): missing ${capability.missingModules.join(', ')}.`,
|
|
759
|
+
);
|
|
760
|
+
error.code = 'THUMBGATE_CAPABILITY_UNAVAILABLE';
|
|
761
|
+
error.errorCategory = 'capability';
|
|
762
|
+
error.isRetryable = false;
|
|
763
|
+
throw error;
|
|
764
|
+
}
|
|
677
765
|
|
|
678
766
|
// Validate tool input contract against schema
|
|
679
767
|
const { TOOLS } = require('../../scripts/tool-registry');
|
|
@@ -1357,7 +1445,7 @@ async function handleRequest(message) {
|
|
|
1357
1445
|
};
|
|
1358
1446
|
}
|
|
1359
1447
|
if (message.method === 'ping') return {};
|
|
1360
|
-
if (message.method === 'tools/list') return { tools:
|
|
1448
|
+
if (message.method === 'tools/list') return { tools: getExposedTools() };
|
|
1361
1449
|
if (message.method === 'tools/call') return callTool(message.params.name, message.params.arguments);
|
|
1362
1450
|
throw new Error(`Unsupported method: ${message.method}`);
|
|
1363
1451
|
}
|
|
@@ -1549,6 +1637,8 @@ if (require.main === module) startStdioServer();
|
|
|
1549
1637
|
|
|
1550
1638
|
module.exports = {
|
|
1551
1639
|
TOOLS,
|
|
1640
|
+
getExposedTools,
|
|
1641
|
+
getToolCapability,
|
|
1552
1642
|
SAFE_DATA_DIR,
|
|
1553
1643
|
handleRequest,
|
|
1554
1644
|
callTool,
|
|
@@ -1559,7 +1649,13 @@ module.exports = {
|
|
|
1559
1649
|
buildSuggestFixResponse,
|
|
1560
1650
|
__test__: {
|
|
1561
1651
|
PRIVATE_MCP_MODULES,
|
|
1652
|
+
PRIVATE_TOOL_MODULE_KEYS,
|
|
1653
|
+
PUBLIC_MCP_MODULES,
|
|
1654
|
+
PUBLIC_TOOL_MODULE_KEYS,
|
|
1655
|
+
PRIVATE_MCP_TOOL_REQUIREMENTS,
|
|
1562
1656
|
loadPrivateMcpModule,
|
|
1657
|
+
isToolAvailable,
|
|
1658
|
+
listAvailableTools,
|
|
1563
1659
|
unavailablePrivateMcpFeature,
|
|
1564
1660
|
callToolInner,
|
|
1565
1661
|
},
|
package/bin/cli.js
CHANGED
|
@@ -444,6 +444,11 @@ function codexPreToolHookSectionBlock() {
|
|
|
444
444
|
return `[hooks.pre_tool_use]\ncommand = "${entry.command}"\nargs = ${formatTomlStringArray(entry.args)}\n`;
|
|
445
445
|
}
|
|
446
446
|
|
|
447
|
+
function codexUserPromptHookSectionBlock() {
|
|
448
|
+
const entry = canonicalCodexCliEntry(['hook-auto-capture']);
|
|
449
|
+
return `[hooks.user_prompt_submit]\ncommand = "${entry.command}"\nargs = ${formatTomlStringArray(entry.args)}\n`;
|
|
450
|
+
}
|
|
451
|
+
|
|
447
452
|
function mcpSectionRegex(name) {
|
|
448
453
|
return new RegExp(
|
|
449
454
|
`^\\[mcp_servers\\.${escapeRegExp(name)}\\]\\n(?:^(?!\\[).*(?:\\n|$))*`,
|
|
@@ -461,6 +466,7 @@ function tomlSectionRegex(name) {
|
|
|
461
466
|
function upsertCodexServerConfig(content) {
|
|
462
467
|
const canonicalBlock = codexMcpSectionBlock(MCP_SERVER_NAME);
|
|
463
468
|
const canonicalHookBlock = codexPreToolHookSectionBlock();
|
|
469
|
+
const canonicalUserPromptHookBlock = codexUserPromptHookSectionBlock();
|
|
464
470
|
const sections = MCP_SERVER_NAMES.map((name) => ({
|
|
465
471
|
name,
|
|
466
472
|
regex: mcpSectionRegex(name),
|
|
@@ -473,7 +479,7 @@ function upsertCodexServerConfig(content) {
|
|
|
473
479
|
const prefix = content.trimEnd();
|
|
474
480
|
return {
|
|
475
481
|
changed: true,
|
|
476
|
-
content: `${prefix}${prefix ? '\n\n' : ''}${canonicalBlock}\n${canonicalHookBlock}`,
|
|
482
|
+
content: `${prefix}${prefix ? '\n\n' : ''}${canonicalBlock}\n${canonicalHookBlock}\n${canonicalUserPromptHookBlock}`,
|
|
477
483
|
};
|
|
478
484
|
}
|
|
479
485
|
|
|
@@ -517,6 +523,19 @@ function upsertCodexServerConfig(content) {
|
|
|
517
523
|
changed = true;
|
|
518
524
|
}
|
|
519
525
|
|
|
526
|
+
const userPromptHookRegex = tomlSectionRegex('hooks.user_prompt_submit');
|
|
527
|
+
if (userPromptHookRegex.test(nextContent)) {
|
|
528
|
+
const current = nextContent.match(userPromptHookRegex)[0];
|
|
529
|
+
if (current !== canonicalUserPromptHookBlock) {
|
|
530
|
+
nextContent = nextContent.replace(userPromptHookRegex, canonicalUserPromptHookBlock);
|
|
531
|
+
changed = true;
|
|
532
|
+
}
|
|
533
|
+
} else {
|
|
534
|
+
const prefix = nextContent.trimEnd();
|
|
535
|
+
nextContent = `${prefix}${prefix ? '\n\n' : ''}${canonicalUserPromptHookBlock}`;
|
|
536
|
+
changed = true;
|
|
537
|
+
}
|
|
538
|
+
|
|
520
539
|
return {
|
|
521
540
|
changed,
|
|
522
541
|
content: nextContent.endsWith('\n') ? nextContent : `${nextContent}\n`,
|
|
@@ -2767,7 +2786,7 @@ function hookAutoCapture() {
|
|
|
2767
2786
|
|| promptEnvelope.prompt;
|
|
2768
2787
|
const { evaluatePromptGuard } = require(path.join(PKG_ROOT, 'scripts', 'prompt-guard'));
|
|
2769
2788
|
const { processInlineFeedback, formatCliOutput } = require(path.join(PKG_ROOT, 'scripts', 'cli-feedback'));
|
|
2770
|
-
const { detectFeedbackSignal } = require(path.join(PKG_ROOT, 'scripts', 'feedback-quality'));
|
|
2789
|
+
const { detectFeedbackSignal, isGenericFeedbackText } = require(path.join(PKG_ROOT, 'scripts', 'feedback-quality'));
|
|
2771
2790
|
const { loadOptionalModule } = require(path.join(PKG_ROOT, 'scripts', 'private-core-boundary'));
|
|
2772
2791
|
const { recordConversationEntry, readRecentConversationWindow } = loadOptionalModule(
|
|
2773
2792
|
path.join(PKG_ROOT, 'scripts', 'feedback-history-distiller'),
|
|
@@ -2800,6 +2819,12 @@ function hookAutoCapture() {
|
|
|
2800
2819
|
|
|
2801
2820
|
const signal = detected.signal;
|
|
2802
2821
|
const conversationWindow = readRecentConversationWindow({ limit: 8 });
|
|
2822
|
+
const chatHistory = conversationWindow.map((entry) => ({
|
|
2823
|
+
role: entry.author === 'assistant' ? 'assistant' : 'user',
|
|
2824
|
+
content: entry.text || '',
|
|
2825
|
+
}));
|
|
2826
|
+
const normalizedSignal = signal === 'down' ? 'negative' : 'positive';
|
|
2827
|
+
const genericFeedback = isGenericFeedbackText(prompt, normalizedSignal);
|
|
2803
2828
|
const { buildFeedbackSourceIdentity } = require(path.join(PKG_ROOT, 'scripts', 'feedback-loop'));
|
|
2804
2829
|
const sourceEvent = buildFeedbackSourceIdentity({
|
|
2805
2830
|
signal,
|
|
@@ -2813,11 +2838,9 @@ function hookAutoCapture() {
|
|
|
2813
2838
|
const result = processInlineFeedback({
|
|
2814
2839
|
signal,
|
|
2815
2840
|
context: prompt,
|
|
2816
|
-
chatHistory
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
whatWentWrong: signal === 'down' ? prompt : undefined,
|
|
2820
|
-
whatWorked: signal === 'up' ? prompt : undefined,
|
|
2841
|
+
chatHistory,
|
|
2842
|
+
whatWentWrong: signal === 'down' && !genericFeedback ? prompt : undefined,
|
|
2843
|
+
whatWorked: signal === 'up' && !genericFeedback ? prompt : undefined,
|
|
2821
2844
|
sourceEvent,
|
|
2822
2845
|
});
|
|
2823
2846
|
if (!result.feedbackResult || !result.feedbackResult.duplicate) {
|
|
@@ -210,7 +210,8 @@
|
|
|
210
210
|
{
|
|
211
211
|
"id": "deny-network-egress",
|
|
212
212
|
"layer": "Cloud",
|
|
213
|
-
"
|
|
213
|
+
"toolNames": ["Bash"],
|
|
214
|
+
"pattern": "(?:\\b(?:curl|wget)\\b[^\\n]{0,600}https?://(?!(?:github\\.com|registry\\.npmjs\\.org|api\\.anthropic\\.com|localhost|127\\.0\\.0\\.1|\\[::1\\]|0\\.0\\.0\\.0)(?:[:/?#]|$))[\\w.-]+|\\b(?:curl|wget)\\b\\s+(?:-\\S+\\s+)*(?!(?:https?://)?(?:github\\.com|registry\\.npmjs\\.org|api\\.anthropic\\.com|localhost|127\\.0\\.0\\.1|\\[::1\\]|0\\.0\\.0\\.0)(?:[:/?#]|$))(?:[\\w.-]+\\.)+[a-z]{2,}(?:[:/?#]|\\s|$)|\\bopen\\b[^\\n]{0,240}https?://(?!(?:github\\.com|registry\\.npmjs\\.org|api\\.anthropic\\.com|localhost|127\\.0\\.0\\.1|\\[::1\\]|0\\.0\\.0\\.0)(?:[:/?#]|$))[\\w.-]+|\\bnode\\b[^\\n]{0,240}\\bfetch\\s*\\(|\\bpython3?\\b[^\\n]{0,240}\\b(?:requests\\.(?:get|post)|urllib\\.request))",
|
|
214
215
|
"action": "warn",
|
|
215
216
|
"unless": "egress_approved",
|
|
216
217
|
"message": "Potential unauthorized network egress detected.",
|
|
@@ -68,6 +68,11 @@
|
|
|
68
68
|
"distribute_context_to_agents",
|
|
69
69
|
"session_report",
|
|
70
70
|
"generate_operator_artifact",
|
|
71
|
+
"run_managed_lesson_agent",
|
|
72
|
+
"managed_agent_status",
|
|
73
|
+
"run_self_distill",
|
|
74
|
+
"self_distill_status",
|
|
75
|
+
"context_stuff_lessons",
|
|
71
76
|
"perplexity_search",
|
|
72
77
|
"perplexity_ask",
|
|
73
78
|
"perplexity_research",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thumbgate",
|
|
3
|
-
"version": "1.28.
|
|
3
|
+
"version": "1.28.3",
|
|
4
4
|
"description": "ThumbGate Pre-Action Checks derive rules from repeated failures, flag risky tool calls, hard-block detected secret leaks, and block matches in strict mode.",
|
|
5
5
|
"homepage": "https://thumbgate.ai",
|
|
6
6
|
"repository": {
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
"scripts/bot-detection.js",
|
|
51
51
|
"scripts/build-metadata.js",
|
|
52
52
|
"scripts/classifier-routing.js",
|
|
53
|
+
"scripts/cross-encoder-reranker.js",
|
|
53
54
|
"scripts/chatgpt-ads-readiness-pack.js",
|
|
54
55
|
"scripts/claude-feedback-sync.js",
|
|
55
56
|
"scripts/cli-demo.js",
|
|
@@ -90,6 +91,7 @@
|
|
|
90
91
|
"scripts/export-hf-dataset.js",
|
|
91
92
|
"scripts/failure-diagnostics.js",
|
|
92
93
|
"scripts/feedback-aggregate.js",
|
|
94
|
+
"scripts/feedback-history-distiller.js",
|
|
93
95
|
"scripts/feedback-attribution.js",
|
|
94
96
|
"scripts/feedback-loop.js",
|
|
95
97
|
"scripts/feedback-paths.js",
|
|
@@ -124,7 +126,10 @@
|
|
|
124
126
|
"scripts/judge-reward-function.js",
|
|
125
127
|
"scripts/lesson-canonical.js",
|
|
126
128
|
"scripts/lesson-db.js",
|
|
129
|
+
"scripts/lesson-embedding-index.js",
|
|
127
130
|
"scripts/lesson-inference.js",
|
|
131
|
+
"scripts/lesson-reranker.js",
|
|
132
|
+
"scripts/lesson-retrieval.js",
|
|
128
133
|
"scripts/lesson-rotation.js",
|
|
129
134
|
"scripts/lesson-search.js",
|
|
130
135
|
"scripts/lesson-synthesis.js",
|
|
@@ -448,7 +453,7 @@
|
|
|
448
453
|
"test:memory-dedup": "node --test tests/memory-dedup.test.js",
|
|
449
454
|
"test:lesson-db": "node --test tests/lesson-db.test.js",
|
|
450
455
|
"test:lesson-rotation": "node --test tests/lesson-rotation.test.js",
|
|
451
|
-
"test:feedback-quality": "node --test tests/feedback-quality.test.js tests/feedback-event-idempotency.test.js",
|
|
456
|
+
"test:feedback-quality": "node --test tests/feedback-quality.test.js tests/feedback-event-idempotency.test.js tests/feedback-capture-e2e.test.js",
|
|
452
457
|
"test:sync-version": "node --test tests/sync-version.test.js",
|
|
453
458
|
"test:release-window": "node --test tests/release-window.test.js",
|
|
454
459
|
"test:team-sync": "node --test tests/team-sync.test.js",
|
|
@@ -473,7 +478,7 @@
|
|
|
473
478
|
"test:okara-money-promo-automation": "node --test tests/okara-money-promo-automation.test.js",
|
|
474
479
|
"test:operator-key-auth": "node --test tests/api-operator-key-auth.test.js",
|
|
475
480
|
"test:cloudflare-sandbox": "node --test tests/cloudflare-dynamic-sandbox.test.js tests/cloudflare-sandbox-api.test.js",
|
|
476
|
-
"test:mcp-config": "node --test tests/mcp-config.test.js",
|
|
481
|
+
"test:mcp-config": "node --test tests/mcp-config.test.js tests/mcp-capability-exposure.test.js",
|
|
477
482
|
"test:mcp-tool-annotations": "node --test tests/mcp-tool-annotations.test.js",
|
|
478
483
|
"test:mcp-oauth": "node --test tests/mcp-oauth.test.js",
|
|
479
484
|
"test:mcp-oauth-flow": "node --test tests/mcp-oauth-flow.test.js",
|
|
@@ -514,7 +519,7 @@
|
|
|
514
519
|
"test:quality": "node --test tests/validate-feedback.test.js tests/feedback-quality-eval-python.test.js tests/eval-gate-classifier.test.js",
|
|
515
520
|
"test:intelligence": "node --test tests/intelligence.test.js",
|
|
516
521
|
"test:training-export": "node --test tests/training-export.test.js tests/databricks-export.test.js",
|
|
517
|
-
"test:deployment": "node --test tests/deployment.test.js tests/deploy-policy.test.js tests/publish-decision.test.js tests/changeset-check.test.js tests/release-notes.test.js tests/sonarcloud-workflow.test.js tests/package-boundary.test.js tests/public-package-boundary.test.js",
|
|
522
|
+
"test:deployment": "node --test tests/deployment.test.js tests/deploy-policy.test.js tests/publish-decision.test.js tests/changeset-check.test.js tests/release-notes.test.js tests/sonarcloud-workflow.test.js tests/package-boundary.test.js tests/public-package-boundary.test.js tests/packed-feedback-retrieval-e2e.test.js",
|
|
518
523
|
"test:operational-integrity": "node --test tests/operational-integrity.test.js tests/sync-branch-protection.test.js",
|
|
519
524
|
"test:workflow": "node --test tests/parallel-workflow.test.js tests/workflow-contract.test.js tests/positioning-contract.test.js tests/docs-claim-hygiene.test.js tests/thumbgate-scope.test.js tests/workflow-runs.test.js tests/workflow-sprint-intake.test.js tests/revenue-pack-utils.test.js tests/apollo-acquisition.test.js tests/sales-pipeline.test.js tests/github-outreach.test.js tests/enterprise-story.test.js tests/guide-conversion-path.test.js tests/buyer-intent-revenue-assist.test.js",
|
|
520
525
|
"test:sales-pipeline": "node --test tests/sales-pipeline.test.js",
|
package/public/index.html
CHANGED
|
@@ -20,7 +20,7 @@ __GOOGLE_SITE_VERIFICATION_META__
|
|
|
20
20
|
<meta property="og:image" content="https://thumbgate.ai/og.png">
|
|
21
21
|
<meta name="twitter:card" content="summary_large_image">
|
|
22
22
|
<meta name="twitter:image" content="https://thumbgate.ai/og.png">
|
|
23
|
-
<meta name="thumbgate-version" content="1.28.
|
|
23
|
+
<meta name="thumbgate-version" content="1.28.3">
|
|
24
24
|
<meta name="keywords" content="ThumbGate, thumbgate, AI agent orchestration, AI experience orchestration, agentic development cycle, AC/DC framework, Guide Generate Verify Solve, agent enforcement layer, pre-action checks, agent governance, Claude Code, Cursor, Codex, Gemini, Amp, Cline, OpenCode, workflow hardening, context engineering, AI authenticity, brand authenticity AI">
|
|
25
25
|
<link rel="canonical" href="__APP_ORIGIN__/">
|
|
26
26
|
<link rel="alternate" type="text/markdown" title="ThumbGate LLM context" href="__APP_ORIGIN__/llm-context.md">
|
|
@@ -1776,7 +1776,7 @@ __GA_BOOTSTRAP__
|
|
|
1776
1776
|
<a href="https://www.linkedin.com/in/igorganapolsky" target="_blank" rel="noopener">LinkedIn</a>
|
|
1777
1777
|
<a href="/blog">Blog</a>
|
|
1778
1778
|
</div>
|
|
1779
|
-
<span class="footer-copy">ยฉ 2026 ThumbGate ยท MIT License ยท npm v1.28.
|
|
1779
|
+
<span class="footer-copy">ยฉ 2026 ThumbGate ยท MIT License ยท npm v1.28.3</span>
|
|
1780
1780
|
</div>
|
|
1781
1781
|
</footer>
|
|
1782
1782
|
|
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.
|
|
28
|
+
"softwareVersion": "1.28.3",
|
|
29
29
|
"url": "https://thumbgate.ai/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.28.
|
|
205
|
+
<div class="freshness">Updated: 2026-05-07 ยท Version 1.28.3</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>
|
|
@@ -452,6 +452,46 @@ function codexConfigPath() {
|
|
|
452
452
|
return path.join(getHome(), '.codex', 'config.json');
|
|
453
453
|
}
|
|
454
454
|
|
|
455
|
+
function codexTomlConfigPath(configPath = codexConfigPath()) {
|
|
456
|
+
return path.join(path.dirname(configPath), 'config.toml');
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function tomlSectionRegex(name) {
|
|
460
|
+
return new RegExp(`^\\[${String(name).replace(/[.*+?^${}()|[\\]\\\\]/g, '\\$&')}\\]\\n(?:^(?!\\[).*(?:\\n|$))*`, 'm');
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function codexUserPromptTomlBlock() {
|
|
464
|
+
const hookCommand = codexUserPromptHookCommand();
|
|
465
|
+
return `[hooks.user_prompt_submit]\ncommand = "sh"\nargs = ["-lc", ${JSON.stringify(hookCommand)}]\n`;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function upsertCodexUserPromptToml(configPath, dryRun = false) {
|
|
469
|
+
const tomlPath = codexTomlConfigPath(configPath);
|
|
470
|
+
const current = fs.existsSync(tomlPath) ? fs.readFileSync(tomlPath, 'utf8') : '';
|
|
471
|
+
const canonicalBlock = codexUserPromptTomlBlock();
|
|
472
|
+
const sectionRegex = tomlSectionRegex('hooks.user_prompt_submit');
|
|
473
|
+
let next = current;
|
|
474
|
+
|
|
475
|
+
if (sectionRegex.test(current)) {
|
|
476
|
+
const existingBlock = current.match(sectionRegex)[0];
|
|
477
|
+
if (existingBlock === canonicalBlock) {
|
|
478
|
+
return { changed: false, settingsPath: tomlPath };
|
|
479
|
+
}
|
|
480
|
+
next = current.replace(sectionRegex, canonicalBlock);
|
|
481
|
+
} else {
|
|
482
|
+
const prefix = current.trimEnd();
|
|
483
|
+
next = `${prefix}${prefix ? '\n\n' : ''}${canonicalBlock}`;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if (!dryRun) {
|
|
487
|
+
const dir = path.dirname(tomlPath);
|
|
488
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
489
|
+
fs.writeFileSync(tomlPath, next.endsWith('\n') ? next : `${next}\n`);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
return { changed: true, settingsPath: tomlPath };
|
|
493
|
+
}
|
|
494
|
+
|
|
455
495
|
function writeJsonFile(filePath, payload, dryRun) {
|
|
456
496
|
if (dryRun) {
|
|
457
497
|
return;
|
|
@@ -500,6 +540,7 @@ function wireCodexHooks(options) {
|
|
|
500
540
|
const configPath = options.settingsPath || codexConfigPath();
|
|
501
541
|
const dryRun = options.dryRun || false;
|
|
502
542
|
const desiredStatusLine = codexStatuslineCommand();
|
|
543
|
+
const tomlResult = upsertCodexUserPromptToml(configPath, dryRun);
|
|
503
544
|
|
|
504
545
|
let config = loadJsonFile(configPath) || {};
|
|
505
546
|
config.hooks = config.hooks || {};
|
|
@@ -519,15 +560,28 @@ function wireCodexHooks(options) {
|
|
|
519
560
|
if (added.length === 0) {
|
|
520
561
|
if (syncCodexStatusLine(config, desiredStatusLine)) {
|
|
521
562
|
writeJsonFile(configPath, config, dryRun);
|
|
522
|
-
|
|
563
|
+
const statusAdded = [{ lifecycle: 'statusLine', command: desiredStatusLine }];
|
|
564
|
+
if (tomlResult.changed) {
|
|
565
|
+
statusAdded.push({ lifecycle: 'UserPromptSubmit', command: `${codexUserPromptHookCommand()} (${tomlResult.settingsPath})` });
|
|
566
|
+
}
|
|
567
|
+
return { changed: true, settingsPath: configPath, added: statusAdded };
|
|
523
568
|
}
|
|
524
|
-
return {
|
|
569
|
+
return {
|
|
570
|
+
changed: tomlResult.changed,
|
|
571
|
+
settingsPath: configPath,
|
|
572
|
+
added: tomlResult.changed
|
|
573
|
+
? [{ lifecycle: 'UserPromptSubmit', command: `${codexUserPromptHookCommand()} (${tomlResult.settingsPath})` }]
|
|
574
|
+
: [],
|
|
575
|
+
};
|
|
525
576
|
}
|
|
526
577
|
|
|
527
578
|
syncCodexStatusLine(config, desiredStatusLine);
|
|
528
579
|
writeJsonFile(configPath, config, dryRun);
|
|
529
580
|
|
|
530
581
|
added.push({ lifecycle: 'statusLine', command: desiredStatusLine });
|
|
582
|
+
if (tomlResult.changed) {
|
|
583
|
+
added.push({ lifecycle: 'UserPromptSubmit', command: `${codexUserPromptHookCommand()} (${tomlResult.settingsPath})` });
|
|
584
|
+
}
|
|
531
585
|
return { changed: true, settingsPath: configPath, added };
|
|
532
586
|
}
|
|
533
587
|
|
|
@@ -702,6 +756,8 @@ module.exports = {
|
|
|
702
756
|
claudeSharedSettingsPath,
|
|
703
757
|
claudeProjectSettingsPath,
|
|
704
758
|
codexConfigPath,
|
|
759
|
+
codexTomlConfigPath,
|
|
760
|
+
upsertCodexUserPromptToml,
|
|
705
761
|
geminiSettingsPath,
|
|
706
762
|
syncClaudeStatusLine,
|
|
707
763
|
forgeConfigPath,
|
|
@@ -20,7 +20,7 @@ const { refreshStatuslineCache } = require('./hook-thumbgate-cache-updater');
|
|
|
20
20
|
const SYNC_STATE_FILE = 'claude-feedback-sync-state.json';
|
|
21
21
|
const DEFAULT_RECENT_FEEDBACK_LIMIT = 250;
|
|
22
22
|
const DEFAULT_PROCESSED_ID_LIMIT = 512;
|
|
23
|
-
const DUPLICATE_WINDOW_MS =
|
|
23
|
+
const DUPLICATE_WINDOW_MS = 5 * 60 * 1000;
|
|
24
24
|
|
|
25
25
|
function getClaudeHistoryPath(options = {}) {
|
|
26
26
|
if (options.historyPath) return options.historyPath;
|
package/scripts/cli-feedback.js
CHANGED
|
@@ -58,6 +58,7 @@ function processInlineFeedback({ signal, context, chatHistory, whatWentWrong, wh
|
|
|
58
58
|
context: context || (isDown ? 'Thumbs down from CLI' : 'Thumbs up from CLI'),
|
|
59
59
|
whatWentWrong: whatWentWrong || undefined,
|
|
60
60
|
whatWorked: whatWorked || undefined,
|
|
61
|
+
chatHistory,
|
|
61
62
|
sourceEvent,
|
|
62
63
|
});
|
|
63
64
|
} catch (err) {
|
|
@@ -115,6 +116,10 @@ function formatCliOutput(result) {
|
|
|
115
116
|
const capturedIds = memoryId ? `${feedbackId}, ${memoryId}` : feedbackId;
|
|
116
117
|
process.stderr.write(`โ
Feedback captured (${capturedIds})\n`);
|
|
117
118
|
}
|
|
119
|
+
if (feedbackResult.promoted === false || !memoryId) {
|
|
120
|
+
const clarification = feedbackResult.prompt || feedbackResult.message || feedbackResult.reason;
|
|
121
|
+
lines.push(`${D} Reusable memory: not created${clarification ? ` โ ${clarification}` : ''}${RST}`);
|
|
122
|
+
}
|
|
118
123
|
} else {
|
|
119
124
|
lines.push(`${R}Feedback not accepted: ${feedbackResult?.reason || 'unknown'}${RST}`);
|
|
120
125
|
}
|