thumbgate 1.27.6 → 1.27.8

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 (96) hide show
  1. package/.claude/commands/thumbgate-blocked.md +27 -0
  2. package/.claude/commands/thumbgate-doctor.md +30 -0
  3. package/.claude/commands/thumbgate-guard.md +36 -0
  4. package/.claude/commands/thumbgate-protect.md +30 -0
  5. package/.claude/commands/thumbgate-rules.md +30 -0
  6. package/.claude-plugin/plugin.json +1 -1
  7. package/.well-known/llms.txt +6 -2
  8. package/.well-known/mcp/server-card.json +1 -1
  9. package/README.md +49 -5
  10. package/adapters/claude/.mcp.json +2 -2
  11. package/adapters/letta/README.md +41 -0
  12. package/adapters/letta/thumbgate-letta-adapter.js +133 -0
  13. package/adapters/mcp/server-stdio.js +16 -1
  14. package/adapters/opencode/opencode.json +1 -1
  15. package/adapters/policy-engine/ethicore-guardian-client.js +68 -0
  16. package/adapters/policy-engine/thumbgate-policy-engine-adapter.js +260 -0
  17. package/bench/observability-eval-suite.json +26 -0
  18. package/bin/cli.js +180 -2
  19. package/bin/postinstall.js +1 -1
  20. package/config/gate-templates.json +84 -0
  21. package/config/gates/claim-verification.json +6 -0
  22. package/config/gates/default.json +20 -0
  23. package/config/github-about.json +1 -1
  24. package/config/model-candidates.json +50 -0
  25. package/package.json +66 -25
  26. package/public/agent-manager.html +41 -1
  27. package/public/agents-cost-savings.html +1 -1
  28. package/public/ai-malpractice-prevention.html +2 -1
  29. package/public/assets/brand/github-social-preview.png +0 -0
  30. package/public/assets/brand/thumbgate-icon-512.png +0 -0
  31. package/public/assets/brand/thumbgate-icon-pro-512.png +0 -0
  32. package/public/assets/brand/thumbgate-icon-team-512.png +0 -0
  33. package/public/assets/brand/thumbgate-logo-1200x360.png +0 -0
  34. package/public/assets/brand/thumbgate-mark-inline.svg +15 -0
  35. package/public/assets/brand/thumbgate-mark-pro.svg +23 -0
  36. package/public/assets/brand/thumbgate-mark-team.svg +26 -0
  37. package/public/assets/brand/thumbgate-mark.svg +15 -0
  38. package/public/assets/brand/thumbgate-wordmark.svg +20 -0
  39. package/public/assets/claude-thumbgate-statusbar.svg +8 -0
  40. package/public/assets/codex-thumbgate-statusbar-test.svg +9 -0
  41. package/public/assets/legal-intake-control-flow.svg +66 -0
  42. package/public/blog.html +1 -1
  43. package/public/brand/thumbgate-mark.svg +15 -0
  44. package/public/brand/thumbgate-og.svg +16 -0
  45. package/public/codex-enterprise.html +1 -1
  46. package/public/codex-plugin.html +1 -1
  47. package/public/compare.html +23 -3
  48. package/public/dashboard.html +312 -30
  49. package/public/federal.html +1 -1
  50. package/public/guide.html +5 -4
  51. package/public/index.html +167 -49
  52. package/public/js/buyer-intent.js +672 -0
  53. package/public/learn.html +74 -7
  54. package/public/lessons.html +2 -1
  55. package/public/numbers.html +3 -3
  56. package/public/pricing.html +63 -15
  57. package/public/pro.html +7 -7
  58. package/scripts/activation-quickstart.js +187 -0
  59. package/scripts/agent-memory-lifecycle.js +211 -0
  60. package/scripts/async-eval-observability.js +236 -0
  61. package/scripts/auto-promote-gates.js +75 -4
  62. package/scripts/build-metadata.js +24 -3
  63. package/scripts/cli-schema.js +22 -0
  64. package/scripts/dashboard-chat.js +2 -1
  65. package/scripts/dashboard.js +8 -0
  66. package/scripts/export-databricks-bundle.js +5 -1
  67. package/scripts/export-dpo-pairs.js +7 -2
  68. package/scripts/feedback-aggregate.js +281 -0
  69. package/scripts/feedback-loop.js +34 -0
  70. package/scripts/filesystem-search.js +35 -10
  71. package/scripts/gates-engine.js +198 -6
  72. package/scripts/gemini-embedding-policy.js +2 -1
  73. package/scripts/hook-stop-anti-claim.js +227 -0
  74. package/scripts/hook-thumbgate-cache-updater.js +18 -2
  75. package/scripts/lesson-inference.js +8 -3
  76. package/scripts/lesson-search.js +17 -1
  77. package/scripts/operational-integrity.js +39 -5
  78. package/scripts/plausible-domain-config.js +4 -2
  79. package/scripts/rate-limiter.js +12 -6
  80. package/scripts/secret-redaction.js +166 -0
  81. package/scripts/security-scanner.js +100 -0
  82. package/scripts/self-distill-agent.js +3 -1
  83. package/scripts/self-harness-optimizer.js +141 -0
  84. package/scripts/seo-gsd.js +635 -0
  85. package/scripts/statusline-cache-path.js +17 -2
  86. package/scripts/statusline-cache-read.js +57 -0
  87. package/scripts/statusline-local-stats.js +9 -1
  88. package/scripts/statusline-meta.js +5 -2
  89. package/scripts/statusline.sh +13 -1
  90. package/scripts/sync-telemetry-from-prod.js +374 -0
  91. package/scripts/telemetry-analytics.js +9 -0
  92. package/scripts/thumbgate-search.js +85 -19
  93. package/scripts/tool-contract-validator.js +76 -0
  94. package/scripts/vector-store.js +44 -0
  95. package/scripts/workspace-evolver.js +62 -2
  96. package/src/api/server.js +715 -86
