thumbgate 1.27.12 → 1.27.14

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 (133) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.well-known/llms.txt +2 -1
  3. package/.well-known/mcp/server-card.json +1 -1
  4. package/README.md +2 -4
  5. package/adapters/claude/.mcp.json +2 -2
  6. package/adapters/mcp/server-stdio.js +1 -1
  7. package/adapters/opencode/opencode.json +1 -1
  8. package/adapters/policy-engine/ethicore-guardian-client.js +68 -0
  9. package/adapters/policy-engine/thumbgate-policy-engine-adapter.js +260 -0
  10. package/bin/cli.js +78 -259
  11. package/config/gate-templates.json +0 -228
  12. package/config/gates/claim-verification.json +0 -18
  13. package/package.json +35 -25
  14. package/public/assets/brand/thumbgate-logo-transparent.svg +22 -0
  15. package/public/assets/brand/thumbgate-mark-inline-v3.svg +19 -0
  16. package/public/assets/brand/thumbgate-mark.svg +11 -5
  17. package/public/blog.html +0 -30
  18. package/public/brand/thumbgate-mark.svg +9 -5
  19. package/public/chatgpt-app.html +2 -2
  20. package/public/compare.html +2 -1
  21. package/public/dashboard.html +1 -1
  22. package/public/federal.html +1 -1
  23. package/public/index.html +95 -216
  24. package/public/learn.html +59 -35
  25. package/public/lessons.html +1 -1
  26. package/public/numbers.html +2 -2
  27. package/public/pro.html +7 -7
  28. package/scripts/agent-readiness.js +142 -0
  29. package/scripts/aws-blocks-guardrails.js +228 -0
  30. package/scripts/cli-schema.js +22 -10
  31. package/scripts/dashboard-chat.js +2 -1
  32. package/scripts/document-intake.js +1 -49
  33. package/scripts/durability/step.js +3 -3
  34. package/scripts/gate-stats.js +5 -11
  35. package/scripts/gates-engine.js +0 -49
  36. package/scripts/gemini-embedding-policy.js +2 -1
  37. package/scripts/hook-stop-anti-claim.js +116 -184
  38. package/scripts/hosted-config.js +0 -12
  39. package/scripts/lesson-search.js +1 -15
  40. package/scripts/llm-client.js +187 -5
  41. package/scripts/plausible-domain-config.js +3 -1
  42. package/scripts/seo-gsd.js +240 -1
  43. package/scripts/tool-registry.js +2 -2
  44. package/scripts/vector-store.js +44 -0
  45. package/scripts/workspace-evolver.js +62 -2
  46. package/src/api/server.js +340 -131
  47. package/public/assets/brand/thumbgate-mark-inline.svg +0 -15
  48. package/public/compare/adopt-ai.html +0 -219
  49. package/public/compare/agentix-labs.html +0 -197
  50. package/public/compare/ai-experience-orchestration.html +0 -216
  51. package/public/compare/anthropic-claude-for-legal.html +0 -260
  52. package/public/compare/anthropic-containment.html +0 -280
  53. package/public/compare/arcade.html +0 -175
  54. package/public/compare/arcjet.html +0 -239
  55. package/public/compare/bumblebee.html +0 -307
  56. package/public/compare/claude-code-hooks.html +0 -294
  57. package/public/compare/databricks-unity-ai-gateway.html +0 -215
  58. package/public/compare/fallow.html +0 -351
  59. package/public/compare/heidi.html +0 -233
  60. package/public/compare/mem0.html +0 -342
  61. package/public/compare/oak-and-sparrow-gatekeeper.html +0 -289
  62. package/public/compare/rein.html +0 -236
  63. package/public/compare/sigmashake.html +0 -256
  64. package/public/compare/speclock.html +0 -342
  65. package/public/guides/agent-harness-optimization.html +0 -342
  66. package/public/guides/agentic-web-governance.html +0 -406
  67. package/public/guides/ai-agent-governance-sprint.html +0 -415
  68. package/public/guides/ai-agent-pre-action-approval-gates.html +0 -401
  69. package/public/guides/ai-agent-workflow-migration-checklist.html +0 -392
  70. package/public/guides/ai-deployment-readiness.html +0 -415
  71. package/public/guides/ai-mode-ads-agent-governance.html +0 -401
  72. package/public/guides/ai-search-topical-presence.html +0 -342
  73. package/public/guides/autoresearch-agent-safety.html +0 -342
  74. package/public/guides/background-agent-governance.html +0 -358
  75. package/public/guides/best-tools-stop-ai-agents-breaking-production.html +0 -363
  76. package/public/guides/browser-automation-safety.html +0 -342
  77. package/public/guides/chatgpt-ads-trust.html +0 -353
  78. package/public/guides/claude-code-feedback.html +0 -339
  79. package/public/guides/claude-code-prevent-repeated-mistakes.html +0 -161
  80. package/public/guides/claude-code-skills-guardrails.html +0 -343
  81. package/public/guides/claude-desktop.html +0 -356
  82. package/public/guides/code-knowledge-graph-guardrails.html +0 -365
  83. package/public/guides/codex-cli-guardrails.html +0 -339
  84. package/public/guides/cursor-agent-guardrails.html +0 -339
  85. package/public/guides/cursor-prevent-repeated-mistakes.html +0 -161
  86. package/public/guides/database-agent-safety.html +0 -406
  87. package/public/guides/deepseek-v4-runtime-guardrails.html +0 -346
  88. package/public/guides/developer-machine-supply-chain-guardrails.html +0 -358
  89. package/public/guides/gcp-mcp-guardrails.html +0 -147
  90. package/public/guides/gemini-cli-feedback-memory.html +0 -339
  91. package/public/guides/gpt-5-5-model-evaluation.html +0 -358
  92. package/public/guides/internal-ai-engineering-stack-guardrails.html +0 -348
  93. package/public/guides/long-running-agent-context-management.html +0 -346
  94. package/public/guides/mcp-tool-governance.html +0 -401
  95. package/public/guides/multica-thumbgate-setup.html +0 -134
  96. package/public/guides/native-messaging-host-security.html +0 -342
  97. package/public/guides/policy-engine-pre-action-gates.html +0 -346
  98. package/public/guides/pre-action-checks.html +0 -342
  99. package/public/guides/pretooluse-hooks-vs-advisory-prompt-rules.html +0 -342
  100. package/public/guides/prompt-tricks-to-workflow-rules.html +0 -365
  101. package/public/guides/proxy-pointer-rag-guardrails.html +0 -352
  102. package/public/guides/rag-precision-tuning-guardrails.html +0 -352
  103. package/public/guides/reasoning-compression-guardrails.html +0 -346
  104. package/public/guides/relational-knowledge-ai-recommendations.html +0 -342
  105. package/public/guides/roo-code-alternative-cline.html +0 -339
  106. package/public/guides/semantic-programmatic-seo-guardrails.html +0 -352
  107. package/public/guides/seo-agent-skills-guardrails.html +0 -344
  108. package/public/guides/stop-repeated-ai-agent-mistakes.html +0 -342
  109. package/public/learn/ac-dc-runtime-enforcement.html +0 -277
  110. package/public/learn/agent-harness-pattern.html +0 -181
  111. package/public/learn/agent-identity-connector-governance.html +0 -146
  112. package/public/learn/agent-swarms-shared-gates.html +0 -173
  113. package/public/learn/agentic-enterprise-context-brain.html +0 -117
  114. package/public/learn/agentic-os-team-governance.html +0 -146
  115. package/public/learn/ai-agent-governance.html +0 -158
  116. package/public/learn/ai-agent-persistent-memory.html +0 -211
  117. package/public/learn/anthropomorphic-claim-gates.html +0 -180
  118. package/public/learn/background-agent-control-layer.html +0 -184
  119. package/public/learn/claude-code-goal-with-rubrics.html +0 -205
  120. package/public/learn/codex-role-plugins-need-governance.html +0 -125
  121. package/public/learn/cost-aware-agent-gate-routing.html +0 -173
  122. package/public/learn/databricks-unity-ai-gateway-runtime-governance.html +0 -157
  123. package/public/learn/deterministic-agent-workflows.html +0 -185
  124. package/public/learn/feedback-loop-vs-decision-layer.html +0 -283
  125. package/public/learn/from-prototype-to-production.html +0 -223
  126. package/public/learn/learn.css +0 -51
  127. package/public/learn/mcp-pre-action-checks-explained.html +0 -172
  128. package/public/learn/pretix-stripe-connect-marketplaces.html +0 -161
  129. package/public/learn/regulated-agent-execution-boundary.html +0 -196
  130. package/public/learn/spec-driven-development.html +0 -168
  131. package/public/learn/stop-ai-agent-force-push.html +0 -134
  132. package/public/learn/vibe-coding-safety-net.html +0 -142
  133. package/scripts/reddit-browser-notification-watch.js +0 -230
