thumbgate 1.27.16 → 1.27.18

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 (165) hide show
  1. package/.claude-plugin/marketplace.json +85 -0
  2. package/.claude-plugin/plugin.json +3 -4
  3. package/.well-known/llms.txt +12 -33
  4. package/.well-known/mcp/server-card.json +8 -8
  5. package/README.md +30 -215
  6. package/adapters/chatgpt/openapi.yaml +1695 -0
  7. package/adapters/claude/.mcp.json +2 -2
  8. package/adapters/gemini/function-declarations.json +0 -1
  9. package/adapters/mcp/server-stdio.js +11 -263
  10. package/adapters/opencode/opencode.json +1 -1
  11. package/bench/thumbgate-bench.json +2 -2
  12. package/bin/cli.js +122 -1421
  13. package/bin/postinstall.js +27 -11
  14. package/config/gate-templates.json +0 -216
  15. package/config/gates/claim-verification.json +0 -12
  16. package/config/gates/default.json +2 -31
  17. package/config/github-about.json +2 -2
  18. package/config/mcp-allowlists.json +13 -23
  19. package/config/merge-quality-checks.json +1 -0
  20. package/config/model-candidates.json +6 -121
  21. package/openapi/openapi.yaml +0 -12
  22. package/package.json +98 -228
  23. package/public/blog.html +4 -4
  24. package/public/codex-plugin.html +20 -72
  25. package/public/compare.html +8 -31
  26. package/public/dashboard.html +166 -930
  27. package/public/federal.html +2 -2
  28. package/public/guide.html +9 -33
  29. package/public/index.html +116 -495
  30. package/public/learn.html +18 -191
  31. package/public/lessons.html +10 -168
  32. package/public/numbers.html +7 -7
  33. package/public/pro.html +11 -34
  34. package/scripts/agent-memory-lifecycle.js +0 -211
  35. package/scripts/agent-readiness.js +3 -167
  36. package/scripts/agent-reward-model.js +1 -53
  37. package/scripts/auto-promote-gates.js +10 -82
  38. package/scripts/auto-wire-hooks.js +0 -14
  39. package/scripts/billing.js +1 -93
  40. package/scripts/build-metadata.js +10 -50
  41. package/scripts/cli-feedback.js +6 -18
  42. package/scripts/cli-schema.js +0 -90
  43. package/scripts/cli-telemetry.js +1 -6
  44. package/scripts/commercial-offer.js +2 -82
  45. package/scripts/context-manager.js +6 -74
  46. package/scripts/dashboard.js +2 -68
  47. package/scripts/durability/step.js +3 -3
  48. package/scripts/export-databricks-bundle.js +1 -5
  49. package/scripts/export-dpo-pairs.js +2 -7
  50. package/scripts/feedback-loop.js +1 -123
  51. package/scripts/feedback-quality.js +0 -87
  52. package/scripts/filesystem-search.js +10 -35
  53. package/scripts/gate-stats.js +0 -89
  54. package/scripts/gates-engine.js +85 -1105
  55. package/scripts/gemini-embedding-policy.js +1 -2
  56. package/scripts/hook-runtime.js +14 -20
  57. package/scripts/hook-thumbgate-cache-updater.js +2 -18
  58. package/scripts/hybrid-feedback-context.js +7 -142
  59. package/scripts/lesson-inference.js +3 -8
  60. package/scripts/lesson-search.js +1 -17
  61. package/scripts/license.js +1 -0
  62. package/scripts/llm-client.js +9 -390
  63. package/scripts/local-model-profile.js +8 -15
  64. package/scripts/memory-scope-readiness.js +0 -159
  65. package/scripts/meta-agent-loop.js +0 -36
  66. package/scripts/operational-integrity.js +5 -39
  67. package/scripts/oss-pr-opportunity-scout.js +5 -35
  68. package/scripts/plausible-server-events.js +6 -9
  69. package/scripts/pro-local-dashboard.js +4 -4
  70. package/scripts/proxy-pointer-rag-guardrails.js +1 -42
  71. package/scripts/rate-limiter.js +13 -64
  72. package/scripts/secret-scanner.js +5 -44
  73. package/scripts/security-scanner.js +10 -260
  74. package/scripts/self-distill-agent.js +1 -3
  75. package/scripts/seo-gsd.js +2 -916
  76. package/scripts/statusline-cache-path.js +2 -17
  77. package/scripts/statusline-local-stats.js +1 -9
  78. package/scripts/statusline-meta.js +2 -28
  79. package/scripts/statusline.sh +4 -20
  80. package/scripts/telemetry-analytics.js +0 -357
  81. package/scripts/thompson-sampling.js +10 -31
  82. package/scripts/thumbgate-bench.js +1 -16
  83. package/scripts/thumbgate-search.js +19 -85
  84. package/scripts/tool-registry.js +1 -169
  85. package/scripts/vector-store.js +0 -45
  86. package/scripts/workflow-sentinel.js +53 -286
  87. package/scripts/workspace-evolver.js +2 -62
  88. package/src/api/server.js +290 -2578
  89. package/.claude/commands/dashboard.md +0 -15
  90. package/.claude/commands/thumbgate-blocked.md +0 -27
  91. package/.claude/commands/thumbgate-dashboard.md +0 -15
  92. package/.claude/commands/thumbgate-doctor.md +0 -30
  93. package/.claude/commands/thumbgate-guard.md +0 -36
  94. package/.claude/commands/thumbgate-protect.md +0 -30
  95. package/.claude/commands/thumbgate-rules.md +0 -30
  96. package/.well-known/agentic-verify.txt +0 -1
  97. package/adapters/gcp/dfcx-webhook-gate.js +0 -295
  98. package/adapters/letta/README.md +0 -41
  99. package/adapters/letta/thumbgate-letta-adapter.js +0 -133
  100. package/adapters/policy-engine/ethicore-guardian-client.js +0 -68
  101. package/adapters/policy-engine/thumbgate-policy-engine-adapter.js +0 -260
  102. package/bench/observability-eval-suite.json +0 -26
  103. package/bin/dashboard-cli.js +0 -7
  104. package/commands/dashboard.md +0 -15
  105. package/commands/thumbgate-dashboard.md +0 -15
  106. package/config/builtin-lessons.json +0 -23
  107. package/config/gate-classifier-routing.json +0 -98
  108. package/config/post-deploy-marketing-pages.json +0 -61
  109. package/public/about.html +0 -162
  110. package/public/agent-manager.html +0 -179
  111. package/public/agents-cost-savings.html +0 -153
  112. package/public/ai-malpractice-prevention.html +0 -818
  113. package/public/assets/brand/github-social-preview.png +0 -0
  114. package/public/assets/brand/thumbgate-icon-512.png +0 -0
  115. package/public/assets/brand/thumbgate-icon-pro-512.png +0 -0
  116. package/public/assets/brand/thumbgate-icon-team-512.png +0 -0
  117. package/public/assets/brand/thumbgate-logo-1200x360.png +0 -0
  118. package/public/assets/brand/thumbgate-logo-transparent.svg +0 -22
  119. package/public/assets/brand/thumbgate-mark-inline-v3.svg +0 -19
  120. package/public/assets/brand/thumbgate-mark-pro.svg +0 -23
  121. package/public/assets/brand/thumbgate-mark-team.svg +0 -26
  122. package/public/assets/brand/thumbgate-mark.svg +0 -21
  123. package/public/assets/brand/thumbgate-wordmark.svg +0 -20
  124. package/public/assets/claude-thumbgate-statusbar.svg +0 -8
  125. package/public/assets/codex-thumbgate-statusbar-test.svg +0 -9
  126. package/public/assets/legal-intake-control-flow.svg +0 -66
  127. package/public/brand/thumbgate-mark.svg +0 -19
  128. package/public/brand/thumbgate-og.svg +0 -16
  129. package/public/chatgpt-app.html +0 -330
  130. package/public/codex-enterprise.html +0 -123
  131. package/public/js/buyer-intent.js +0 -672
  132. package/public/pricing.html +0 -399
  133. package/scripts/action-receipts.js +0 -324
  134. package/scripts/activation-quickstart.js +0 -187
  135. package/scripts/agent-operations-planner.js +0 -621
  136. package/scripts/ai-component-inventory.js +0 -367
  137. package/scripts/async-eval-observability.js +0 -236
  138. package/scripts/audit.js +0 -65
  139. package/scripts/aws-blocks-guardrails.js +0 -228
  140. package/scripts/classifier-routing.js +0 -130
  141. package/scripts/dashboard-chat.js +0 -332
  142. package/scripts/feedback-aggregate.js +0 -281
  143. package/scripts/feedback-sanitizer.js +0 -105
  144. package/scripts/hob-pack.js +0 -591
  145. package/scripts/hook-stop-anti-claim.js +0 -301
  146. package/scripts/install-shim.js +0 -87
  147. package/scripts/mcp-oauth.js +0 -293
  148. package/scripts/noop-detect.js +0 -285
  149. package/scripts/omlx-smoke.js +0 -192
  150. package/scripts/parallel-workflow-orchestrator.js +0 -293
  151. package/scripts/plan-gate.js +0 -243
  152. package/scripts/plausible-domain-config.js +0 -99
  153. package/scripts/qa-scenario-planner.js +0 -136
  154. package/scripts/repeat-metric.js +0 -137
  155. package/scripts/secret-fixture-tokens.js +0 -61
  156. package/scripts/secret-redaction.js +0 -166
  157. package/scripts/self-harness-optimizer.js +0 -141
  158. package/scripts/self-healing-check.js +0 -193
  159. package/scripts/silent-failure-cluster.js +0 -531
  160. package/scripts/statusline-cache-read.js +0 -57
  161. package/scripts/sync-telemetry-from-prod.js +0 -374
  162. package/scripts/tool-contract-validator.js +0 -76
  163. package/scripts/trajectory-scorer.js +0 -63
  164. package/scripts/verify-marketing-pages-deployed.js +0 -195
  165. package/scripts/visitor-journey.js +0 -172
