thumbgate 1.27.17 → 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.
- package/.claude-plugin/marketplace.json +85 -0
- package/.claude-plugin/plugin.json +3 -4
- package/.well-known/llms.txt +12 -33
- package/.well-known/mcp/server-card.json +8 -8
- package/README.md +30 -221
- package/adapters/chatgpt/openapi.yaml +1695 -0
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/gemini/function-declarations.json +0 -1
- package/adapters/mcp/server-stdio.js +11 -263
- package/adapters/opencode/opencode.json +1 -1
- package/bench/thumbgate-bench.json +2 -2
- package/bin/cli.js +122 -1421
- package/bin/postinstall.js +27 -11
- package/config/gate-templates.json +0 -216
- package/config/gates/claim-verification.json +0 -12
- package/config/gates/default.json +2 -31
- package/config/github-about.json +2 -2
- package/config/mcp-allowlists.json +13 -23
- package/config/merge-quality-checks.json +1 -0
- package/config/model-candidates.json +6 -121
- package/openapi/openapi.yaml +0 -12
- package/package.json +99 -229
- package/public/blog.html +4 -4
- package/public/codex-plugin.html +20 -72
- package/public/compare.html +8 -31
- package/public/dashboard.html +166 -930
- package/public/federal.html +2 -2
- package/public/guide.html +9 -33
- package/public/index.html +116 -495
- package/public/learn.html +18 -191
- package/public/lessons.html +10 -168
- package/public/numbers.html +7 -7
- package/public/pro.html +11 -34
- package/scripts/agent-memory-lifecycle.js +0 -211
- package/scripts/agent-readiness.js +3 -167
- package/scripts/agent-reward-model.js +1 -53
- package/scripts/auto-promote-gates.js +10 -82
- package/scripts/auto-wire-hooks.js +0 -14
- package/scripts/billing.js +1 -93
- package/scripts/build-metadata.js +10 -50
- package/scripts/cli-feedback.js +6 -18
- package/scripts/cli-schema.js +0 -90
- package/scripts/cli-telemetry.js +1 -6
- package/scripts/commercial-offer.js +2 -82
- package/scripts/context-manager.js +6 -74
- package/scripts/dashboard.js +2 -68
- package/scripts/durability/step.js +3 -3
- package/scripts/export-databricks-bundle.js +1 -5
- package/scripts/export-dpo-pairs.js +2 -7
- package/scripts/feedback-loop.js +1 -145
- package/scripts/feedback-quality.js +0 -87
- package/scripts/filesystem-search.js +10 -35
- package/scripts/gate-stats.js +0 -89
- package/scripts/gates-engine.js +85 -1105
- package/scripts/gemini-embedding-policy.js +1 -2
- package/scripts/hook-runtime.js +14 -20
- package/scripts/hook-thumbgate-cache-updater.js +2 -18
- package/scripts/hybrid-feedback-context.js +7 -142
- package/scripts/lesson-inference.js +3 -8
- package/scripts/lesson-search.js +1 -17
- package/scripts/license.js +1 -0
- package/scripts/llm-client.js +9 -390
- package/scripts/local-model-profile.js +8 -15
- package/scripts/memory-scope-readiness.js +0 -159
- package/scripts/meta-agent-loop.js +0 -36
- package/scripts/operational-integrity.js +5 -39
- package/scripts/oss-pr-opportunity-scout.js +5 -35
- package/scripts/plausible-server-events.js +6 -9
- package/scripts/pro-local-dashboard.js +4 -4
- package/scripts/proxy-pointer-rag-guardrails.js +1 -42
- package/scripts/rate-limiter.js +13 -64
- package/scripts/secret-scanner.js +5 -44
- package/scripts/security-scanner.js +10 -260
- package/scripts/self-distill-agent.js +1 -3
- package/scripts/seo-gsd.js +2 -916
- package/scripts/statusline-cache-path.js +2 -17
- package/scripts/statusline-local-stats.js +1 -9
- package/scripts/statusline-meta.js +2 -28
- package/scripts/statusline.sh +4 -20
- package/scripts/telemetry-analytics.js +0 -357
- package/scripts/thompson-sampling.js +10 -31
- package/scripts/thumbgate-bench.js +1 -16
- package/scripts/thumbgate-search.js +19 -85
- package/scripts/tool-registry.js +1 -169
- package/scripts/vector-store.js +0 -45
- package/scripts/workflow-sentinel.js +53 -286
- package/scripts/workspace-evolver.js +2 -62
- package/src/api/server.js +290 -2578
- package/.claude/commands/dashboard.md +0 -15
- package/.claude/commands/thumbgate-blocked.md +0 -27
- package/.claude/commands/thumbgate-dashboard.md +0 -15
- package/.claude/commands/thumbgate-doctor.md +0 -30
- package/.claude/commands/thumbgate-guard.md +0 -36
- package/.claude/commands/thumbgate-protect.md +0 -30
- package/.claude/commands/thumbgate-rules.md +0 -30
- package/.well-known/agentic-verify.txt +0 -1
- package/adapters/gcp/dfcx-webhook-gate.js +0 -295
- package/adapters/letta/README.md +0 -41
- package/adapters/letta/thumbgate-letta-adapter.js +0 -133
- package/adapters/policy-engine/ethicore-guardian-client.js +0 -68
- package/adapters/policy-engine/thumbgate-policy-engine-adapter.js +0 -260
- package/bench/observability-eval-suite.json +0 -26
- package/bin/dashboard-cli.js +0 -7
- package/commands/dashboard.md +0 -15
- package/commands/thumbgate-dashboard.md +0 -15
- package/config/builtin-lessons.json +0 -23
- package/config/gate-classifier-routing.json +0 -98
- package/config/post-deploy-marketing-pages.json +0 -61
- package/public/about.html +0 -162
- package/public/agent-manager.html +0 -179
- package/public/agents-cost-savings.html +0 -153
- package/public/ai-malpractice-prevention.html +0 -818
- package/public/assets/brand/github-social-preview.png +0 -0
- package/public/assets/brand/thumbgate-icon-512.png +0 -0
- package/public/assets/brand/thumbgate-icon-pro-512.png +0 -0
- package/public/assets/brand/thumbgate-icon-team-512.png +0 -0
- package/public/assets/brand/thumbgate-logo-1200x360.png +0 -0
- package/public/assets/brand/thumbgate-logo-transparent.svg +0 -22
- package/public/assets/brand/thumbgate-mark-inline-v3.svg +0 -19
- package/public/assets/brand/thumbgate-mark-pro.svg +0 -23
- package/public/assets/brand/thumbgate-mark-team.svg +0 -26
- package/public/assets/brand/thumbgate-mark.svg +0 -21
- package/public/assets/brand/thumbgate-wordmark.svg +0 -20
- package/public/assets/claude-thumbgate-statusbar.svg +0 -8
- package/public/assets/codex-thumbgate-statusbar-test.svg +0 -9
- package/public/assets/legal-intake-control-flow.svg +0 -66
- package/public/brand/thumbgate-mark.svg +0 -19
- package/public/brand/thumbgate-og.svg +0 -16
- package/public/chatgpt-app.html +0 -330
- package/public/codex-enterprise.html +0 -123
- package/public/js/buyer-intent.js +0 -672
- package/public/pricing.html +0 -399
- package/scripts/action-receipts.js +0 -324
- package/scripts/activation-quickstart.js +0 -187
- package/scripts/agent-operations-planner.js +0 -621
- package/scripts/ai-component-inventory.js +0 -367
- package/scripts/async-eval-observability.js +0 -236
- package/scripts/audit.js +0 -65
- package/scripts/aws-blocks-guardrails.js +0 -228
- package/scripts/classifier-routing.js +0 -130
- package/scripts/dashboard-chat.js +0 -332
- package/scripts/feedback-aggregate.js +0 -281
- package/scripts/feedback-sanitizer.js +0 -105
- package/scripts/hob-pack.js +0 -591
- package/scripts/hook-stop-anti-claim.js +0 -301
- package/scripts/install-shim.js +0 -87
- package/scripts/mcp-oauth.js +0 -293
- package/scripts/noop-detect.js +0 -285
- package/scripts/omlx-smoke.js +0 -192
- package/scripts/parallel-workflow-orchestrator.js +0 -293
- package/scripts/plan-gate.js +0 -243
- package/scripts/plausible-domain-config.js +0 -99
- package/scripts/qa-scenario-planner.js +0 -136
- package/scripts/repeat-metric.js +0 -137
- package/scripts/secret-fixture-tokens.js +0 -61
- package/scripts/secret-redaction.js +0 -166
- package/scripts/self-harness-optimizer.js +0 -141
- package/scripts/self-healing-check.js +0 -193
- package/scripts/silent-failure-cluster.js +0 -531
- package/scripts/statusline-cache-read.js +0 -57
- package/scripts/sync-telemetry-from-prod.js +0 -374
- package/scripts/tool-contract-validator.js +0 -76
- package/scripts/trajectory-scorer.js +0 -63
- package/scripts/verify-marketing-pages-deployed.js +0 -195
- package/scripts/visitor-journey.js +0 -172
|
@@ -282,8 +282,6 @@ function buildPromotedGate(candidate, metrics, runId) {
|
|
|
282
282
|
occurrences: metrics.hits,
|
|
283
283
|
promotedAt: new Date().toISOString(),
|
|
284
284
|
source: 'meta-agent',
|
|
285
|
-
// origin distinguishes silent-failure-clustered candidates from feedback-derived ones
|
|
286
|
-
origin: candidate.origin || 'user-feedback',
|
|
287
285
|
runId,
|
|
288
286
|
score: parseFloat(metrics.score.toFixed(3)),
|
|
289
287
|
hitRate: parseFloat(metrics.hitRate.toFixed(3)),
|
|
@@ -373,38 +371,6 @@ async function runMetaAgentLoop({ dryRun = false, verbose = false } = {}) {
|
|
|
373
371
|
candidates = generateCandidatesHeuristic(failures, blockPatterns);
|
|
374
372
|
}
|
|
375
373
|
|
|
376
|
-
// Tag existing-pipeline candidates with their origin so downstream precision
|
|
377
|
-
// measurement (silentFailureDerivedGates vs user-feedback-derived) is possible.
|
|
378
|
-
candidates = candidates.map((c) => (c.origin ? c : { ...c, origin: 'user-feedback' }));
|
|
379
|
-
|
|
380
|
-
// Step 3b: Silent-failure clustering — DEFAULT-ON as of 2026-05-21
|
|
381
|
-
// (flipped from opt-in by PR #2289). Opt out via
|
|
382
|
-
// THUMBGATE_SILENT_FAILURE_CLUSTERING=0 (or NODE_ENV=test). Candidates flow
|
|
383
|
-
// through the SAME scoring / fp-rate eval below; we do not bypass any
|
|
384
|
-
// guardrail. The point of this clustering is to cover the case where users
|
|
385
|
-
// are lazy and never give thumbs-down — keeping it opt-in meant the users
|
|
386
|
-
// who need it most never got the benefit.
|
|
387
|
-
let silentFailureStats = null;
|
|
388
|
-
const { isSilentFailureClusteringEnabled, generateSilentFailureCandidates } = require('./silent-failure-cluster');
|
|
389
|
-
if (isSilentFailureClusteringEnabled()) {
|
|
390
|
-
try {
|
|
391
|
-
const sfResult = generateSilentFailureCandidates({ feedbackLogPath });
|
|
392
|
-
silentFailureStats = sfResult.stats;
|
|
393
|
-
if (sfResult.candidates && sfResult.candidates.length > 0) {
|
|
394
|
-
candidates = candidates.concat(sfResult.candidates);
|
|
395
|
-
}
|
|
396
|
-
if (verbose) {
|
|
397
|
-
process.stdout.write(
|
|
398
|
-
`[meta-agent] silent-failure-cluster: candidates=${sfResult.candidates.length} `
|
|
399
|
-
+ `failed=${sfResult.stats.failedCalls} clusters=${sfResult.stats.clusters} `
|
|
400
|
-
+ `skipped=${sfResult.stats.skippedReason || 'none'}\n`
|
|
401
|
-
);
|
|
402
|
-
}
|
|
403
|
-
} catch (err) {
|
|
404
|
-
if (verbose) process.stdout.write(`[meta-agent] silent-failure-cluster failed (non-fatal): ${err.message}\n`);
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
|
|
408
374
|
if (verbose) {
|
|
409
375
|
process.stdout.write(`[meta-agent] candidates generated: ${candidates.length} (mode=${analysisMode})\n`);
|
|
410
376
|
}
|
|
@@ -541,8 +507,6 @@ async function runMetaAgentLoop({ dryRun = false, verbose = false } = {}) {
|
|
|
541
507
|
skipped: evolutionResult.skipped || false,
|
|
542
508
|
}
|
|
543
509
|
: null,
|
|
544
|
-
silentFailureCluster: silentFailureStats,
|
|
545
|
-
silentFailureDerivedGates: promotedGates.filter((g) => g.origin === 'silent-failure-cluster').length,
|
|
546
510
|
};
|
|
547
511
|
|
|
548
512
|
if (!dryRun) {
|
|
@@ -580,51 +580,17 @@ function resolveGitHubRepository(env = process.env) {
|
|
|
580
580
|
function findOpenPrForBranch({ branchName, runner = runGh, env = process.env } = {}) {
|
|
581
581
|
const normalizedBranch = String(branchName || '').trim();
|
|
582
582
|
if (!normalizedBranch) return null;
|
|
583
|
-
|
|
584
|
-
if (!token) {
|
|
583
|
+
if (!env.GH_TOKEN && !env.GITHUB_TOKEN) {
|
|
585
584
|
return null;
|
|
586
585
|
}
|
|
586
|
+
const args = ['pr', 'list'];
|
|
587
587
|
const repository = resolveGitHubRepository(env);
|
|
588
|
-
if (
|
|
589
|
-
|
|
590
|
-
// Try REST API first as it is much more robust and doesn't rely on gh CLI's GraphQL auth
|
|
591
|
-
try {
|
|
592
|
-
const owner = repository.split('/')[0];
|
|
593
|
-
const url = `https://api.github.com/repos/${repository}/pulls?head=${encodeURIComponent(owner + ':' + normalizedBranch)}&state=open`;
|
|
594
|
-
const curlConfig = `header = "Authorization: token ${token}"\nheader = "User-Agent: ThumbGate-CI"`;
|
|
595
|
-
const curlResult = spawnSync('curl', [
|
|
596
|
-
'-s',
|
|
597
|
-
'-K', '-',
|
|
598
|
-
url
|
|
599
|
-
], {
|
|
600
|
-
encoding: 'utf8',
|
|
601
|
-
input: curlConfig
|
|
602
|
-
});
|
|
603
|
-
|
|
604
|
-
if (curlResult && curlResult.status === 0) {
|
|
605
|
-
const prs = JSON.parse(curlResult.stdout || '[]');
|
|
606
|
-
if (Array.isArray(prs) && prs.length > 0) {
|
|
607
|
-
return {
|
|
608
|
-
number: prs[0].number,
|
|
609
|
-
state: prs[0].state,
|
|
610
|
-
isDraft: prs[0].draft || false,
|
|
611
|
-
url: prs[0].html_url
|
|
612
|
-
};
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
} catch (err) {
|
|
616
|
-
console.warn(`[findOpenPrForBranch] REST fallback failed: ${err.message}`);
|
|
588
|
+
if (repository) {
|
|
589
|
+
args.push('--repo', repository);
|
|
617
590
|
}
|
|
618
|
-
|
|
619
|
-
// Fall back to gh CLI
|
|
620
|
-
const args = ['pr', 'list', '--repo', repository, '--head', normalizedBranch, '--state', 'open', '--json', 'number,state,isDraft,url'];
|
|
591
|
+
args.push('--head', normalizedBranch, '--state', 'open', '--json', 'number,state,isDraft,url');
|
|
621
592
|
const result = runner(args);
|
|
622
593
|
if (!result || result.status !== 0) {
|
|
623
|
-
if (result) {
|
|
624
|
-
console.warn(`[findOpenPrForBranch] gh command failed with status ${result.status}. Stderr: ${result.stderr || ''}`);
|
|
625
|
-
} else {
|
|
626
|
-
console.warn(`[findOpenPrForBranch] gh command failed to spawn.`);
|
|
627
|
-
}
|
|
628
594
|
return null;
|
|
629
595
|
}
|
|
630
596
|
try {
|
|
@@ -8,7 +8,6 @@ const DEFAULT_PACKAGE_PATH = path.join(__dirname, '..', 'package.json');
|
|
|
8
8
|
const DEFAULT_OUTPUT_DIR = path.join(__dirname, '..', 'docs', 'marketing');
|
|
9
9
|
const KNOWN_REPOS = Object.freeze({
|
|
10
10
|
'@anthropic-ai/sdk': 'anthropics/anthropic-sdk-typescript',
|
|
11
|
-
'@modelcontextprotocol/sdk': 'modelcontextprotocol/typescript-sdk',
|
|
12
11
|
'@google/genai': 'googleapis/js-genai',
|
|
13
12
|
'@huggingface/transformers': 'huggingface/transformers.js',
|
|
14
13
|
'@lancedb/lancedb': 'lancedb/lancedb',
|
|
@@ -24,24 +23,6 @@ const KNOWN_REPOS = Object.freeze({
|
|
|
24
23
|
undici: 'nodejs/undici',
|
|
25
24
|
});
|
|
26
25
|
|
|
27
|
-
// Communities where ThumbGate's buyers live even though they are not npm
|
|
28
|
-
// dependencies. ThumbGate ships an MCP server, so the Model Context Protocol
|
|
29
|
-
// repos are the single highest-ROI ecosystem to contribute to — but the
|
|
30
|
-
// dependency-scan above would never surface them. These are always scouted on
|
|
31
|
-
// the default (no explicit --dependencies) path, de-duped against package.json.
|
|
32
|
-
const STRATEGIC_DEPENDENCIES = Object.freeze([
|
|
33
|
-
'@modelcontextprotocol/sdk', // MCP TypeScript SDK — the protocol ThumbGate implements
|
|
34
|
-
'modelcontextprotocol/servers', // MCP servers ecosystem — where MCP authors (our buyers) collaborate
|
|
35
|
-
]);
|
|
36
|
-
|
|
37
|
-
// Honest, repo-accurate framing for the outreach draft. ThumbGate does not
|
|
38
|
-
// `import` these — it implements the protocol — so the generic "while using X"
|
|
39
|
-
// line would be a false claim. Keep drafts truthful (CEO honesty rule).
|
|
40
|
-
const RELATIONSHIP_OVERRIDES = Object.freeze({
|
|
41
|
-
'@modelcontextprotocol/sdk': 'building ThumbGate as an MCP server against the Model Context Protocol spec',
|
|
42
|
-
'modelcontextprotocol/servers': 'building and testing ThumbGate as an MCP server alongside the reference MCP servers',
|
|
43
|
-
});
|
|
44
|
-
|
|
45
26
|
const BOUNTY_KEYWORDS = [
|
|
46
27
|
'bug bounty',
|
|
47
28
|
'bounty',
|
|
@@ -89,10 +70,7 @@ function dependencyNames(pkg = {}) {
|
|
|
89
70
|
}
|
|
90
71
|
|
|
91
72
|
function repoFromDependency(name) {
|
|
92
|
-
|
|
93
|
-
// A strategic identifier may itself be an "owner/repo" slug (not an npm name).
|
|
94
|
-
if (!name.startsWith('@') && /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(name)) return name;
|
|
95
|
-
return '';
|
|
73
|
+
return KNOWN_REPOS[name] || '';
|
|
96
74
|
}
|
|
97
75
|
|
|
98
76
|
function buildIssueSearchQueries(repo) {
|
|
@@ -111,18 +89,14 @@ function scoreOpportunity(depName, repo, options = {}) {
|
|
|
111
89
|
score += 20;
|
|
112
90
|
reasons.push('known upstream repository');
|
|
113
91
|
}
|
|
114
|
-
if (/sdk|genai|stripe|playwright|lancedb|transformers|sqlite|undici
|
|
92
|
+
if (/sdk|genai|stripe|playwright|lancedb|transformers|sqlite|undici/i.test(depName)) {
|
|
115
93
|
score += 20;
|
|
116
94
|
reasons.push('high product adjacency for agent tooling');
|
|
117
95
|
}
|
|
118
|
-
if (/anthropic|google|huggingface|stripe|microsoft|nodejs
|
|
96
|
+
if (/anthropic|google|huggingface|stripe|microsoft|nodejs/i.test(repo)) {
|
|
119
97
|
score += 15;
|
|
120
98
|
reasons.push('large ecosystem visibility');
|
|
121
99
|
}
|
|
122
|
-
if (/modelcontext|mcp/i.test(depName) || /modelcontextprotocol/i.test(repo)) {
|
|
123
|
-
score += 12;
|
|
124
|
-
reasons.push("ThumbGate's own protocol surface — buyers are MCP authors");
|
|
125
|
-
}
|
|
126
100
|
if (options.includeBounties) {
|
|
127
101
|
score += 10;
|
|
128
102
|
reasons.push('bounty search enabled');
|
|
@@ -164,7 +138,7 @@ function buildOpportunity(depName, options = {}) {
|
|
|
164
138
|
'no bounty, security, or maintainer-policy claim without source link',
|
|
165
139
|
],
|
|
166
140
|
outreachDraft: repo
|
|
167
|
-
? `I found this while
|
|
141
|
+
? `I found this while using ${depName} in ThumbGate. I reproduced the issue, added a minimal fix with tests, and kept the PR scoped to the maintainer's issue.`
|
|
168
142
|
: '',
|
|
169
143
|
};
|
|
170
144
|
}
|
|
@@ -175,9 +149,7 @@ function buildOssPrOpportunityScoutPlan(rawOptions = {}) {
|
|
|
175
149
|
const explicitDeps = splitList(rawOptions.dependencies || rawOptions.deps);
|
|
176
150
|
const includeBounties = rawOptions.includeBounties !== false && rawOptions['include-bounties'] !== false;
|
|
177
151
|
const maxRepos = Math.max(1, Number.parseInt(String(rawOptions.maxRepos || rawOptions['max-repos'] || 12), 10) || 12);
|
|
178
|
-
const deps = explicitDeps.length
|
|
179
|
-
? explicitDeps
|
|
180
|
-
: [...new Set([...dependencyNames(pkg), ...STRATEGIC_DEPENDENCIES])];
|
|
152
|
+
const deps = explicitDeps.length ? explicitDeps : dependencyNames(pkg);
|
|
181
153
|
const opportunities = deps
|
|
182
154
|
.map((dep) => buildOpportunity(dep, { includeBounties }))
|
|
183
155
|
.filter((opportunity) => opportunity.repo)
|
|
@@ -251,8 +223,6 @@ function writeOssPrOpportunityScoutPack(outputDir = DEFAULT_OUTPUT_DIR, options
|
|
|
251
223
|
|
|
252
224
|
module.exports = {
|
|
253
225
|
KNOWN_REPOS,
|
|
254
|
-
STRATEGIC_DEPENDENCIES,
|
|
255
|
-
RELATIONSHIP_OVERRIDES,
|
|
256
226
|
buildIssueSearchQueries,
|
|
257
227
|
buildOpportunity,
|
|
258
228
|
buildOssPrOpportunityScoutPlan,
|
|
@@ -24,25 +24,23 @@
|
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
26
|
const https = require('node:https');
|
|
27
|
-
const {
|
|
28
|
-
resolvePlausibleDataDomain,
|
|
29
|
-
} = require('./plausible-domain-config');
|
|
30
27
|
|
|
28
|
+
const DEFAULT_PLAUSIBLE_DOMAIN = 'thumbgate-production.up.railway.app';
|
|
31
29
|
const PLAUSIBLE_ENDPOINT = 'https://plausible.io/api/event';
|
|
32
30
|
const REQUEST_TIMEOUT_MS = 2_000;
|
|
33
31
|
|
|
34
32
|
function isPlausibleDisabled() {
|
|
35
33
|
if (process.env.THUMBGATE_PLAUSIBLE_DISABLE === '1') return true;
|
|
36
34
|
if (process.env.DO_NOT_TRACK === '1') return true;
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
// NODE_ENV-based detection was tried and dropped: `node --test` does not
|
|
36
|
+
// set NODE_ENV automatically, so the check produced surprising behavior
|
|
37
|
+
// in test vs. production. Tests must opt out explicitly via the dedicated
|
|
38
|
+
// THUMBGATE_PLAUSIBLE_DISABLE flag.
|
|
41
39
|
return false;
|
|
42
40
|
}
|
|
43
41
|
|
|
44
42
|
function getPlausibleDomain() {
|
|
45
|
-
return
|
|
43
|
+
return process.env.THUMBGATE_PLAUSIBLE_DOMAIN || DEFAULT_PLAUSIBLE_DOMAIN;
|
|
46
44
|
}
|
|
47
45
|
|
|
48
46
|
/**
|
|
@@ -144,7 +142,6 @@ const CHECKOUT_EVENT_NAMES = Object.freeze({
|
|
|
144
142
|
view: 'Checkout Pro Viewed',
|
|
145
143
|
emailSubmitted: 'Checkout Pro Email Submitted',
|
|
146
144
|
stripeRedirect: 'Checkout Pro Stripe Redirect Started',
|
|
147
|
-
purchase: 'Checkout Pro Purchase Completed',
|
|
148
145
|
});
|
|
149
146
|
|
|
150
147
|
function recordCheckoutFunnelEvent(stage, options = {}) {
|
|
@@ -16,7 +16,7 @@ const CREATOR_SYNTHETIC_KEY = process.env.THUMBGATE_DEV_KEY || '';
|
|
|
16
16
|
* 2. Env var: THUMBGATE_DEV_BYPASS=[set via THUMBGATE_DEV_SECRET env var]
|
|
17
17
|
* Requires a specific non-obvious value (not boolean) to prevent accidental activation.
|
|
18
18
|
*/
|
|
19
|
-
function isCreatorDev({ env = process.env, homeDir =
|
|
19
|
+
function isCreatorDev({ env = process.env, homeDir = os.homedir() } = {}) {
|
|
20
20
|
// Layer 1: env var with specific value
|
|
21
21
|
if (CREATOR_BYPASS_VALUE && String(env[CREATOR_BYPASS_ENV] || '') === CREATOR_BYPASS_VALUE) {
|
|
22
22
|
return true;
|
|
@@ -37,7 +37,7 @@ function isCreatorDev({ env = process.env, homeDir = env.HOME || env.USERPROFILE
|
|
|
37
37
|
* with any non-empty bypass value. No env var needed — just the config file.
|
|
38
38
|
* Used by the server to skip auth on localhost during local development.
|
|
39
39
|
*/
|
|
40
|
-
function hasDevOverride(homeDir =
|
|
40
|
+
function hasDevOverride(homeDir = os.homedir()) {
|
|
41
41
|
// Disabled during test runs to avoid interfering with auth assertions
|
|
42
42
|
if (process.env.NODE_TEST_CONTEXT || process.env.THUMBGATE_TESTING) return false;
|
|
43
43
|
try {
|
|
@@ -47,11 +47,11 @@ function hasDevOverride(homeDir = process.env.HOME || process.env.USERPROFILE ||
|
|
|
47
47
|
} catch { return false; }
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
function getLicenseDir(homeDir =
|
|
50
|
+
function getLicenseDir(homeDir = os.homedir()) {
|
|
51
51
|
return path.join(homeDir, '.thumbgate');
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
function getLicensePath(homeDir =
|
|
54
|
+
function getLicensePath(homeDir = os.homedir()) {
|
|
55
55
|
return path.join(getLicenseDir(homeDir), 'license.json');
|
|
56
56
|
}
|
|
57
57
|
|
|
@@ -39,23 +39,9 @@ function normalizeOptions(options = {}) {
|
|
|
39
39
|
...splitCsv(options.documents),
|
|
40
40
|
...splitCsv(options['document-ids']),
|
|
41
41
|
]);
|
|
42
|
-
const sourcePointers = unique([
|
|
43
|
-
...splitCsv(options['source-pointers']),
|
|
44
|
-
...splitCsv(options.pointers),
|
|
45
|
-
...splitCsv(options.sources),
|
|
46
|
-
]);
|
|
47
42
|
const candidateImages = Number.isFinite(Number(options['candidate-images']))
|
|
48
43
|
? Number(options['candidate-images'])
|
|
49
44
|
: null;
|
|
50
|
-
const extractedEntities = Number.isFinite(Number(options['extracted-entities']))
|
|
51
|
-
? Number(options['extracted-entities'])
|
|
52
|
-
: 0;
|
|
53
|
-
const extractedRelations = Number.isFinite(Number(options['extracted-relations']))
|
|
54
|
-
? Number(options['extracted-relations'])
|
|
55
|
-
: 0;
|
|
56
|
-
const promotionThreshold = Number.isFinite(Number(options['promotion-threshold']))
|
|
57
|
-
? Number(options['promotion-threshold'])
|
|
58
|
-
: 3;
|
|
59
45
|
|
|
60
46
|
return {
|
|
61
47
|
ragTool: String(options['rag-tool'] || options.tool || 'proxy-pointer-rag').trim() || 'proxy-pointer-rag',
|
|
@@ -63,15 +49,10 @@ function normalizeOptions(options = {}) {
|
|
|
63
49
|
sectionIds,
|
|
64
50
|
imagePointers,
|
|
65
51
|
documentIds,
|
|
66
|
-
sourcePointers,
|
|
67
52
|
candidateImages,
|
|
68
|
-
extractedEntities,
|
|
69
|
-
extractedRelations,
|
|
70
|
-
promotionThreshold,
|
|
71
53
|
crossDocumentPolicy: String(options['cross-doc-policy'] || options['cross-document-policy'] || '').trim().toLowerCase(),
|
|
72
54
|
visionFilter: normalizeBoolean(options['vision-filter']),
|
|
73
55
|
visualClaims: normalizeBoolean(options['visual-claims']),
|
|
74
|
-
pointerFirst: normalizeBoolean(options['pointer-first']) || normalizeBoolean(options['proxy-pointer']),
|
|
75
56
|
};
|
|
76
57
|
}
|
|
77
58
|
|
|
@@ -91,14 +72,6 @@ function gateApplicability(template, options) {
|
|
|
91
72
|
return false;
|
|
92
73
|
}
|
|
93
74
|
|
|
94
|
-
function hasExtractionSprawl(options) {
|
|
95
|
-
const extractedFacts = options.extractedEntities + options.extractedRelations;
|
|
96
|
-
if (extractedFacts === 0) return false;
|
|
97
|
-
if (options.pointerFirst) return true;
|
|
98
|
-
if (options.sourcePointers.length === 0) return true;
|
|
99
|
-
return extractedFacts > options.sourcePointers.length * Math.max(2, options.promotionThreshold);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
75
|
function buildSignalSummary(options) {
|
|
103
76
|
const signals = [];
|
|
104
77
|
if (options.treePath || options.sectionIds.length > 0) {
|
|
@@ -137,19 +110,6 @@ function buildSignalSummary(options) {
|
|
|
137
110
|
risk: 'answers that describe image content may need a vision-model sanity check',
|
|
138
111
|
});
|
|
139
112
|
}
|
|
140
|
-
if (hasExtractionSprawl(options)) {
|
|
141
|
-
signals.push({
|
|
142
|
-
id: 'entity_relation_sprawl',
|
|
143
|
-
label: 'Entity/relation extraction sprawl',
|
|
144
|
-
values: unique([
|
|
145
|
-
`${options.extractedEntities} extracted entities`,
|
|
146
|
-
`${options.extractedRelations} extracted relations`,
|
|
147
|
-
`${options.sourcePointers.length} source pointers`,
|
|
148
|
-
`promotion threshold ${options.promotionThreshold}`,
|
|
149
|
-
]),
|
|
150
|
-
risk: 'eager graph extraction can create stale aliases, weak edges, and unauditable memory; keep source pointers first and promote relations only after repeated retrieval value',
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
113
|
return signals;
|
|
154
114
|
}
|
|
155
115
|
|
|
@@ -179,12 +139,11 @@ function buildProxyPointerRagGuardrailsPlan(rawOptions = {}, templatesPath) {
|
|
|
179
139
|
templates: recommendedTemplates,
|
|
180
140
|
nextActions: [
|
|
181
141
|
'Preserve document hierarchy, section IDs, and image file paths during ingestion.',
|
|
182
|
-
'Store source pointers before extracting entities or relations; promote a relation only after repeated retrieval value and source verification.',
|
|
183
142
|
'Pass section-tree and image-pointer metadata into the agent before it answers with visuals.',
|
|
184
143
|
'Enable the recommended Document RAG Safety templates as pre-action gates.',
|
|
185
144
|
'Use a vision filter only for high-impact answers that make claims about visual content.',
|
|
186
145
|
],
|
|
187
|
-
exampleCommand: 'npx thumbgate proxy-pointer-rag-guardrails --tree-path=.rag/tree.json --
|
|
146
|
+
exampleCommand: 'npx thumbgate proxy-pointer-rag-guardrails --tree-path=.rag/tree.json --image-pointers=paper-1/figures/fig2.png --documents=paper-1 --visual-claims --json',
|
|
188
147
|
};
|
|
189
148
|
}
|
|
190
149
|
|
package/scripts/rate-limiter.js
CHANGED
|
@@ -6,78 +6,42 @@ const path = require('path');
|
|
|
6
6
|
const {
|
|
7
7
|
PRO_MONTHLY_PAYMENT_LINK,
|
|
8
8
|
PRO_PRICE_LABEL,
|
|
9
|
-
|
|
9
|
+
TEAM_PRICE_LABEL,
|
|
10
10
|
} = require('./commercial-offer');
|
|
11
11
|
|
|
12
12
|
const USAGE_FILE = path.join(process.env.HOME || '/tmp', '.thumbgate', 'usage-limits.json');
|
|
13
13
|
|
|
14
14
|
// ──────────────────────────────────────────────────────────
|
|
15
|
-
// Free tier:
|
|
16
|
-
//
|
|
17
|
-
//
|
|
15
|
+
// Free tier: generous on captures (habit formation) and rules
|
|
16
|
+
// (5 active gates), gated on Pro-only features (recall, search,
|
|
17
|
+
// exports). Dashboard, exports, and unlimited rules drive Pro.
|
|
18
18
|
// ──────────────────────────────────────────────────────────
|
|
19
19
|
const FREE_TIER_LIMITS = {
|
|
20
|
-
capture_feedback: { daily:
|
|
21
|
-
prevention_rules: { daily:
|
|
20
|
+
capture_feedback: { daily: Infinity, lifetime: Infinity, label: 'feedback captures' },
|
|
21
|
+
prevention_rules: { daily: Infinity, lifetime: Infinity, label: 'prevention rules generated' },
|
|
22
22
|
recall: { daily: 0, lifetime: 0, label: 'recall queries (Pro only)' },
|
|
23
23
|
search_lessons: { daily: 0, lifetime: 0, label: 'lesson searches (Pro only)' },
|
|
24
24
|
search_thumbgate: { daily: 0, lifetime: 0, label: 'ThumbGate searches (Pro only)' },
|
|
25
25
|
commerce_recall: { daily: 0, lifetime: 0, label: 'commerce recalls (Pro only)' },
|
|
26
26
|
export_dpo: { daily: 0, lifetime: 0, label: 'DPO exports (Pro only)' },
|
|
27
27
|
export_databricks: { daily: 0, lifetime: 0, label: 'Databricks exports (Pro only)' },
|
|
28
|
-
construct_context_pack: { daily:
|
|
28
|
+
construct_context_pack: { daily: Infinity, lifetime: Infinity, label: 'context packs' },
|
|
29
29
|
};
|
|
30
30
|
|
|
31
|
-
const FREE_TIER_MAX_GATES =
|
|
32
|
-
const FREE_TIER_DAILY_BLOCKS = 2; // 3 gate blocks/day on free; after limit, deny → warn + upgrade CTA
|
|
31
|
+
const FREE_TIER_MAX_GATES = 5; // 5 active prevention rules on free; Pro is unlimited
|
|
33
32
|
|
|
34
|
-
const UPGRADE_MESSAGE = `Pro: ${PRO_PRICE_LABEL} — unlimited rules, recall, lesson search, dashboard, and exports: ${PRO_MONTHLY_PAYMENT_LINK}\n
|
|
33
|
+
const UPGRADE_MESSAGE = `Pro: ${PRO_PRICE_LABEL} — unlimited rules, recall, lesson search, dashboard, and exports: ${PRO_MONTHLY_PAYMENT_LINK}\n Team: ${TEAM_PRICE_LABEL} after workflow qualification.`;
|
|
35
34
|
|
|
36
35
|
const PAYWALL_MESSAGES = {
|
|
37
|
-
|
|
38
|
-
prevention_rules: 'Free tier includes 3 active prevention rules and 2 rule generations/day. Upgrade to Pro for unlimited rules.',
|
|
36
|
+
prevention_rules: 'Free tier includes 5 active prevention rules. Promote more or unlock unlimited rules with Pro.',
|
|
39
37
|
recall: 'Recall is a Pro feature. Your past feedback is stored locally — upgrade to search and reuse it.',
|
|
40
38
|
search_lessons: 'Lesson search is a Pro feature. Upgrade to find patterns in your agent\'s mistakes.',
|
|
41
|
-
construct_context_pack: 'Free tier: 3 context packs/day. Upgrade to Pro for unlimited.',
|
|
42
39
|
default: 'This feature requires Pro. Start Pro — card required; billed today.',
|
|
43
40
|
};
|
|
44
41
|
|
|
45
|
-
const TRIAL_DAYS = 7;
|
|
46
|
-
|
|
47
|
-
function getInstallAgeDays() {
|
|
48
|
-
try {
|
|
49
|
-
const { INSTALL_ID_PATH } = require('./cli-telemetry');
|
|
50
|
-
if (!fs.existsSync(INSTALL_ID_PATH)) return null;
|
|
51
|
-
// Use mtimeMs — birthtimeMs is unreliable on Linux (ext4 doesn't backdate creation time).
|
|
52
|
-
// The install-id file is written once at install, so mtime == creation time in practice.
|
|
53
|
-
const stat = fs.statSync(INSTALL_ID_PATH);
|
|
54
|
-
const created = stat.mtimeMs || stat.birthtimeMs;
|
|
55
|
-
if (!Number.isFinite(created) || created <= 0) return null;
|
|
56
|
-
return (Date.now() - created) / (1000 * 60 * 60 * 24);
|
|
57
|
-
} catch (_) {
|
|
58
|
-
return null;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function isInTrialPeriod() {
|
|
63
|
-
if (process.env.CI || process.env.GITHUB_ACTIONS) return false;
|
|
64
|
-
if (process.env.THUMBGATE_NO_TRIAL === '1') return false;
|
|
65
|
-
const age = getInstallAgeDays();
|
|
66
|
-
if (age === null) return false;
|
|
67
|
-
if (age < 0.0007) return false; // <1 minute old — just-created install, not a real trial
|
|
68
|
-
return age < TRIAL_DAYS;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function trialDaysRemaining() {
|
|
72
|
-
const age = getInstallAgeDays();
|
|
73
|
-
if (age === null) return 0;
|
|
74
|
-
return Math.max(0, Math.ceil(TRIAL_DAYS - age));
|
|
75
|
-
}
|
|
76
|
-
|
|
77
42
|
function isProTier(authContext) {
|
|
78
43
|
if (authContext && authContext.tier === 'pro') return true;
|
|
79
|
-
if (process.env.THUMBGATE_API_KEY) return true;
|
|
80
|
-
if (process.env.THUMBGATE_NO_RATE_LIMIT === '1') return true;
|
|
44
|
+
if (process.env.THUMBGATE_API_KEY || process.env.THUMBGATE_PRO_MODE === '1' || process.env.THUMBGATE_NO_RATE_LIMIT === '1') return true;
|
|
81
45
|
// Creator/dogfooding bypass: when the owner has the dev secret + bypass
|
|
82
46
|
// configured (env or ~/.config/thumbgate/dev.json), treat the install as Pro
|
|
83
47
|
// so marketing nudges and rate limits stop firing on the maintainer's own
|
|
@@ -93,9 +57,6 @@ function isProTier(authContext) {
|
|
|
93
57
|
const { isProLicensed } = require('./license');
|
|
94
58
|
if (isProLicensed()) return true;
|
|
95
59
|
} catch (_) {}
|
|
96
|
-
// 7-day reverse trial: new installs get full Pro access, then hit a clear
|
|
97
|
-
// hosted-sync/unlimited-rules pay moment while the product is still fresh.
|
|
98
|
-
if (isInTrialPeriod()) return true;
|
|
99
60
|
return false;
|
|
100
61
|
}
|
|
101
62
|
|
|
@@ -153,18 +114,12 @@ function checkLimit(action, authContext) {
|
|
|
153
114
|
const dailyCurrent = usage.counts[action] || 0;
|
|
154
115
|
const lifetimeCurrent = usage.lifetime[action] || 0;
|
|
155
116
|
|
|
156
|
-
const isCapture = action === 'capture_feedback';
|
|
157
|
-
const monthlyLink = isCapture
|
|
158
|
-
? 'https://buy.stripe.com/7sYfZhaiE1eSbO99uj3sI0d'
|
|
159
|
-
: PRO_MONTHLY_PAYMENT_LINK;
|
|
160
|
-
const upgradeMessage = `Pro: ${PRO_PRICE_LABEL} — unlimited rules, recall, lesson search, dashboard, and exports: ${monthlyLink}\n Enterprise: ${ENTERPRISE_PRICE_LABEL} after workflow qualification.`;
|
|
161
|
-
|
|
162
117
|
// Check lifetime limit first (the hard wall)
|
|
163
118
|
if (lifetimeLimit !== Infinity && lifetimeCurrent >= lifetimeLimit) {
|
|
164
119
|
const paywallMsg = PAYWALL_MESSAGES[action] || PAYWALL_MESSAGES.default;
|
|
165
120
|
return {
|
|
166
121
|
allowed: false,
|
|
167
|
-
message: `${paywallMsg}\n\n${
|
|
122
|
+
message: `${paywallMsg}\n\n${UPGRADE_MESSAGE}`,
|
|
168
123
|
used: lifetimeCurrent,
|
|
169
124
|
limit: lifetimeLimit,
|
|
170
125
|
limitType: 'lifetime',
|
|
@@ -173,10 +128,9 @@ function checkLimit(action, authContext) {
|
|
|
173
128
|
|
|
174
129
|
// Check daily limit
|
|
175
130
|
if (dailyLimit !== Infinity && dailyCurrent >= dailyLimit) {
|
|
176
|
-
const paywallMsg = PAYWALL_MESSAGES[action] || PAYWALL_MESSAGES.default;
|
|
177
131
|
return {
|
|
178
132
|
allowed: false,
|
|
179
|
-
message: `Daily limit reached. ${
|
|
133
|
+
message: `Daily limit reached. ${UPGRADE_MESSAGE}`,
|
|
180
134
|
used: dailyCurrent,
|
|
181
135
|
limit: dailyLimit,
|
|
182
136
|
limitType: 'daily',
|
|
@@ -225,17 +179,12 @@ function getUsage(action, authContext) {
|
|
|
225
179
|
module.exports = {
|
|
226
180
|
checkLimit,
|
|
227
181
|
getUsage,
|
|
228
|
-
getInstallAgeDays,
|
|
229
182
|
isProTier,
|
|
230
|
-
isInTrialPeriod,
|
|
231
|
-
trialDaysRemaining,
|
|
232
183
|
loadUsage,
|
|
233
184
|
saveUsage,
|
|
234
185
|
todayKey,
|
|
235
186
|
FREE_TIER_LIMITS,
|
|
236
187
|
FREE_TIER_MAX_GATES,
|
|
237
|
-
FREE_TIER_DAILY_BLOCKS,
|
|
238
|
-
TRIAL_DAYS,
|
|
239
188
|
UPGRADE_MESSAGE,
|
|
240
189
|
PAYWALL_MESSAGES,
|
|
241
190
|
USAGE_FILE,
|
|
@@ -55,11 +55,6 @@ const BASH_SECRET_READ_PREFIXES = [
|
|
|
55
55
|
];
|
|
56
56
|
|
|
57
57
|
const EDIT_LIKE_TOOLS = new Set(['Edit', 'Write', 'MultiEdit']);
|
|
58
|
-
const SAFE_SECRET_STORAGE_DIRS = [
|
|
59
|
-
'.resume_secrets',
|
|
60
|
-
'.thumbgate/secrets',
|
|
61
|
-
'.config/thumbgate',
|
|
62
|
-
];
|
|
63
58
|
|
|
64
59
|
function redactText(text) {
|
|
65
60
|
if (!text) return '';
|
|
@@ -177,13 +172,6 @@ function heuristicScanText(text, source = 'text') {
|
|
|
177
172
|
pattern.regex.lastIndex = 0;
|
|
178
173
|
let match = pattern.regex.exec(input);
|
|
179
174
|
while (match) {
|
|
180
|
-
// Safe test key bypass
|
|
181
|
-
const matchedString = match[0].toLowerCase();
|
|
182
|
-
if (pattern.id === 'generic_assignment' && (matchedString.includes('sk_test_') || matchedString.includes('test_token'))) {
|
|
183
|
-
match = pattern.regex.exec(input);
|
|
184
|
-
continue;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
175
|
findings.push({
|
|
188
176
|
id: pattern.id,
|
|
189
177
|
label: pattern.label,
|
|
@@ -307,26 +295,6 @@ function resolvePathToken(token, cwd) {
|
|
|
307
295
|
return path.join(cwd || process.cwd(), normalized);
|
|
308
296
|
}
|
|
309
297
|
|
|
310
|
-
function normalizePathForPolicy(filePath) {
|
|
311
|
-
return path.resolve(String(filePath || '').replace(/^~(?=\/|$)/, os.homedir()));
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
function isSafeSecretStoragePath(filePath) {
|
|
315
|
-
if (!filePath) return false;
|
|
316
|
-
const normalized = normalizePathForPolicy(filePath);
|
|
317
|
-
const home = normalizePathForPolicy(os.homedir());
|
|
318
|
-
return SAFE_SECRET_STORAGE_DIRS.some((dir) => {
|
|
319
|
-
const allowedRoot = path.join(home, dir);
|
|
320
|
-
return normalized === allowedRoot || normalized.startsWith(`${allowedRoot}${path.sep}`);
|
|
321
|
-
});
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
function isSafeSecretStorageWrite(toolName, toolInput = {}, cwd = process.cwd()) {
|
|
325
|
-
if (!EDIT_LIKE_TOOLS.has(toolName)) return false;
|
|
326
|
-
const paths = getToolInputPaths(toolInput, cwd);
|
|
327
|
-
return paths.length > 0 && paths.every((filePath) => isSafeSecretStoragePath(filePath));
|
|
328
|
-
}
|
|
329
|
-
|
|
330
298
|
function scanBashCommand(command, options = {}) {
|
|
331
299
|
const cwd = options.cwd || process.cwd();
|
|
332
300
|
const findings = [];
|
|
@@ -379,7 +347,6 @@ function scanHookInput(input = {}, options = {}) {
|
|
|
379
347
|
let provider = resolveProvider(options.provider);
|
|
380
348
|
let commandHash = null;
|
|
381
349
|
let fileHashes = [];
|
|
382
|
-
const safeSecretStorageWrite = isSafeSecretStorageWrite(toolName, toolInput, cwd);
|
|
383
350
|
|
|
384
351
|
const contentFields = [
|
|
385
352
|
toolInput.content,
|
|
@@ -409,13 +376,11 @@ function scanHookInput(input = {}, options = {}) {
|
|
|
409
376
|
}
|
|
410
377
|
}
|
|
411
378
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
findings.push(...result.findings);
|
|
418
|
-
}
|
|
379
|
+
for (const content of contentFields) {
|
|
380
|
+
const result = scanText(content, { provider, source: 'tool_input' });
|
|
381
|
+
if (result.detected) {
|
|
382
|
+
provider = result.provider;
|
|
383
|
+
findings.push(...result.findings);
|
|
419
384
|
}
|
|
420
385
|
}
|
|
421
386
|
|
|
@@ -437,8 +402,6 @@ function buildSafeSummary(findings, prefix) {
|
|
|
437
402
|
module.exports = {
|
|
438
403
|
SECRET_PATTERNS,
|
|
439
404
|
SECRET_FILE_PATTERNS,
|
|
440
|
-
SAFE_SECRET_STORAGE_DIRS,
|
|
441
|
-
EDIT_LIKE_TOOLS,
|
|
442
405
|
redactText,
|
|
443
406
|
resolveProvider,
|
|
444
407
|
scanText,
|
|
@@ -446,8 +409,6 @@ module.exports = {
|
|
|
446
409
|
scanBashCommand,
|
|
447
410
|
scanHookInput,
|
|
448
411
|
classifySecretPath,
|
|
449
|
-
isSafeSecretStoragePath,
|
|
450
|
-
isSafeSecretStorageWrite,
|
|
451
412
|
buildSafeSummary,
|
|
452
413
|
tokenizeCommand,
|
|
453
414
|
};
|