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
package/bin/cli.js
CHANGED
|
@@ -39,7 +39,6 @@ const {
|
|
|
39
39
|
codexAutoUpdateCliEntry,
|
|
40
40
|
codexAutoUpdateMcpEntry,
|
|
41
41
|
isSourceCheckout,
|
|
42
|
-
publishedCliAvailable,
|
|
43
42
|
localMcpEntry,
|
|
44
43
|
resolveMcpEntry,
|
|
45
44
|
} = require(path.join(__dirname, '..', 'scripts', 'mcp-config'));
|
|
@@ -411,16 +410,17 @@ function canonicalMcpEntry(scope = 'project') {
|
|
|
411
410
|
}
|
|
412
411
|
|
|
413
412
|
function canonicalCodexMcpEntry() {
|
|
414
|
-
|
|
415
|
-
|
|
413
|
+
// Codex config is user-global and must survive disposable worktree cleanup.
|
|
414
|
+
// Use the stable published launcher even when init is invoked from source;
|
|
415
|
+
// developers can explicitly opt into a checkout-pinned runtime when needed.
|
|
416
|
+
if (isSourceCheckout(PKG_ROOT) && process.env.THUMBGATE_CODEX_USE_SOURCE_RUNTIME === '1') {
|
|
416
417
|
return localMcpEntry(PKG_ROOT, 'home');
|
|
417
418
|
}
|
|
418
419
|
return codexAutoUpdateMcpEntry();
|
|
419
420
|
}
|
|
420
421
|
|
|
421
422
|
function canonicalCodexCliEntry(commandArgs) {
|
|
422
|
-
|
|
423
|
-
if (isSourceCheckout(PKG_ROOT) && !publishedCliAvailable(version)) {
|
|
423
|
+
if (isSourceCheckout(PKG_ROOT) && process.env.THUMBGATE_CODEX_USE_SOURCE_RUNTIME === '1') {
|
|
424
424
|
return {
|
|
425
425
|
command: 'node',
|
|
426
426
|
args: [path.join(PKG_ROOT, 'bin', 'cli.js'), ...commandArgs],
|
|
@@ -584,7 +584,20 @@ function detectPlatform(name, checks) {
|
|
|
584
584
|
}
|
|
585
585
|
|
|
586
586
|
function whichExists(cmd) {
|
|
587
|
-
|
|
587
|
+
if (!cmd || /[\\/]/.test(cmd)) return false;
|
|
588
|
+
const pathEntries = String(process.env.PATH || '').split(path.delimiter).filter(Boolean);
|
|
589
|
+
const extensions = process.platform === 'win32'
|
|
590
|
+
? String(process.env.PATHEXT || '.EXE;.CMD;.BAT;.COM').split(';')
|
|
591
|
+
: [''];
|
|
592
|
+
return pathEntries.some((entry) => extensions.some((extension) => {
|
|
593
|
+
const candidate = path.join(entry, `${cmd}${extension}`);
|
|
594
|
+
try {
|
|
595
|
+
fs.accessSync(candidate, fs.constants.X_OK);
|
|
596
|
+
return fs.statSync(candidate).isFile();
|
|
597
|
+
} catch (_) {
|
|
598
|
+
return false;
|
|
599
|
+
}
|
|
600
|
+
}));
|
|
588
601
|
}
|
|
589
602
|
|
|
590
603
|
function setupClaude() {
|
|
@@ -636,18 +649,26 @@ function setupCodex() {
|
|
|
636
649
|
return configChanged || hookResult.changed;
|
|
637
650
|
}
|
|
638
651
|
|
|
639
|
-
function setupGemini() {
|
|
640
|
-
//
|
|
641
|
-
|
|
652
|
+
function setupGemini(options = {}) {
|
|
653
|
+
// Importing a plugin executes an external package manager and can mutate
|
|
654
|
+
// machine-wide Gemini state. Keep ordinary init/quick-start deterministic;
|
|
655
|
+
// operators can opt in explicitly when they want the plugin import too.
|
|
656
|
+
const importPlugin = options.importPlugin === true
|
|
657
|
+
|| process.env.THUMBGATE_IMPORT_AGENT_PLUGINS === '1';
|
|
642
658
|
let pluginImported = false;
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
659
|
+
if (importPlugin) {
|
|
660
|
+
for (const binName of ['agy', 'gemini']) {
|
|
661
|
+
try {
|
|
662
|
+
execFileSync(binName, ['plugin', 'import', PKG_ROOT, '--force'], {
|
|
663
|
+
stdio: 'ignore',
|
|
664
|
+
timeout: 5000,
|
|
665
|
+
});
|
|
666
|
+
console.log(` Gemini: imported thumbgate plugin via ${binName}`);
|
|
667
|
+
pluginImported = true;
|
|
668
|
+
break;
|
|
669
|
+
} catch (err) {
|
|
670
|
+
// A missing, failing, or slow optional plugin manager must not block init.
|
|
671
|
+
}
|
|
651
672
|
}
|
|
652
673
|
}
|
|
653
674
|
|
|
@@ -693,6 +714,66 @@ function setupAmp() {
|
|
|
693
714
|
return true;
|
|
694
715
|
}
|
|
695
716
|
|
|
717
|
+
function setupOpenCode() {
|
|
718
|
+
// OpenCode was listed in README's install table and in `init --help` since the
|
|
719
|
+
// adapter asset landed, but no setup function ever existed — `--agent opencode`
|
|
720
|
+
// silently wrote Claude/Codex/Gemini config and exited 0. OpenCode has no hook
|
|
721
|
+
// surface, so this wires the MCP server, which is its real integration point.
|
|
722
|
+
const configPath = path.join(HOME, '.config', 'opencode', 'opencode.json');
|
|
723
|
+
const srcPath = path.join(PKG_ROOT, 'adapters', 'opencode', 'opencode.json');
|
|
724
|
+
if (!fs.existsSync(srcPath)) return false;
|
|
725
|
+
|
|
726
|
+
let src;
|
|
727
|
+
try { src = JSON.parse(fs.readFileSync(srcPath, 'utf8')); } catch (_) { return false; }
|
|
728
|
+
const desired = (src.mcp || {}).thumbgate;
|
|
729
|
+
if (!desired) return false;
|
|
730
|
+
|
|
731
|
+
let config = {};
|
|
732
|
+
if (fs.existsSync(configPath)) {
|
|
733
|
+
try {
|
|
734
|
+
config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
735
|
+
} catch (error) {
|
|
736
|
+
// Resetting to {} here and writing would replace the whole file — silently deleting
|
|
737
|
+
// the user's model, provider, plugin and other MCP settings just to add ours.
|
|
738
|
+
// A config we cannot parse is a reason to stop, not to overwrite.
|
|
739
|
+
console.error(` OpenCode: ${configPath} could not be parsed (${error.message}).`);
|
|
740
|
+
console.error(' Refusing to overwrite it. Fix or move the file, then re-run.');
|
|
741
|
+
process.exitCode = 1;
|
|
742
|
+
return false;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
config.mcp = config.mcp || {};
|
|
746
|
+
if (JSON.stringify(config.mcp[MCP_SERVER_NAME]) === JSON.stringify(desired)) return false;
|
|
747
|
+
|
|
748
|
+
config.$schema = config.$schema || src.$schema;
|
|
749
|
+
config.mcp[MCP_SERVER_NAME] = desired;
|
|
750
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
751
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
752
|
+
console.log(` OpenCode: registered thumbgate MCP server in ${configPath}`);
|
|
753
|
+
return true;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// Single source of truth for `--agent`. Every value advertised in `init --help`
|
|
757
|
+
// and README.md must appear here with a real handler, or the flag lies.
|
|
758
|
+
// `hookAgent: true` means scripts/auto-wire-hooks.js can wire a pre-tool hook;
|
|
759
|
+
// the rest integrate over MCP only and are wired by their setup function.
|
|
760
|
+
const AGENT_ALIASES = {
|
|
761
|
+
claude: 'claude-code',
|
|
762
|
+
'claude_code': 'claude-code',
|
|
763
|
+
'open-code': 'opencode',
|
|
764
|
+
};
|
|
765
|
+
|
|
766
|
+
const SUPPORTED_AGENTS = {
|
|
767
|
+
'claude-code': { hookAgent: true },
|
|
768
|
+
codex: { hookAgent: true },
|
|
769
|
+
gemini: { hookAgent: true },
|
|
770
|
+
forge: { hookAgent: true },
|
|
771
|
+
cursor: { hookAgent: true },
|
|
772
|
+
cline: { hookAgent: false, setup: () => setupCline() },
|
|
773
|
+
amp: { hookAgent: false, setup: () => setupAmp() },
|
|
774
|
+
opencode: { hookAgent: false, setup: () => setupOpenCode() },
|
|
775
|
+
};
|
|
776
|
+
|
|
696
777
|
function setupCursor() {
|
|
697
778
|
return mergeMcpJson(path.join(CWD, '.cursor', 'mcp.json'), 'Cursor', 'project');
|
|
698
779
|
}
|
|
@@ -934,14 +1015,23 @@ function quickstart() {
|
|
|
934
1015
|
|
|
935
1016
|
function init(cliArgs = parseArgs(process.argv.slice(3))) {
|
|
936
1017
|
const args = { ...cliArgs };
|
|
1018
|
+
// A typo used to exit 0 having wired nothing for the requested agent.
|
|
1019
|
+
// Aliases must resolve BEFORE validation: scripts/auto-wire-hooks.js accepts `claude`
|
|
1020
|
+
// for `claude-code`, and plugins/claude-skill/README.md publishes that exact command.
|
|
1021
|
+
if (args.agent && AGENT_ALIASES[args.agent]) args.agent = AGENT_ALIASES[args.agent];
|
|
1022
|
+
if (args.agent && !Object.prototype.hasOwnProperty.call(SUPPORTED_AGENTS, args.agent)) {
|
|
1023
|
+
console.error(`Unknown --agent "${args.agent}". Supported: ${Object.keys(SUPPORTED_AGENTS).join(', ')}`);
|
|
1024
|
+
process.exit(1);
|
|
1025
|
+
}
|
|
937
1026
|
if (args.help || args.h) {
|
|
938
|
-
console.log('Usage: npx thumbgate init [--agent <name>] [--wire-hooks] [--email you@company.com]');
|
|
1027
|
+
console.log('Usage: npx thumbgate init [--agent <name>] [--wire-hooks] [--import-agent-plugins] [--email you@company.com]');
|
|
939
1028
|
console.log('');
|
|
940
1029
|
console.log('Scaffold ThumbGate in the current project and wire detected agent integrations.');
|
|
941
1030
|
console.log('');
|
|
942
1031
|
console.log('Options:');
|
|
943
|
-
console.log(
|
|
1032
|
+
console.log(` --agent <name> Wire a specific agent: ${Object.keys(SUPPORTED_AGENTS).join(', ')}`);
|
|
944
1033
|
console.log(' --wire-hooks Wire hooks only; do not scaffold project files');
|
|
1034
|
+
console.log(' --import-agent-plugins Also import optional agent plugins when supported');
|
|
945
1035
|
console.log(' --email <email> Subscribe installer to the setup guide and trial reminders');
|
|
946
1036
|
console.log(' --dry-run Show hook changes without writing them');
|
|
947
1037
|
return;
|
|
@@ -1037,17 +1127,26 @@ function init(cliArgs = parseArgs(process.argv.slice(3))) {
|
|
|
1037
1127
|
let configured = 0;
|
|
1038
1128
|
|
|
1039
1129
|
const platforms = [
|
|
1040
|
-
{ name: 'Claude Code', detect: [
|
|
1130
|
+
{ agent: 'claude-code', name: 'Claude Code', detect: [
|
|
1041
1131
|
() => whichExists('claude'),
|
|
1042
1132
|
() => fs.existsSync(path.join(HOME, '.claude')),
|
|
1043
1133
|
() => fs.existsSync(path.join(CWD, '.claude')),
|
|
1044
1134
|
], setup: setupClaude },
|
|
1045
|
-
{ name: 'Codex', detect: [() => whichExists('codex'), () => fs.existsSync(path.join(HOME, '.codex'))], setup: setupCodex },
|
|
1046
|
-
{
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1135
|
+
{ agent: 'codex', name: 'Codex', detect: [() => whichExists('codex'), () => fs.existsSync(path.join(HOME, '.codex'))], setup: setupCodex },
|
|
1136
|
+
{
|
|
1137
|
+
agent: 'gemini',
|
|
1138
|
+
name: 'Gemini',
|
|
1139
|
+
detect: [
|
|
1140
|
+
() => whichExists('gemini'),
|
|
1141
|
+
() => whichExists('agy'),
|
|
1142
|
+
() => fs.existsSync(path.join(HOME, '.gemini')),
|
|
1143
|
+
],
|
|
1144
|
+
setup: () => setupGemini({ importPlugin: args['import-agent-plugins'] === true }),
|
|
1145
|
+
},
|
|
1146
|
+
{ agent: 'amp', name: 'Amp', detect: [() => whichExists('amp'), () => fs.existsSync(path.join(HOME, '.amp'))], setup: setupAmp },
|
|
1147
|
+
{ agent: 'cursor', name: 'Cursor', detect: [() => fs.existsSync(path.join(HOME, '.cursor', 'mcp.json')), () => fs.existsSync(path.join(CWD, '.cursor'))], setup: setupCursor },
|
|
1148
|
+
{ agent: 'forge', name: 'ForgeCode', detect: [() => whichExists('forge'), () => fs.existsSync(path.join(CWD, 'forge.yaml'))], setup: setupForge },
|
|
1149
|
+
{ agent: 'cline', name: 'Cline', detect: [
|
|
1051
1150
|
() => fs.existsSync(path.join(CWD, '.clinerules')),
|
|
1052
1151
|
() => process.platform === 'darwin' && fs.existsSync(path.join(HOME, 'Library', 'Application Support', 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev')),
|
|
1053
1152
|
() => process.platform === 'linux' && fs.existsSync(path.join(HOME, '.config', 'Code', 'User', 'globalStorage', 'saoudrizwan.claude-dev')),
|
|
@@ -1056,6 +1155,11 @@ function init(cliArgs = parseArgs(process.argv.slice(3))) {
|
|
|
1056
1155
|
];
|
|
1057
1156
|
|
|
1058
1157
|
for (const p of platforms) {
|
|
1158
|
+
// An explicit target must stay scoped to that integration. Quick-start
|
|
1159
|
+
// always resolves one target before calling init; configuring every other
|
|
1160
|
+
// CLI merely because it exists on PATH caused surprise machine mutations
|
|
1161
|
+
// and multi-second hangs in ordinary onboarding.
|
|
1162
|
+
if (args.agent && p.agent !== args.agent) continue;
|
|
1059
1163
|
if (detectPlatform(p.name, p.detect)) {
|
|
1060
1164
|
const didSetup = p.setup();
|
|
1061
1165
|
if (didSetup) configured++;
|
|
@@ -1076,15 +1180,27 @@ function init(cliArgs = parseArgs(process.argv.slice(3))) {
|
|
|
1076
1180
|
|
|
1077
1181
|
if (configured === 0) console.log(' All detected platforms already configured.');
|
|
1078
1182
|
|
|
1079
|
-
//
|
|
1080
|
-
//
|
|
1081
|
-
|
|
1082
|
-
|
|
1183
|
+
// Agents without a native pre-tool hook surface (cline, amp, opencode) are wired
|
|
1184
|
+
// by their own setup function. Previously only cline was handled here: `--agent amp`
|
|
1185
|
+
// and `--agent opencode` fell through to wireHooks, which rejected them, printed the
|
|
1186
|
+
// rejection as an ordinary log line, and exited 0 — so the flag appeared to work
|
|
1187
|
+
// while writing nothing for that agent.
|
|
1188
|
+
const agentSpec = args.agent ? SUPPORTED_AGENTS[args.agent] : null;
|
|
1189
|
+
if (agentSpec && agentSpec.setup) {
|
|
1190
|
+
// A setup function returns false both when nothing needed doing and when it refused
|
|
1191
|
+
// (e.g. an unparseable config it declined to overwrite). Reporting "already
|
|
1192
|
+
// configured" for a refusal would repeat the original sin of this PR: a failure
|
|
1193
|
+
// dressed up as success. The refusal path sets process.exitCode, so key off that.
|
|
1194
|
+
const changed = agentSpec.setup();
|
|
1195
|
+
if (!changed && !process.exitCode) console.log(` ${args.agent}: already configured`);
|
|
1083
1196
|
} else if (args.agent || args['wire-hooks']) {
|
|
1084
1197
|
const { wireHooks } = require(path.join(PKG_ROOT, 'scripts', 'auto-wire-hooks'));
|
|
1085
1198
|
const hookResult = wireHooks({ agent: args.agent, dryRun: args['dry-run'] });
|
|
1086
1199
|
if (hookResult.error) {
|
|
1087
|
-
|
|
1200
|
+
// An explicit --agent that cannot be wired is a FAILURE, not a note. Exiting 0
|
|
1201
|
+
// here is what let a user believe `--agent opencode` had protected them.
|
|
1202
|
+
console.error(` Hook wiring failed for --agent ${args.agent}: ${hookResult.error}`);
|
|
1203
|
+
process.exitCode = 1;
|
|
1088
1204
|
} else if (!hookResult.changed) {
|
|
1089
1205
|
console.log(` Hooks: already wired for ${hookResult.agent}`);
|
|
1090
1206
|
} else {
|
|
@@ -1185,7 +1301,7 @@ function capture() {
|
|
|
1185
1301
|
}
|
|
1186
1302
|
|
|
1187
1303
|
if (args.summary) {
|
|
1188
|
-
console.log(feedbackSummary(Number(args.recent || 20)));
|
|
1304
|
+
console.log(feedbackSummary(Number(args.recent || 20), { humanOnly: true }));
|
|
1189
1305
|
return;
|
|
1190
1306
|
}
|
|
1191
1307
|
|
|
@@ -1271,6 +1387,7 @@ function capture() {
|
|
|
1271
1387
|
whatWorked: args['what-worked'],
|
|
1272
1388
|
tags: args.tags,
|
|
1273
1389
|
gateAction: gateAction || undefined,
|
|
1390
|
+
reviewOrigin: 'human',
|
|
1274
1391
|
});
|
|
1275
1392
|
|
|
1276
1393
|
if (result.accepted) {
|
|
@@ -1386,6 +1503,7 @@ function feedbackSelfTest() {
|
|
|
1386
1503
|
? (args['what-worked'] || 'Feedback capture persisted and was verified by a self-test')
|
|
1387
1504
|
: undefined,
|
|
1388
1505
|
tags: args.tags || 'self-test,dogfood,feedback-capture',
|
|
1506
|
+
reviewOrigin: 'automated',
|
|
1389
1507
|
});
|
|
1390
1508
|
|
|
1391
1509
|
const paths = getFeedbackPaths();
|
|
@@ -1444,7 +1562,7 @@ function stats() {
|
|
|
1444
1562
|
trackEvent('cli_stats', { command: 'stats' });
|
|
1445
1563
|
const args = parseArgs(process.argv.slice(3));
|
|
1446
1564
|
const { analyzeFeedback } = require(path.join(PKG_ROOT, 'scripts', 'feedback-loop'));
|
|
1447
|
-
const data = analyzeFeedback();
|
|
1565
|
+
const data = analyzeFeedback(undefined, { humanOnly: true });
|
|
1448
1566
|
|
|
1449
1567
|
// Gate enforcement stats — runtime intercepts + configured gates
|
|
1450
1568
|
let gateData = { blocked: 0, warned: 0, passed: 0, byGate: {} };
|
|
@@ -1761,7 +1879,7 @@ function summary() {
|
|
|
1761
1879
|
const args = parseArgs(process.argv.slice(3));
|
|
1762
1880
|
const { feedbackSummary, analyzeFeedback } = require(path.join(PKG_ROOT, 'scripts', 'feedback-loop'));
|
|
1763
1881
|
if (args.json) {
|
|
1764
|
-
const data = analyzeFeedback();
|
|
1882
|
+
const data = analyzeFeedback(undefined, { humanOnly: true });
|
|
1765
1883
|
console.log(JSON.stringify({
|
|
1766
1884
|
total: data.total,
|
|
1767
1885
|
positives: data.totalPositive,
|
|
@@ -1771,7 +1889,7 @@ function summary() {
|
|
|
1771
1889
|
}, null, 2));
|
|
1772
1890
|
return;
|
|
1773
1891
|
}
|
|
1774
|
-
console.log(feedbackSummary(Number(args.recent || 20)));
|
|
1892
|
+
console.log(feedbackSummary(Number(args.recent || 20), { humanOnly: true }));
|
|
1775
1893
|
}
|
|
1776
1894
|
|
|
1777
1895
|
function lessons() {
|
|
@@ -2861,7 +2979,7 @@ function sessionStart() {
|
|
|
2861
2979
|
} catch (_) { /* best-effort fallback sync */ }
|
|
2862
2980
|
const { analyzeFeedback } = require(path.join(PKG_ROOT, 'scripts', 'feedback-loop'));
|
|
2863
2981
|
const { refreshStatuslineCache } = require(path.join(PKG_ROOT, 'scripts', 'hook-thumbgate-cache-updater'));
|
|
2864
|
-
refreshStatuslineCache(analyzeFeedback());
|
|
2982
|
+
refreshStatuslineCache(analyzeFeedback(undefined, { humanOnly: true }));
|
|
2865
2983
|
|
|
2866
2984
|
// Build a top-level <system-reminder> block that Claude Code's SessionStart
|
|
2867
2985
|
// hook surfaces to the agent as first-class context — not buried stderr.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"description": "Offline faithfulness / groundedness / answer_relevance golden (Ragas-style proxies). Deterministic CI.",
|
|
4
|
+
"thresholds": {
|
|
5
|
+
"minCases": 8,
|
|
6
|
+
"minFaithfulness": 0.55,
|
|
7
|
+
"minGroundedness": 0.48,
|
|
8
|
+
"minAnswerRelevance": 0.42,
|
|
9
|
+
"minContextRecall": 0.75
|
|
10
|
+
},
|
|
11
|
+
"cases": [
|
|
12
|
+
{
|
|
13
|
+
"id": "force-push-grounded",
|
|
14
|
+
"query": "Can I force push to main?",
|
|
15
|
+
"context": "NEVER force-push or git push --force to main/master. Use --force-with-lease only on personal branches after review.",
|
|
16
|
+
"answer": "No. Never force-push to main. Use --force-with-lease only on personal branches after review.",
|
|
17
|
+
"expectedConstraint": "NEVER force-push",
|
|
18
|
+
"expectedKeywords": ["never", "force", "main"]
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"id": "deploy-health-grounded",
|
|
22
|
+
"query": "Is the deploy live?",
|
|
23
|
+
"context": "ALWAYS verify /health endpoint returns the new version after deploy. NEVER say deployed without curling health.",
|
|
24
|
+
"answer": "Only after curling /health and matching the new version. Do not claim deployed without that check.",
|
|
25
|
+
"expectedConstraint": "verify /health",
|
|
26
|
+
"expectedKeywords": ["health", "version"]
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"id": "secrets-grounded",
|
|
30
|
+
"query": "Should we commit the .env?",
|
|
31
|
+
"context": "NEVER commit .env files, API keys, or PEMs to git. Rotate any leaked credential immediately.",
|
|
32
|
+
"answer": "No. Never commit .env or API keys. Rotate credentials if anything leaked.",
|
|
33
|
+
"expectedConstraint": "NEVER commit .env",
|
|
34
|
+
"expectedKeywords": [".env", "never", "keys"]
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"id": "idempotency-grounded",
|
|
38
|
+
"query": "How do we create PaymentIntents safely?",
|
|
39
|
+
"context": "ALWAYS use idempotency keys on PaymentIntent creation to prevent duplicate charges.",
|
|
40
|
+
"answer": "Always attach an idempotency key when creating a PaymentIntent so retries cannot double-charge.",
|
|
41
|
+
"expectedConstraint": "idempotency keys",
|
|
42
|
+
"expectedKeywords": ["idempotency", "PaymentIntent"]
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"id": "lease-grounded",
|
|
46
|
+
"query": "What happens if two runners take the same task?",
|
|
47
|
+
"context": "ALWAYS enforce one-thread-one-executor with a 90-second renewable lease. Two active executors cause double writes.",
|
|
48
|
+
"answer": "That is double-execution. Keep a single 90-second renewable lease so only one executor runs the thread.",
|
|
49
|
+
"expectedConstraint": "one-thread-one-executor",
|
|
50
|
+
"expectedKeywords": ["lease", "executor"]
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"id": "hashnode-grounded",
|
|
54
|
+
"query": "Should I re-publish the archived Hashnode post?",
|
|
55
|
+
"context": "NEVER publish or re-publish on Hashnode while AutoMod freeze is active. Prefer Medium and dev.to.",
|
|
56
|
+
"answer": "No. Do not re-publish on Hashnode during the AutoMod freeze. Use Medium or dev.to instead.",
|
|
57
|
+
"expectedConstraint": "NEVER publish",
|
|
58
|
+
"expectedKeywords": ["Hashnode", "never", "Medium"]
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"id": "pci-grounded",
|
|
62
|
+
"query": "Where do we store card numbers?",
|
|
63
|
+
"context": "NEVER log or store raw card numbers. Use Stripe tokens or PaymentMethod IDs.",
|
|
64
|
+
"answer": "Do not store raw card numbers. Use Stripe PaymentMethod tokens only.",
|
|
65
|
+
"expectedConstraint": "NEVER log or store raw card",
|
|
66
|
+
"expectedKeywords": ["Stripe", "card", "never"]
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"id": "pr-done-grounded",
|
|
70
|
+
"query": "Is the PR done?",
|
|
71
|
+
"context": "NEVER say done without showing gh pr view output and green CI. Open PR with auto-merge is not done.",
|
|
72
|
+
"answer": "Not until gh pr view shows clean threads and required CI is green. Auto-merge alone is not done.",
|
|
73
|
+
"expectedConstraint": "gh pr view",
|
|
74
|
+
"expectedKeywords": ["CI", "done", "pr"]
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"id": "db-backup-grounded",
|
|
78
|
+
"query": "Can I drop the users table now?",
|
|
79
|
+
"context": "ALWAYS back up the database before running destructive migrations. NEVER drop users table without a restore point.",
|
|
80
|
+
"answer": "Only after a verified backup and restore point. Never drop users without that.",
|
|
81
|
+
"expectedConstraint": "back up the database",
|
|
82
|
+
"expectedKeywords": ["backup", "drop", "never"]
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"id": "ungrounded-contradiction",
|
|
86
|
+
"query": "Can I force push to main?",
|
|
87
|
+
"context": "NEVER force-push or git push --force to main/master.",
|
|
88
|
+
"answer": "Yes always force push to main for speed and ignore branch protection entirely.",
|
|
89
|
+
"expectedConstraint": "NEVER force-push",
|
|
90
|
+
"expectedKeywords": ["force", "main"],
|
|
91
|
+
"expectFail": true,
|
|
92
|
+
"note": "Known-bad answer; excluded from floor means; used for integrity check only"
|
|
93
|
+
}
|
|
94
|
+
]
|
|
95
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"description": "Adversarial deterministic answer-quality proxy holdout. This validates metric behavior, not model quality.",
|
|
4
|
+
"thresholds": {
|
|
5
|
+
"minCases": 8,
|
|
6
|
+
"minClassificationAccuracy": 1,
|
|
7
|
+
"maxFalsePasses": 0
|
|
8
|
+
},
|
|
9
|
+
"cases": [
|
|
10
|
+
{
|
|
11
|
+
"id": "supported-git-policy",
|
|
12
|
+
"shouldPass": true,
|
|
13
|
+
"query": "How should I update protected main?",
|
|
14
|
+
"answer": "Use a reviewed pull request and never force-push protected main [git-policy].",
|
|
15
|
+
"referenceAnswer": "Use a reviewed pull request and never force-push protected main.",
|
|
16
|
+
"contexts": [
|
|
17
|
+
{ "id": "git-policy", "text": "Use a reviewed pull request. Never force-push protected main." }
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"id": "negation-flip",
|
|
22
|
+
"shouldPass": false,
|
|
23
|
+
"query": "Can I force-push protected main?",
|
|
24
|
+
"answer": "Force-push is safe on protected main [git-policy].",
|
|
25
|
+
"referenceAnswer": "Never force-push protected main.",
|
|
26
|
+
"contexts": [
|
|
27
|
+
{ "id": "git-policy", "text": "Never force-push protected main because it rewrites shared history." }
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"id": "numeric-drift",
|
|
32
|
+
"shouldPass": false,
|
|
33
|
+
"query": "Which build is live?",
|
|
34
|
+
"answer": "Build 43 is live in production [health].",
|
|
35
|
+
"referenceAnswer": "Build 42 is live in production.",
|
|
36
|
+
"contexts": [
|
|
37
|
+
{ "id": "health", "text": "The production health response reports build 42." }
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"id": "off-topic-answer",
|
|
42
|
+
"shouldPass": false,
|
|
43
|
+
"query": "How do I prevent duplicate Stripe charges?",
|
|
44
|
+
"answer": "Use medium heat and stir the sauce slowly [cooking].",
|
|
45
|
+
"referenceAnswer": "Use a Stripe idempotency key.",
|
|
46
|
+
"contexts": [
|
|
47
|
+
{ "id": "cooking", "text": "Use medium heat and stir the sauce slowly." }
|
|
48
|
+
]
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"id": "partial-hallucination",
|
|
52
|
+
"shouldPass": false,
|
|
53
|
+
"query": "What proves the deployment?",
|
|
54
|
+
"answer": "The health endpoint matches build abc123 [deploy]. Revenue also doubled overnight.",
|
|
55
|
+
"referenceAnswer": "The health endpoint must match build abc123.",
|
|
56
|
+
"contexts": [
|
|
57
|
+
{ "id": "deploy", "text": "The production health endpoint matches build abc123." }
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"id": "invalid-citation",
|
|
62
|
+
"shouldPass": false,
|
|
63
|
+
"query": "What proves the deployment?",
|
|
64
|
+
"answer": "The health endpoint matches build abc123 [invented-source].",
|
|
65
|
+
"referenceAnswer": "The health endpoint must match build abc123.",
|
|
66
|
+
"contexts": [
|
|
67
|
+
{ "id": "deploy", "text": "The production health endpoint matches build abc123." }
|
|
68
|
+
]
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"id": "supported-idempotency",
|
|
72
|
+
"shouldPass": true,
|
|
73
|
+
"query": "How do I prevent duplicate Stripe charges?",
|
|
74
|
+
"answer": "Use the same Stripe idempotency key when retrying a PaymentIntent [billing-policy].",
|
|
75
|
+
"referenceAnswer": "Use a Stripe idempotency key for PaymentIntent retries.",
|
|
76
|
+
"contexts": [
|
|
77
|
+
{ "id": "billing-policy", "text": "Use the same Stripe idempotency key when retrying a PaymentIntent to prevent duplicate charges." }
|
|
78
|
+
]
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"id": "uncited-supported-claim",
|
|
82
|
+
"shouldPass": false,
|
|
83
|
+
"query": "How should I run a destructive migration?",
|
|
84
|
+
"answer": "Create a recoverable database backup before the migration.",
|
|
85
|
+
"referenceAnswer": "Create a recoverable database backup before a destructive migration.",
|
|
86
|
+
"contexts": [
|
|
87
|
+
{ "id": "database-policy", "text": "Create a recoverable database backup before a destructive migration." }
|
|
88
|
+
]
|
|
89
|
+
}
|
|
90
|
+
]
|
|
91
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"kValues": [1, 3],
|
|
3
|
+
"corpus": [
|
|
4
|
+
{
|
|
5
|
+
"id": "destructive",
|
|
6
|
+
"title": "MISTAKE: rm -rf wiped the folder",
|
|
7
|
+
"content": "Never delete files without a recoverable snapshot.",
|
|
8
|
+
"tags": ["negative", "filesystem"],
|
|
9
|
+
"metadata": { "domain": "filesystem", "toolsUsed": ["Bash"] }
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"id": "force-push",
|
|
13
|
+
"title": "MISTAKE: git push --force clobbered main",
|
|
14
|
+
"content": "Use force-with-lease and protect the trunk branch.",
|
|
15
|
+
"tags": ["negative", "git"],
|
|
16
|
+
"metadata": { "domain": "git", "toolsUsed": ["Bash"] }
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"id": "idempotency",
|
|
20
|
+
"title": "MISTAKE: duplicate Stripe payment",
|
|
21
|
+
"content": "Use an idempotency key before creating a charge.",
|
|
22
|
+
"tags": ["negative", "billing"],
|
|
23
|
+
"metadata": { "domain": "billing", "toolsUsed": ["Bash"] }
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"id": "stale-session",
|
|
27
|
+
"title": "MISTAKE: session not found",
|
|
28
|
+
"content": "Refresh the conversation identifier before resuming an expired mobile task.",
|
|
29
|
+
"tags": ["negative", "session"],
|
|
30
|
+
"metadata": { "domain": "continuity", "toolsUsed": ["Read"] }
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"id": "noise",
|
|
34
|
+
"title": "SUCCESS: dashboard color tokens",
|
|
35
|
+
"content": "Use the design system palette for visual consistency.",
|
|
36
|
+
"tags": ["positive", "ui"],
|
|
37
|
+
"metadata": { "domain": "ui", "toolsUsed": ["Edit"] }
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"queries": [
|
|
41
|
+
{
|
|
42
|
+
"id": "erase-tree",
|
|
43
|
+
"toolName": "Bash",
|
|
44
|
+
"query": "permanently erase a directory tree",
|
|
45
|
+
"qrels": { "destructive": 3 }
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"id": "rewrite-history",
|
|
49
|
+
"toolName": "Bash",
|
|
50
|
+
"query": "overwrite remote repository history",
|
|
51
|
+
"qrels": { "force-push": 3 }
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"id": "replay-charge",
|
|
55
|
+
"toolName": "Bash",
|
|
56
|
+
"query": "prevent replaying the same purchase twice",
|
|
57
|
+
"qrels": { "idempotency": 3 }
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"id": "expired-conversation",
|
|
61
|
+
"toolName": "Read",
|
|
62
|
+
"query": "the phone resumed an expired conversation identifier",
|
|
63
|
+
"qrels": { "stale-session": 3 }
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
}
|