@@ -1,367 +0,0 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- const fs = require('fs');
5
- const path = require('path');
6
- const crypto = require('crypto');
7
-
8
- const DEFAULT_MAX_FILES = 2500;
9
- const DEFAULT_MAX_BYTES = 1024 * 1024;
10
-
11
- const IGNORE_DIRS = new Set([
12
- '.git',
13
- '.hg',
14
- '.svn',
15
- '.thumbgate',
16
- '.venv',
17
- 'venv',
18
- '__pycache__',
19
- 'node_modules',
20
- 'dist',
21
- 'build',
22
- 'coverage',
23
- '.next',
24
- '.turbo',
25
- '.cache',
26
- ]);
27
-
28
- const TEXT_EXTENSIONS = new Set([
29
- '.js',
30
- '.jsx',
31
- '.ts',
32
- '.tsx',
33
- '.mjs',
34
- '.cjs',
35
- '.py',
36
- '.ipynb',
37
- '.go',
38
- '.java',
39
- '.rb',
40
- '.rs',
41
- '.php',
42
- '.cs',
43
- '.swift',
44
- '.kt',
45
- '.scala',
46
- '.sh',
47
- '.yaml',
48
- '.yml',
49
- '.toml',
50
- '.json',
51
- '.ini',
52
- ]);
53
-
54
- const MODEL_EXTENSIONS = new Map([
55
- ['.gguf', { name: 'GGUF model artifact', category: 'model_artifact', ecosystem: 'local-model' }],
56
- ['.safetensors', { name: 'SafeTensors model artifact', category: 'model_artifact', ecosystem: 'local-model' }],
57
- ['.onnx', { name: 'ONNX model artifact', category: 'model_artifact', ecosystem: 'onnx' }],
58
- ['.pt', { name: 'PyTorch model artifact', category: 'model_artifact', ecosystem: 'pytorch' }],
59
- ['.pth', { name: 'PyTorch model artifact', category: 'model_artifact', ecosystem: 'pytorch' }],
60
- ['.tflite', { name: 'TensorFlow Lite model artifact', category: 'model_artifact', ecosystem: 'tensorflow' }],
61
- ]);
62
-
63
- const SOURCE_PATTERNS = [
64
- component('openai', 'OpenAI SDK/API', 'provider_sdk', 'openai', /\b(from\s+openai\s+import|import\s+openai\b|require\(['"]openai['"]\)|from\s+['"]openai['"]|@openai\/agents|new\s+OpenAI\s*\()/i),
65
- component('anthropic', 'Anthropic Claude SDK/API', 'provider_sdk', 'anthropic', /\b(@anthropic-ai\/sdk|from\s+anthropic\s+import|import\s+anthropic\b|require\(['"]@anthropic-ai\/sdk['"]\)|new\s+Anthropic\s*\()/i),
66
- component('google-gemini', 'Google Gemini SDK/API', 'provider_sdk', 'google', /\b(@google\/generative-ai|google-genai|from\s+google\s+import\s+genai|GoogleGenerativeAI|GenerativeModel)/i),
67
- component('vertex-ai', 'Google Vertex AI', 'ai_platform', 'google-cloud', /\b(vertexai|aiplatform|@google-cloud\/vertexai|PredictionServiceClient|projects\.locations\.publishers\.models)/i),
68
- component('dialogflow-cx', 'Google Dialogflow CX', 'conversation_ai', 'google-cloud', /\b(dialogflowcx|dialogflow-cx|@google-cloud\/dialogflow-cx|SessionsClient|DetectIntentRequest)/i),
69
- component('langchain', 'LangChain', 'agent_framework', 'langchain', /\b(@langchain\/|langchain\b|from\s+langchain(_community|_core)?\b)/i),
70
- component('llamaindex', 'LlamaIndex', 'agent_framework', 'llamaindex', /\b(llama_index|llamaindex|from\s+llama_index\b)/i),
71
- component('semantic-kernel', 'Semantic Kernel', 'agent_framework', 'microsoft', /\b(semantic-kernel|semantic_kernel|Microsoft\.SemanticKernel)/i),
72
- component('crewai', 'CrewAI', 'agent_framework', 'crewai', /\b(crewai|from\s+crewai\b)/i),
73
- component('autogen', 'AutoGen', 'agent_framework', 'microsoft', /\b(autogen|pyautogen|@microsoft\/autogen)/i),
74
- component('transformers', 'Hugging Face Transformers', 'ml_framework', 'huggingface', /\b(transformers|AutoModel|AutoTokenizer|pipeline\s*\()/i),
75
- component('sentence-transformers', 'Sentence Transformers', 'embedding_model', 'huggingface', /\b(sentence_transformers|SentenceTransformer)/i),
76
- component('pytorch', 'PyTorch', 'ml_framework', 'pytorch', /\b(import\s+torch\b|from\s+torch\b|torch\.)/i),
77
- component('tensorflow', 'TensorFlow/Keras', 'ml_framework', 'tensorflow', /\b(import\s+tensorflow\b|from\s+tensorflow\b|import\s+keras\b|from\s+keras\b|tf\.keras)/i),
78
- component('scikit-learn', 'scikit-learn', 'ml_framework', 'scikit-learn', /\b(sklearn|scikit-learn|from\s+sklearn\b)/i),
79
- component('onnxruntime', 'ONNX Runtime', 'ml_runtime', 'onnx', /\b(onnxruntime|InferenceSession)/i),
80
- component('pinecone', 'Pinecone vector database', 'vector_database', 'pinecone', /\b(pinecone|@pinecone-database\/pinecone)/i),
81
- component('weaviate', 'Weaviate vector database', 'vector_database', 'weaviate', /\b(weaviate|weaviate-client)/i),
82
- component('qdrant', 'Qdrant vector database', 'vector_database', 'qdrant', /\b(qdrant|@qdrant\/js-client-rest|qdrant-client)/i),
83
- component('chroma', 'Chroma vector database', 'vector_database', 'chroma', /\b(chromadb|chroma-client|ChromaClient)/i),
84
- component('lancedb', 'LanceDB vector database', 'vector_database', 'lancedb', /\b(lancedb|@lancedb\/lancedb)/i),
85
- component('faiss', 'FAISS vector index', 'vector_database', 'faiss', /\b(faiss|faiss-cpu|faiss-gpu)/i),
86
- component('pgvector', 'Postgres pgvector', 'vector_database', 'postgres', /\b(pgvector|vector\(\d+\)|CREATE\s+EXTENSION\s+vector)/i),
87
- ];
88
-
89
- const MANIFEST_FILES = new Set([
90
- 'package.json',
91
- 'requirements.txt',
92
- 'pyproject.toml',
93
- 'poetry.lock',
94
- 'Pipfile',
95
- 'Gemfile',
96
- 'go.mod',
97
- 'Cargo.toml',
98
- ]);
99
-
100
- function component(id, name, category, ecosystem, pattern) {
101
- return { id, name, category, ecosystem, pattern };
102
- }
103
-
104
- function relativePath(rootDir, filePath) {
105
- return path.relative(rootDir, filePath).split(path.sep).join('/');
106
- }
107
-
108
- function shouldIgnoreDir(name) {
109
- return IGNORE_DIRS.has(name);
110
- }
111
-
112
- function isTextLike(filePath) {
113
- const base = path.basename(filePath);
114
- if (MANIFEST_FILES.has(base)) return true;
115
- return TEXT_EXTENSIONS.has(path.extname(filePath).toLowerCase());
116
- }
117
-
118
- function walkFiles(rootDir, options = {}) {
119
- const maxFiles = Number(options.maxFiles || DEFAULT_MAX_FILES);
120
- const files = [];
121
- const queue = [rootDir];
122
-
123
- while (queue.length && files.length < maxFiles) {
124
- const dir = queue.shift();
125
- let entries = [];
126
- try {
127
- entries = fs.readdirSync(dir, { withFileTypes: true });
128
- } catch (_) {
129
- continue;
130
- }
131
-
132
- for (const entry of entries) {
133
- const fullPath = path.join(dir, entry.name);
134
- if (entry.isDirectory()) {
135
- if (!shouldIgnoreDir(entry.name)) queue.push(fullPath);
136
- continue;
137
- }
138
- if (entry.isFile()) {
139
- files.push(fullPath);
140
- if (files.length >= maxFiles) break;
141
- }
142
- }
143
- }
144
-
145
- return files;
146
- }
147
-
148
- function readLines(filePath, maxBytes = DEFAULT_MAX_BYTES) {
149
- let stat;
150
- try {
151
- stat = fs.statSync(filePath);
152
- } catch (_) {
153
- return null;
154
- }
155
- if (!stat.isFile() || stat.size > maxBytes) return null;
156
-
157
- try {
158
- return fs.readFileSync(filePath, 'utf8').split(/\r?\n/);
159
- } catch (_) {
160
- return null;
161
- }
162
- }
163
-
164
- function addEvidence(map, componentDef, evidence, maxEvidencePerComponent) {
165
- const current = map.get(componentDef.id) || {
166
- id: componentDef.id,
167
- name: componentDef.name,
168
- category: componentDef.category,
169
- ecosystem: componentDef.ecosystem,
170
- evidence: [],
171
- };
172
-
173
- if (current.evidence.length < maxEvidencePerComponent) {
174
- const duplicate = current.evidence.some((item) => (
175
- item.file === evidence.file && item.line === evidence.line && item.kind === evidence.kind
176
- ));
177
- if (!duplicate) current.evidence.push(evidence);
178
- }
179
- map.set(componentDef.id, current);
180
- }
181
-
182
- function scanSourceFile(rootDir, filePath, map, options) {
183
- const lines = readLines(filePath, options.maxBytes);
184
- if (!lines) return;
185
-
186
- const rel = relativePath(rootDir, filePath);
187
- lines.forEach((line, idx) => {
188
- for (const def of SOURCE_PATTERNS) {
189
- if (!def.pattern.test(line)) continue;
190
- addEvidence(map, def, {
191
- kind: 'source',
192
- file: rel,
193
- line: idx + 1,
194
- snippet: options.includeSnippets === false ? undefined : line.trim().slice(0, 220),
195
- }, options.maxEvidencePerComponent);
196
- }
197
- });
198
- }
199
-
200
- function scanManifestFile(rootDir, filePath, map, options) {
201
- const lines = readLines(filePath, options.maxBytes);
202
- if (!lines) return;
203
- const rel = relativePath(rootDir, filePath);
204
-
205
- lines.forEach((line, idx) => {
206
- for (const def of SOURCE_PATTERNS) {
207
- if (!def.pattern.test(line)) continue;
208
- addEvidence(map, def, {
209
- kind: 'manifest',
210
- file: rel,
211
- line: idx + 1,
212
- snippet: options.includeSnippets === false ? undefined : line.trim().slice(0, 220),
213
- }, options.maxEvidencePerComponent);
214
- }
215
- });
216
- }
217
-
218
- function scanModelArtifact(rootDir, filePath, map, options) {
219
- const ext = path.extname(filePath).toLowerCase();
220
- const def = MODEL_EXTENSIONS.get(ext);
221
- if (!def) return;
222
-
223
- let stat = null;
224
- try {
225
- stat = fs.statSync(filePath);
226
- } catch (_) {
227
- stat = null;
228
- }
229
-
230
- addEvidence(map, {
231
- id: `${def.ecosystem}-${ext.slice(1)}-artifact`,
232
- ...def,
233
- }, {
234
- kind: 'artifact',
235
- file: relativePath(rootDir, filePath),
236
- line: null,
237
- bytes: stat ? stat.size : undefined,
238
- }, options.maxEvidencePerComponent);
239
- }
240
-
241
- function summarizeComponents(components) {
242
- const byCategory = {};
243
- const byEcosystem = {};
244
- for (const item of components) {
245
- byCategory[item.category] = (byCategory[item.category] || 0) + 1;
246
- byEcosystem[item.ecosystem] = (byEcosystem[item.ecosystem] || 0) + 1;
247
- }
248
- return { byCategory, byEcosystem };
249
- }
250
-
251
- function scanAiComponents(options = {}) {
252
- const rootDir = path.resolve(options.rootDir || process.cwd());
253
- const maxEvidencePerComponent = Number(options.maxEvidencePerComponent || 10);
254
- const scanOptions = {
255
- maxFiles: options.maxFiles || DEFAULT_MAX_FILES,
256
- maxBytes: options.maxBytes || DEFAULT_MAX_BYTES,
257
- includeSnippets: options.includeSnippets !== false,
258
- maxEvidencePerComponent,
259
- };
260
-
261
- const files = walkFiles(rootDir, scanOptions);
262
- const map = new Map();
263
-
264
- for (const filePath of files) {
265
- scanModelArtifact(rootDir, filePath, map, scanOptions);
266
- const base = path.basename(filePath);
267
- if (MANIFEST_FILES.has(base)) {
268
- scanManifestFile(rootDir, filePath, map, scanOptions);
269
- continue;
270
- }
271
- if (isTextLike(filePath)) scanSourceFile(rootDir, filePath, map, scanOptions);
272
- }
273
-
274
- const components = Array.from(map.values()).sort((a, b) => a.id.localeCompare(b.id));
275
- const summary = summarizeComponents(components);
276
- return {
277
- schemaVersion: 'thumbgate.ai-inventory.v1',
278
- generatedAt: new Date().toISOString(),
279
- rootDir,
280
- filesScanned: files.length,
281
- componentCount: components.length,
282
- summary,
283
- components,
284
- };
285
- }
286
-
287
- function buildCycloneDxMlBom(inventory, options = {}) {
288
- const serialHash = crypto
289
- .createHash('sha256')
290
- .update(JSON.stringify({
291
- rootDir: inventory.rootDir,
292
- components: inventory.components.map((item) => [item.id, item.evidence.map((e) => e.file)]),
293
- }))
294
- .digest('hex')
295
- .slice(0, 32);
296
-
297
- return {
298
- bomFormat: 'CycloneDX',
299
- specVersion: '1.5',
300
- serialNumber: `urn:uuid:${serialHash.slice(0, 8)}-${serialHash.slice(8, 12)}-${serialHash.slice(12, 16)}-${serialHash.slice(16, 20)}-${serialHash.slice(20, 32)}`,
301
- version: 1,
302
- metadata: {
303
- timestamp: inventory.generatedAt,
304
- tools: [
305
- {
306
- vendor: 'ThumbGate',
307
- name: 'AI Component Inventory',
308
- version: options.version || 'local',
309
- },
310
- ],
311
- properties: [
312
- { name: 'thumbgate:rootDir', value: inventory.rootDir },
313
- { name: 'thumbgate:filesScanned', value: String(inventory.filesScanned) },
314
- { name: 'thumbgate:componentCount', value: String(inventory.componentCount) },
315
- ],
316
- },
317
- components: inventory.components.map((item) => ({
318
- type: item.category === 'model_artifact' ? 'machine-learning-model' : 'library',
319
- name: item.name,
320
- group: item.ecosystem,
321
- bomRef: `thumbgate:${item.id}`,
322
- properties: [
323
- { name: 'thumbgate:category', value: item.category },
324
- { name: 'thumbgate:evidenceCount', value: String(item.evidence.length) },
325
- { name: 'thumbgate:evidence', value: JSON.stringify(item.evidence.map((e) => ({ file: e.file, line: e.line, kind: e.kind }))) },
326
- ],
327
- })),
328
- };
329
- }
330
-
331
- function formatInventoryText(inventory) {
332
- const lines = [];
333
- lines.push('ThumbGate AI Component Inventory');
334
- lines.push(` Root : ${inventory.rootDir}`);
335
- lines.push(` Files scanned : ${inventory.filesScanned}`);
336
- lines.push(` AI components : ${inventory.componentCount}`);
337
- lines.push('');
338
- lines.push('By category:');
339
- const categories = Object.entries(inventory.summary.byCategory).sort((a, b) => a[0].localeCompare(b[0]));
340
- if (!categories.length) lines.push(' none detected');
341
- for (const [category, count] of categories) lines.push(` ${category}: ${count}`);
342
- lines.push('');
343
- lines.push('Evidence:');
344
- if (!inventory.components.length) lines.push(' none detected');
345
- for (const item of inventory.components) {
346
- lines.push(` - ${item.name} (${item.category}, ${item.ecosystem})`);
347
- for (const evidence of item.evidence.slice(0, 3)) {
348
- const loc = evidence.line ? `${evidence.file}:${evidence.line}` : evidence.file;
349
- lines.push(` ${loc} [${evidence.kind}]`);
350
- }
351
- }
352
- return lines.join('\n');
353
- }
354
-
355
- function writeOutput(filePath, data) {
356
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
357
- fs.writeFileSync(filePath, data);
358
- }
359
-
360
- module.exports = {
361
- SOURCE_PATTERNS,
362
- MODEL_EXTENSIONS,
363
- scanAiComponents,
364
- buildCycloneDxMlBom,
365
- formatInventoryText,
366
- writeOutput,
367
- };
@@ -1,236 +0,0 @@
1
- #!/usr/bin/env node
2
- 'use strict';
3
-
4
- const fs = require('node:fs');
5
- const path = require('node:path');
6
-
7
- const DEFAULT_THRESHOLDS = {
8
- faithfulness: 0.72,
9
- answerRelevance: 0.45,
10
- contextPrecision: 0.5,
11
- };
12
-
13
- function tokenize(value) {
14
- return String(value || '')
15
- .toLowerCase()
16
- .split(/[^a-z0-9]+/)
17
- .filter((token) => token.length > 2);
18
- }
19
-
20
- function unique(values) {
21
- return [...new Set(values.filter(Boolean))];
22
- }
23
-
24
- function overlapScore(left, right) {
25
- const leftTokens = unique(tokenize(left));
26
- const rightSet = new Set(tokenize(right));
27
- if (leftTokens.length === 0) return 0;
28
- const matches = leftTokens.filter((token) => rightSet.has(token)).length;
29
- return matches / leftTokens.length;
30
- }
31
-
32
- function splitClaims(response) {
33
- return String(response || '')
34
- .split(/(?:[.!?]\s+|\n+)/)
35
- .map((claim) => claim.trim())
36
- .filter((claim) => claim.length > 0);
37
- }
38
-
39
- function normalizeContexts(contexts) {
40
- if (Array.isArray(contexts)) return contexts.map(String).filter(Boolean);
41
- if (contexts) return [String(contexts)];
42
- return [];
43
- }
44
-
45
- function scoreFaithfulness(response, contexts) {
46
- const claims = splitClaims(response);
47
- const contextText = normalizeContexts(contexts).join('\n');
48
- if (claims.length === 0) return { score: 0, supportedClaims: 0, totalClaims: 0 };
49
- const supportedClaims = claims.filter((claim) => {
50
- const normalized = claim.toLowerCase();
51
- return contextText.toLowerCase().includes(normalized) || overlapScore(claim, contextText) >= 0.58;
52
- }).length;
53
- return {
54
- score: Number((supportedClaims / claims.length).toFixed(4)),
55
- supportedClaims,
56
- totalClaims: claims.length,
57
- };
58
- }
59
-
60
- function scoreAnswerRelevance(question, response) {
61
- const score = overlapScore(question, response);
62
- return {
63
- score: Number(score.toFixed(4)),
64
- matchedQuestionTerms: unique(tokenize(question).filter((token) => tokenize(response).includes(token))),
65
- };
66
- }
67
-
68
- function scoreContextPrecision(question, contexts, reference = '') {
69
- const normalizedContexts = normalizeContexts(contexts);
70
- const target = [question, reference].filter(Boolean).join('\n');
71
- if (normalizedContexts.length === 0) return { score: 0, relevantContexts: 0, totalContexts: 0 };
72
-
73
- let precisionSum = 0;
74
- let relevantContexts = 0;
75
- normalizedContexts.forEach((context, index) => {
76
- const relevant = overlapScore(target, context) >= 0.22 || overlapScore(context, target) >= 0.22;
77
- if (relevant) relevantContexts += 1;
78
- const precisionAtK = relevantContexts / (index + 1);
79
- if (relevant) precisionSum += precisionAtK;
80
- });
81
-
82
- const score = relevantContexts === 0 ? 0 : precisionSum / relevantContexts;
83
- return {
84
- score: Number(score.toFixed(4)),
85
- relevantContexts,
86
- totalContexts: normalizedContexts.length,
87
- };
88
- }
89
-
90
- function evaluateGeneration(testCase, options = {}) {
91
- const thresholds = { ...DEFAULT_THRESHOLDS, ...(options.thresholds || {}) };
92
- const contexts = normalizeContexts(testCase.retrievedContexts || testCase.contexts || testCase.retrieved_contexts);
93
- const faithfulness = scoreFaithfulness(testCase.response || testCase.answer, contexts);
94
- const answerRelevance = scoreAnswerRelevance(testCase.question || testCase.user_input, testCase.response || testCase.answer);
95
- const contextPrecision = scoreContextPrecision(
96
- testCase.question || testCase.user_input,
97
- contexts,
98
- testCase.reference || testCase.groundTruth || ''
99
- );
100
- const scores = {
101
- faithfulness: faithfulness.score,
102
- answerRelevance: answerRelevance.score,
103
- contextPrecision: contextPrecision.score,
104
- };
105
- const passed = scores.faithfulness >= thresholds.faithfulness
106
- && scores.answerRelevance >= thresholds.answerRelevance
107
- && scores.contextPrecision >= thresholds.contextPrecision;
108
-
109
- return {
110
- id: String(testCase.id || testCase.traceId || 'case'),
111
- traceId: String(testCase.traceId || testCase.id || ''),
112
- passed,
113
- scores,
114
- thresholds,
115
- details: {
116
- faithfulness,
117
- answerRelevance,
118
- contextPrecision,
119
- },
120
- };
121
- }
122
-
123
- function buildRagasCompatibleRows(cases) {
124
- return cases.map((testCase) => ({
125
- user_input: testCase.question || testCase.user_input || '',
126
- response: testCase.response || testCase.answer || '',
127
- retrieved_contexts: normalizeContexts(testCase.retrievedContexts || testCase.contexts || testCase.retrieved_contexts),
128
- reference: testCase.reference || testCase.groundTruth || '',
129
- }));
130
- }
131
-
132
- function buildLangSmithCompatibleRuns(cases, results) {
133
- return cases.map((testCase, index) => ({
134
- id: testCase.traceId || testCase.id || `case-${index + 1}`,
135
- name: 'thumbgate_async_rag_eval',
136
- inputs: { question: testCase.question || testCase.user_input || '' },
137
- outputs: { response: testCase.response || testCase.answer || '' },
138
- metadata: {
139
- evaluator: 'thumbgate-async-eval-observability',
140
- caseId: testCase.id || null,
141
- },
142
- feedback: Object.entries(results[index].scores).map(([key, score]) => ({
143
- key,
144
- score,
145
- })),
146
- }));
147
- }
148
-
149
- function buildEvalReport(cases, options = {}) {
150
- const normalizedCases = Array.isArray(cases) ? cases : [];
151
- const results = normalizedCases.map((testCase) => evaluateGeneration(testCase, options));
152
- const passed = results.filter((result) => result.passed).length;
153
- const failed = results.length - passed;
154
- const aggregate = {
155
- faithfulness: average(results.map((result) => result.scores.faithfulness)),
156
- answerRelevance: average(results.map((result) => result.scores.answerRelevance)),
157
- contextPrecision: average(results.map((result) => result.scores.contextPrecision)),
158
- };
159
-
160
- return {
161
- generatedAt: new Date().toISOString(),
162
- mode: 'async-post-generation',
163
- total: results.length,
164
- passed,
165
- failed,
166
- passRate: results.length === 0 ? 0 : Number(((passed / results.length) * 100).toFixed(2)),
167
- aggregate,
168
- passedThreshold: failed === 0,
169
- metrics: ['faithfulness', 'answerRelevance', 'contextPrecision'],
170
- sinks: {
171
- ci: true,
172
- langsmithCompatible: true,
173
- ragasCompatible: true,
174
- },
175
- results,
176
- ragasDataset: buildRagasCompatibleRows(normalizedCases),
177
- langsmithRuns: buildLangSmithCompatibleRuns(normalizedCases, results),
178
- };
179
- }
180
-
181
- function average(values) {
182
- const numeric = values.filter((value) => Number.isFinite(value));
183
- if (numeric.length === 0) return 0;
184
- return Number((numeric.reduce((sum, value) => sum + value, 0) / numeric.length).toFixed(4));
185
- }
186
-
187
- async function runAsyncEvaluation(cases, options = {}) {
188
- const report = await new Promise((resolve) => {
189
- setImmediate(() => resolve(buildEvalReport(cases, options)));
190
- });
191
- if (options.outputPath) {
192
- fs.mkdirSync(path.dirname(options.outputPath), { recursive: true });
193
- fs.writeFileSync(options.outputPath, `${JSON.stringify(report, null, 2)}\n`);
194
- }
195
- return report;
196
- }
197
-
198
- function loadCases(inputPath) {
199
- const payload = JSON.parse(fs.readFileSync(inputPath, 'utf8'));
200
- return Array.isArray(payload) ? payload : payload.cases || [];
201
- }
202
-
203
- async function main(argv = process.argv.slice(2)) {
204
- const inputIndex = argv.indexOf('--input');
205
- const outputIndex = argv.indexOf('--output');
206
- const inputPath = inputIndex >= 0 ? argv[inputIndex + 1] : 'bench/observability-eval-suite.json';
207
- const outputPath = outputIndex >= 0 ? argv[outputIndex + 1] : 'proof/async-eval-observability-report.json';
208
- const report = await runAsyncEvaluation(loadCases(inputPath), { outputPath });
209
- process.stdout.write(`${JSON.stringify({
210
- outputPath,
211
- total: report.total,
212
- passed: report.passed,
213
- failed: report.failed,
214
- passRate: report.passRate,
215
- }, null, 2)}\n`);
216
- if (!report.passedThreshold) process.exitCode = 1;
217
- }
218
-
219
- module.exports = {
220
- DEFAULT_THRESHOLDS,
221
- buildEvalReport,
222
- buildLangSmithCompatibleRuns,
223
- buildRagasCompatibleRows,
224
- evaluateGeneration,
225
- runAsyncEvaluation,
226
- scoreAnswerRelevance,
227
- scoreContextPrecision,
228
- scoreFaithfulness,
229
- };
230
-
231
- if (require.main === module) {
232
- main().catch((err) => {
233
- console.error(err.stack || err.message);
234
- process.exitCode = 1;
235
- });
236
- }
package/scripts/audit.js DELETED
@@ -1,65 +0,0 @@
1
- 'use strict';
2
-
3
- /**
4
- * scripts/audit.js
5
- *
6
- * Heuristic-based AI bill auditor. Finds repeated mistakes in agent transcripts.
7
- */
8
-
9
- const fs = require('fs');
10
-
11
- const PATTERNS = [
12
- {
13
- id: 'force-push-retry',
14
- name: 'git push --force after correction',
15
- regex: /git\s+push.*--force/gi,
16
- tokenEstimate: 6000,
17
- costPerRepeat: 0.44,
18
- why: 'Full diff context reload on error.'
19
- },
20
- {
21
- id: 'import-loop',
22
- name: 'Hallucinated import retry',
23
- regex: /(Cannot find module|Module not found|import .* from .*error)/gi,
24
- tokenEstimate: 4000,
25
- costPerRepeat: 0.12,
26
- why: 'Re-indexing and path searching.'
27
- },
28
- {
29
- id: 'apology-loop',
30
- name: '"I apologize" retry cycle',
31
- regex: /(I apologize|Let me try a different approach|I will now attempt)/gi,
32
- tokenEstimate: 5000,
33
- costPerRepeat: 0.15,
34
- why: 'Reasoning chain reset.'
35
- }
36
- ];
37
-
38
- function runAudit(filePath) {
39
- if (!fs.existsSync(filePath)) {
40
- return { error: 'File not found: ' + filePath };
41
- }
42
-
43
- const content = fs.readFileSync(filePath, 'utf8');
44
- const results = [];
45
- let totalWaste = 0;
46
-
47
- PATTERNS.forEach(p => {
48
- const matches = (content.match(p.regex) || []).length;
49
- if (matches > 1) { // It's only a "repeat" if it happens more than once
50
- const repeats = matches - 1;
51
- const waste = repeats * p.costPerRepeat;
52
- totalWaste += waste;
53
- results.push({
54
- pattern: p.name,
55
- occurrences: repeats,
56
- waste: waste.toFixed(2),
57
- why: p.why
58
- });
59
- }
60
- });
61
-
62
- return { results, totalWaste: totalWaste.toFixed(2) };
63
- }
64
-
65
- module.exports = { runAudit, PATTERNS };