thumbgate 1.27.17 → 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 +85 -0
- package/.claude-plugin/plugin.json +1 -1
- package/.well-known/mcp/server-card.json +1 -1
- package/README.md +33 -5
- package/adapters/chatgpt/openapi.yaml +1707 -0
- 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/bin/cli.js +63 -54
- package/bin/postinstall.js +23 -14
- package/config/post-deploy-marketing-pages.json +19 -0
- package/config/tessl-tiles.json +1 -3
- package/package.json +99 -229
- package/public/guide.html +0 -4
- package/public/index.html +28 -49
- package/public/learn.html +0 -8
- package/public/numbers.html +2 -2
- package/scripts/agent-readiness.js +0 -147
- package/scripts/bot-detection.js +61 -3
- package/scripts/cli-feedback.js +4 -14
- package/scripts/cli-schema.js +7 -0
- package/scripts/durability/step.js +3 -3
- package/scripts/feedback-loop.js +10 -32
- package/scripts/gates-engine.js +72 -1
- package/scripts/hook-runtime.js +2 -2
- package/scripts/license.js +10 -9
- package/scripts/llm-client.js +44 -260
- package/scripts/mcp-config.js +7 -1
- package/scripts/published-cli.js +0 -8
- package/scripts/seo-gsd.js +0 -5
- package/src/api/server.js +111 -35
- package/.claude/commands/dashboard.md +0 -15
- package/.claude/commands/thumbgate-blocked.md +0 -27
- package/.claude/commands/thumbgate-dashboard.md +0 -15
- package/.claude/commands/thumbgate-doctor.md +0 -30
- package/.claude/commands/thumbgate-guard.md +0 -36
- package/.claude/commands/thumbgate-protect.md +0 -30
- package/.claude/commands/thumbgate-rules.md +0 -30
- package/adapters/gcp/dfcx-webhook-gate.js +0 -295
- package/adapters/letta/README.md +0 -41
- package/adapters/letta/thumbgate-letta-adapter.js +0 -133
- package/adapters/policy-engine/ethicore-guardian-client.js +0 -68
- package/adapters/policy-engine/thumbgate-policy-engine-adapter.js +0 -260
- package/bench/observability-eval-suite.json +0 -26
- package/bin/dashboard-cli.js +0 -7
- package/commands/dashboard.md +0 -15
- package/commands/thumbgate-dashboard.md +0 -15
- package/config/builtin-lessons.json +0 -23
- package/public/about.html +0 -162
- package/public/agent-manager.html +0 -179
- package/public/agents-cost-savings.html +0 -153
- package/public/ai-malpractice-prevention.html +0 -818
- package/public/assets/brand/github-social-preview.png +0 -0
- package/public/assets/brand/thumbgate-icon-512.png +0 -0
- package/public/assets/brand/thumbgate-icon-pro-512.png +0 -0
- package/public/assets/brand/thumbgate-icon-team-512.png +0 -0
- package/public/assets/brand/thumbgate-logo-1200x360.png +0 -0
- package/public/assets/brand/thumbgate-logo-transparent.svg +0 -22
- package/public/assets/brand/thumbgate-mark-inline-v3.svg +0 -19
- package/public/assets/brand/thumbgate-mark-pro.svg +0 -23
- package/public/assets/brand/thumbgate-mark-team.svg +0 -26
- package/public/assets/brand/thumbgate-mark.svg +0 -21
- package/public/assets/brand/thumbgate-wordmark.svg +0 -20
- package/public/assets/claude-thumbgate-statusbar.svg +0 -8
- package/public/assets/codex-thumbgate-statusbar-test.svg +0 -9
- package/public/assets/legal-intake-control-flow.svg +0 -66
- package/public/brand/thumbgate-mark.svg +0 -19
- package/public/brand/thumbgate-og.svg +0 -16
- package/public/chatgpt-app.html +0 -330
- package/public/codex-enterprise.html +0 -123
- package/public/js/buyer-intent.js +0 -672
- package/public/pricing.html +0 -399
- package/scripts/action-receipts.js +0 -324
- package/scripts/activation-quickstart.js +0 -187
- package/scripts/agent-operations-planner.js +0 -621
- package/scripts/ai-component-inventory.js +0 -367
- package/scripts/async-eval-observability.js +0 -236
- package/scripts/audit.js +0 -65
- package/scripts/aws-blocks-guardrails.js +0 -228
- package/scripts/bot-detector.js +0 -50
- package/scripts/classifier-routing.js +0 -130
- package/scripts/dashboard-chat.js +0 -332
- package/scripts/feedback-aggregate.js +0 -281
- package/scripts/feedback-sanitizer.js +0 -105
- package/scripts/hob-pack.js +0 -591
- package/scripts/hook-stop-anti-claim.js +0 -301
- package/scripts/install-shim.js +0 -87
- package/scripts/mcp-oauth.js +0 -293
- package/scripts/noop-detect.js +0 -285
- package/scripts/omlx-smoke.js +0 -192
- package/scripts/parallel-workflow-orchestrator.js +0 -293
- package/scripts/plan-gate.js +0 -243
- package/scripts/plausible-domain-config.js +0 -99
- package/scripts/qa-scenario-planner.js +0 -136
- package/scripts/repeat-metric.js +0 -137
- package/scripts/secret-fixture-tokens.js +0 -61
- package/scripts/secret-redaction.js +0 -166
- package/scripts/self-harness-optimizer.js +0 -141
- package/scripts/self-healing-check.js +0 -193
- package/scripts/silent-failure-cluster.js +0 -531
- package/scripts/statusline-cache-read.js +0 -57
- package/scripts/sync-telemetry-from-prod.js +0 -374
- package/scripts/tool-contract-validator.js +0 -76
- package/scripts/trajectory-scorer.js +0 -63
- package/scripts/verify-marketing-pages-deployed.js +0 -195
- package/scripts/visitor-journey.js +0 -172
|
@@ -1,260 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
const { normalizeProviderAction } = require('../../scripts/provider-action-normalizer');
|
|
5
|
-
|
|
6
|
-
const BLOCK_DECISIONS = new Set([
|
|
7
|
-
'block',
|
|
8
|
-
'blocked',
|
|
9
|
-
'deny',
|
|
10
|
-
'denied',
|
|
11
|
-
'disallow',
|
|
12
|
-
'disallowed',
|
|
13
|
-
'fail',
|
|
14
|
-
'failed',
|
|
15
|
-
'forbid',
|
|
16
|
-
'forbidden',
|
|
17
|
-
'reject',
|
|
18
|
-
'rejected',
|
|
19
|
-
'unsafe',
|
|
20
|
-
'violation',
|
|
21
|
-
]);
|
|
22
|
-
|
|
23
|
-
const REVIEW_DECISIONS = new Set([
|
|
24
|
-
'approval',
|
|
25
|
-
'approval-required',
|
|
26
|
-
'approval_required',
|
|
27
|
-
'approve',
|
|
28
|
-
'human-review',
|
|
29
|
-
'human_review',
|
|
30
|
-
'manual-review',
|
|
31
|
-
'manual_review',
|
|
32
|
-
'review',
|
|
33
|
-
'requires-approval',
|
|
34
|
-
'requires_approval',
|
|
35
|
-
'requires-review',
|
|
36
|
-
'requires_review',
|
|
37
|
-
]);
|
|
38
|
-
|
|
39
|
-
const ALLOW_DECISIONS = new Set([
|
|
40
|
-
'accept',
|
|
41
|
-
'accepted',
|
|
42
|
-
'allow',
|
|
43
|
-
'allowed',
|
|
44
|
-
'ok',
|
|
45
|
-
'pass',
|
|
46
|
-
'passed',
|
|
47
|
-
'permit',
|
|
48
|
-
'permitted',
|
|
49
|
-
'safe',
|
|
50
|
-
]);
|
|
51
|
-
|
|
52
|
-
function asObject(value) {
|
|
53
|
-
return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function asArray(value) {
|
|
57
|
-
return Array.isArray(value) ? value : [];
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function firstString(...values) {
|
|
61
|
-
for (const value of values) {
|
|
62
|
-
const text = String(value || '').trim();
|
|
63
|
-
if (text) return text;
|
|
64
|
-
}
|
|
65
|
-
return '';
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function normalizeDecisionToken(value) {
|
|
69
|
-
return String(value || '')
|
|
70
|
-
.trim()
|
|
71
|
-
.toLowerCase()
|
|
72
|
-
.replace(/\s+/g, '-');
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function normalizeEvidence(value) {
|
|
76
|
-
const direct = asArray(value.evidence);
|
|
77
|
-
const citations = asArray(value.citations);
|
|
78
|
-
const violations = asArray(value.violations);
|
|
79
|
-
const reasons = asArray(value.reasons);
|
|
80
|
-
const reasoning = asArray(value.reasoning);
|
|
81
|
-
const threatTypes = asArray(value.threat_types || value.threatTypes)
|
|
82
|
-
.map((threatType) => ({
|
|
83
|
-
code: String(threatType || '').trim(),
|
|
84
|
-
message: `Threat type: ${String(threatType || '').trim()}`,
|
|
85
|
-
source: 'guardian',
|
|
86
|
-
severity: firstString(value.threat_level, value.threatLevel),
|
|
87
|
-
}));
|
|
88
|
-
return [...direct, ...citations, ...violations, ...reasons, ...reasoning, ...threatTypes]
|
|
89
|
-
.map((entry) => {
|
|
90
|
-
if (typeof entry === 'string') return { text: entry };
|
|
91
|
-
const object = asObject(entry);
|
|
92
|
-
if (!Object.keys(object).length) return null;
|
|
93
|
-
return {
|
|
94
|
-
id: firstString(object.id, object.ruleId, object.rule_id, object.code),
|
|
95
|
-
text: firstString(object.text, object.reason, object.message, object.description, object.title),
|
|
96
|
-
source: firstString(object.source, object.provider, object.policy),
|
|
97
|
-
severity: firstString(object.severity, object.level),
|
|
98
|
-
raw: object,
|
|
99
|
-
};
|
|
100
|
-
})
|
|
101
|
-
.filter(Boolean);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function extractPolicyDecision(input = {}) {
|
|
105
|
-
const event = asObject(input);
|
|
106
|
-
for (const candidate of [
|
|
107
|
-
event.policyDecision,
|
|
108
|
-
event.policy_decision,
|
|
109
|
-
event.guardrailResult,
|
|
110
|
-
event.guardrail_result,
|
|
111
|
-
event.result,
|
|
112
|
-
]) {
|
|
113
|
-
const object = asObject(candidate);
|
|
114
|
-
if (Object.keys(object).length) return object;
|
|
115
|
-
}
|
|
116
|
-
return event;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function classifyPolicyDecision(input = {}) {
|
|
120
|
-
const value = extractPolicyDecision(input);
|
|
121
|
-
const token = normalizeDecisionToken(firstString(
|
|
122
|
-
value.decision,
|
|
123
|
-
value.action,
|
|
124
|
-
value.status,
|
|
125
|
-
value.result,
|
|
126
|
-
value.verdict,
|
|
127
|
-
value.outcome,
|
|
128
|
-
value.effect,
|
|
129
|
-
value.recommended_action,
|
|
130
|
-
value.recommendedAction,
|
|
131
|
-
));
|
|
132
|
-
|
|
133
|
-
if (
|
|
134
|
-
value.allowed === false
|
|
135
|
-
|| value.is_safe === false
|
|
136
|
-
|| value.isSafe === false
|
|
137
|
-
|| value.accepted === false
|
|
138
|
-
|| value.blocked === true
|
|
139
|
-
|| value.denied === true
|
|
140
|
-
|| BLOCK_DECISIONS.has(token)
|
|
141
|
-
) {
|
|
142
|
-
return 'block';
|
|
143
|
-
}
|
|
144
|
-
if (
|
|
145
|
-
value.requiresApproval === true
|
|
146
|
-
|| value.requires_approval === true
|
|
147
|
-
|| value.reviewRequired === true
|
|
148
|
-
|| value.review_required === true
|
|
149
|
-
|| REVIEW_DECISIONS.has(token)
|
|
150
|
-
) {
|
|
151
|
-
return 'approval_required';
|
|
152
|
-
}
|
|
153
|
-
if (
|
|
154
|
-
value.allowed === true
|
|
155
|
-
|| value.is_safe === true
|
|
156
|
-
|| value.isSafe === true
|
|
157
|
-
|| value.accepted === true
|
|
158
|
-
|| ALLOW_DECISIONS.has(token)
|
|
159
|
-
) {
|
|
160
|
-
return 'allow';
|
|
161
|
-
}
|
|
162
|
-
return 'unknown';
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function normalizePolicyDecision(input = {}, options = {}) {
|
|
166
|
-
const value = extractPolicyDecision(input);
|
|
167
|
-
const decision = classifyPolicyDecision(value);
|
|
168
|
-
const source = firstString(
|
|
169
|
-
options.source,
|
|
170
|
-
value.source,
|
|
171
|
-
value.provider,
|
|
172
|
-
value.engine,
|
|
173
|
-
value.policyEngine,
|
|
174
|
-
value.policy_engine,
|
|
175
|
-
'policy-engine'
|
|
176
|
-
);
|
|
177
|
-
const reason = firstString(
|
|
178
|
-
value.reason,
|
|
179
|
-
value.message,
|
|
180
|
-
value.explanation,
|
|
181
|
-
value.summary,
|
|
182
|
-
asArray(value.reasoning).join('; '),
|
|
183
|
-
asArray(value.reasons).join('; '),
|
|
184
|
-
decision === 'unknown' ? 'Policy engine returned an unknown decision; approval required before execution.' : ''
|
|
185
|
-
);
|
|
186
|
-
|
|
187
|
-
return {
|
|
188
|
-
allowed: decision === 'allow',
|
|
189
|
-
blocked: decision === 'block',
|
|
190
|
-
approvalRequired: decision === 'approval_required' || decision === 'unknown',
|
|
191
|
-
decision,
|
|
192
|
-
reason,
|
|
193
|
-
source,
|
|
194
|
-
confidence: Number.isFinite(Number(value.confidence)) ? Number(value.confidence) : null,
|
|
195
|
-
policyId: firstString(value.policyId, value.policy_id, value.ruleId, value.rule_id, value.id),
|
|
196
|
-
severity: firstString(value.severity, value.level, value.threat_level, value.threatLevel),
|
|
197
|
-
score: Number.isFinite(Number(value.score))
|
|
198
|
-
? Number(value.score)
|
|
199
|
-
: (Number.isFinite(Number(value.threat_score)) ? Number(value.threat_score) : null),
|
|
200
|
-
evidence: normalizeEvidence(value),
|
|
201
|
-
raw: value,
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
function normalizePolicyAction(input = {}) {
|
|
206
|
-
const event = asObject(input);
|
|
207
|
-
return {
|
|
208
|
-
...normalizeProviderAction({
|
|
209
|
-
...event,
|
|
210
|
-
provider: firstString(event.provider, event.agentRuntime, event.runtime, 'policy-engine'),
|
|
211
|
-
toolName: firstString(event.toolName, event.tool_name, event.name),
|
|
212
|
-
input: asObject(event.toolInput || event.input || event.arguments || event.args),
|
|
213
|
-
}),
|
|
214
|
-
policyContext: asObject(event.policyContext || event.policy_context),
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
function createPolicyEngineGuard({
|
|
219
|
-
policyCheck,
|
|
220
|
-
executeTool,
|
|
221
|
-
gateCheck,
|
|
222
|
-
onDecision,
|
|
223
|
-
source = 'policy-engine',
|
|
224
|
-
} = {}) {
|
|
225
|
-
if (typeof policyCheck !== 'function') {
|
|
226
|
-
throw new TypeError('createPolicyEngineGuard requires a policyCheck function');
|
|
227
|
-
}
|
|
228
|
-
if (typeof executeTool !== 'function') {
|
|
229
|
-
throw new TypeError('createPolicyEngineGuard requires an executeTool function');
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
return async function guardedPolicyTool(input = {}) {
|
|
233
|
-
const normalizedAction = normalizePolicyAction(input);
|
|
234
|
-
const policyDecision = normalizePolicyDecision(await policyCheck(normalizedAction), { source });
|
|
235
|
-
const gateDecision = typeof gateCheck === 'function'
|
|
236
|
-
? normalizePolicyDecision(await gateCheck({ normalizedAction, policyDecision }), { source: 'thumbgate' })
|
|
237
|
-
: null;
|
|
238
|
-
const effectiveDecision = gateDecision && !gateDecision.allowed ? gateDecision : policyDecision;
|
|
239
|
-
|
|
240
|
-
if (typeof onDecision === 'function') {
|
|
241
|
-
await onDecision({ normalizedAction, policyDecision, gateDecision, effectiveDecision });
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
if (!effectiveDecision.allowed) {
|
|
245
|
-
const error = new Error(effectiveDecision.reason || 'ThumbGate blocked this action before execution.');
|
|
246
|
-
error.code = effectiveDecision.approvalRequired ? 'THUMBGATE_APPROVAL_REQUIRED' : 'THUMBGATE_BLOCKED';
|
|
247
|
-
error.thumbgate = { normalizedAction, policyDecision, gateDecision, effectiveDecision };
|
|
248
|
-
throw error;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
return executeTool(input, { normalizedAction, policyDecision, gateDecision, effectiveDecision });
|
|
252
|
-
};
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
module.exports = {
|
|
256
|
-
createPolicyEngineGuard,
|
|
257
|
-
extractPolicyDecision,
|
|
258
|
-
normalizePolicyAction,
|
|
259
|
-
normalizePolicyDecision,
|
|
260
|
-
};
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"cases": [
|
|
3
|
-
{
|
|
4
|
-
"id": "checkout-link-grounding",
|
|
5
|
-
"traceId": "trace_checkout_001",
|
|
6
|
-
"question": "Is the Pro checkout link working?",
|
|
7
|
-
"response": "The Pro checkout link is working because the checkout diagnostic confirms Stripe Pro checkout is reachable.",
|
|
8
|
-
"retrievedContexts": [
|
|
9
|
-
"Stripe Pro checkout diagnostic confirms the Pro checkout link is reachable and returns a valid checkout target.",
|
|
10
|
-
"The workflow sprint checkout is intake-led and should not expose a stale payment link."
|
|
11
|
-
],
|
|
12
|
-
"reference": "The Pro checkout link is reachable when the Stripe diagnostic passes."
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
"id": "letta-tool-gate",
|
|
16
|
-
"traceId": "trace_letta_001",
|
|
17
|
-
"question": "Should Letta execute a force push tool call?",
|
|
18
|
-
"response": "ThumbGate blocks force push before execution for Letta tool calls.",
|
|
19
|
-
"retrievedContexts": [
|
|
20
|
-
"ThumbGate's Letta adapter wraps Letta tool calls and runs gate-check before the executor is called.",
|
|
21
|
-
"Force push to main is a blocked high-risk git action."
|
|
22
|
-
],
|
|
23
|
-
"reference": "ThumbGate blocks high-risk Letta tool calls before execution."
|
|
24
|
-
}
|
|
25
|
-
]
|
|
26
|
-
}
|
package/bin/dashboard-cli.js
DELETED
package/commands/dashboard.md
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: dashboard
|
|
3
|
-
description: Open the local HTTP dashboard for the current project in your web browser.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Open Dashboard
|
|
7
|
-
|
|
8
|
-
Open the local HTTP dashboard for the current project in your web browser.
|
|
9
|
-
|
|
10
|
-
## Instructions
|
|
11
|
-
Execute the following command in the project directory to open the browser dashboard scoped to the current repository:
|
|
12
|
-
```bash
|
|
13
|
-
thumbgate-dashboard
|
|
14
|
-
```
|
|
15
|
-
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: thumbgate-dashboard
|
|
3
|
-
description: Open the local HTTP dashboard for the current project in your web browser.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Open Scoped ThumbGate Dashboard
|
|
7
|
-
|
|
8
|
-
Open the local HTTP dashboard for the current project in your web browser.
|
|
9
|
-
|
|
10
|
-
## Instructions
|
|
11
|
-
Execute the following command in the project directory to open the browser dashboard:
|
|
12
|
-
```bash
|
|
13
|
-
thumbgate-dashboard
|
|
14
|
-
```
|
|
15
|
-
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 1,
|
|
3
|
-
"lessons": [
|
|
4
|
-
{
|
|
5
|
-
"id": "builtin-response-quality-shallow-positive-closeout",
|
|
6
|
-
"title": "MISTAKE: Assistant gave shallow acknowledgement after user said thumbs up",
|
|
7
|
-
"content": "What went wrong: Assistant gave shallow acknowledgement after positive feedback, such as thumbs up, perfect, good, or thank you, instead of staying quiet or giving an evidence checkpoint.\nHow to avoid: If the previous user message is positive feedback and the proposed final response is a low-value social closeout, block it and require either silence-level brevity or a compact evidence checkpoint with proof, result, residual risk, and next state.\nAction needed: Enforce this at the final-response boundary, not only as PreToolUse context.\nReasoning: Stored lessons and retrieval are not enough when the model can still ignore the lesson at generation time.",
|
|
8
|
-
"category": "error",
|
|
9
|
-
"importance": "high",
|
|
10
|
-
"tags": [
|
|
11
|
-
"feedback",
|
|
12
|
-
"negative",
|
|
13
|
-
"response-quality",
|
|
14
|
-
"final-response",
|
|
15
|
-
"positive-feedback",
|
|
16
|
-
"shallow-closeout",
|
|
17
|
-
"enforcement"
|
|
18
|
-
],
|
|
19
|
-
"timestamp": "2026-06-20T19:01:58.000Z",
|
|
20
|
-
"source": "packaged-builtin"
|
|
21
|
-
}
|
|
22
|
-
]
|
|
23
|
-
}
|
package/public/about.html
DELETED
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta name="generator" content="ThumbGate">
|
|
5
|
-
<meta name="author" content="Igor Ganapolsky">
|
|
6
|
-
<meta charset="UTF-8">
|
|
7
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
8
|
-
__GOOGLE_SITE_VERIFICATION_META__
|
|
9
|
-
<link rel="icon" type="image/png" href="/thumbgate-icon.png">
|
|
10
|
-
<link rel="alternate icon" type="image/svg+xml" href="/assets/brand/thumbgate-mark.svg">
|
|
11
|
-
<title>About Igor Ganapolsky — ThumbGate</title>
|
|
12
|
-
<meta name="description" content="Igor Ganapolsky — Senior AI & Mobile Engineer building ThumbGate, an agent-governance layer for Claude Code, Cursor, Codex, and Gemini. Production Stripe Connect, Agentic RAG, and React Native experience.">
|
|
13
|
-
<meta property="og:title" content="About Igor Ganapolsky — ThumbGate">
|
|
14
|
-
<meta property="og:description" content="Senior AI & Mobile Engineer. Builder of ThumbGate. Production Stripe Connect + Agentic RAG + React Native engineer for hire.">
|
|
15
|
-
<meta property="og:type" content="profile">
|
|
16
|
-
<meta property="og:url" content="https://thumbgate.ai/about">
|
|
17
|
-
<meta property="og:image" content="https://thumbgate.ai/og.png">
|
|
18
|
-
<meta name="twitter:card" content="summary_large_image">
|
|
19
|
-
<link rel="canonical" href="https://thumbgate.ai/about">
|
|
20
|
-
<link rel="alternate" type="text/markdown" title="ThumbGate LLM context" href="https://thumbgate.ai/llm-context.md">
|
|
21
|
-
|
|
22
|
-
<script type="application/ld+json">
|
|
23
|
-
{
|
|
24
|
-
"@context": "https://schema.org",
|
|
25
|
-
"@type": "Person",
|
|
26
|
-
"name": "Igor Ganapolsky",
|
|
27
|
-
"givenName": "Igor",
|
|
28
|
-
"familyName": "Ganapolsky",
|
|
29
|
-
"alternateName": ["iganapolsky", "IgorGanapolsky"],
|
|
30
|
-
"url": "https://thumbgate.ai/about",
|
|
31
|
-
"image": "https://thumbgate.ai/og.png",
|
|
32
|
-
"jobTitle": "Senior AI & Mobile Engineer",
|
|
33
|
-
"description": "Production Stripe Connect, Agentic RAG, React Native, and AI-agent governance engineer. Creator of ThumbGate — pre-action defense for AI coding agents.",
|
|
34
|
-
"knowsAbout": [
|
|
35
|
-
"Agentic RAG",
|
|
36
|
-
"Stripe Connect",
|
|
37
|
-
"Stripe Billing",
|
|
38
|
-
"React Native",
|
|
39
|
-
"Android",
|
|
40
|
-
"Claude Code",
|
|
41
|
-
"Cursor",
|
|
42
|
-
"Codex",
|
|
43
|
-
"Gemini",
|
|
44
|
-
"AI agent governance",
|
|
45
|
-
"LLM cost optimization",
|
|
46
|
-
"Pre-action checks",
|
|
47
|
-
"Workflow hardening",
|
|
48
|
-
"Context engineering",
|
|
49
|
-
"MCP servers",
|
|
50
|
-
"LangChain",
|
|
51
|
-
"LangGraph",
|
|
52
|
-
"Patchright",
|
|
53
|
-
"Playwright",
|
|
54
|
-
"OpenAI API",
|
|
55
|
-
"Anthropic API"
|
|
56
|
-
],
|
|
57
|
-
"address": {
|
|
58
|
-
"@type": "PostalAddress",
|
|
59
|
-
"addressLocality": "Coral Springs",
|
|
60
|
-
"addressRegion": "FL",
|
|
61
|
-
"addressCountry": "US"
|
|
62
|
-
},
|
|
63
|
-
"email": "iganapolsky@gmail.com",
|
|
64
|
-
"sameAs": [
|
|
65
|
-
"https://github.com/IgorGanapolsky",
|
|
66
|
-
"https://www.linkedin.com/in/igor-ganapolsky-5324b7330",
|
|
67
|
-
"https://dev.to/igorganapolsky",
|
|
68
|
-
"https://www.upwork.com/freelancers/~01b5f08ed8b1a9a3c1",
|
|
69
|
-
"https://huggingface.co/IgorGanapolsky",
|
|
70
|
-
"https://x.com/igorganapolsky"
|
|
71
|
-
],
|
|
72
|
-
"worksFor": {
|
|
73
|
-
"@type": "Organization",
|
|
74
|
-
"name": "ThumbGate",
|
|
75
|
-
"url": "https://thumbgate.ai/"
|
|
76
|
-
},
|
|
77
|
-
"owns": {
|
|
78
|
-
"@type": "SoftwareApplication",
|
|
79
|
-
"name": "ThumbGate",
|
|
80
|
-
"applicationCategory": "DeveloperApplication",
|
|
81
|
-
"description": "Pre-action defense for AI coding agents — thumbs up/down become history-aware lessons across Claude Code, Cursor, Codex, Gemini, Amp, Cline, OpenCode."
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
</script>
|
|
85
|
-
|
|
86
|
-
<style>
|
|
87
|
-
body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"SF Pro Text",Inter,system-ui,sans-serif;line-height:1.6;color:#0f172a;background:#fafafa}
|
|
88
|
-
header{padding:1rem 1.5rem;border-bottom:1px solid #e5e7eb;background:#fff;display:flex;align-items:center;gap:1rem}
|
|
89
|
-
header a{color:#0f172a;text-decoration:none;font-weight:600}
|
|
90
|
-
main{max-width:780px;margin:0 auto;padding:2.5rem 1.5rem}
|
|
91
|
-
h1{font-size:2rem;margin:0 0 .25rem}
|
|
92
|
-
h2{margin-top:2rem;font-size:1.25rem}
|
|
93
|
-
.lede{font-size:1.125rem;color:#475569}
|
|
94
|
-
.chips{display:flex;flex-wrap:wrap;gap:.4rem;margin:.75rem 0 0}
|
|
95
|
-
.chip{display:inline-block;padding:.2rem .55rem;border:1px solid #e2e8f0;border-radius:9999px;font-size:.85rem;color:#334155;background:#fff}
|
|
96
|
-
ul.links{list-style:none;padding:0;margin:1rem 0;display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:.5rem}
|
|
97
|
-
ul.links li a{display:block;padding:.6rem .8rem;background:#fff;border:1px solid #e2e8f0;border-radius:.5rem;color:#0f172a;text-decoration:none}
|
|
98
|
-
ul.links li a:hover{border-color:#0f172a}
|
|
99
|
-
.section{background:#fff;border:1px solid #e5e7eb;border-radius:.75rem;padding:1.25rem 1.5rem;margin:1.25rem 0}
|
|
100
|
-
footer{padding:2rem 1.5rem;color:#64748b;font-size:.9rem;text-align:center}
|
|
101
|
-
</style>
|
|
102
|
-
</head>
|
|
103
|
-
<body>
|
|
104
|
-
|
|
105
|
-
<header>
|
|
106
|
-
<a href="/">ThumbGate</a>
|
|
107
|
-
<span style="color:#94a3b8">/</span>
|
|
108
|
-
<span>About</span>
|
|
109
|
-
</header>
|
|
110
|
-
|
|
111
|
-
<main>
|
|
112
|
-
|
|
113
|
-
<h1>Igor Ganapolsky</h1>
|
|
114
|
-
<p class="lede">Senior AI & Mobile Engineer. Builder of <a href="/">ThumbGate</a> — pre-action defense for AI coding agents. Available for high-leverage Stripe Connect, Agentic RAG, and React Native contract engagements.</p>
|
|
115
|
-
|
|
116
|
-
<div class="chips">
|
|
117
|
-
<span class="chip">Coral Springs, FL · US</span>
|
|
118
|
-
<span class="chip">Agentic RAG</span>
|
|
119
|
-
<span class="chip">Stripe Connect</span>
|
|
120
|
-
<span class="chip">React Native</span>
|
|
121
|
-
<span class="chip">Android</span>
|
|
122
|
-
<span class="chip">Claude Code · Cursor · Codex · Gemini</span>
|
|
123
|
-
</div>
|
|
124
|
-
|
|
125
|
-
<section class="section">
|
|
126
|
-
<h2>What I build</h2>
|
|
127
|
-
<p><strong>ThumbGate</strong> is the agent-governance layer that turns 👍 / 👎 signals into history-aware lessons across Claude Code, Cursor, Codex, Gemini, Amp, Cline, and OpenCode. Pre-Action Checks block the next repeated mistake before the tool call runs — workflow shape, inspection evidence, token budget, and repeated-failure memory in one gate.</p>
|
|
128
|
-
<p>Outside ThumbGate, I ship production Stripe Connect marketplaces, Agentic RAG systems with LanceDB / pgvector / FAISS retrieval, React Native + Android apps, and LLM cost-instrumentation layers.</p>
|
|
129
|
-
</section>
|
|
130
|
-
|
|
131
|
-
<section class="section">
|
|
132
|
-
<h2>Recent ships</h2>
|
|
133
|
-
<ul>
|
|
134
|
-
<li><strong>Hilltown Media Group · Pretix Stripe Connect plugin</strong> — production payments + automation, 100% test coverage, milestone-funded delivery.</li>
|
|
135
|
-
<li><strong>ThumbGate v1.27</strong> — pre-action governance for AI coding agents, available on npm as <code>thumbgate</code>.</li>
|
|
136
|
-
<li><strong>Resume revenue ML stack</strong> — Thompson-sampling bandit + copy/buyer logistic regression + Agentic RAG for closed-loop UpWork bidding.</li>
|
|
137
|
-
</ul>
|
|
138
|
-
</section>
|
|
139
|
-
|
|
140
|
-
<section class="section">
|
|
141
|
-
<h2>For hire</h2>
|
|
142
|
-
<p>Available for fixed-price engagements ≥ $2K and ongoing retainers. Standard rate $120–150/hr 1099. Strong signal on Stripe Connect & Billing audits, RAG + LLM evaluation harnesses, and React Native + Android engineering. Email <a href="mailto:iganapolsky@gmail.com">iganapolsky@gmail.com</a> or reach me on <a href="https://www.upwork.com/freelancers/~01b5f08ed8b1a9a3c1">Upwork</a>.</p>
|
|
143
|
-
</section>
|
|
144
|
-
|
|
145
|
-
<h2>Links</h2>
|
|
146
|
-
<ul class="links">
|
|
147
|
-
<li><a href="https://github.com/IgorGanapolsky" rel="me">GitHub</a></li>
|
|
148
|
-
<li><a href="https://www.linkedin.com/in/igor-ganapolsky-5324b7330" rel="me">LinkedIn</a></li>
|
|
149
|
-
<li><a href="https://dev.to/igorganapolsky" rel="me">dev.to</a></li>
|
|
150
|
-
<li><a href="https://www.upwork.com/freelancers/~01b5f08ed8b1a9a3c1" rel="me">Upwork</a></li>
|
|
151
|
-
<li><a href="https://huggingface.co/IgorGanapolsky" rel="me">Hugging Face</a></li>
|
|
152
|
-
<li><a href="https://x.com/igorganapolsky" rel="me">X</a></li>
|
|
153
|
-
</ul>
|
|
154
|
-
|
|
155
|
-
</main>
|
|
156
|
-
|
|
157
|
-
<footer>
|
|
158
|
-
© 2026 ThumbGate · Built by <a href="/about">Igor Ganapolsky</a>
|
|
159
|
-
</footer>
|
|
160
|
-
|
|
161
|
-
</body>
|
|
162
|
-
</html>
|