@@ -0,0 +1,260 @@
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
+ };
@@ -0,0 +1,26 @@
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/cli.js CHANGED
@@ -61,14 +61,19 @@ const TRIAL_DAYS = 7;
61
61
 
62
62
  function checkoutUrlFor(source, content) {
63
63
  try {
64
- const url = new URL(PRO_CHECKOUT_URL);
64
+ const base = content === 'capture_feedback'
65
+ ? 'https://buy.stripe.com/7sYfZhaiE1eSbO99uj3sI0d'
66
+ : PRO_CHECKOUT_URL;
67
+ const url = new URL(base);
65
68
  url.searchParams.set('utm_source', source || 'cli');
66
69
  url.searchParams.set('utm_medium', 'cli');
67
70
  url.searchParams.set('utm_campaign', 'pro_conversion');
68
71
  if (content) url.searchParams.set('utm_content', content);
69
72
  return url.toString();
70
73
  } catch (_) {
71
- return PRO_CHECKOUT_URL;
74
+ return content === 'capture_feedback'
75
+ ? 'https://buy.stripe.com/7sYfZhaiE1eSbO99uj3sI0d'
76
+ : PRO_CHECKOUT_URL;
72
77
  }
73
78
  }
74
79
 
@@ -852,6 +857,14 @@ function quickStart() {
852
857
  console.log('');
853
858
  }
854
859
 
