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.
Files changed (107) hide show
  1. package/.claude-plugin/marketplace.json +85 -0
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.well-known/mcp/server-card.json +1 -1
  4. package/README.md +33 -5
  5. package/adapters/chatgpt/openapi.yaml +1707 -0
  6. package/adapters/claude/.mcp.json +2 -2
  7. package/adapters/codex/config.toml +2 -2
  8. package/adapters/mcp/server-stdio.js +1 -1
  9. package/adapters/opencode/opencode.json +1 -1
  10. package/bin/cli.js +63 -54
  11. package/bin/postinstall.js +23 -14
  12. package/config/post-deploy-marketing-pages.json +19 -0
  13. package/config/tessl-tiles.json +1 -3
  14. package/package.json +99 -229
  15. package/public/guide.html +0 -4
  16. package/public/index.html +28 -49
  17. package/public/learn.html +0 -8
  18. package/public/numbers.html +2 -2
  19. package/scripts/agent-readiness.js +0 -147
  20. package/scripts/bot-detection.js +61 -3
  21. package/scripts/cli-feedback.js +4 -14
  22. package/scripts/cli-schema.js +7 -0
  23. package/scripts/durability/step.js +3 -3
  24. package/scripts/feedback-loop.js +10 -32
  25. package/scripts/gates-engine.js +72 -1
  26. package/scripts/hook-runtime.js +2 -2
  27. package/scripts/license.js +10 -9
  28. package/scripts/llm-client.js +44 -260
  29. package/scripts/mcp-config.js +7 -1
  30. package/scripts/published-cli.js +0 -8
  31. package/scripts/seo-gsd.js +0 -5
  32. package/src/api/server.js +111 -35
  33. package/.claude/commands/dashboard.md +0 -15
  34. package/.claude/commands/thumbgate-blocked.md +0 -27
  35. package/.claude/commands/thumbgate-dashboard.md +0 -15
  36. package/.claude/commands/thumbgate-doctor.md +0 -30
  37. package/.claude/commands/thumbgate-guard.md +0 -36
  38. package/.claude/commands/thumbgate-protect.md +0 -30
  39. package/.claude/commands/thumbgate-rules.md +0 -30
  40. package/adapters/gcp/dfcx-webhook-gate.js +0 -295
  41. package/adapters/letta/README.md +0 -41
  42. package/adapters/letta/thumbgate-letta-adapter.js +0 -133
  43. package/adapters/policy-engine/ethicore-guardian-client.js +0 -68
  44. package/adapters/policy-engine/thumbgate-policy-engine-adapter.js +0 -260
  45. package/bench/observability-eval-suite.json +0 -26
  46. package/bin/dashboard-cli.js +0 -7
  47. package/commands/dashboard.md +0 -15
  48. package/commands/thumbgate-dashboard.md +0 -15
  49. package/config/builtin-lessons.json +0 -23
  50. package/public/about.html +0 -162
  51. package/public/agent-manager.html +0 -179
  52. package/public/agents-cost-savings.html +0 -153
  53. package/public/ai-malpractice-prevention.html +0 -818
  54. package/public/assets/brand/github-social-preview.png +0 -0
  55. package/public/assets/brand/thumbgate-icon-512.png +0 -0
  56. package/public/assets/brand/thumbgate-icon-pro-512.png +0 -0
  57. package/public/assets/brand/thumbgate-icon-team-512.png +0 -0
  58. package/public/assets/brand/thumbgate-logo-1200x360.png +0 -0
  59. package/public/assets/brand/thumbgate-logo-transparent.svg +0 -22
  60. package/public/assets/brand/thumbgate-mark-inline-v3.svg +0 -19
  61. package/public/assets/brand/thumbgate-mark-pro.svg +0 -23
  62. package/public/assets/brand/thumbgate-mark-team.svg +0 -26
  63. package/public/assets/brand/thumbgate-mark.svg +0 -21
  64. package/public/assets/brand/thumbgate-wordmark.svg +0 -20
  65. package/public/assets/claude-thumbgate-statusbar.svg +0 -8
  66. package/public/assets/codex-thumbgate-statusbar-test.svg +0 -9
  67. package/public/assets/legal-intake-control-flow.svg +0 -66
  68. package/public/brand/thumbgate-mark.svg +0 -19
  69. package/public/brand/thumbgate-og.svg +0 -16
  70. package/public/chatgpt-app.html +0 -330
  71. package/public/codex-enterprise.html +0 -123
  72. package/public/js/buyer-intent.js +0 -672
  73. package/public/pricing.html +0 -399
  74. package/scripts/action-receipts.js +0 -324
  75. package/scripts/activation-quickstart.js +0 -187
  76. package/scripts/agent-operations-planner.js +0 -621
  77. package/scripts/ai-component-inventory.js +0 -367
  78. package/scripts/async-eval-observability.js +0 -236
  79. package/scripts/audit.js +0 -65
  80. package/scripts/aws-blocks-guardrails.js +0 -228
  81. package/scripts/bot-detector.js +0 -50
  82. package/scripts/classifier-routing.js +0 -130
  83. package/scripts/dashboard-chat.js +0 -332
  84. package/scripts/feedback-aggregate.js +0 -281
  85. package/scripts/feedback-sanitizer.js +0 -105
  86. package/scripts/hob-pack.js +0 -591
  87. package/scripts/hook-stop-anti-claim.js +0 -301
  88. package/scripts/install-shim.js +0 -87
  89. package/scripts/mcp-oauth.js +0 -293
  90. package/scripts/noop-detect.js +0 -285
  91. package/scripts/omlx-smoke.js +0 -192
  92. package/scripts/parallel-workflow-orchestrator.js +0 -293
  93. package/scripts/plan-gate.js +0 -243
  94. package/scripts/plausible-domain-config.js +0 -99
  95. package/scripts/qa-scenario-planner.js +0 -136
  96. package/scripts/repeat-metric.js +0 -137
  97. package/scripts/secret-fixture-tokens.js +0 -61
  98. package/scripts/secret-redaction.js +0 -166
  99. package/scripts/self-harness-optimizer.js +0 -141
  100. package/scripts/self-healing-check.js +0 -193
  101. package/scripts/silent-failure-cluster.js +0 -531
  102. package/scripts/statusline-cache-read.js +0 -57
  103. package/scripts/sync-telemetry-from-prod.js +0 -374
  104. package/scripts/tool-contract-validator.js +0 -76
  105. package/scripts/trajectory-scorer.js +0 -63
  106. package/scripts/verify-marketing-pages-deployed.js +0 -195
  107. 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
- }
@@ -1,7 +0,0 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- // Insert 'dashboard' and '--open' as the subcommands/arguments
5
- process.argv.splice(2, 0, 'dashboard', '--open');
6
-
7
- require('./cli.js');
@@ -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 &amp; 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 &amp; 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 &amp; 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 &amp; 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>