thumbgate 1.29.2 → 1.31.0
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/mcp/server-card.json +1 -1
- package/README.md +54 -16
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/forge/forge.yaml +3 -3
- package/adapters/mcp/server-stdio.js +143 -14
- package/adapters/opencode/opencode.json +1 -1
- package/bench/observability-eval-suite.json +2 -2
- package/bin/cli.js +154 -36
- package/config/evals/generation-quality-golden.json +95 -0
- package/config/evals/rag-answer-quality-golden.json +91 -0
- package/config/evals/retrieval-hybrid-ablation.json +66 -0
- package/config/evals/retrieval-ranking-golden.json +522 -0
- package/config/gates/default.json +217 -50
- package/config/mcp-allowlists.json +215 -185
- package/config/model-tiers.json +7 -2
- package/config/post-deploy-marketing-pages.json +26 -1
- package/glama.json +6 -0
- package/package.json +94 -11
- package/public/architecture.html +130 -0
- package/public/assets/diagrams/agent-integration.png +0 -0
- package/public/assets/diagrams/before-after.svg +22 -0
- package/public/assets/diagrams/decision.svg +36 -0
- package/public/assets/diagrams/feedback-pipeline.png +0 -0
- package/public/assets/diagrams/hero-thumbs.svg +68 -0
- package/public/assets/diagrams/loop.svg +40 -0
- package/public/assets/diagrams/plugin-topology.png +0 -0
- package/public/assets/diagrams/pre-action-gate-loop.svg +59 -0
- package/public/assets/diagrams/self-improving-thumbs-loop.svg +105 -0
- package/public/assets/diagrams/stack.svg +18 -0
- package/public/assets/diagrams/thumbgate-architecture.png +0 -0
- package/public/case-studies.html +151 -0
- package/public/compare.html +1 -0
- package/public/dashboard.html +126 -28
- package/public/eval-scorecard.html +195 -0
- package/public/eval-scorecard.json +18 -0
- package/public/evaluations.html +168 -0
- package/public/index.html +143 -13
- package/public/numbers.html +3 -2
- package/public/pricing.html +143 -30
- package/public/whitepaper.html +189 -0
- package/scripts/a-plus-evidence-scorecard.js +303 -0
- package/scripts/activation-quickstart.js +1 -0
- package/scripts/agent-outcome-monitor.js +71 -1
- package/scripts/async-eval-observability.js +36 -11
- package/scripts/audit-trail.js +37 -1
- package/scripts/auto-promote-gates.js +149 -34
- package/scripts/billing.js +3 -1
- package/scripts/claude-feedback-sync.js +3 -2
- package/scripts/cli-feedback.js +13 -7
- package/scripts/colbert-style-maxsim.js +236 -0
- package/scripts/cross-encoder-reranker.js +359 -126
- package/scripts/dashboard-chat.js +350 -17
- package/scripts/document-intake.js +283 -7
- package/scripts/eval-quality-suite.js +204 -0
- package/scripts/feedback-aggregate.js +5 -2
- package/scripts/feedback-loop.js +359 -189
- package/scripts/feedback-paths.js +32 -13
- package/scripts/feedback-quality.js +53 -0
- package/scripts/filesystem-search.js +17 -7
- package/scripts/gates-engine.js +98 -4
- package/scripts/generate-case-study-outreach.js +253 -0
- package/scripts/generate-eval-scorecard.js +276 -0
- package/scripts/growth-campaigns.js +183 -0
- package/scripts/harness-tool-names.js +70 -0
- package/scripts/hook-runtime.js +10 -3
- package/scripts/jsonl-watcher.js +1 -0
- package/scripts/lesson-db.js +16 -5
- package/scripts/lesson-embedding-index.js +67 -20
- package/scripts/lesson-embedding-maintenance.js +177 -0
- package/scripts/lesson-inference.js +23 -4
- package/scripts/lesson-reranker.js +55 -9
- package/scripts/lesson-retrieval.js +375 -32
- package/scripts/lesson-search.js +48 -11
- package/scripts/llm-client.js +304 -15
- package/scripts/mcp-config.js +26 -5
- package/scripts/mcp-oauth.js +37 -2
- package/scripts/model-eval.js +308 -0
- package/scripts/model-tier-router.js +593 -0
- package/scripts/parallel-workflow-orchestrator.js +86 -22
- package/scripts/pragmatic-hybrid-search.js +379 -0
- package/scripts/published-cli.js +11 -1
- package/scripts/rag-document-pipeline.js +461 -0
- package/scripts/rag-structured-output.js +441 -0
- package/scripts/ragas-style-metrics.js +351 -0
- package/scripts/refresh-proof-pack.js +261 -0
- package/scripts/request-envelope.js +178 -0
- package/scripts/rerank-pipeline.js +370 -0
- package/scripts/rerank-quality-eval.js +155 -0
- package/scripts/retrieval-hybrid-ablation.js +120 -0
- package/scripts/retrieval-quality-tier.js +118 -0
- package/scripts/risk-scorer.js +144 -15
- package/scripts/secret-scanner.js +395 -4
- package/scripts/self-distill-agent.js +7 -1
- package/scripts/self-healing-check.js +25 -0
- package/scripts/skill-packs.js +183 -0
- package/scripts/slow-loop.js +72 -0
- package/scripts/statusline-links.js +1 -1
- package/scripts/statusline-local-stats.js +1 -1
- package/scripts/statusline.sh +8 -1
- package/scripts/telemetry-analytics.js +13 -1
- package/scripts/thumbgate-bench.js +13 -0
- package/scripts/thumbgate-search.js +98 -6
- package/scripts/tier-budget-guard.js +186 -0
- package/scripts/tool-kpi-tracker.js +124 -0
- package/scripts/tool-registry.js +95 -1
- package/scripts/vector-store.js +108 -4
- package/scripts/verify-marketing-pages-deployed.js +85 -3
- package/server.json +44 -0
- package/smithery.yaml +17 -0
- package/src/api/server.js +424 -99
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thumbgate",
|
|
3
3
|
"description": "One 👎 becomes a hard rule the agent cannot bypass. Captures thumbs-down feedback, distills it into PreToolUse Pre-Action Checks, enforced across every future Claude Code session.",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.31.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Igor Ganapolsky",
|
|
7
7
|
"email": "ig5973700@gmail.com",
|
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# ThumbGate
|
|
2
2
|
|
|
3
|
+
[](https://mcptoplist.com/server/glama%2FIgorGanapolsky%2FThumbGate)
|
|
4
|
+
|
|
3
5
|
<p align="center">
|
|
4
6
|
<a href="https://thumbgate.ai">
|
|
5
7
|
<img src="public/assets/brand/thumbgate-icon-512.png" alt="ThumbGate" width="128" height="128" />
|
|
@@ -28,6 +30,19 @@ Accepted feedback is stored as local lessons. Repeated concrete failures can bec
|
|
|
28
30
|
npx thumbgate init # auto-detects the supported agent and wires its integration
|
|
29
31
|
```
|
|
30
32
|
|
|
33
|
+
### MCP / Glama / registry install (stdio)
|
|
34
|
+
|
|
35
|
+
Directories and clients that install ThumbGate as an MCP server must start **stdio MCP**, not the HTTP API:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npx -y thumbgate serve
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
- Equivalent: `npx -y thumbgate mcp`
|
|
42
|
+
- Do **not** use `npm start` for MCP — that launches the hosted HTTP API (`src/api/server.js`), not the agent-facing stdio server.
|
|
43
|
+
- Canonical package metadata: `server.json` (`runtimeHint: npx` + `packageArguments: ["serve"]`), Smithery: `smithery.yaml`, maintainers: `glama.json`.
|
|
44
|
+
- Product name is **ThumbGate** only (npm: `thumbgate`). Retired legacy package aliases are not active product surfaces.
|
|
45
|
+
|
|
31
46
|
Works with **Claude Code, Cursor, Codex, Gemini CLI, Amp, Cline, OpenCode** and MCP-compatible agents after their integration is configured. Free tier: 2 feedback captures/day (10 total) and up to 3 active auto-promoted prevention rules. [Pro: $19/mo or $149/yr](https://thumbgate.ai/checkout/pro?utm_source=github&utm_medium=readme) is the individual tier for unlimited rules, history-aware lessons, feedback sessions, a personal dashboard, and DPO export. Enterprise is custom and scoped after intake; hosted team sync and a hosted org dashboard are not in the current general-availability runtime.
|
|
32
47
|
|
|
33
48
|
[](https://github.com/IgorGanapolsky/ThumbGate/actions/workflows/ci.yml)
|
|
@@ -256,6 +271,11 @@ evaluation/observability—is maintained in
|
|
|
256
271
|
architecture exists, what can fail, how it is measured and secured, how it is
|
|
257
272
|
deployed, and what evidence is required before claiming it works.
|
|
258
273
|
|
|
274
|
+
The full framework decision and one end-to-end RAG request—tenant authorization,
|
|
275
|
+
query transformation, parent-child retrieval, hybrid fusion, reranking,
|
|
276
|
+
structured generation, evaluation, and tracing—are documented in
|
|
277
|
+
[`docs/RAG_PRODUCTION_ARCHITECTURE.md`](./docs/RAG_PRODUCTION_ARCHITECTURE.md).
|
|
278
|
+
|
|
259
279
|
### Retrieval & latency: local-first, zero network hops
|
|
260
280
|
|
|
261
281
|
ThumbGate's latency advantage is structural, not a tuned cloud cluster: there is no retrieval service and no model on the enforcement path, so the gate decision never leaves your machine.
|
|
@@ -302,22 +322,40 @@ Each recommendation ships with the benchmark commands to run next: feedback-deri
|
|
|
302
322
|
|
|
303
323
|
## Install for Your Agent
|
|
304
324
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
|
311
|
-
|
|
312
|
-
| **
|
|
313
|
-
| **
|
|
314
|
-
| **
|
|
315
|
-
| **
|
|
316
|
-
| **
|
|
317
|
-
| **
|
|
318
|
-
| **
|
|
319
|
-
|
|
320
|
-
|
|
325
|
+
Enforcement depends on what the harness lets us intercept, so the table says which
|
|
326
|
+
you get. This distinction is real: with a pre-tool hook ThumbGate stops the action
|
|
327
|
+
before it runs; over MCP only, ThumbGate answers `gate_check` and the agent decides
|
|
328
|
+
whether to obey.
|
|
329
|
+
|
|
330
|
+
| Agent | Command | Enforcement |
|
|
331
|
+
|-------|---------|-------------|
|
|
332
|
+
| **Claude Code** | `npx thumbgate init --agent claude-code` | Hard — PreToolUse hook |
|
|
333
|
+
| **Codex** | `npx thumbgate init --agent codex` | Hard — `pre_tool_use` hook |
|
|
334
|
+
| **Gemini CLI** | `npx thumbgate init --agent gemini` | Hard — PreToolUse hook |
|
|
335
|
+
| **ForgeCode** | `npx thumbgate init --agent forge` | Hard — `pre_tool_use` trigger |
|
|
336
|
+
| **Cursor** | `npx thumbgate init --agent cursor` | Advisory — MCP `gate_check` |
|
|
337
|
+
| **Cline** (Roo Code successor) | `npx thumbgate init --agent cline` | Advisory — MCP `gate_check` + `.clinerules` |
|
|
338
|
+
| **OpenCode** | `npx thumbgate init --agent opencode` | Advisory — MCP `gate_check` |
|
|
339
|
+
| **Any MCP agent** | `npx thumbgate serve` | Advisory — MCP `gate_check` |
|
|
340
|
+
| **Amp** | `npx thumbgate init --agent amp` | Feedback capture only |
|
|
341
|
+
| **Claude Desktop** | [Download extension bundle](https://github.com/IgorGanapolsky/ThumbGate/releases/latest/download/thumbgate-claude-desktop.mcpb) | Advisory — MCP |
|
|
342
|
+
| **VS Code / Open VSX** | [plugins/vscode-extension/README.md](plugins/vscode-extension/README.md) | See plugin README |
|
|
343
|
+
| **Antigravity-compatible** | [plugins/antigravity-extension/INSTALL.md](plugins/antigravity-extension/INSTALL.md) | See plugin README |
|
|
344
|
+
| **JetBrains** | [plugins/jetbrains-plugin/README.md](plugins/jetbrains-plugin/README.md) | See plugin README |
|
|
345
|
+
|
|
346
|
+
**Advisory means the agent can ignore it.** Harnesses without a pre-tool hook expose no
|
|
347
|
+
interception point, so ThumbGate cannot stop the call itself — it returns a verdict the
|
|
348
|
+
agent is instructed to honor. Treat advisory coverage as a strong default, not a
|
|
349
|
+
guarantee, and prefer a hard-enforcement harness for anything irreversible.
|
|
350
|
+
|
|
351
|
+
The gate is **model-agnostic**: verdicts come from deterministic policy evaluation over
|
|
352
|
+
the proposed tool call (`scripts/gates-engine.js`), never from an LLM. Swapping the model
|
|
353
|
+
behind any harness does not change what is allowed.
|
|
354
|
+
|
|
355
|
+
> **Enforcement posture:** ThumbGate ships **warn-by-default** — a matched gate is logged
|
|
356
|
+
> and surfaced, not blocked. Set `THUMBGATE_STRICT_ENFORCEMENT=1` to hard-block. The
|
|
357
|
+
> `gate_check` tool reports `warn` (never `allow`) when a gate matched but posture
|
|
358
|
+
> downgraded it, so an agent is never told a flagged action is fine.
|
|
321
359
|
|
|
322
360
|
### Install scope: machine-wide vs per-project
|
|
323
361
|
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
"mcpServers": {
|
|
3
3
|
"thumbgate": {
|
|
4
4
|
"command": "npx",
|
|
5
|
-
"args": ["--yes", "--package", "thumbgate@1.
|
|
5
|
+
"args": ["--yes", "--package", "thumbgate@1.31.0", "thumbgate", "serve"]
|
|
6
6
|
}
|
|
7
7
|
},
|
|
8
8
|
"hooks": {
|
|
9
9
|
"preToolUse": {
|
|
10
10
|
"command": "npx",
|
|
11
|
-
"args": ["--yes", "--package", "thumbgate@1.
|
|
11
|
+
"args": ["--yes", "--package", "thumbgate@1.31.0", "thumbgate", "gate-check"]
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
}
|
|
@@ -9,12 +9,12 @@ version: "1"
|
|
|
9
9
|
skills:
|
|
10
10
|
thumbgate-gate-check:
|
|
11
11
|
description: "ThumbGate PreToolUse gate — blocks known-bad tool calls"
|
|
12
|
-
command: "npx --yes --package thumbgate@1.
|
|
12
|
+
command: "npx --yes --package thumbgate@1.31.0 thumbgate gate-check"
|
|
13
13
|
trigger: pre_tool_use
|
|
14
14
|
|
|
15
15
|
thumbgate-feedback:
|
|
16
16
|
description: "ThumbGate feedback capture — logs user prompt context"
|
|
17
|
-
command: "npx --yes --package thumbgate@1.
|
|
17
|
+
command: "npx --yes --package thumbgate@1.31.0 thumbgate hook-auto-capture"
|
|
18
18
|
trigger: user_prompt
|
|
19
19
|
|
|
20
20
|
mcp:
|
|
@@ -23,6 +23,6 @@ mcp:
|
|
|
23
23
|
args:
|
|
24
24
|
- "--yes"
|
|
25
25
|
- "--package"
|
|
26
|
-
- "thumbgate@1.
|
|
26
|
+
- "thumbgate@1.31.0"
|
|
27
27
|
- "thumbgate"
|
|
28
28
|
- "serve"
|
|
@@ -96,6 +96,7 @@ const {
|
|
|
96
96
|
requestEscalation,
|
|
97
97
|
} = require('../../scripts/human-escalation');
|
|
98
98
|
const { recordReasoningTrace } = require('../../scripts/agent-reasoning-traces');
|
|
99
|
+
const { recordToolCall } = require('../../scripts/tool-kpi-tracker');
|
|
99
100
|
const {
|
|
100
101
|
evaluateOperationalIntegrity,
|
|
101
102
|
} = require('../../scripts/operational-integrity');
|
|
@@ -132,9 +133,7 @@ const {
|
|
|
132
133
|
throw error;
|
|
133
134
|
},
|
|
134
135
|
}));
|
|
135
|
-
const {
|
|
136
|
-
searchThumbgate,
|
|
137
|
-
} = require('../../scripts/thumbgate-search');
|
|
136
|
+
const { searchThumbgateAsync } = require('../../scripts/thumbgate-search');
|
|
138
137
|
const {
|
|
139
138
|
buildMultimodalRetrievalPlan,
|
|
140
139
|
} = require('../../scripts/multimodal-retrieval-plan');
|
|
@@ -316,7 +315,7 @@ const {
|
|
|
316
315
|
finalizeSession: finalizeFeedbackSession,
|
|
317
316
|
} = require('../../scripts/feedback-session');
|
|
318
317
|
|
|
319
|
-
const SERVER_INFO = { name: 'thumbgate-mcp', version: '1.
|
|
318
|
+
const SERVER_INFO = { name: 'thumbgate-mcp', version: '1.31.0' };
|
|
320
319
|
const COMMERCE_CATEGORIES = [
|
|
321
320
|
'product_recommendation',
|
|
322
321
|
'brand_compliance',
|
|
@@ -379,6 +378,9 @@ function toTextResult(payload) {
|
|
|
379
378
|
const text = typeof payload === 'string' ? payload : JSON.stringify(payload, null, 2);
|
|
380
379
|
return {
|
|
381
380
|
content: [{ type: 'text', text }],
|
|
381
|
+
...(payload !== null && typeof payload === 'object'
|
|
382
|
+
? { structuredContent: payload }
|
|
383
|
+
: {}),
|
|
382
384
|
};
|
|
383
385
|
}
|
|
384
386
|
|
|
@@ -416,7 +418,7 @@ function toCaptureFeedbackTextResult(result) {
|
|
|
416
418
|
if (reminder) {
|
|
417
419
|
blocks.push({ type: 'text', text: reminder });
|
|
418
420
|
}
|
|
419
|
-
return { content: blocks };
|
|
421
|
+
return { content: blocks, structuredContent: result };
|
|
420
422
|
}
|
|
421
423
|
|
|
422
424
|
function formatContextPack(pack) {
|
|
@@ -758,10 +760,27 @@ function buildEstimateUncertaintyResponse(args = {}) {
|
|
|
758
760
|
}
|
|
759
761
|
|
|
760
762
|
async function callTool(name, args = {}) {
|
|
763
|
+
const attemptStartMs = Date.now();
|
|
761
764
|
const activeProfile = getActiveMcpProfile();
|
|
762
|
-
|
|
765
|
+
try {
|
|
766
|
+
assertToolAllowed(name, activeProfile);
|
|
767
|
+
} catch (error) {
|
|
768
|
+
recordMcpToolTrace(name, args, {
|
|
769
|
+
success: false,
|
|
770
|
+
category: 'profile_denied',
|
|
771
|
+
evidence: [error.message],
|
|
772
|
+
latencyMs: Date.now() - attemptStartMs,
|
|
773
|
+
});
|
|
774
|
+
throw error;
|
|
775
|
+
}
|
|
763
776
|
const capability = getToolCapability(name);
|
|
764
777
|
if (!capability.available) {
|
|
778
|
+
recordMcpToolTrace(name, args, {
|
|
779
|
+
success: false,
|
|
780
|
+
category: 'capability',
|
|
781
|
+
evidence: capability.missingModules,
|
|
782
|
+
latencyMs: Date.now() - attemptStartMs,
|
|
783
|
+
});
|
|
765
784
|
if (capability.availability === 'private_core') {
|
|
766
785
|
return unavailablePrivateMcpFeature(name);
|
|
767
786
|
}
|
|
@@ -788,6 +807,7 @@ async function callTool(name, args = {}) {
|
|
|
788
807
|
success: false,
|
|
789
808
|
category: 'contract',
|
|
790
809
|
evidence: validation.errors,
|
|
810
|
+
latencyMs: Date.now() - attemptStartMs,
|
|
791
811
|
});
|
|
792
812
|
throw err;
|
|
793
813
|
}
|
|
@@ -803,6 +823,7 @@ async function callTool(name, args = {}) {
|
|
|
803
823
|
success: false,
|
|
804
824
|
category: 'permission',
|
|
805
825
|
evidence: [firewallResult.message],
|
|
826
|
+
latencyMs: Date.now() - attemptStartMs,
|
|
806
827
|
});
|
|
807
828
|
throw err;
|
|
808
829
|
}
|
|
@@ -816,11 +837,24 @@ async function callTool(name, args = {}) {
|
|
|
816
837
|
success: false,
|
|
817
838
|
category: err.errorCategory || 'execution',
|
|
818
839
|
evidence: [err.code || err.message || 'tool execution failed'],
|
|
819
|
-
latencyMs: Date.now() -
|
|
840
|
+
latencyMs: Date.now() - attemptStartMs,
|
|
820
841
|
});
|
|
821
842
|
throw err;
|
|
822
843
|
}
|
|
823
844
|
const latencyMs = Date.now() - startMs;
|
|
845
|
+
const outputValidation = validateMcpToolOutput(toolDef, result);
|
|
846
|
+
if (!outputValidation.valid) {
|
|
847
|
+
const err = new Error(`Structured output contract violation on '${name}': ${outputValidation.errors.join('; ')}`);
|
|
848
|
+
err.errorCategory = 'output_contract';
|
|
849
|
+
err.isRetryable = false;
|
|
850
|
+
recordMcpToolTrace(name, args, {
|
|
851
|
+
success: false,
|
|
852
|
+
category: 'output_contract',
|
|
853
|
+
evidence: outputValidation.errors,
|
|
854
|
+
latencyMs,
|
|
855
|
+
});
|
|
856
|
+
throw err;
|
|
857
|
+
}
|
|
824
858
|
recordMcpToolTrace(name, args, {
|
|
825
859
|
success: true,
|
|
826
860
|
category: 'success',
|
|
@@ -840,7 +874,31 @@ async function callTool(name, args = {}) {
|
|
|
840
874
|
return result;
|
|
841
875
|
}
|
|
842
876
|
|
|
877
|
+
function validateMcpToolOutput(toolDef, result) {
|
|
878
|
+
if (!toolDef || !toolDef.outputSchema) return { valid: true, errors: [] };
|
|
879
|
+
const { validateStructuredOutput } = require('../../scripts/tool-contract-validator');
|
|
880
|
+
if (!result || result.structuredContent === undefined) {
|
|
881
|
+
return { valid: false, errors: ['Tool response is missing structuredContent'] };
|
|
882
|
+
}
|
|
883
|
+
return validateStructuredOutput(result.structuredContent, toolDef.outputSchema);
|
|
884
|
+
}
|
|
885
|
+
|
|
843
886
|
function recordMcpToolTrace(name, args, outcome = {}) {
|
|
887
|
+
try {
|
|
888
|
+
recordToolCall({
|
|
889
|
+
toolName: name,
|
|
890
|
+
serverName: 'mcp',
|
|
891
|
+
latencyMs: Number(outcome.latencyMs || 0),
|
|
892
|
+
success: outcome.success === true,
|
|
893
|
+
agentId: args.agentId || args.processId || args.taskId || 'unknown',
|
|
894
|
+
metadata: {
|
|
895
|
+
category: outcome.category || 'unknown',
|
|
896
|
+
traceId: args.traceId || args.taskId || null,
|
|
897
|
+
},
|
|
898
|
+
});
|
|
899
|
+
} catch {
|
|
900
|
+
// KPI telemetry must not change the tool's functional outcome.
|
|
901
|
+
}
|
|
844
902
|
try {
|
|
845
903
|
const traceId = args.traceId || args.taskId || `mcp-${Date.now()}-${name}`;
|
|
846
904
|
recordReasoningTrace({
|
|
@@ -888,10 +946,13 @@ async function callToolInner(name, args) {
|
|
|
888
946
|
// action receipt (this action -> this outcome) before promotion. Returns
|
|
889
947
|
// args unchanged when there is no matching receipt (non-breaking).
|
|
890
948
|
const pairedFeedback = pairFeedbackWithReceipt(args);
|
|
891
|
-
return toCaptureFeedbackTextResult(captureFeedback(
|
|
949
|
+
return toCaptureFeedbackTextResult(captureFeedback({
|
|
950
|
+
...pairedFeedback,
|
|
951
|
+
reviewOrigin: 'automated',
|
|
952
|
+
}));
|
|
892
953
|
}
|
|
893
954
|
case 'feedback_summary':
|
|
894
|
-
return toTextResult(feedbackSummary(Number(args.recent || 20)));
|
|
955
|
+
return toTextResult(feedbackSummary(Number(args.recent || 20), { humanOnly: true }));
|
|
895
956
|
case 'search_lessons': {
|
|
896
957
|
const module = loadPrivateMcpModule('lessonSearch');
|
|
897
958
|
if (!module) return unavailablePrivateMcpFeature('search_lessons');
|
|
@@ -899,25 +960,34 @@ async function callToolInner(name, args) {
|
|
|
899
960
|
limit: Number(args.limit || 10),
|
|
900
961
|
category: args.category,
|
|
901
962
|
tags: Array.isArray(args.tags) ? args.tags : [],
|
|
963
|
+
scope: args.scope,
|
|
964
|
+
requireScope: args.requireScope === true,
|
|
965
|
+
includeShared: args.includeShared !== false,
|
|
902
966
|
}));
|
|
903
967
|
}
|
|
904
968
|
case 'suggest_fix':
|
|
905
969
|
return buildSuggestFixResponse(args);
|
|
906
970
|
case 'retrieve_lessons': {
|
|
907
971
|
// Cross-encoder reranking: retrieve more candidates, then rerank for precision
|
|
908
|
-
const {
|
|
909
|
-
|
|
972
|
+
const { retrieveWithReranking } = loadOptionalModule(path.join(__dirname, '../../scripts/cross-encoder-reranker'), () => ({
|
|
973
|
+
retrieveWithReranking: async (toolName, actionContext, options = {}) => retrieveRelevantLessons(
|
|
910
974
|
toolName,
|
|
911
975
|
actionContext,
|
|
912
976
|
{ maxResults: options.maxResults || 5 },
|
|
913
977
|
),
|
|
914
978
|
}));
|
|
915
|
-
return toTextResult(
|
|
979
|
+
return toTextResult(await retrieveWithReranking(
|
|
916
980
|
args.toolName,
|
|
917
981
|
args.actionContext || '',
|
|
918
982
|
{
|
|
919
983
|
candidateCount: 20,
|
|
920
984
|
maxResults: Number(args.maxResults || 5),
|
|
985
|
+
scope: args.scope,
|
|
986
|
+
requireScope: args.requireScope === true,
|
|
987
|
+
includeShared: args.includeShared !== false,
|
|
988
|
+
metadataFilters: args.filters,
|
|
989
|
+
queryRewrite: args.queryRewrite !== false,
|
|
990
|
+
includeRetrievalMeta: args.includeRetrievalMeta === true,
|
|
921
991
|
},
|
|
922
992
|
));
|
|
923
993
|
}
|
|
@@ -940,11 +1010,13 @@ async function callToolInner(name, args) {
|
|
|
940
1010
|
}
|
|
941
1011
|
case 'search_thumbgate':
|
|
942
1012
|
enforceLimit('search_thumbgate');
|
|
943
|
-
return toTextResult(
|
|
1013
|
+
return toTextResult(await searchThumbgateAsync({
|
|
944
1014
|
query: args.query,
|
|
945
1015
|
limit: args.limit,
|
|
946
1016
|
source: args.source,
|
|
947
1017
|
signal: args.signal,
|
|
1018
|
+
metadataFilters: args.filters,
|
|
1019
|
+
queryRewrite: args.queryRewrite !== false,
|
|
948
1020
|
}));
|
|
949
1021
|
case 'import_document':
|
|
950
1022
|
return toTextResult(importDocument({
|
|
@@ -969,8 +1041,63 @@ async function callToolInner(name, args) {
|
|
|
969
1041
|
}
|
|
970
1042
|
return toTextResult(document);
|
|
971
1043
|
}
|
|
1044
|
+
case 'gate_check': {
|
|
1045
|
+
// Same engine the PreToolUse hook uses, so an MCP client and a hook cannot
|
|
1046
|
+
// disagree about whether an action is allowed.
|
|
1047
|
+
const { runAsync } = require('../../scripts/gates-engine');
|
|
1048
|
+
const { canonicalizeToolCall } = require('../../scripts/harness-tool-names');
|
|
1049
|
+
const canonical = canonicalizeToolCall(args.tool_name, args.tool_input || {});
|
|
1050
|
+
const raw = await runAsync({
|
|
1051
|
+
tool_name: canonical.toolName,
|
|
1052
|
+
tool_input: canonical.toolInput,
|
|
1053
|
+
});
|
|
1054
|
+
let decision = 'allow';
|
|
1055
|
+
let reason = '';
|
|
1056
|
+
let flagged = false;
|
|
1057
|
+
try {
|
|
1058
|
+
const parsed = JSON.parse(raw);
|
|
1059
|
+
const hook = parsed.hookSpecificOutput || {};
|
|
1060
|
+
const verdict = hook.permissionDecision || parsed.decision || '';
|
|
1061
|
+
reason = hook.permissionDecisionReason || parsed.reason || hook.additionalContext || '';
|
|
1062
|
+
// The hook wire format says "deny"; the documented tool contract says "block".
|
|
1063
|
+
if (verdict === 'deny' || verdict === 'block') {
|
|
1064
|
+
decision = 'block';
|
|
1065
|
+
flagged = true;
|
|
1066
|
+
} else if (/\[GATE:/.test(reason)) {
|
|
1067
|
+
// A gate MATCHED but the warn-by-default posture downgraded it. Reporting
|
|
1068
|
+
// "allow" here is how this tool would become theater: .clinerules tells the
|
|
1069
|
+
// agent to abort only on "block", so a matched rm -rf / would have been run
|
|
1070
|
+
// with the warning text ignored. "warn" is the honest third state.
|
|
1071
|
+
decision = 'warn';
|
|
1072
|
+
flagged = true;
|
|
1073
|
+
}
|
|
1074
|
+
} catch (_) {
|
|
1075
|
+
// Unparseable engine output must never read as "allow" — fail closed.
|
|
1076
|
+
decision = 'error';
|
|
1077
|
+
reason = 'gate engine returned unparseable output';
|
|
1078
|
+
}
|
|
1079
|
+
const strict = process.env.THUMBGATE_STRICT_ENFORCEMENT === '1';
|
|
1080
|
+
return {
|
|
1081
|
+
content: [{
|
|
1082
|
+
type: 'text',
|
|
1083
|
+
text: JSON.stringify({
|
|
1084
|
+
decision,
|
|
1085
|
+
flagged,
|
|
1086
|
+
enforcement: strict ? 'strict' : 'warn-by-default',
|
|
1087
|
+
guidance: decision === 'block'
|
|
1088
|
+
? 'Do NOT run this action. Surface the reason to the user.'
|
|
1089
|
+
: decision === 'warn'
|
|
1090
|
+
? 'A policy gate matched but enforcement is warn-by-default. Do NOT run this action without explicit user confirmation; show them the reason.'
|
|
1091
|
+
: decision === 'error'
|
|
1092
|
+
? 'Gate evaluation failed. Treat as unsafe and ask the user.'
|
|
1093
|
+
: 'No policy gate matched.',
|
|
1094
|
+
reason,
|
|
1095
|
+
}, null, 2),
|
|
1096
|
+
}],
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
972
1099
|
case 'feedback_stats':
|
|
973
|
-
return toTextResult(analyzeFeedback());
|
|
1100
|
+
return toTextResult(analyzeFeedback(undefined, { humanOnly: true }));
|
|
974
1101
|
case 'diagnose_failure':
|
|
975
1102
|
return buildDiagnoseFailureResponse(args);
|
|
976
1103
|
case 'reflect_on_feedback':
|
|
@@ -1148,6 +1275,7 @@ async function callToolInner(name, args) {
|
|
|
1148
1275
|
workflowContract: args.workflowContract,
|
|
1149
1276
|
repoPath: args.repoPath,
|
|
1150
1277
|
localOnly: args.localOnly === true,
|
|
1278
|
+
ttlMs: args.ttlMs,
|
|
1151
1279
|
clear: args.clear === true,
|
|
1152
1280
|
}),
|
|
1153
1281
|
});
|
|
@@ -1744,5 +1872,6 @@ module.exports = {
|
|
|
1744
1872
|
listAvailableTools,
|
|
1745
1873
|
unavailablePrivateMcpFeature,
|
|
1746
1874
|
callToolInner,
|
|
1875
|
+
validateMcpToolOutput,
|
|
1747
1876
|
},
|
|
1748
1877
|
};
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"id": "checkout-link-grounding",
|
|
5
5
|
"traceId": "trace_checkout_001",
|
|
6
6
|
"question": "Is the Pro checkout link working?",
|
|
7
|
-
"response": "The Pro checkout link is working because the checkout diagnostic confirms Stripe Pro checkout is reachable.",
|
|
7
|
+
"response": "The Pro checkout link is working because the checkout diagnostic confirms Stripe Pro checkout is reachable [context-1].",
|
|
8
8
|
"retrievedContexts": [
|
|
9
9
|
"Stripe Pro checkout diagnostic confirms the Pro checkout link is reachable and returns a valid checkout target.",
|
|
10
10
|
"The workflow sprint checkout is intake-led and should not expose a stale payment link."
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"id": "letta-tool-gate",
|
|
16
16
|
"traceId": "trace_letta_001",
|
|
17
17
|
"question": "Should Letta execute a force push tool call?",
|
|
18
|
-
"response": "ThumbGate blocks force push before execution for Letta tool calls.",
|
|
18
|
+
"response": "ThumbGate blocks force push before execution for Letta tool calls [context-1].",
|
|
19
19
|
"retrievedContexts": [
|
|
20
20
|
"ThumbGate's Letta adapter wraps Letta tool calls and runs gate-check before the executor is called.",
|
|
21
21
|
"Force push to main is a blocked high-risk git action."
|