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.
- package/.claude-plugin/plugin.json +1 -1
- package/.well-known/llms.txt +2 -1
- package/.well-known/mcp/server-card.json +1 -1
- package/README.md +2 -4
- package/adapters/claude/.mcp.json +2 -2
- 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/bin/cli.js +78 -259
- package/config/gate-templates.json +0 -228
- package/config/gates/claim-verification.json +0 -18
- package/package.json +35 -25
- package/public/assets/brand/thumbgate-logo-transparent.svg +22 -0
- package/public/assets/brand/thumbgate-mark-inline-v3.svg +19 -0
- package/public/assets/brand/thumbgate-mark.svg +11 -5
- package/public/blog.html +0 -30
- package/public/brand/thumbgate-mark.svg +9 -5
- package/public/chatgpt-app.html +2 -2
- package/public/compare.html +2 -1
- package/public/dashboard.html +1 -1
- package/public/federal.html +1 -1
- package/public/index.html +95 -216
- package/public/learn.html +59 -35
- package/public/lessons.html +1 -1
- package/public/numbers.html +2 -2
- package/public/pro.html +7 -7
- package/scripts/agent-readiness.js +142 -0
- package/scripts/aws-blocks-guardrails.js +228 -0
- package/scripts/cli-schema.js +22 -10
- package/scripts/dashboard-chat.js +2 -1
- package/scripts/document-intake.js +1 -49
- package/scripts/durability/step.js +3 -3
- package/scripts/gate-stats.js +5 -11
- package/scripts/gates-engine.js +0 -49
- package/scripts/gemini-embedding-policy.js +2 -1
- package/scripts/hook-stop-anti-claim.js +116 -184
- package/scripts/hosted-config.js +0 -12
- package/scripts/lesson-search.js +1 -15
- package/scripts/llm-client.js +187 -5
- package/scripts/plausible-domain-config.js +3 -1
- package/scripts/seo-gsd.js +240 -1
- package/scripts/tool-registry.js +2 -2
- package/scripts/vector-store.js +44 -0
- package/scripts/workspace-evolver.js +62 -2
- package/src/api/server.js +340 -131
- package/public/assets/brand/thumbgate-mark-inline.svg +0 -15
- package/public/compare/adopt-ai.html +0 -219
- package/public/compare/agentix-labs.html +0 -197
- package/public/compare/ai-experience-orchestration.html +0 -216
- package/public/compare/anthropic-claude-for-legal.html +0 -260
- package/public/compare/anthropic-containment.html +0 -280
- package/public/compare/arcade.html +0 -175
- package/public/compare/arcjet.html +0 -239
- package/public/compare/bumblebee.html +0 -307
- package/public/compare/claude-code-hooks.html +0 -294
- package/public/compare/databricks-unity-ai-gateway.html +0 -215
- package/public/compare/fallow.html +0 -351
- package/public/compare/heidi.html +0 -233
- package/public/compare/mem0.html +0 -342
- package/public/compare/oak-and-sparrow-gatekeeper.html +0 -289
- package/public/compare/rein.html +0 -236
- package/public/compare/sigmashake.html +0 -256
- package/public/compare/speclock.html +0 -342
- package/public/guides/agent-harness-optimization.html +0 -342
- package/public/guides/agentic-web-governance.html +0 -406
- package/public/guides/ai-agent-governance-sprint.html +0 -415
- package/public/guides/ai-agent-pre-action-approval-gates.html +0 -401
- package/public/guides/ai-agent-workflow-migration-checklist.html +0 -392
- package/public/guides/ai-deployment-readiness.html +0 -415
- package/public/guides/ai-mode-ads-agent-governance.html +0 -401
- package/public/guides/ai-search-topical-presence.html +0 -342
- package/public/guides/autoresearch-agent-safety.html +0 -342
- package/public/guides/background-agent-governance.html +0 -358
- package/public/guides/best-tools-stop-ai-agents-breaking-production.html +0 -363
- package/public/guides/browser-automation-safety.html +0 -342
- package/public/guides/chatgpt-ads-trust.html +0 -353
- package/public/guides/claude-code-feedback.html +0 -339
- package/public/guides/claude-code-prevent-repeated-mistakes.html +0 -161
- package/public/guides/claude-code-skills-guardrails.html +0 -343
- package/public/guides/claude-desktop.html +0 -356
- package/public/guides/code-knowledge-graph-guardrails.html +0 -365
- package/public/guides/codex-cli-guardrails.html +0 -339
- package/public/guides/cursor-agent-guardrails.html +0 -339
- package/public/guides/cursor-prevent-repeated-mistakes.html +0 -161
- package/public/guides/database-agent-safety.html +0 -406
- package/public/guides/deepseek-v4-runtime-guardrails.html +0 -346
- package/public/guides/developer-machine-supply-chain-guardrails.html +0 -358
- package/public/guides/gcp-mcp-guardrails.html +0 -147
- package/public/guides/gemini-cli-feedback-memory.html +0 -339
- package/public/guides/gpt-5-5-model-evaluation.html +0 -358
- package/public/guides/internal-ai-engineering-stack-guardrails.html +0 -348
- package/public/guides/long-running-agent-context-management.html +0 -346
- package/public/guides/mcp-tool-governance.html +0 -401
- package/public/guides/multica-thumbgate-setup.html +0 -134
- package/public/guides/native-messaging-host-security.html +0 -342
- package/public/guides/policy-engine-pre-action-gates.html +0 -346
- package/public/guides/pre-action-checks.html +0 -342
- package/public/guides/pretooluse-hooks-vs-advisory-prompt-rules.html +0 -342
- package/public/guides/prompt-tricks-to-workflow-rules.html +0 -365
- package/public/guides/proxy-pointer-rag-guardrails.html +0 -352
- package/public/guides/rag-precision-tuning-guardrails.html +0 -352
- package/public/guides/reasoning-compression-guardrails.html +0 -346
- package/public/guides/relational-knowledge-ai-recommendations.html +0 -342
- package/public/guides/roo-code-alternative-cline.html +0 -339
- package/public/guides/semantic-programmatic-seo-guardrails.html +0 -352
- package/public/guides/seo-agent-skills-guardrails.html +0 -344
- package/public/guides/stop-repeated-ai-agent-mistakes.html +0 -342
- package/public/learn/ac-dc-runtime-enforcement.html +0 -277
- package/public/learn/agent-harness-pattern.html +0 -181
- package/public/learn/agent-identity-connector-governance.html +0 -146
- package/public/learn/agent-swarms-shared-gates.html +0 -173
- package/public/learn/agentic-enterprise-context-brain.html +0 -117
- package/public/learn/agentic-os-team-governance.html +0 -146
- package/public/learn/ai-agent-governance.html +0 -158
- package/public/learn/ai-agent-persistent-memory.html +0 -211
- package/public/learn/anthropomorphic-claim-gates.html +0 -180
- package/public/learn/background-agent-control-layer.html +0 -184
- package/public/learn/claude-code-goal-with-rubrics.html +0 -205
- package/public/learn/codex-role-plugins-need-governance.html +0 -125
- package/public/learn/cost-aware-agent-gate-routing.html +0 -173
- package/public/learn/databricks-unity-ai-gateway-runtime-governance.html +0 -157
- package/public/learn/deterministic-agent-workflows.html +0 -185
- package/public/learn/feedback-loop-vs-decision-layer.html +0 -283
- package/public/learn/from-prototype-to-production.html +0 -223
- package/public/learn/learn.css +0 -51
- package/public/learn/mcp-pre-action-checks-explained.html +0 -172
- package/public/learn/pretix-stripe-connect-marketplaces.html +0 -161
- package/public/learn/regulated-agent-execution-boundary.html +0 -196
- package/public/learn/spec-driven-development.html +0 -168
- package/public/learn/stop-ai-agent-force-push.html +0 -134
- package/public/learn/vibe-coding-safety-net.html +0 -142
- package/scripts/reddit-browser-notification-watch.js +0 -230
|
@@ -16,11 +16,10 @@
|
|
|
16
16
|
* ThumbGate dogfood — we are the prevention-rule generator and a perfect
|
|
17
17
|
* customer for our own gate.
|
|
18
18
|
*
|
|
19
|
-
* Wires through .claude/settings.json Stop hooks list. Always exits 0
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* written the assistant response.
|
|
19
|
+
* Wires through .claude/settings.json Stop hooks list. Always exits 0
|
|
20
|
+
* (informational): the goal is to surface a system reminder in the next
|
|
21
|
+
* turn so the agent corrects mid-conversation rather than to hard-block
|
|
22
|
+
* the turn that already happened.
|
|
24
23
|
*
|
|
25
24
|
* Stdin: Claude Code passes the hook payload as JSON on stdin. We read
|
|
26
25
|
* `transcript_path` to locate the JSONL session log and scan the last
|
|
@@ -44,8 +43,20 @@ const CLAIM_PATTERNS = [
|
|
|
44
43
|
/\beverything\s+(?:is\s+)?(?:done|working|ready)\b/i,
|
|
45
44
|
/\b(?:github|repo|repository)\s+(?:about|metadata|description|topics?)\b.*\b(?:updated|verified|fixed|match(?:es|ed)?)\b/i,
|
|
46
45
|
/\b(?:about|metadata|description|topics?)\b.*\b(?:updated|verified|fixed|match(?:es|ed)?)\b.*\b(?:github|repo|repository)\b/i,
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
// Added 2026-06-11 after a cross-project failure analysis: these completion
|
|
47
|
+
// claims ("all green / stable / verified / race over / tests pass") were
|
|
48
|
+
// asserted without proof and slipped past the original set. The proof-gate
|
|
49
|
+
// below suppresses them whenever the SAME turn ran a verification tool, so a
|
|
50
|
+
// "verified" claim backed by a test/curl/Read stays silent.
|
|
51
|
+
/\b(?:all\s+)?(?:tests?|checks?|ci)\s+(?:are\s+)?(?:now\s+)?passing\b/i,
|
|
52
|
+
/\ball\s+(?:tests?|checks?)\s+pass(?:ed)?\b/i,
|
|
53
|
+
/\bverified\b/i,
|
|
54
|
+
/\bconfirmed\b/i,
|
|
55
|
+
/\b(?:is|are|it'?s|now)\s+stable\b/i,
|
|
56
|
+
/\ball\s+clear\b/i,
|
|
57
|
+
/\bgood\s+to\s+go\b/i,
|
|
58
|
+
/\brace\s+(?:is\s+)?over\b/i,
|
|
59
|
+
/\bno\s+longer\s+racing\b/i,
|
|
49
60
|
];
|
|
50
61
|
|
|
51
62
|
// Proof-of-verification patterns. If the SAME turn included one of these
|
|
@@ -69,63 +80,97 @@ const PROOF_PATTERNS = [
|
|
|
69
80
|
/\bshopify\b/,
|
|
70
81
|
/\bsquare\b/,
|
|
71
82
|
/\bquickbooks\b/,
|
|
72
|
-
/\bgh\s+api\b/,
|
|
73
|
-
/\bls\b/,
|
|
74
|
-
/\bcat\b/,
|
|
75
83
|
/Read\s*\(/, // Claude Code Read tool call
|
|
76
84
|
/Bash\s*\(/, // Claude Code Bash tool call
|
|
77
85
|
];
|
|
78
86
|
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
+
const COMMERCIAL_CLAIM_SUBJECTS = [
|
|
88
|
+
'money',
|
|
89
|
+
'payment',
|
|
90
|
+
'charge',
|
|
91
|
+
'checkout',
|
|
92
|
+
'revenue',
|
|
93
|
+
'price',
|
|
94
|
+
'pricing',
|
|
95
|
+
'invoice',
|
|
96
|
+
'billing',
|
|
97
|
+
'tax',
|
|
98
|
+
'sales tax',
|
|
99
|
+
'inventory',
|
|
100
|
+
'stock',
|
|
101
|
+
'permission',
|
|
102
|
+
'access',
|
|
103
|
+
'customer facing',
|
|
87
104
|
];
|
|
88
105
|
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
106
|
+
const COMMERCIAL_CLAIM_STATES = [
|
|
107
|
+
'correct',
|
|
108
|
+
'accurate',
|
|
109
|
+
'verified',
|
|
110
|
+
'valid',
|
|
111
|
+
'matches',
|
|
112
|
+
'working',
|
|
113
|
+
'fixed',
|
|
114
|
+
'resolved',
|
|
115
|
+
'calculated',
|
|
116
|
+
'configured',
|
|
93
117
|
];
|
|
94
118
|
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
119
|
+
const GREEN_CLAIM_PHRASES = [
|
|
120
|
+
'all green',
|
|
121
|
+
'all tests green',
|
|
122
|
+
'all checks green',
|
|
123
|
+
'all the tests green',
|
|
124
|
+
'all the checks green',
|
|
125
|
+
'all tests are green',
|
|
126
|
+
'all checks are green',
|
|
101
127
|
];
|
|
102
128
|
|
|
103
|
-
function
|
|
104
|
-
|
|
129
|
+
function normalizeClaimText(text) {
|
|
130
|
+
return String(text || '')
|
|
131
|
+
.toLowerCase()
|
|
132
|
+
.replace(/[-_]+/g, ' ')
|
|
133
|
+
.replace(/[^a-z0-9]+/g, ' ')
|
|
134
|
+
.replace(/\s+/g, ' ')
|
|
135
|
+
.trim();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function containsPhrase(normalizedText, phrase) {
|
|
139
|
+
return ` ${normalizedText} `.includes(` ${phrase} `);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function findTokenListClaim(text, subjects, states, label) {
|
|
143
|
+
const normalized = normalizeClaimText(text);
|
|
144
|
+
if (!normalized) return null;
|
|
145
|
+
const subject = subjects.find((candidate) => containsPhrase(normalized, candidate));
|
|
146
|
+
const state = states.find((candidate) => containsPhrase(normalized, candidate));
|
|
147
|
+
return subject && state ? `${label}: ${subject} ${state}` : null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function findGreenClaim(text) {
|
|
151
|
+
const normalized = normalizeClaimText(text);
|
|
152
|
+
return GREEN_CLAIM_PHRASES.find((phrase) => containsPhrase(normalized, phrase)) || null;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function readLastAssistantTurn(transcriptPath) {
|
|
156
|
+
if (!transcriptPath || !fs.existsSync(transcriptPath)) return null;
|
|
105
157
|
let content;
|
|
106
158
|
try {
|
|
107
159
|
content = fs.readFileSync(transcriptPath, 'utf8');
|
|
108
160
|
} catch {
|
|
109
|
-
return
|
|
161
|
+
return null;
|
|
110
162
|
}
|
|
111
|
-
|
|
112
|
-
.trim()
|
|
113
|
-
.split('\n')
|
|
114
|
-
.map((raw) => {
|
|
115
|
-
try {
|
|
116
|
-
return JSON.parse(raw);
|
|
117
|
-
} catch {
|
|
118
|
-
return null;
|
|
119
|
-
}
|
|
120
|
-
})
|
|
121
|
-
.filter(Boolean);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function readLastAssistantTurn(transcriptPath) {
|
|
125
|
-
const entries = readTranscriptEntries(transcriptPath);
|
|
163
|
+
const lines = content.trim().split('\n');
|
|
126
164
|
// Walk backwards to find the last assistant message
|
|
127
|
-
for (let i =
|
|
128
|
-
const
|
|
165
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
166
|
+
const raw = lines[i].trim();
|
|
167
|
+
if (!raw) continue;
|
|
168
|
+
let entry;
|
|
169
|
+
try {
|
|
170
|
+
entry = JSON.parse(raw);
|
|
171
|
+
} catch {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
129
174
|
if (entry.type === 'assistant' && entry.message) {
|
|
130
175
|
return entry.message;
|
|
131
176
|
}
|
|
@@ -133,22 +178,6 @@ function readLastAssistantTurn(transcriptPath) {
|
|
|
133
178
|
return null;
|
|
134
179
|
}
|
|
135
180
|
|
|
136
|
-
function readPreviousUserText(transcriptPath) {
|
|
137
|
-
const entries = readTranscriptEntries(transcriptPath);
|
|
138
|
-
let seenAssistant = false;
|
|
139
|
-
for (let i = entries.length - 1; i >= 0; i--) {
|
|
140
|
-
const entry = entries[i];
|
|
141
|
-
if (!seenAssistant && entry.type === 'assistant') {
|
|
142
|
-
seenAssistant = true;
|
|
143
|
-
continue;
|
|
144
|
-
}
|
|
145
|
-
if (seenAssistant && entry.type === 'user' && entry.message) {
|
|
146
|
-
return extractText(entry.message);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
return '';
|
|
150
|
-
}
|
|
151
|
-
|
|
152
181
|
function extractText(message) {
|
|
153
182
|
if (!message || !Array.isArray(message.content)) return '';
|
|
154
183
|
return message.content
|
|
@@ -175,24 +204,16 @@ function extractToolUseSummary(message) {
|
|
|
175
204
|
.join('\n');
|
|
176
205
|
}
|
|
177
206
|
|
|
178
|
-
function wordCount(text) {
|
|
179
|
-
return String(text || '').trim().split(/\s+/).filter(Boolean).length;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function hasPositiveFeedback(text) {
|
|
183
|
-
return POSITIVE_FEEDBACK_PATTERNS.some((p) => p.test(text || ''));
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
function isLowValueCloseout(text, toolUseSummary = '') {
|
|
187
|
-
const normalized = String(text || '').trim();
|
|
188
|
-
if (!normalized) return false;
|
|
189
|
-
if (toolUseSummary.trim()) return false;
|
|
190
|
-
if (wordCount(normalized) > 45) return false;
|
|
191
|
-
if (SUBSTANTIVE_CLOSEOUT_PATTERNS.some((p) => p.test(normalized))) return false;
|
|
192
|
-
return LOW_VALUE_CLOSEOUT_PATTERNS.some((p) => p.test(normalized));
|
|
193
|
-
}
|
|
194
|
-
|
|
195
207
|
function findClaim(text) {
|
|
208
|
+
const commercialClaim = findTokenListClaim(
|
|
209
|
+
text,
|
|
210
|
+
COMMERCIAL_CLAIM_SUBJECTS,
|
|
211
|
+
COMMERCIAL_CLAIM_STATES,
|
|
212
|
+
'commercial truth',
|
|
213
|
+
);
|
|
214
|
+
if (commercialClaim) return commercialClaim;
|
|
215
|
+
const greenClaim = findGreenClaim(text);
|
|
216
|
+
if (greenClaim) return greenClaim;
|
|
196
217
|
for (const p of CLAIM_PATTERNS) {
|
|
197
218
|
const m = text.match(p);
|
|
198
219
|
if (m) return m[0];
|
|
@@ -204,79 +225,6 @@ function hasProof(combined) {
|
|
|
204
225
|
return PROOF_PATTERNS.some((p) => p.test(combined));
|
|
205
226
|
}
|
|
206
227
|
|
|
207
|
-
function extractPayloadText(payload) {
|
|
208
|
-
if (!payload || typeof payload !== 'object') return '';
|
|
209
|
-
const candidates = [
|
|
210
|
-
payload.response,
|
|
211
|
-
payload.assistant_response,
|
|
212
|
-
payload.assistantResponse,
|
|
213
|
-
payload.final_response,
|
|
214
|
-
payload.finalResponse,
|
|
215
|
-
payload.text,
|
|
216
|
-
payload.output,
|
|
217
|
-
payload.message,
|
|
218
|
-
];
|
|
219
|
-
for (const candidate of candidates) {
|
|
220
|
-
if (typeof candidate === 'string' && candidate.trim()) return candidate;
|
|
221
|
-
if (candidate && typeof candidate === 'object') {
|
|
222
|
-
const extracted = extractText(candidate);
|
|
223
|
-
if (extracted.trim()) return extracted;
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
return '';
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function extractPayloadPreviousUserText(payload) {
|
|
230
|
-
if (!payload || typeof payload !== 'object') return '';
|
|
231
|
-
const candidates = [
|
|
232
|
-
payload.previous_user_text,
|
|
233
|
-
payload.previousUserText,
|
|
234
|
-
payload.user_prompt,
|
|
235
|
-
payload.userPrompt,
|
|
236
|
-
payload.prompt,
|
|
237
|
-
];
|
|
238
|
-
for (const candidate of candidates) {
|
|
239
|
-
if (typeof candidate === 'string' && candidate.trim()) return candidate;
|
|
240
|
-
if (candidate && typeof candidate === 'object') {
|
|
241
|
-
const extracted = extractText(candidate);
|
|
242
|
-
if (extracted.trim()) return extracted;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
return '';
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
function buildResponseQualityReason() {
|
|
249
|
-
return [
|
|
250
|
-
'ThumbGate response-quality gate: final response answered positive feedback',
|
|
251
|
-
'with a low-value social closeout instead of silence-level brevity or an evidence checkpoint.',
|
|
252
|
-
'Positive feedback after operational work should trigger either no extra noise,',
|
|
253
|
-
'a compact evidence checkpoint, or a concrete next-state update.',
|
|
254
|
-
'Do not reply with generic "Good / Great / Use X/Y" filler.',
|
|
255
|
-
].join(' ');
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
function buildResponseQualityReminder() {
|
|
259
|
-
return [
|
|
260
|
-
'⚠️ ThumbGate response-quality gate: previous turn answered positive feedback',
|
|
261
|
-
' with a low-value social closeout instead of silence-level brevity or an evidence checkpoint.',
|
|
262
|
-
' Positive feedback after operational work should trigger either no extra noise,',
|
|
263
|
-
' a compact evidence checkpoint, or a concrete next-state update.',
|
|
264
|
-
' Do not reply with generic "Good / Great / Use X/Y" filler.',
|
|
265
|
-
].join('\n');
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
function writeBlock(reason) {
|
|
269
|
-
process.stdout.write(JSON.stringify({
|
|
270
|
-
decision: 'block',
|
|
271
|
-
reason,
|
|
272
|
-
hookSpecificOutput: {
|
|
273
|
-
hookEventName: 'Stop',
|
|
274
|
-
permissionDecision: 'deny',
|
|
275
|
-
permissionDecisionReason: reason,
|
|
276
|
-
},
|
|
277
|
-
}) + '\n');
|
|
278
|
-
}
|
|
279
|
-
|
|
280
228
|
function readStdinSync() {
|
|
281
229
|
try {
|
|
282
230
|
return fs.readFileSync(0, 'utf8');
|
|
@@ -295,42 +243,34 @@ function main() {
|
|
|
295
243
|
}
|
|
296
244
|
|
|
297
245
|
const transcriptPath = payload.transcript_path || process.env.CLAUDE_TRANSCRIPT_PATH;
|
|
298
|
-
const directText = extractPayloadText(payload) || process.env.CLAUDE_RESPONSE || '';
|
|
299
|
-
const directPreviousUserText = extractPayloadPreviousUserText(payload)
|
|
300
|
-
|| process.env.CLAUDE_PREVIOUS_USER_TEXT
|
|
301
|
-
|| process.env.CLAUDE_PREVIOUS_USER
|
|
302
|
-
|| '';
|
|
303
|
-
|
|
304
|
-
if (directText && hasPositiveFeedback(directPreviousUserText) && isLowValueCloseout(directText, '')) {
|
|
305
|
-
writeBlock(buildResponseQualityReason());
|
|
306
|
-
return;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
246
|
const message = readLastAssistantTurn(transcriptPath);
|
|
310
247
|
if (!message) return; // no transcript visible; nothing to check
|
|
311
248
|
|
|
312
249
|
const text = extractText(message);
|
|
313
250
|
const toolUseSummary = extractToolUseSummary(message);
|
|
314
|
-
const previousUserText = readPreviousUserText(transcriptPath);
|
|
315
|
-
|
|
316
|
-
if (hasPositiveFeedback(previousUserText) && isLowValueCloseout(text, toolUseSummary)) {
|
|
317
|
-
process.stdout.write(buildResponseQualityReminder() + '\n');
|
|
318
|
-
return;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
251
|
const claim = findClaim(text);
|
|
322
252
|
if (!claim) return; // no completion claim made; silent
|
|
323
253
|
|
|
324
254
|
const proofText = `${text}\n${toolUseSummary}`;
|
|
325
255
|
if (hasProof(proofText)) return; // claim backed by proof in same turn
|
|
326
256
|
|
|
327
|
-
//
|
|
257
|
+
// Strict mode (THUMBGATE_STRICT_ENFORCEMENT=1): hard-block the stop. Emit a
|
|
258
|
+
// Stop-hook block decision so Claude Code does NOT end the turn — the agent
|
|
259
|
+
// must run the verification (or retract) before it can stop. Default mode
|
|
260
|
+
// stays soft (a reminder for the next turn) so we don't break existing wiring.
|
|
261
|
+
if (process.env.THUMBGATE_STRICT_ENFORCEMENT === '1') {
|
|
262
|
+
const reason = `ThumbGate anti-claim gate (strict): you claimed completion ("${claim}") without a proof tool call in the same message. Run the verification (curl / grep / test / Read) and restate with the proof, or retract — do not end the turn on an unverified claim.`;
|
|
263
|
+
process.stdout.write(JSON.stringify({ decision: 'block', reason }) + '\n');
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Default (soft): surface a system reminder for the NEXT turn. Do not hard-block.
|
|
328
268
|
const reminder = [
|
|
329
269
|
'⚠️ ThumbGate anti-claim gate: previous turn claimed completion',
|
|
330
270
|
` ("${claim}") without a proof tool call in the same message.`,
|
|
331
|
-
' Per CLAUDE.md anti-lying: never claim "done / live / deployed / fixed
|
|
332
|
-
' or commercial truth (money / tax / inventory /
|
|
333
|
-
' without curl / grep / test / source-of-truth output in the SAME turn.',
|
|
271
|
+
' Per CLAUDE.md anti-lying: never claim "done / live / deployed / fixed /',
|
|
272
|
+
' verified / all green / stable" or commercial truth (money / tax / inventory /',
|
|
273
|
+
' permissions / customer-facing state) without curl / grep / test / source-of-truth output in the SAME turn.',
|
|
334
274
|
' If the work really is verified, re-state the claim with the proof.',
|
|
335
275
|
' If not, retract and run the verification before re-asserting.',
|
|
336
276
|
].join('\n');
|
|
@@ -354,16 +294,8 @@ if (path.resolve(process.argv[1] || '') === path.resolve(__filename)) {
|
|
|
354
294
|
module.exports = {
|
|
355
295
|
CLAIM_PATTERNS,
|
|
356
296
|
PROOF_PATTERNS,
|
|
357
|
-
POSITIVE_FEEDBACK_PATTERNS,
|
|
358
|
-
LOW_VALUE_CLOSEOUT_PATTERNS,
|
|
359
297
|
findClaim,
|
|
360
298
|
hasProof,
|
|
361
|
-
hasPositiveFeedback,
|
|
362
|
-
isLowValueCloseout,
|
|
363
|
-
extractPayloadText,
|
|
364
|
-
extractPayloadPreviousUserText,
|
|
365
|
-
buildResponseQualityReason,
|
|
366
299
|
extractText,
|
|
367
300
|
extractToolUseSummary,
|
|
368
|
-
readPreviousUserText,
|
|
369
301
|
};
|
package/scripts/hosted-config.js
CHANGED
|
@@ -13,7 +13,6 @@ const DEFAULT_PRO_PRICE_DOLLARS = PRO_MONTHLY_PRICE_DOLLARS;
|
|
|
13
13
|
const DEFAULT_PRO_PRICE_LABEL = PRO_PRICE_LABEL;
|
|
14
14
|
const DEFAULT_SPRINT_DIAGNOSTIC_PRICE_DOLLARS = 499;
|
|
15
15
|
const DEFAULT_WORKFLOW_SPRINT_PRICE_DOLLARS = 1500;
|
|
16
|
-
const DEFAULT_SNAPSHOT_PRICE_DOLLARS = 97;
|
|
17
16
|
const GA_MEASUREMENT_ID_PATTERN = /^G-[A-Z0-9]+$/i;
|
|
18
17
|
|
|
19
18
|
function normalizeOrigin(value) {
|
|
@@ -130,10 +129,6 @@ function resolveHostedBillingConfig({ requestOrigin } = {}, env = process.env) {
|
|
|
130
129
|
const googleSiteVerification = normalizeTrackingId(env.THUMBGATE_GOOGLE_SITE_VERIFICATION);
|
|
131
130
|
const sprintDiagnosticCheckoutUrl = normalizeAbsoluteUrl(env.THUMBGATE_SPRINT_DIAGNOSTIC_CHECKOUT_URL);
|
|
132
131
|
const workflowSprintCheckoutUrl = normalizeAbsoluteUrl(env.THUMBGATE_WORKFLOW_SPRINT_CHECKOUT_URL);
|
|
133
|
-
const paypalDiagnosticCheckoutUrl = normalizeAbsoluteUrl(env.THUMBGATE_PAYPAL_DIAGNOSTIC_CHECKOUT_URL);
|
|
134
|
-
const paypalWorkflowSprintCheckoutUrl = normalizeAbsoluteUrl(env.THUMBGATE_PAYPAL_WORKFLOW_SPRINT_CHECKOUT_URL);
|
|
135
|
-
const morSnapshotCheckoutUrl = normalizeAbsoluteUrl(env.THUMBGATE_MOR_SNAPSHOT_CHECKOUT_URL);
|
|
136
|
-
const morProvider = String(env.THUMBGATE_MOR_PROVIDER || '').trim();
|
|
137
132
|
|
|
138
133
|
return {
|
|
139
134
|
appOrigin,
|
|
@@ -147,16 +142,10 @@ function resolveHostedBillingConfig({ requestOrigin } = {}, env = process.env) {
|
|
|
147
142
|
proPriceLabel,
|
|
148
143
|
sprintDiagnosticCheckoutUrl,
|
|
149
144
|
workflowSprintCheckoutUrl,
|
|
150
|
-
paypalDiagnosticCheckoutUrl,
|
|
151
|
-
paypalWorkflowSprintCheckoutUrl,
|
|
152
|
-
morSnapshotCheckoutUrl,
|
|
153
|
-
morProvider,
|
|
154
145
|
sprintDiagnosticPriceDollars: normalizePriceDollars(env.THUMBGATE_SPRINT_DIAGNOSTIC_PRICE_DOLLARS)
|
|
155
146
|
|| DEFAULT_SPRINT_DIAGNOSTIC_PRICE_DOLLARS,
|
|
156
147
|
workflowSprintPriceDollars: normalizePriceDollars(env.THUMBGATE_WORKFLOW_SPRINT_PRICE_DOLLARS)
|
|
157
148
|
|| DEFAULT_WORKFLOW_SPRINT_PRICE_DOLLARS,
|
|
158
|
-
snapshotPriceDollars: normalizePriceDollars(env.THUMBGATE_SNAPSHOT_PRICE_DOLLARS)
|
|
159
|
-
|| DEFAULT_SNAPSHOT_PRICE_DOLLARS,
|
|
160
149
|
gaMeasurementId,
|
|
161
150
|
googleSiteVerification,
|
|
162
151
|
posthogApiKey,
|
|
@@ -170,7 +159,6 @@ module.exports = {
|
|
|
170
159
|
DEFAULT_PRO_PRICE_LABEL,
|
|
171
160
|
DEFAULT_SPRINT_DIAGNOSTIC_PRICE_DOLLARS,
|
|
172
161
|
DEFAULT_WORKFLOW_SPRINT_PRICE_DOLLARS,
|
|
173
|
-
DEFAULT_SNAPSHOT_PRICE_DOLLARS,
|
|
174
162
|
GA_MEASUREMENT_ID_PATTERN,
|
|
175
163
|
normalizeAbsoluteUrl,
|
|
176
164
|
normalizeOrigin,
|
package/scripts/lesson-search.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const path = require('node:path');
|
|
4
|
-
const fs = require('node:fs');
|
|
5
4
|
const { readJSONL, getFeedbackPaths } = require('./feedback-loop');
|
|
6
5
|
const { buildMemoryLifecycleView, scoreHybridMemoryMatch } = require('./agent-memory-lifecycle');
|
|
7
6
|
const { loadOptionalModule } = require('./private-core-boundary');
|
|
@@ -167,17 +166,6 @@ function resolveLessonPaths(options = {}) {
|
|
|
167
166
|
};
|
|
168
167
|
}
|
|
169
168
|
|
|
170
|
-
function readPackagedBuiltinLessons() {
|
|
171
|
-
if (process.env.THUMBGATE_DISABLE_BUILTIN_LESSONS === '1') return [];
|
|
172
|
-
const builtinPath = path.resolve(__dirname, '..', 'config', 'builtin-lessons.json');
|
|
173
|
-
try {
|
|
174
|
-
const parsed = JSON.parse(fs.readFileSync(builtinPath, 'utf8'));
|
|
175
|
-
return Array.isArray(parsed.lessons) ? parsed.lessons : [];
|
|
176
|
-
} catch {
|
|
177
|
-
return [];
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
169
|
function readPreventionRuleMatches(queryText, limit = 3, options = {}) {
|
|
182
170
|
const { PREVENTION_RULES_PATH } = resolveLessonPaths(options);
|
|
183
171
|
if (!PREVENTION_RULES_PATH) return [];
|
|
@@ -493,9 +481,7 @@ function searchLessons(query = '', options = {}) {
|
|
|
493
481
|
const sqliteResults = tryFts5Search(query, options);
|
|
494
482
|
if (sqliteResults) return sqliteResults;
|
|
495
483
|
|
|
496
|
-
const
|
|
497
|
-
const builtinMemories = options.includeBuiltinLessons === false ? [] : readPackagedBuiltinLessons();
|
|
498
|
-
const memories = [...builtinMemories, ...localMemories];
|
|
484
|
+
const memories = readJSONL(MEMORY_LOG_PATH);
|
|
499
485
|
const feedbackEntries = readJSONL(FEEDBACK_LOG_PATH);
|
|
500
486
|
const feedbackById = new Map(feedbackEntries.map((entry) => [entry.id, entry]));
|
|
501
487
|
const parsedLimit = Number(options.limit || 10);
|