@@ -0,0 +1,228 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('node:fs');
5
+ const path = require('node:path');
6
+
7
+ const AWS_BLOCKS_DEPENDENCY_PATTERN = /(^|\/)@aws-blocks\/(?:blocks|[^/\s"']+)/;
8
+
9
+ function toList(value) {
10
+ if (Array.isArray(value)) return value.map(String).map((entry) => entry.trim()).filter(Boolean);
11
+ if (typeof value === 'string') return value.split(',').map((entry) => entry.trim()).filter(Boolean);
12
+ return [];
13
+ }
14
+
15
+ function normalizeText(value) {
16
+ if (value == null) return '';
17
+ if (typeof value === 'string') return value;
18
+ try {
19
+ return JSON.stringify(value);
20
+ } catch {
21
+ return String(value);
22
+ }
23
+ }
24
+
25
+ function readPackageJson(projectDir) {
26
+ try {
27
+ return JSON.parse(fs.readFileSync(path.join(projectDir, 'package.json'), 'utf8'));
28
+ } catch {
29
+ return null;
30
+ }
31
+ }
32
+
33
+ function packageUsesAwsBlocks(pkg) {
34
+ if (!pkg || typeof pkg !== 'object') return false;
35
+ const dependencyText = JSON.stringify({
36
+ dependencies: pkg.dependencies || {},
37
+ devDependencies: pkg.devDependencies || {},
38
+ peerDependencies: pkg.peerDependencies || {},
39
+ optionalDependencies: pkg.optionalDependencies || {},
40
+ });
41
+ const scriptsText = JSON.stringify(pkg.scripts || {});
42
+ return AWS_BLOCKS_DEPENDENCY_PATTERN.test(dependencyText) || /aws-blocks|blocks-app|cdk|sandbox/i.test(scriptsText);
43
+ }
44
+
45
+ function detectAwsBlocksProject(projectDir = process.cwd()) {
46
+ const pkg = readPackageJson(projectDir);
47
+ if (packageUsesAwsBlocks(pkg)) return true;
48
+ return fs.existsSync(path.join(projectDir, 'aws-blocks', 'index.ts'))
49
+ || fs.existsSync(path.join(projectDir, 'aws-blocks', 'index.js'))
50
+ || fs.existsSync(path.join(projectDir, 'blocks.spec.json'));
51
+ }
52
+
53
+ function detectAction(input = {}) {
54
+ const command = normalizeText(input.command || input.args || input.shell || input.toolInput);
55
+ const toolName = normalizeText(input.toolName || input.tool || input.name);
56
+ const code = normalizeText(input.code || input.file || input.diff || input.body);
57
+ const combined = `${toolName}\n${command}\n${code}`;
58
+ const lower = combined.toLowerCase();
59
+
60
+ const signals = [];
61
+ const add = (id, label, severity = 'medium') => signals.push({ id, label, severity });
62
+
63
+ if (/\b(cdk\s+deploy|npm\s+run\s+deploy|pnpm\s+deploy|yarn\s+deploy|sst\s+deploy|blocks?\s+deploy)\b/i.test(combined)) {
64
+ add('aws-blocks-production-deploy', 'production deploy from an AWS Blocks workflow', 'high');
65
+ }
66
+
67
+ if (/\b(cdk\s+destroy|npm\s+run\s+destroy|pnpm\s+destroy|yarn\s+destroy|sandbox\b.*--destroy|--destroy\b|aws\s+cloudformation\s+delete-stack)\b/i.test(combined)) {
68
+ add('aws-blocks-destroy', 'destroy command can remove AWS resources created from local Blocks code', 'critical');
69
+ }
70
+
71
+ if (/\b(drop\s+(table|database|schema|index|column)|truncate\s+table|delete\s+from\s+[\w".-]+(?:\s*;|\s*$)|update\s+[\w".-]+\s+set\b(?![\s\S]{0,160}\bwhere\b))/i.test(combined)) {
72
+ add('destructive-sql-or-ddl', 'destructive or unscoped SQL mutation', 'critical');
73
+ }
74
+
75
+ if (/\b(aws\s+dynamodb\s+delete-table|aws\s+rds\s+delete-db-instance|aws\s+s3\s+rm\b[\s\S]*--recursive|aws\s+lambda\s+delete-function|aws\s+bedrock-agent\s+delete-|aws\s+bedrock-agent-runtime\b)/i.test(combined)) {
76
+ add('destructive-aws-cli', 'destructive AWS CLI or Bedrock agent action', 'critical');
77
+ }
78
+
79
+ if (/\b(gcloud|aws)\b[\s\S]{0,220}\b(add-iam-policy-binding|put-role-policy|attach-role-policy|create-policy-version)\b/i.test(combined)
80
+ || /\b(AdministratorAccess|roles\/owner|iam:PassRole|serviceAccountTokenCreator)\b/i.test(combined)) {
81
+ add('iam-escalation', 'agent session attempts to grant broad IAM authority', 'critical');
82
+ }
83
+
84
+ if (/\b(new\s+Agent\s*\(|@aws-blocks\/(?:agent|blocks)|\bAgent\b[\s\S]{0,120}\btools?\b)/.test(combined)) {
85
+ add('blocks-agent-tool-call', 'AWS Blocks Agent or Bedrock-style tool action needs a tool boundary', 'medium');
86
+ }
87
+
88
+ if (/\b(npm\s+run\s+dev|npm\s+start|pnpm\s+dev|yarn\s+dev|create-blocks-app)\b/i.test(combined)) {
89
+ add('local-dev', 'local AWS Blocks development loop', 'low');
90
+ }
91
+
92
+ return {
93
+ command,
94
+ toolName,
95
+ code,
96
+ signals,
97
+ highRisk: signals.some((signal) => ['high', 'critical'].includes(signal.severity)),
98
+ localOnly: signals.some((signal) => signal.id === 'local-dev') && signals.every((signal) => signal.severity === 'low'),
99
+ lower,
100
+ };
101
+ }
102
+
103
+ function evaluateAwsBlocksAction(input = {}) {
104
+ const projectDir = input.projectDir || input.cwd || process.cwd();
105
+ const projectUsesAwsBlocks = Boolean(
106
+ input.projectUsesAwsBlocks
107
+ || input.awsBlocks
108
+ || input.blocksProject
109
+ || detectAwsBlocksProject(projectDir)
110
+ );
111
+ const evidence = new Set(toList(input.evidence || input.proof || input.receipts));
112
+ const action = detectAction(input);
113
+ const requiredEvidence = [];
114
+
115
+ const requireEvidence = (id, label) => {
116
+ if (!evidence.has(id)) requiredEvidence.push({ id, label });
117
+ };
118
+
119
+ if (projectUsesAwsBlocks && action.signals.some((signal) => signal.id === 'aws-blocks-production-deploy')) {
120
+ requireEvidence('local-tests-pass', 'local AWS Blocks tests pass against local implementations');
121
+ requireEvidence('cdk-diff-reviewed', 'CDK diff or synthesized CloudFormation change set reviewed');
122
+ requireEvidence('cost-blast-radius-reviewed', 'AWS cost and resource blast radius reviewed');
123
+ }
124
+
125
+ if (action.signals.some((signal) => signal.id === 'aws-blocks-destroy')) {
126
+ requireEvidence('resource-inventory-exported', 'resource inventory exported before destroy');
127
+ requireEvidence('human-destroy-approval', 'named human approval for destroy');
128
+ }
129
+
130
+ if (action.signals.some((signal) => signal.id === 'destructive-sql-or-ddl')) {
131
+ requireEvidence('backup-or-rollback-ready', 'backup, rollback, or restore point exists');
132
+ requireEvidence('bounded-row-count-reviewed', 'row/table impact was previewed before mutation');
133
+ }
134
+
135
+ if (action.signals.some((signal) => signal.id === 'destructive-aws-cli')) {
136
+ requireEvidence('aws-account-and-region-confirmed', 'target AWS account and region confirmed');
137
+ requireEvidence('rollback-plan-attached', 'rollback or recovery plan attached');
138
+ }
139
+
140
+ if (action.signals.some((signal) => signal.id === 'iam-escalation')) {
141
+ requireEvidence('least-privilege-review', 'least-privilege review completed');
142
+ requireEvidence('security-owner-approval', 'security owner approval captured');
143
+ }
144
+
145
+ if (action.signals.some((signal) => signal.id === 'blocks-agent-tool-call')) {
146
+ requireEvidence('agent-tool-allowlist', 'agent tool allowlist and data boundary declared');
147
+ }
148
+
149
+ const shouldBlock = projectUsesAwsBlocks && requiredEvidence.length > 0;
150
+ const status = shouldBlock ? 'blocked' : (action.highRisk ? 'needs-review' : 'allowed');
151
+
152
+ return {
153
+ name: 'thumbgate-aws-blocks-guardrails',
154
+ status,
155
+ projectUsesAwsBlocks,
156
+ signals: action.signals,
157
+ requiredEvidence,
158
+ enforcementBoundary: 'local AWS Blocks confidence must not automatically become production AWS authority',
159
+ gates: [
160
+ 'allow local AWS Blocks dev loops without AWS credentials',
161
+ 'require local test proof plus CDK diff before production deploy',
162
+ 'block destroy and destructive data mutations until backup, blast-radius, and human approval evidence exists',
163
+ 'block IAM escalation and Bedrock/Agent tool expansion until an owner approves the tool boundary',
164
+ 'write a ThumbGate receipt for every allowed high-risk cloud action',
165
+ ],
166
+ nextActions: shouldBlock
167
+ ? requiredEvidence.map((item) => `Provide ${item.id}: ${item.label}`)
168
+ : ['Record an allow receipt when this action touches real AWS resources'],
169
+ };
170
+ }
171
+
172
+ function buildAwsBlocksHardeningOffer(input = {}) {
173
+ const workflow = String(input.workflow || input.name || 'AWS Blocks backend').trim();
174
+ const buyer = String(input.buyer || input.owner || 'platform owner').trim();
175
+ return {
176
+ name: 'thumbgate-aws-blocks-hardening-offer',
177
+ status: 'ready-for-positioning',
178
+ buyer,
179
+ workflow,
180
+ headline: 'AWS Blocks helps agents build the backend. ThumbGate stops them before unsafe cloud actions run.',
181
+ offer: 'AWS Blocks Agent Safety Review',
182
+ diagnosticPrice: '$499',
183
+ proofPlan: [
184
+ 'map one AWS Blocks local-to-cloud workflow',
185
+ 'install ThumbGate against the agent running that workflow',
186
+ 'add gates for deploy, destroy, data mutation, IAM, Bedrock Agent, and cost-blast-radius actions',
187
+ 'produce a receipt showing the first blocked repeat and the evidence required to allow it',
188
+ ],
189
+ cta: 'Send one AWS Blocks workflow that is about to deploy, mutate data, or call Bedrock tools.',
190
+ };
191
+ }
192
+
193
+ function parseArgs(argv = process.argv.slice(2)) {
194
+ const args = {};
195
+ for (const arg of argv) {
196
+ if (arg === '--json') args.json = true;
197
+ else if (arg === '--aws-blocks' || arg === '--project-uses-aws-blocks') args.projectUsesAwsBlocks = true;
198
+ else if (arg.startsWith('--command=')) args.command = arg.slice('--command='.length);
199
+ else if (arg.startsWith('--tool=')) args.toolName = arg.slice('--tool='.length);
200
+ else if (arg.startsWith('--code=')) args.code = arg.slice('--code='.length);
201
+ else if (arg.startsWith('--cwd=')) args.projectDir = arg.slice('--cwd='.length);
202
+ else if (arg.startsWith('--evidence=')) args.evidence = arg.slice('--evidence='.length);
203
+ else if (arg.startsWith('--workflow=')) args.workflow = arg.slice('--workflow='.length);
204
+ else if (arg.startsWith('--buyer=')) args.buyer = arg.slice('--buyer='.length);
205
+ else if (arg === 'offer') args.commandName = 'offer';
206
+ }
207
+ return args;
208
+ }
209
+
210
+ function runCli(args = parseArgs()) {
211
+ const report = args.commandName === 'offer'
212
+ ? buildAwsBlocksHardeningOffer(args)
213
+ : evaluateAwsBlocksAction(args);
214
+ if (args.json) console.log(JSON.stringify(report, null, 2));
215
+ else {
216
+ console.log(`${report.name}: ${report.status}`);
217
+ for (const action of report.nextActions || report.proofPlan || []) console.log(`- ${action}`);
218
+ }
219
+ }
220
+
221
+ if (require.main === module) runCli();
222
+
223
+ module.exports = {
224
+ detectAwsBlocksProject,
225
+ detectAction,
226
+ evaluateAwsBlocksAction,
227
+ buildAwsBlocksHardeningOffer,
228
+ };
@@ -123,16 +123,6 @@ const CLI_COMMANDS = [
123
123
  { name: 'remote', type: 'boolean', description: 'Fetch from hosted Railway instance' },
124
124
  ],
125
125
  },
126
- {
127
- name: 'community',
128
- aliases: ['registry'],
129
- description: 'Query or share verified prevention rules with the community knowledge registry',
130
- group: 'discovery',
131
- flags: [
132
- { name: 'json', type: 'boolean', description: 'Output as JSON' },
133
- { name: 'remote', type: 'boolean', description: 'Fetch from community remote API' },
134
- ],
135
- },
136
126
  {
137
127
  name: 'gate-stats',
138
128
  description: 'Check engine statistics — active checks, blocks, warns, time saved',
@@ -515,6 +505,12 @@ const CLI_COMMANDS = [
515
505
  group: 'gates',
516
506
  flags: [],
517
507
  },
508
+ {
509
+ name: 'hermes-gate',
510
+ description: 'Hermes Agent pre_tool_call hook: gate runtime tool calls (incl. skill_manage) before they run',
511
+ group: 'gates',
512
+ flags: [],
513
+ },
518
514
  {
519
515
  name: 'force-gate',
520
516
  description: 'Immediately create a blocking gate from a pattern string',
@@ -660,6 +656,22 @@ const CLI_COMMANDS = [
660
656
  { name: 'json', type: 'boolean', description: 'Output results as JSON' },
661
657
  ],
662
658
  },
659
+ {
660
+ name: 'check-update',
661
+ aliases: ['upgrade-check'],
662
+ description: 'Check for newer versions of ThumbGate from npm or GitHub',
663
+ group: 'ops',
664
+ flags: [
665
+ { name: 'json', type: 'boolean', description: 'Output results as JSON' },
666
+ ],
667
+ },
668
+ {
669
+ name: 'self-update',
670
+ aliases: ['upgrade-cli'],
671
+ description: 'Automatically install the latest version of ThumbGate globally',
672
+ group: 'ops',
673
+ flags: [],
674
+ },
663
675
  ];
664
676
 
665
677
  /**
@@ -317,7 +317,8 @@ async function answerDataQuestion(question, opts = {}) {
317
317
  if (isPerplexity) return await callPerplexityEndpoint({ apiKey, prompt, fetchImpl, sources });
318
318
  return await callGeminiEndpoint({ apiKey, model, prompt, fetchImpl, sources });
319
319
  } catch (err) {
320
- return { ok: false, error: 'network', message: err?.message || String(err), sources };
320
+ const safeMessage = (err && err.message) ? String(err.message).split('\n')[0].slice(0, 100) : 'An unexpected error occurred.';
321
+ return { ok: false, error: 'network', message: safeMessage, sources };
321
322
  }
322
323
  }
323
324
 
@@ -708,7 +708,6 @@ function buildDocumentSummary(document) {
708
708
  sourcePath: document.sourcePath || null,
709
709
  sourceName: document.sourceName || null,
710
710
  sourceFormat: document.sourceFormat,
711
- sourceUrl: document.sourceUrl || null,
712
711
  importedAt: document.importedAt,
713
712
  tags: normalizeTags(document.tags),
714
713
  excerpt: document.excerpt,
@@ -769,11 +768,7 @@ function persistDocument(document, options = {}) {
769
768
  const summaries = listImportedDocuments({
770
769
  ...options,
771
770
  limit: MAX_SEARCH_SCAN,
772
- }).documents.filter((entry) => {
773
- if (entry.documentId === document.documentId) return false;
774
- if (document.sourceUrl && entry.sourceUrl === document.sourceUrl) return false;
775
- return true;
776
- });
771
+ }).documents.filter((entry) => entry.documentId !== document.documentId);
777
772
  const nextSummaries = [
778
773
  buildDocumentSummary(document),
779
774
  ...summaries,
@@ -887,48 +882,6 @@ function importDocument(options = {}) {
887
882
  sourceFormat,
888
883
  });
889
884
  const fingerprint = sha256(`${title}\n${normalizedContent}`);
890
-
891
- // -- deduplication and RAG drift tracking ----------------------------------
892
- const paths = getDocumentStorePaths(options);
893
- let duplicate = null;
894
- if (fs.existsSync(paths.catalogPath)) {
895
- try {
896
- const catalog = readJsonl(paths.catalogPath);
897
- const urlMatch = options.sourceUrl ? String(options.sourceUrl).trim() : null;
898
- const matchedSummary = catalog.find((summary) =>
899
- (urlMatch && summary.sourceUrl === urlMatch) ||
900
- (summary.fingerprint === fingerprint)
901
- );
902
- if (matchedSummary) {
903
- const fullDoc = readImportedDocument(matchedSummary.documentId, options);
904
- if (fullDoc) {
905
- if (fullDoc.fingerprint === fingerprint) {
906
- // Case A: Content is identical
907
- const dedupReason = urlMatch && fullDoc.sourceUrl === urlMatch
908
- ? 'url-and-content-unchanged'
909
- : 'content-identical';
910
- return {
911
- ...fullDoc,
912
- duplicate: true,
913
- updated: false,
914
- dedupReason,
915
- };
916
- } else {
917
- // Case B: URL matches but content changed (RAG Drift!)
918
- duplicate = {
919
- previousDocumentId: fullDoc.documentId,
920
- previousFingerprint: fullDoc.fingerprint,
921
- updated: true,
922
- dedupReason: 'url-content-updated',
923
- };
924
- }
925
- }
926
- }
927
- } catch (err) {
928
- // best-effort
929
- }
930
- }
931
-
932
885
  const importedAt = nowIso();
933
886
  const sourceName = sourcePath ? path.basename(sourcePath) : null;
934
887
  const documentId = `doc_${slugify(title || sourceName || 'document').slice(0, 24) || 'document'}_${fingerprint.slice(0, 12)}`;
@@ -948,7 +901,6 @@ function importDocument(options = {}) {
948
901
  contentBytes: Buffer.byteLength(normalizedContent, 'utf8'),
949
902
  lineCount: normalizedContent.split('\n').filter(Boolean).length,
950
903
  headings: extractHeadings(normalizedContent),
951
- ...(duplicate || {}),
952
904
  };
953
905
  document.proposals = options.proposeGates === false
954
906
  ? []
@@ -7,11 +7,11 @@
7
7
  * the full durable-execution runtime. Gives each external call (HTTP,
8
8
  * LanceDB, LLM) a uniform retry + idempotency wrapper:
9
9
  *
10
- * const result = await runStep('zernio.publishPost', {
10
+ * const result = await runStep('directSocial.publishPost', {
11
11
  * retries: 3,
12
12
  * idempotencyKey: idempotencyKey(content, platforms),
13
13
  * }, async ({ attempt }) => {
14
- * return zernioFetch('POST', '/posts', body, { idempotencyKey: ... });
14
+ * return directSocialFetch('POST', '/posts', body, { idempotencyKey: ... });
15
15
  * });
16
16
  *
17
17
  * Why a custom helper instead of Vercel Workflows / Temporal / Inngest?
@@ -98,7 +98,7 @@ function idempotencyKey(...parts) {
98
98
  * `fn` resolves to, or throws the last error after exhausting retries /
99
99
  * hitting a non-retryable verdict.
100
100
  *
101
- * @param {string} name Step name, used in logs. e.g. 'zernio.publishPost'.
101
+ * @param {string} name Step name, used in logs. e.g. 'directSocial.publishPost'.
102
102
  * @param {object|function} options { retries, backoffMs, classify, onRetry, onFail, logger }
103
103
  * (may be passed directly as the callback shorthand)
104
104
  * @param {function({attempt:number}):Promise} fn The actual work.
@@ -11,11 +11,6 @@ const PROJECT_ROOT = path.join(__dirname, '..');
11
11
  const MANUAL_GATES_PATH = path.join(PROJECT_ROOT, 'config', 'gates', 'default.json');
12
12
  const STATS_PATH = path.join(process.env.HOME || '/tmp', '.thumbgate', 'gate-stats.json');
13
13
 
14
- function safeOccurrenceCount(value) {
15
- const n = Number(value);
16
- return Number.isFinite(n) && n > 0 ? n : 0;
17
- }
18
-
19
14
  function loadGatesFile(filePath) {
20
15
  if (!fs.existsSync(filePath)) return [];
21
16
  try {
@@ -44,16 +39,16 @@ function calculateStats() {
44
39
  // Count total blocks/warns from occurrences in auto-promoted gates
45
40
  const totalBlocked = autoGates
46
41
  .filter((g) => g.action === 'block')
47
- .reduce((sum, g) => sum + safeOccurrenceCount(g.occurrences), 0);
42
+ .reduce((sum, g) => sum + (g.occurrences || 0), 0);
48
43
  const totalWarned = autoGates
49
44
  .filter((g) => g.action === 'warn')
50
- .reduce((sum, g) => sum + safeOccurrenceCount(g.occurrences), 0);
45
+ .reduce((sum, g) => sum + (g.occurrences || 0), 0);
51
46
 
52
47
  // Top blocked gate. A configured block rule with zero occurrences is not a
53
48
  // "top blocker"; only recorded block events should appear here.
54
49
  const topBlocked = [...allGates]
55
- .filter((g) => g.action === 'block' && safeOccurrenceCount(g.occurrences) > 0)
56
- .sort((a, b) => safeOccurrenceCount(b.occurrences) - safeOccurrenceCount(a.occurrences))
50
+ .filter((g) => g.action === 'block' && Number(g.occurrences || 0) > 0)
51
+ .sort((a, b) => (b.occurrences || 0) - (a.occurrences || 0))
57
52
  .at(0) || null;
58
53
 
59
54
  // Last promotion event
@@ -110,7 +105,7 @@ function computeCalibration(gates) {
110
105
  const calibration = [];
111
106
  for (const gate of gates || []) {
112
107
  if (!gate || !gate.id) continue;
113
- const occurrences = safeOccurrenceCount(gate.occurrences);
108
+ const occurrences = Number(gate.occurrences || 0);
114
109
  const action = gate.action || 'unknown';
115
110
  // Only annotate gates with recorded occurrence data
116
111
  if (occurrences === 0) continue;
@@ -263,7 +258,6 @@ module.exports = {
263
258
  loadGatesFile,
264
259
  tryComputeBayesErrorRate,
265
260
  computeCalibration,
266
- safeOccurrenceCount,
267
261
  MANUAL_GATES_PATH,
268
262
  STATS_PATH,
269
263
  };
@@ -16,13 +16,6 @@ const {
16
16
  const {
17
17
  evaluateWorkflowSentinel,
18
18
  } = require('./workflow-sentinel');
19
- const {
20
- extractPayloadText,
21
- extractPayloadPreviousUserText,
22
- hasPositiveFeedback,
23
- isLowValueCloseout,
24
- buildResponseQualityReason,
25
- } = require('./hook-stop-anti-claim');
26
19
  const {
27
20
  recordDecisionEvaluation,
28
21
  recordDecisionOutcome,
@@ -2592,39 +2585,6 @@ function buildReminderOutput(context) {
2592
2585
  });
2593
2586
  }
2594
2587
 
2595
- function inferHookEventName(input = {}) {
2596
- const explicit = input.hook_event_name || input.hookEventName || input.event || input.lifecycle;
2597
- if (explicit) return String(explicit);
2598
- return extractPayloadText(input) ? 'Stop' : 'PreToolUse';
2599
- }
2600
-
2601
- function buildResponseQualityBlockOutput(reason, input = {}) {
2602
- return JSON.stringify({
2603
- decision: 'block',
2604
- reason,
2605
- hookSpecificOutput: {
2606
- hookEventName: inferHookEventName(input),
2607
- permissionDecision: 'deny',
2608
- permissionDecisionReason: reason,
2609
- },
2610
- });
2611
- }
2612
-
2613
- function evaluateFinalResponseQualityGate(input = {}) {
2614
- const finalText = extractPayloadText(input) || process.env.CLAUDE_RESPONSE || '';
2615
- const previousUserText = extractPayloadPreviousUserText(input)
2616
- || process.env.CLAUDE_PREVIOUS_USER_TEXT
2617
- || process.env.CLAUDE_PREVIOUS_USER
2618
- || '';
2619
-
2620
- if (!finalText || !hasPositiveFeedback(previousUserText)) return null;
2621
- if (!isLowValueCloseout(finalText, '')) return null;
2622
- recordStat('response-quality-shallow-closeout', 'block', null, {
2623
- hookEventName: inferHookEventName(input),
2624
- });
2625
- return buildResponseQualityBlockOutput(buildResponseQualityReason(), input);
2626
- }
2627
-
2628
2588
  // ---------------------------------------------------------------------------
2629
2589
  // Upgrade nudge: surfaces Pro value at usage milestones and trial expiry.
2630
2590
  // Block-action Pro CTA: brief upgrade mention after a deny/warn decision.
@@ -2955,9 +2915,6 @@ function mergeContextStrings(...ctxs) {
2955
2915
  }
2956
2916
 
2957
2917
  async function runAsync(input) {
2958
- const responseQualityGate = evaluateFinalResponseQualityGate(input);
2959
- if (responseQualityGate) return responseQualityGate;
2960
-
2961
2918
  const secretGuard = evaluateSecretGuard(input);
2962
2919
  if (secretGuard) {
2963
2920
  return formatOutput(secretGuard);
@@ -3005,9 +2962,6 @@ async function runAsync(input) {
3005
2962
  }
3006
2963
 
3007
2964
  function run(input) {
3008
- const responseQualityGate = evaluateFinalResponseQualityGate(input);
3009
- if (responseQualityGate) return responseQualityGate;
3010
-
3011
2965
  const secretGuard = evaluateSecretGuard(input);
3012
2966
  if (secretGuard) {
3013
2967
  return formatOutput(secretGuard);
@@ -3342,9 +3296,6 @@ module.exports = {
3342
3296
  evaluateGatesAsync,
3343
3297
  computeExecutableHash,
3344
3298
  formatOutput,
3345
- inferHookEventName,
3346
- buildResponseQualityBlockOutput,
3347
- evaluateFinalResponseQualityGate,
3348
3299
  isApprovalGatesEnabled,
3349
3300
  run,
3350
3301
  runAsync,
@@ -122,7 +122,7 @@ function resolveGeminiEmbeddingConfig(env = process.env) {
122
122
 
123
123
  return {
124
124
  enabled,
125
- provider: enabled ? 'gemini' : 'local',
125
+ provider: provider === 'coreai' ? 'coreai' : (enabled ? 'gemini' : 'local'),
126
126
  model: String(env.THUMBGATE_GEMINI_EMBED_MODEL || GEMINI_EMBEDDING_2_MODEL).trim() || GEMINI_EMBEDDING_2_MODEL,
127
127
  apiKey,
128
128
  apiBaseUrl: trimTrailingSlashes(env.THUMBGATE_GEMINI_API_BASE_URL || 'https://generativelanguage.googleapis.com/v1beta'),
@@ -171,6 +171,7 @@ function buildGeminiEmbeddingRolloutPlan(args = {}) {
171
171
  },
172
172
  rolloutSteps: [
173
173
  'Keep local embeddings as the default offline path.',
174
+ 'For Apple Silicon developers, route local queries through Core AI (AOT compiled models) to bypass CPU overhead.',
174
175
  'Enable Gemini Embedding 2 only when a Gemini API key is present.',
175
176
  'Use task-specific query/document prefixes at index and retrieval time.',
176
177
  'Start at 768 dimensions, then benchmark 1536 only if recall misses show up.',