860
+ // Activation walkthrough (guided first rule + live demonstrated block).
861
+ // Implementation lives in scripts/activation-quickstart.js so it can be unit
862
+ // tested without executing the CLI's top-level command switch. `init` is
863
+ // deliberately untouched — this is an additive, separate command.
864
+ function quickstart() {
865
+ return require(path.join(PKG_ROOT, 'scripts', 'activation-quickstart')).quickstart();
866
+ }
867
+
855
868
  function init(cliArgs = parseArgs(process.argv.slice(3))) {
856
869
  const args = { ...cliArgs };
857
870
  if (args.help || args.h) {
@@ -1856,6 +1869,7 @@ function modelCandidatesCmd() {
1856
1869
  const maxCandidates = args.max ? Number(args.max) : undefined;
1857
1870
  const { reportPath, report } = writeModelCandidatesReport(undefined, {
1858
1871
  workload: args.workload,
1872
+ workloadFile: args['workload-file'] || args.workloadFile,
1859
1873
  provider: args.provider,
1860
1874
  family: args.family,
1861
1875
  gateway: args.gateway,
@@ -2174,6 +2188,26 @@ function pulse() {
2174
2188
  });
2175
2189
  }
2176
2190
 
2191
+ function checkUpdateCmd() {
2192
+ const { checkUpdate } = require(path.join(PKG_ROOT, 'scripts', 'check-update'));
2193
+ const args = parseArgs(process.argv.slice(3));
2194
+ checkUpdate({ verbose: !args.json, force: args.force }).then((res) => {
2195
+ if (args.json) {
2196
+ console.log(JSON.stringify(res, null, 2));
2197
+ }
2198
+ process.exit(0);
2199
+ }).catch((err) => {
2200
+ console.error(err && err.message ? err.message : err);
2201
+ process.exit(1);
2202
+ });
2203
+ }
2204
+
2205
+ function selfUpdateCmd() {
2206
+ const { selfUpdate } = require(path.join(PKG_ROOT, 'scripts', 'check-update'));
2207
+ const success = selfUpdate();
2208
+ process.exit(success ? 0 : 1);
2209
+ }
2210
+
2177
2211
  function dispatchBrief() {
2178
2212
  const args = parseArgs(process.argv.slice(3));
2179
2213
  const {
@@ -3137,6 +3171,7 @@ const SUBCOMMAND_HELP = {
3137
3171
  lessons: 'Usage: npx thumbgate lessons [--query="..."] [--limit=N]\n\nSearch the lesson database (Pro feature).',
3138
3172
  search: 'Usage: npx thumbgate search <query>\n\nSearch ThumbGate knowledge base (Pro feature).',
3139
3173
  'gate-check': 'Usage: npx thumbgate gate-check\n\nPreToolUse hook interface: reads tool call JSON from stdin, outputs gate verdict.',
3174
+ 'hermes-gate': 'Usage: npx thumbgate hermes-gate\n\nNous Research Hermes Agent pre_tool_call shell hook: reads Hermes tool-call JSON from stdin, runs the ThumbGate gate pipeline (strict by default), and outputs {"decision":"block","reason":...} to veto or {} to allow. Gates terminal/patch/skill_manage etc. See adapters/hermes/config.yaml.',
3140
3175
  'break-glass': 'Usage: npx thumbgate break-glass --reason="why" [--ttl=5m] [--json]\n\nShort-lived recovery path for over-firing gates. Allows hook settings edits and satisfies PR-create/thread-check gates without disabling core destructive-action protections.',
3141
3176
  serve: 'Usage: npx thumbgate serve\n\nStart the MCP stdio server. This is for agent runtimes, not the local HTTP dashboard.',
3142
3177
  mcp: 'Usage: npx thumbgate mcp\n\nAlias for `thumbgate serve`.',
@@ -3153,6 +3188,7 @@ const SUBCOMMAND_HELP = {
3153
3188
  'setup-vertex': 'Usage: npx thumbgate setup-vertex [--dry-run]\n\nAuto-enable Vertex AI API on GCP and write local Vertex routing config to .env. With --dry-run, only detect the active account/project and print the planned changes. This does not create or verify a Dialogflow CX agent; use the Dialogflow CX REST API or console for live-agent evidence.',
3154
3189
  'ai-inventory': 'Usage: npx thumbgate ai-inventory [--root <dir>] [--format=summary|json|cyclonedx] [--output <path>] [--max-files=N]\n\nScan source/manifests/model artifacts for AI, ML, agent-framework, vector DB, Vertex, Gemini, and Dialogflow CX components. Use --format=cyclonedx to produce exportable ML-BOM evidence for enterprise reviews.',
3155
3190
  brain: 'Usage: npx thumbgate brain [--write] [--json] [--limit=N]\n\nBuild the agent-readable "context brain" — a single artifact consolidating this\nrepo\'s lessons, prevention rules, active gates, and project context for a coding\nagent to read BEFORE acting. --write saves it to .thumbgate/BRAIN.md (versioned,\ndeterministic). --json emits the structured model. --limit caps lessons (default 15).',
3191
+ 'team-sync': 'Usage: npx thumbgate team-sync\n\nSynchronize prevention rules and context brain with your team\'s git repository (git pull --rebase & git push), then auto-rebuild the local brain.',
3156
3192
  };
3157
3193
 
3158
3194
  if (_wantsHelp && COMMAND && SUBCOMMAND_HELP[COMMAND]) {
@@ -3282,7 +3318,90 @@ function cmdBrain(args = {}) {
3282
3318
  return 0;
3283
3319
  }
3284
3320
 
3321
+ async function teamSync() {
3322
+ const { execSync } = require('child_process');
3323
+
3324
+ // Verify we are in a Git repo
3325
+ try {
3326
+ execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore', cwd: CWD });
3327
+ } catch (err) {
3328
+ console.error('❌ Error: The current directory is not a Git repository.');
3329
+ process.exit(1);
3330
+ }
3331
+
3332
+ console.log('🔄 Checking shared prevention rules status...');
3333
+
3334
+ const rulesRelative = '.thumbgate/prevention-rules.md';
3335
+ const brainRelative = '.thumbgate/BRAIN.md';
3336
+ const rulesPath = path.join(CWD, rulesRelative);
3337
+
3338
+ if (!fs.existsSync(rulesPath)) {
3339
+ console.log('⚠️ No local prevention rules file found to sync.');
3340
+ }
3341
+
3342
+ let statusOutput = '';
3343
+ try {
3344
+ statusOutput = execSync('git status --porcelain', { encoding: 'utf8', cwd: CWD }) || '';
3345
+ } catch (_) {}
3346
+ const hasLocalChanges = statusOutput.includes(rulesRelative) || statusOutput.includes(brainRelative) || statusOutput.includes('.thumbgate/');
3347
+
3348
+ if (hasLocalChanges) {
3349
+ console.log('📝 Local changes detected in prevention rules. Committing locally...');
3350
+ try {
3351
+ const filesToAdd = [rulesRelative, brainRelative].filter(f => fs.existsSync(path.join(CWD, f)));
3352
+ if (filesToAdd.length > 0) {
3353
+ const filesStr = filesToAdd.map(f => `"${f}"`).join(' ');
3354
+ execSync(`git add -f ${filesStr}`, { cwd: CWD });
3355
+ execSync('git commit -m "chore(thumbgate): update shared prevention rules [skip ci]"', { cwd: CWD });
3356
+ console.log('✅ Local rules committed successfully.');
3357
+ } else {
3358
+ console.log('✨ No local rules files exist to commit.');
3359
+ }
3360
+ } catch (e) {
3361
+ console.log('✨ No changes to commit (already staged/clean).');
3362
+ }
3363
+ } else {
3364
+ console.log('✨ No local rules changes to commit.');
3365
+ }
3366
+
3367
+ // Pull from remote
3368
+ console.log('📥 Pulling rules from teammate remote (git pull --rebase)...');
3369
+ try {
3370
+ execSync('git pull --rebase', { stdio: 'inherit', cwd: CWD });
3371
+ } catch (pullErr) {
3372
+ console.error('❌ Git pull failed. Please resolve conflicts manually.');
3373
+ process.exit(1);
3374
+ }
3375
+
3376
+ // Push to remote
3377
+ console.log('📤 Pushing rules to teammate remote (git push)...');
3378
+ try {
3379
+ execSync('git push', { stdio: 'inherit', cwd: CWD });
3380
+ } catch (pushErr) {
3381
+ console.error('❌ Git push failed. You may need to run git push manually.');
3382
+ process.exit(1);
3383
+ }
3384
+
3385
+ // Rebuild the context brain (.thumbgate/BRAIN.md) from the newly merged rules
3386
+ console.log('🧠 Rebuilding local context brain from merged rules...');
3387
+ try {
3388
+ const brainArgs = { write: true };
3389
+ cmdBrain(brainArgs);
3390
+ } catch (brainErr) {
3391
+ console.warn(`⚠️ Failed to rebuild context brain: ${brainErr.message}`);
3392
+ }
3393
+
3394
+ console.log('\n🚀 Team rules synchronization complete! Your agents now share team-wide learning.');
3395
+ }
3396
+
3285
3397
  switch (COMMAND) {
3398
+ case 'team-sync':
3399
+ case 'git-sync':
3400
+ teamSync().catch((err) => {
3401
+ console.error(err && err.message ? err.message : err);
3402
+ process.exit(1);
3403
+ });
3404
+ break;
3286
3405
  case '--version':
3287
3406
  case '-v':
3288
3407
  case 'version':
@@ -3292,6 +3411,10 @@ switch (COMMAND) {
3292
3411
  init();
3293
3412
  upgradeNudge();
3294
3413
  break;
3414
+ case 'quickstart':
3415
+ case 'first-rule':
3416
+ quickstart();
3417
+ break;
3295
3418
  case 'quick-start':
3296
3419
  quickStart();
3297
3420
  break;
@@ -3740,6 +3863,14 @@ switch (COMMAND) {
3740
3863
  case 'pulse':
3741
3864
  pulse();
3742
3865
  break;
3866
+ case 'check-update':
3867
+ case 'upgrade-check':
3868
+ checkUpdateCmd();
3869
+ break;
3870
+ case 'self-update':
3871
+ case 'upgrade-cli':
3872
+ selfUpdateCmd();
3873
+ break;
3743
3874
  case 'dispatch':
3744
3875
  case 'dispatch-brief':
3745
3876
  dispatchBrief();
@@ -3765,6 +3896,53 @@ switch (COMMAND) {
3765
3896
  });
3766
3897
  break;
3767
3898
  }
3899
+ case 'hermes-gate': {
3900
+ // Nous Research Hermes Agent `pre_tool_call` shell hook.
3901
+ // Hermes pipes each pending tool call as JSON to stdin and reads a decision from stdout;
3902
+ // {"decision":"block","reason":...} vetoes the call. We reuse the SAME gate pipeline as
3903
+ // `gate-check` (runAsync → secret guard, security scan, force-push / skill_manage / learned
3904
+ // prevention rules) and translate the verdict into Hermes's format.
3905
+ //
3906
+ // Hermes `pre_tool_call` is binary (block or allow) with no warn channel, and the whole point
3907
+ // of wiring it is to gate, so we run STRICT enforcement by default — otherwise ThumbGate's
3908
+ // warn-by-default posture would pass every deny through and the hook would block nothing.
3909
+ // Opt out with THUMBGATE_HERMES_WARN_ONLY=1; THUMBGATE_HOTFIX_BYPASS=1 still disables checks.
3910
+ // Wire it in ~/.hermes/config.yaml — see adapters/hermes/config.yaml.
3911
+ if (process.env.THUMBGATE_HERMES_WARN_ONLY !== '1' && process.env.THUMBGATE_HOTFIX_BYPASS !== '1') {
3912
+ process.env.THUMBGATE_STRICT_ENFORCEMENT = '1';
3913
+ }
3914
+ const { runAsync: hermesGateRun } = require(path.join(PKG_ROOT, 'scripts', 'gates-engine'));
3915
+ let hermesStdin = '';
3916
+ process.stdin.setEncoding('utf8');
3917
+ process.stdin.on('data', (chunk) => { hermesStdin += chunk; });
3918
+ process.stdin.on('end', async () => {
3919
+ try {
3920
+ const payload = JSON.parse(hermesStdin);
3921
+ // Hermes sends snake_case tool_name/tool_input — gates-engine reads these directly.
3922
+ const verdict = await hermesGateRun({ tool_name: payload.tool_name, tool_input: payload.tool_input });
3923
+ let parsed = {};
3924
+ try { parsed = JSON.parse(verdict); } catch (_e) { parsed = {}; }
3925
+ const hso = parsed.hookSpecificOutput || {};
3926
+ if (hso.permissionDecision === 'deny') {
3927
+ process.stdout.write(JSON.stringify({
3928
+ decision: 'block',
3929
+ reason: hso.permissionDecisionReason || 'Blocked by ThumbGate prevention rule.',
3930
+ }) + '\n');
3931
+ } else {
3932
+ // warn / no match → allow. The gate engine already logged the decision.
3933
+ process.stdout.write(JSON.stringify({}) + '\n');
3934
+ }
3935
+ process.exit(0);
3936
+ } catch (err) {
3937
+ // Hermes hooks fail OPEN on error/timeout — emit an explicit allow so a gate fault
3938
+ // never wedges the agent (reliability ≈ enforcement; keep this fast).
3939
+ process.stderr.write(`hermes-gate error: ${err.message}\n`);
3940
+ process.stdout.write(JSON.stringify({}) + '\n');
3941
+ process.exit(0);
3942
+ }
3943
+ });
3944
+ break;
3945
+ }
3768
3946
  case 'gate-stats':
3769
3947
  gateStats();
3770
3948
  break;
@@ -31,7 +31,7 @@ process.stderr.write(`
31
31
  │ Start now: npx thumbgate init │
32
32
  │ Updates: npx thumbgate subscribe you@company.com│
33
33
  │ │
34
- │ Free after trial: 3 rules, 5 captures/day. │
34
+ │ Free after trial: 3 rules, 2 captures/day. │
35
35
  │ Pro ($19/mo): unlimited everything. │
36
36
  ╰─────────────────────────────────────────────────────╯
37
37
 
@@ -157,6 +157,18 @@
157
157
  "roi": "Connects prevention with remediation: what credential lived where, who touched it, and whether rotation is required.",
158
158
  "rollout": "Use with incident-response runbooks and secrets scanner output from GitGuardian or internal tooling."
159
159
  },
160
+ {
161
+ "id": "require-local-dependency-vulnerability-scan",
162
+ "name": "Require local dependency vulnerability scan before installation",
163
+ "category": "Supply Chain Safety",
164
+ "signal": "👎",
165
+ "defaultAction": "block",
166
+ "severity": "high",
167
+ "pattern": "(npm install|pnpm add|yarn add|bun add|npm i|pnpm i|yarn i).*(vulnerable|cve|high|critical|osv|cve-lite)",
168
+ "problem": "Blocks package installations that introduce dependencies containing known high-severity or critical vulnerabilities (CVEs) before they are written to the lockfile.",
169
+ "roi": "Prevents AI agents from introducing vulnerable packages into the workspace. Complements zero-egress local-first scanning toolings (like OWASP CVE Lite CLI) directly at the terminal level.",
170
+ "rollout": "Enable on every repo where agents can install dependencies or modify lockfiles."
171
+ },
160
172
  {
161
173
  "id": "require-section-tree-before-multimodal-answer",
162
174
  "name": "Require section tree before multimodal answers",
@@ -576,6 +588,78 @@
576
588
  "problem": "Claw agents have broad device-level (local/shared) file system access. Must be strictly gated, especially in on-prem/air-gapped enterprise environments where most data lives.",
577
589
  "roi": "Directly supports the hybrid/on-prem reality emphasized in EnterpriseClaw coverage. Prevents broad access from becoming broad exfil or corruption. Ties to ThumbGate's existing path globs and protected files.",
578
590
  "rollout": "Use existing protected-paths + new claw-specific rules. Start with read-only for most, explicit approval for writes on sensitive dirs."
591
+ },
592
+ {
593
+ "id": "block-unauthorized-multi-channel-posts",
594
+ "name": "Block unauthorized multi-channel posts by Hermes Agent",
595
+ "category": "Nous Research Hermes Agent Governance",
596
+ "signal": "👎",
597
+ "defaultAction": "block",
598
+ "severity": "high",
599
+ "pattern": "(slack|telegram|discord|signal|whatsapp|send_message|post_message|channel_post).*(credentials|api_key|token|auth|password|unreviewed|unapproved)",
600
+ "problem": "Hermes Agent can post directly to messaging platforms. This gate prevents leaked credentials or unapproved messages from being posted publicly.",
601
+ "roi": "High: Prevents public-facing compliance violations, credential leaks, and messaging spam across Telegram, Slack, and Discord.",
602
+ "rollout": "Enable by default for agents connected to external communication channels."
603
+ },
604
+ {
605
+ "id": "prevent-infinite-skill-synthesis-loops",
606
+ "name": "Prevent infinite skill synthesis loops",
607
+ "category": "Nous Research Hermes Agent Governance",
608
+ "signal": "👎",
609
+ "defaultAction": "block",
610
+ "severity": "medium",
611
+ "pattern": "(hermes|skill synthesis|write skill|synthesize skill|evolve skill|self-improve).*(loop|repeated|stuck|regenerate|no progress)",
612
+ "problem": "Hermes Agent can get stuck repeatedly rewriting or generating the same skills in Markdown format without resolving the user's core task.",
613
+ "roi": "Reduces token burn and infinite loop execution loops by gating repeated skill write attempts.",
614
+ "rollout": "Warn/block when skill edits occur more than 3 times within the same session context."
615
+ },
616
+ {
617
+ "id": "validate-synthesized-skills-against-rules",
618
+ "name": "Validate synthesized skills against prevention rules",
619
+ "category": "Nous Research Hermes Agent Governance",
620
+ "signal": "👎",
621
+ "defaultAction": "block",
622
+ "severity": "critical",
623
+ "pattern": "(write skill|synthesize skill|create skill|update skill).*(override|bypass|ignore|violate).*(prevention rule|gate|thumbgate)",
624
+ "problem": "Hermes Agent's self-improvement loop can write new skills that conflict with or attempt to bypass established ThumbGate prevention rules.",
625
+ "roi": "Preserves security invariants by ensuring that synthesized skills never write code patterns blocked by active ThumbGate rules.",
626
+ "rollout": "Scan synthesized skill markdown content for pattern overlap with active prevention rules before writing to the skills directory."
627
+ },
628
+ {
629
+ "id": "require-human-in-the-loop-pause",
630
+ "name": "Enforce Human-in-the-Loop pause for critical decisions",
631
+ "category": "AI Engineering Stack Safety",
632
+ "signal": "👎",
633
+ "defaultAction": "block",
634
+ "severity": "high",
635
+ "pattern": "(agent_sdk|openrouter_sdk|human_in_loop|auto_resolve).*(auto_resolve|bypass|skip_pause|critical_action)",
636
+ "problem": "Prevents autonomous agents using the Agent SDK from auto-resolving critical decisions (such as model migration, credential rotations, or financial transactions) without pausing for explicit human approval.",
637
+ "roi": "Ensures human oversight remains functional on highest-risk actions, preventing the agent from acting entirely unsupervised on critical gates.",
638
+ "rollout": "Turn on for all production deployments utilizing OpenRouter or other Agent SDKs with auto-resolve capabilities."
639
+ },
640
+ {
641
+ "id": "careful-mode",
642
+ "name": "Enforce careful mode for destructive commands",
643
+ "category": "On-Demand Dynamic Gating",
644
+ "signal": "👎",
645
+ "defaultAction": "block",
646
+ "severity": "critical",
647
+ "pattern": "(rm\\s+-rf|drop\\s+table|force-push|git\\s+push\\s+-[fF]|git\\s+push\\s+--force|kubectl\\s+delete)",
648
+ "problem": "Blocks dangerous or destructive command sequences (rm -rf, DROP TABLE, force-push, kubectl delete) during sessions where careful mode is activated.",
649
+ "roi": "Protects production environments and main branches from accidental destructive commands executed by autonomous agents.",
650
+ "rollout": "Enable dynamically on-demand using /careful or env overrides when executing in sensitive or production workspaces."
651
+ },
652
+ {
653
+ "id": "freeze-mode",
654
+ "name": "Freeze directory edits",
655
+ "category": "On-Demand Dynamic Gating",
656
+ "signal": "👎",
657
+ "defaultAction": "block",
658
+ "severity": "high",
659
+ "pattern": "^freeze_edit_outside_path.*",
660
+ "problem": "Blocks any file edit or write tool call that is not within a specified active/target directory.",
661
+ "roi": "Restricts agent edits to a controlled workspace, preventing scope drift, accidental modification of configuration or source files, and unwanted global refactors during focused task debugging.",
662
+ "rollout": "Enable dynamically on-demand using /freeze to specify the only path(s) the agent is allowed to write to during the session."
579
663
  }
580
664
  ]
581
665
  }
@@ -30,6 +30,12 @@
30
30
  "requiredActions": ["github_metadata_verified"],
31
31
  "message": "You claimed GitHub repository metadata was updated or verified without source-of-truth evidence. Read it back with gh api/gh repo view and rendered HTML, then call track_action('github_metadata_verified').",
32
32
  "createdAt": 1780677400000
33
+ },
34
+ {
35
+ "pattern": "(money|payment|charge|checkout|revenue|price|pricing|invoice|billing|tax|sales tax|inventory|stock|permission|access|customer-facing|customer facing).*(correct|accurate|verified|valid|matches|working|fixed|resolved|calculated|configured)|(correct|accurate|verified|valid|matches|working|fixed|resolved|calculated|configured).*(money|payment|charge|checkout|revenue|price|pricing|invoice|billing|tax|sales tax|inventory|stock|permission|access|customer-facing|customer facing)",
36
+ "requiredActions": ["commercial_truth_verified"],
37
+ "message": "You claimed a commercial-data fact (money, tax, inventory, permissions, or customer-facing state) without external source-of-truth evidence. Read the authoritative system first, then call track_action('commercial_truth_verified').",
38
+ "createdAt": 1781640000000
33
39
  }
34
40
  ]
35
41
  }