thumbgate 1.27.19 → 1.27.20
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/commands/dashboard.md +15 -0
- package/.claude/commands/thumbgate-blocked.md +27 -0
- package/.claude/commands/thumbgate-dashboard.md +15 -0
- package/.claude/commands/thumbgate-doctor.md +30 -0
- package/.claude/commands/thumbgate-guard.md +36 -0
- package/.claude/commands/thumbgate-protect.md +30 -0
- package/.claude/commands/thumbgate-rules.md +30 -0
- package/.claude-plugin/plugin.json +1 -1
- package/.well-known/mcp/server-card.json +1 -1
- package/README.md +0 -3
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/gcp/dfcx-webhook-gate.js +295 -0
- package/adapters/letta/README.md +41 -0
- package/adapters/letta/thumbgate-letta-adapter.js +133 -0
- package/adapters/mcp/server-stdio.js +1 -1
- package/adapters/opencode/opencode.json +1 -1
- package/adapters/policy-engine/ethicore-guardian-client.js +68 -0
- package/adapters/policy-engine/thumbgate-policy-engine-adapter.js +260 -0
- package/bench/observability-eval-suite.json +26 -0
- package/bin/cli.js +27 -1
- package/bin/dashboard-cli.js +7 -0
- package/bin/postinstall.js +14 -23
- package/commands/dashboard.md +15 -0
- package/commands/thumbgate-dashboard.md +15 -0
- package/package.json +225 -100
- package/public/about.html +162 -0
- package/public/agent-manager.html +179 -0
- package/public/agents-cost-savings.html +153 -0
- package/public/ai-malpractice-prevention.html +818 -0
- 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 +28 -0
- package/public/assets/brand/thumbgate-mark-inline-v3.svg +18 -0
- package/public/assets/brand/thumbgate-mark-pro.svg +23 -0
- package/public/assets/brand/thumbgate-mark-team.svg +26 -0
- package/public/assets/brand/thumbgate-mark.svg +21 -0
- package/public/assets/brand/thumbgate-wordmark.svg +20 -0
- package/public/assets/claude-thumbgate-statusbar.svg +8 -0
- package/public/assets/codex-thumbgate-statusbar-test.svg +9 -0
- package/public/assets/legal-intake-control-flow.svg +66 -0
- package/public/brand/thumbgate-mark.svg +19 -0
- package/public/brand/thumbgate-og.svg +16 -0
- package/public/chatgpt-app.html +330 -0
- package/public/codex-enterprise.html +123 -0
- package/public/diagnostic.html +345 -0
- package/public/index.html +2 -2
- package/public/install.html +193 -0
- package/public/js/buyer-intent.js +672 -0
- package/public/numbers.html +2 -2
- package/public/pricing.html +399 -0
- package/scripts/action-receipts.js +324 -0
- package/scripts/activation-quickstart.js +187 -0
- package/scripts/agent-operations-planner.js +621 -0
- package/scripts/ai-component-inventory.js +367 -0
- package/scripts/async-eval-observability.js +236 -0
- package/scripts/audit.js +65 -0
- package/scripts/aws-blocks-guardrails.js +272 -0
- package/scripts/classifier-routing.js +130 -0
- package/scripts/dashboard-chat.js +332 -0
- package/scripts/feedback-aggregate.js +281 -0
- package/scripts/feedback-sanitizer.js +105 -0
- package/scripts/hook-stop-anti-claim.js +301 -0
- package/scripts/install-shim.js +87 -0
- package/scripts/mcp-oauth.js +293 -0
- package/scripts/noop-detect.js +285 -0
- package/scripts/parallel-workflow-orchestrator.js +293 -0
- package/scripts/plan-gate.js +243 -0
- package/scripts/plausible-domain-config.js +99 -0
- package/scripts/qa-scenario-planner.js +136 -0
- package/scripts/repeat-metric.js +137 -0
- package/scripts/secret-fixture-tokens.js +61 -0
- package/scripts/secret-redaction.js +166 -0
- package/scripts/self-harness-optimizer.js +141 -0
- package/scripts/self-healing-check.js +193 -0
- package/scripts/self-protection.js +90 -0
- package/scripts/silent-failure-cluster.js +531 -0
- package/scripts/statusline-cache-read.js +57 -0
- package/scripts/sync-telemetry-from-prod.js +374 -0
- package/scripts/tool-contract-validator.js +76 -0
- package/scripts/trajectory-scorer.js +63 -0
- package/scripts/verify-marketing-pages-deployed.js +212 -0
- package/scripts/visitor-journey.js +172 -0
- package/.claude-plugin/marketplace.json +0 -85
- package/adapters/chatgpt/openapi.yaml +0 -1707
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const fs = require('node:fs');
|
|
3
|
+
const os = require('node:os');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { spawnSync } = require('node:child_process');
|
|
6
|
+
const { diagnoseFailure } = require('./failure-diagnostics');
|
|
7
|
+
const { appendDiagnosticRecord } = require('./feedback-loop');
|
|
8
|
+
|
|
9
|
+
const PROJECT_ROOT = path.join(__dirname, '..');
|
|
10
|
+
const DEFAULT_MAX_BUFFER_BYTES = 64 * 1024 * 1024;
|
|
11
|
+
const DEFAULT_TESTS_TIMEOUT_MS = Number.parseInt(
|
|
12
|
+
process.env.THUMBGATE_SELF_HEAL_TEST_TIMEOUT_MS || '',
|
|
13
|
+
10,
|
|
14
|
+
) || 60 * 60_000;
|
|
15
|
+
|
|
16
|
+
const DEFAULT_CHECKS = [
|
|
17
|
+
{ name: 'budget_status', command: ['npm', 'run', 'budget:status'], timeoutMs: 60_000 },
|
|
18
|
+
{ name: 'tests', command: ['npm', 'test'], timeoutMs: DEFAULT_TESTS_TIMEOUT_MS },
|
|
19
|
+
{ name: 'prove_adapters', command: ['npm', 'run', 'prove:adapters'], timeoutMs: 10 * 60_000, useTempProofDir: true },
|
|
20
|
+
{ name: 'prove_automation', command: ['npm', 'run', 'prove:automation'], timeoutMs: 10 * 60_000, useTempProofDir: true },
|
|
21
|
+
{ name: 'prove_data_pipeline', command: ['npm', 'run', 'prove:data-pipeline'], timeoutMs: 10 * 60_000, useTempProofDir: true },
|
|
22
|
+
{ name: 'prove_tessl', command: ['npm', 'run', 'prove:tessl'], timeoutMs: 10 * 60_000, useTempProofDir: true },
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
function runCommand(command, {
|
|
26
|
+
cwd = PROJECT_ROOT,
|
|
27
|
+
timeoutMs = 5 * 60_000,
|
|
28
|
+
env = process.env,
|
|
29
|
+
maxBufferBytes = DEFAULT_MAX_BUFFER_BYTES,
|
|
30
|
+
} = {}) {
|
|
31
|
+
const [cmd, ...args] = command;
|
|
32
|
+
const started = Date.now();
|
|
33
|
+
const result = spawnSync(cmd, args, {
|
|
34
|
+
cwd,
|
|
35
|
+
env,
|
|
36
|
+
encoding: 'utf-8',
|
|
37
|
+
timeout: timeoutMs,
|
|
38
|
+
maxBuffer: maxBufferBytes,
|
|
39
|
+
shell: false,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const durationMs = Date.now() - started;
|
|
43
|
+
const status = Number.isInteger(result.status) ? result.status : 1;
|
|
44
|
+
return {
|
|
45
|
+
exitCode: status,
|
|
46
|
+
durationMs,
|
|
47
|
+
stdout: result.stdout || '',
|
|
48
|
+
stderr: result.stderr || '',
|
|
49
|
+
error: result.error ? result.error.message : null,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function createCheckEnvironment(check) {
|
|
54
|
+
const environment = { ...process.env };
|
|
55
|
+
let cleanup = null;
|
|
56
|
+
|
|
57
|
+
if (check.useTempProofDir) {
|
|
58
|
+
const proofDir = fs.mkdtempSync(path.join(os.tmpdir(), `thumbgate-${check.name}-`));
|
|
59
|
+
environment.THUMBGATE_PROOF_DIR = proofDir;
|
|
60
|
+
if (check.name === 'prove_automation') {
|
|
61
|
+
environment.THUMBGATE_AUTOMATION_PROOF_DIR = proofDir;
|
|
62
|
+
}
|
|
63
|
+
cleanup = () => {
|
|
64
|
+
fs.rmSync(proofDir, { recursive: true, force: true });
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return { env: environment, cleanup };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function collectHealthReport({
|
|
72
|
+
checks = DEFAULT_CHECKS,
|
|
73
|
+
runner = runCommand,
|
|
74
|
+
cwd = PROJECT_ROOT,
|
|
75
|
+
persistDiagnostics = false,
|
|
76
|
+
} = {}) {
|
|
77
|
+
const startedAt = new Date();
|
|
78
|
+
const results = checks.map((check) => {
|
|
79
|
+
const { env, cleanup } = createCheckEnvironment(check);
|
|
80
|
+
let run;
|
|
81
|
+
try {
|
|
82
|
+
run = runner(check.command, { cwd, timeoutMs: check.timeoutMs, env });
|
|
83
|
+
} finally {
|
|
84
|
+
if (cleanup) {
|
|
85
|
+
cleanup();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const diagnosis = run.exitCode === 0
|
|
89
|
+
? null
|
|
90
|
+
: diagnoseFailure({
|
|
91
|
+
step: check.name,
|
|
92
|
+
context: check.command.join(' '),
|
|
93
|
+
healthCheck: {
|
|
94
|
+
name: check.name,
|
|
95
|
+
exitCode: run.exitCode,
|
|
96
|
+
status: 'unhealthy',
|
|
97
|
+
outputTail: `${run.stdout}\n${run.stderr}`.trim().slice(-2000),
|
|
98
|
+
},
|
|
99
|
+
exitCode: run.exitCode,
|
|
100
|
+
error: run.error,
|
|
101
|
+
output: `${run.stdout}\n${run.stderr}`.trim(),
|
|
102
|
+
});
|
|
103
|
+
const persistedDiagnosis = persistDiagnostics && diagnosis
|
|
104
|
+
? appendDiagnosticRecord({
|
|
105
|
+
source: 'self_heal_check',
|
|
106
|
+
step: check.name,
|
|
107
|
+
context: check.command.join(' '),
|
|
108
|
+
diagnosis,
|
|
109
|
+
metadata: {
|
|
110
|
+
command: check.command.join(' '),
|
|
111
|
+
},
|
|
112
|
+
})
|
|
113
|
+
: null;
|
|
114
|
+
return {
|
|
115
|
+
name: check.name,
|
|
116
|
+
command: check.command.join(' '),
|
|
117
|
+
status: run.exitCode === 0 ? 'healthy' : 'unhealthy',
|
|
118
|
+
exitCode: run.exitCode,
|
|
119
|
+
durationMs: run.durationMs,
|
|
120
|
+
error: run.error,
|
|
121
|
+
outputTail: `${run.stdout}\n${run.stderr}`.trim().slice(-2000),
|
|
122
|
+
diagnosis,
|
|
123
|
+
persistedDiagnosis,
|
|
124
|
+
};
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const healthyCount = results.filter((x) => x.status === 'healthy').length;
|
|
128
|
+
const unhealthyCount = results.length - healthyCount;
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
generatedAt: startedAt.toISOString(),
|
|
132
|
+
durationMs: Date.now() - startedAt.getTime(),
|
|
133
|
+
overall_status: unhealthyCount === 0 ? 'healthy' : 'unhealthy',
|
|
134
|
+
summary: {
|
|
135
|
+
total: results.length,
|
|
136
|
+
healthy: healthyCount,
|
|
137
|
+
unhealthy: unhealthyCount,
|
|
138
|
+
},
|
|
139
|
+
checks: results,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function reportToText(report) {
|
|
144
|
+
const lines = [];
|
|
145
|
+
lines.push(`Self-Healing Health Check @ ${report.generatedAt}`);
|
|
146
|
+
lines.push(`Overall: ${report.overall_status.toUpperCase()}`);
|
|
147
|
+
lines.push(`Checks: ${report.summary.healthy}/${report.summary.total} healthy`);
|
|
148
|
+
lines.push('');
|
|
149
|
+
|
|
150
|
+
report.checks.forEach((check) => {
|
|
151
|
+
const icon = check.status === 'healthy' ? '✅' : '❌';
|
|
152
|
+
lines.push(`${icon} ${check.name} (${check.durationMs}ms)`);
|
|
153
|
+
if (check.status !== 'healthy') {
|
|
154
|
+
lines.push(` command: ${check.command}`);
|
|
155
|
+
if (check.error) lines.push(` error: ${check.error}`);
|
|
156
|
+
if (check.diagnosis && check.diagnosis.rootCauseCategory) {
|
|
157
|
+
lines.push(` diagnosis: ${check.diagnosis.rootCauseCategory}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
return `${lines.join('\n')}\n`;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function runCli() {
|
|
166
|
+
const args = new Set(process.argv.slice(2));
|
|
167
|
+
const emitJson = args.has('--json');
|
|
168
|
+
const noFail = args.has('--no-fail');
|
|
169
|
+
const report = collectHealthReport({ persistDiagnostics: true });
|
|
170
|
+
|
|
171
|
+
if (emitJson) {
|
|
172
|
+
console.log(JSON.stringify(report, null, 2));
|
|
173
|
+
} else {
|
|
174
|
+
process.stdout.write(reportToText(report));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (!noFail && report.overall_status !== 'healthy') {
|
|
178
|
+
process.exit(1);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
module.exports = {
|
|
183
|
+
DEFAULT_CHECKS,
|
|
184
|
+
DEFAULT_TESTS_TIMEOUT_MS,
|
|
185
|
+
DEFAULT_MAX_BUFFER_BYTES,
|
|
186
|
+
runCommand,
|
|
187
|
+
collectHealthReport,
|
|
188
|
+
reportToText,
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
if (require.main === module) {
|
|
192
|
+
runCli();
|
|
193
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* self-protection.js — guard the files that configure ThumbGate itself.
|
|
5
|
+
*
|
|
6
|
+
* Added 2026-07-08 after Andy Martin's review: in the shipped `gate-check`
|
|
7
|
+
* path, editing ThumbGate's own hook wiring / gate config was ALLOWED by
|
|
8
|
+
* default (warn-by-default posture only denies under strict enforcement), so
|
|
9
|
+
* an agent could disable the firewall before continuing and the surfaced
|
|
10
|
+
* verdict was a clean ALLOW.
|
|
11
|
+
*
|
|
12
|
+
* This module is the single source of truth for "is this edit touching the
|
|
13
|
+
* firewall's own kill-switches", used by BOTH the shipped `npx thumbgate
|
|
14
|
+
* gate-check` entrypoint and this repo's dogfood PreToolUse hook.
|
|
15
|
+
*
|
|
16
|
+
* Posture threads three constraints:
|
|
17
|
+
* * Andy's ask: don't let the agent quietly rewrite/disable the gate.
|
|
18
|
+
* * CEO warn-by-default (2026-06-04): never hard-block legitimate work.
|
|
19
|
+
* * Self-lockout lesson (2026-07-07): a PreToolUse hook must NEVER deny the
|
|
20
|
+
* tools needed to repair its own config, or the agent is bricked.
|
|
21
|
+
*
|
|
22
|
+
* Resolution:
|
|
23
|
+
* default -> WARN (surfaced + logged, never silent)
|
|
24
|
+
* THUMBGATE_STRICT_ENFORCEMENT=1 -> BLOCK
|
|
25
|
+
* THUMBGATE_ALLOW_SELF_EDIT=1 -> full opt-out (preserves the repair path)
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
const SELF_GOVERNANCE_PATH_PATTERNS = [
|
|
29
|
+
/(?:^|\/)\.claude\/settings(?:\.local)?\.json$/i, // hook wiring / can disable ThumbGate
|
|
30
|
+
/(?:^|\/)\.codex\/config\.toml$/i,
|
|
31
|
+
/(?:^|\/)scripts\/hook-[^/]+\.(?:js|sh)$/i, // the hook scripts themselves
|
|
32
|
+
/(?:^|\/)config\/gates\//i, // gate definitions
|
|
33
|
+
/(?:^|\/)config\/enforcement\.json$/i, // enforcement policy
|
|
34
|
+
/(?:^|\/)config\/mcp-allowlists\.json$/i, // MCP policy surface
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const EDIT_LIKE_TOOLS = new Set(['Edit', 'Write', 'MultiEdit']);
|
|
38
|
+
|
|
39
|
+
function isTrueEnv(value) {
|
|
40
|
+
if (value === undefined || value === null) return false;
|
|
41
|
+
const v = String(value).trim().toLowerCase();
|
|
42
|
+
return v === '1' || v === 'true' || v === 'yes' || v === 'on';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Returns the matched governance file path, or null. Accepts both camelCase
|
|
47
|
+
* (`tool_name`/`tool_input`) and the already-normalized shapes callers use.
|
|
48
|
+
*/
|
|
49
|
+
function selfProtectionTarget(toolName, toolInput) {
|
|
50
|
+
if (!EDIT_LIKE_TOOLS.has(toolName)) return null;
|
|
51
|
+
const filePath = String((toolInput && (toolInput.file_path || toolInput.filePath)) || '');
|
|
52
|
+
if (!filePath) return null;
|
|
53
|
+
return SELF_GOVERNANCE_PATH_PATTERNS.some((re) => re.test(filePath)) ? filePath : null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Evaluate the self-protection posture for a tool call.
|
|
58
|
+
* @returns {{action:'block'|'warn', target:string, message:string}|null}
|
|
59
|
+
*/
|
|
60
|
+
function evaluateSelfProtection(toolName, toolInput, env = process.env) {
|
|
61
|
+
const target = selfProtectionTarget(toolName, toolInput);
|
|
62
|
+
if (!target) return null;
|
|
63
|
+
// Escape hatch: an operator repairing the gate opts out explicitly. Honors the
|
|
64
|
+
// self-lockout lesson — the repair path is never denied.
|
|
65
|
+
if (isTrueEnv(env.THUMBGATE_ALLOW_SELF_EDIT)) return null;
|
|
66
|
+
if (isTrueEnv(env.THUMBGATE_STRICT_ENFORCEMENT)) {
|
|
67
|
+
return {
|
|
68
|
+
action: 'block',
|
|
69
|
+
target,
|
|
70
|
+
message:
|
|
71
|
+
`ThumbGate self-protection: blocked ${toolName} to its own governance file "${target}". `
|
|
72
|
+
+ `Editing the firewall's own hook wiring / gate config while strict enforcement is on is denied. `
|
|
73
|
+
+ `Set THUMBGATE_ALLOW_SELF_EDIT=1 to make an intentional repair.`,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
action: 'warn',
|
|
78
|
+
target,
|
|
79
|
+
message:
|
|
80
|
+
`⚠️ ThumbGate self-protection: this ${toolName} targets a governance file that configures the firewall itself `
|
|
81
|
+
+ `("${target}"). It is being ALLOWED and LOGGED (warn-by-default). An agent editing this could weaken or disable `
|
|
82
|
+
+ `its own guardrails — confirm this is intentional. Set THUMBGATE_STRICT_ENFORCEMENT=1 to hard-block such edits.`,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
module.exports = {
|
|
87
|
+
SELF_GOVERNANCE_PATH_PATTERNS,
|
|
88
|
+
selfProtectionTarget,
|
|
89
|
+
evaluateSelfProtection,
|
|
90
|
+
};
|