thumbgate 1.28.4 → 1.29.2
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 +11 -1
- package/.claude/commands/thumbgate-dashboard.md +23 -8
- package/.claude-plugin/plugin.json +1 -1
- package/.well-known/llms.txt +18 -10
- package/.well-known/mcp/server-card.json +1 -1
- package/README.md +66 -3
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/forge/forge.yaml +3 -3
- package/adapters/mcp/server-stdio.js +88 -2
- package/adapters/opencode/opencode.json +1 -1
- package/bin/cli.js +8 -8
- package/bin/postinstall.js +4 -13
- package/commands/dashboard.md +11 -1
- package/commands/thumbgate-dashboard.md +23 -8
- package/config/agent-outcome-monitor-thresholds.json +63 -0
- package/config/evals/agent-outcomes-baseline.json +17 -0
- package/config/evals/agent-outcomes-golden.json +412 -0
- package/config/evals/prompt-eval-baseline.json +23 -0
- package/config/github-about.json +5 -4
- package/config/post-deploy-marketing-pages.json +6 -6
- package/config/schemas/task-outcome-receipt.schema.json +296 -0
- package/docs/integrations/grafana/README.md +109 -0
- package/docs/integrations/grafana/thumbgate-revenue-evidence-dashboard.json +1930 -0
- package/openapi/openapi.yaml +475 -5
- package/package.json +75 -22
- package/public/agent-manager.html +10 -11
- package/public/agents-cost-savings.html +2 -2
- package/public/assets/brand/thumbgate-logo-transparent.svg +6 -11
- package/public/assets/brand/thumbgate-mark-inline-v3.svg +11 -10
- package/public/assets/brand/thumbgate-mark.svg +10 -11
- package/public/blog/inside-your-boundary.html +114 -0
- package/public/blog/process-over-outcome-gates.html +119 -0
- package/public/blog.html +296 -402
- package/public/brand/thumbgate-mark.svg +5 -9
- package/public/codex-enterprise.html +2 -2
- package/public/compare.html +12 -3
- package/public/diagnostic.html +79 -29
- package/public/guide.html +4 -4
- package/public/index.html +1090 -2098
- package/public/install.html +3 -3
- package/public/js/buyer-intent.js +33 -18
- package/public/numbers.html +2 -2
- package/public/pricing.html +268 -408
- package/public/pro.html +4 -4
- package/scripts/agent-outcome-eval.js +130 -0
- package/scripts/agent-outcome-monitor.js +261 -0
- package/scripts/agent-reasoning-traces.js +8 -9
- package/scripts/async-job-runner.js +107 -13
- package/scripts/billing.js +456 -126
- package/scripts/buyer-paths.js +102 -0
- package/scripts/cli-feedback.js +2 -2
- package/scripts/commercial-offer.js +18 -10
- package/scripts/durability/step.js +121 -12
- package/scripts/external-customer-audit.js +881 -0
- package/scripts/feedback-loop.js +26 -0
- package/scripts/gates-engine.js +554 -19
- package/scripts/grafana-revenue-evidence.js +856 -0
- package/scripts/human-escalation.js +265 -0
- package/scripts/hybrid-feedback-context.js +93 -50
- package/scripts/jsonl-window.js +89 -0
- package/scripts/judge-reward-function.js +30 -18
- package/scripts/lesson-embedding-index.js +3 -7
- package/scripts/meta-agent-loop.js +20 -2
- package/scripts/observability-env.js +139 -0
- package/scripts/observability-setup.js +55 -0
- package/scripts/plausible-domain-config.js +4 -0
- package/scripts/prompt-eval.js +81 -4
- package/scripts/provider-live-evidence.js +1290 -0
- package/scripts/provider-payment-reconciler.js +442 -0
- package/scripts/provider-revenue-evidence.js +249 -0
- package/scripts/rate-limiter.js +1 -5
- package/scripts/revenue-action-eligibility.js +414 -0
- package/scripts/revenue-evidence-remediation.js +694 -0
- package/scripts/revenue-offer-system.js +709 -0
- package/scripts/sales-pipeline.js +1117 -0
- package/scripts/schedule-manager.js +249 -0
- package/scripts/seo-gsd.js +8 -4
- package/scripts/stripe-credentials.js +37 -0
- package/scripts/stripe-revenue-catalog-audit.js +363 -0
- package/scripts/stripe-revenue-catalog.js +164 -0
- package/scripts/task-outcomes.js +425 -0
- package/scripts/telemetry-analytics.js +23 -3
- package/scripts/tool-contract-validator.js +287 -59
- package/scripts/tool-registry.js +143 -0
- package/scripts/vector-store.js +83 -7
- package/scripts/workflow-intake-queue.js +483 -0
- package/src/api/server.js +647 -118
|
@@ -581,6 +581,20 @@ async function main() {
|
|
|
581
581
|
const args = process.argv.slice(2);
|
|
582
582
|
const dryRun = args.includes('--dry-run');
|
|
583
583
|
const verbose = args.includes('--verbose') || args.includes('-v');
|
|
584
|
+
let hookMode = args.includes('--hook');
|
|
585
|
+
|
|
586
|
+
// Settings may already be cached by a running agent process. Detect the
|
|
587
|
+
// current Stop payload as well, so the legacy command (without --hook) is
|
|
588
|
+
// immediately JSON-safe before the host reloads its settings.
|
|
589
|
+
if (!hookMode && !process.stdin.isTTY) {
|
|
590
|
+
try {
|
|
591
|
+
const raw = fs.readFileSync(0, 'utf8');
|
|
592
|
+
const payload = raw ? JSON.parse(raw) : {};
|
|
593
|
+
hookMode = payload.hook_event_name === 'Stop';
|
|
594
|
+
} catch {
|
|
595
|
+
// Normal CLI invocation with empty/non-JSON stdin.
|
|
596
|
+
}
|
|
597
|
+
}
|
|
584
598
|
|
|
585
599
|
if (args.includes('--status')) {
|
|
586
600
|
const status = getMetaAgentStatus();
|
|
@@ -593,9 +607,13 @@ async function main() {
|
|
|
593
607
|
}
|
|
594
608
|
|
|
595
609
|
const mode = dryRun ? 'DRY RUN' : 'LIVE';
|
|
596
|
-
console.log(`Meta-agent loop starting [${mode}]...`);
|
|
610
|
+
if (!hookMode) console.log(`Meta-agent loop starting [${mode}]...`);
|
|
611
|
+
|
|
612
|
+
const manifest = await runMetaAgentLoop({ dryRun, verbose: hookMode ? false : true });
|
|
597
613
|
|
|
598
|
-
|
|
614
|
+
// Stop hooks may emit only empty stdout or a valid JSON object. The loop's
|
|
615
|
+
// work is intentionally silent in hook mode.
|
|
616
|
+
if (hookMode) return;
|
|
599
617
|
|
|
600
618
|
console.log(`Run ID : ${manifest.runId}`);
|
|
601
619
|
console.log(`Analysis mode : ${manifest.analysisMode}`);
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Load operator-local observability credentials into process.env.
|
|
5
|
+
*
|
|
6
|
+
* Sources (first wins per key, never overwrite an already-set env var):
|
|
7
|
+
* 1. process.env
|
|
8
|
+
* 2. ~/.config/thumbgate/observability.json
|
|
9
|
+
* 3. ~/.config/thumbgate/operator.json (operatorKey / baseUrl only)
|
|
10
|
+
* 4. Stripe managed secret files via resolveStripeSecretKey
|
|
11
|
+
*
|
|
12
|
+
* The JSON file is gitignored operator state. Never print secret values.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const fs = require('node:fs');
|
|
16
|
+
const os = require('node:os');
|
|
17
|
+
const path = require('node:path');
|
|
18
|
+
|
|
19
|
+
const OBSERVABILITY_CONFIG_PATH = path.join(os.homedir(), '.config', 'thumbgate', 'observability.json');
|
|
20
|
+
const OPERATOR_CONFIG_PATH = path.join(os.homedir(), '.config', 'thumbgate', 'operator.json');
|
|
21
|
+
|
|
22
|
+
const JSON_KEY_TO_ENV = Object.freeze({
|
|
23
|
+
stripeSecretKey: 'STRIPE_SECRET_KEY',
|
|
24
|
+
plausibleApiKey: 'PLAUSIBLE_API_KEY',
|
|
25
|
+
plausibleSiteId: 'PLAUSIBLE_SITE_ID',
|
|
26
|
+
plausibleSiteIds: 'PLAUSIBLE_SITE_IDS',
|
|
27
|
+
plausibleRegisteredDomains: 'THUMBGATE_PLAUSIBLE_REGISTERED_DOMAINS',
|
|
28
|
+
posthogPersonalApiKey: 'POSTHOG_PERSONAL_API_KEY',
|
|
29
|
+
posthogProjectId: 'POSTHOG_PROJECT_ID',
|
|
30
|
+
operatorKey: 'THUMBGATE_OPERATOR_KEY',
|
|
31
|
+
apiKey: 'THUMBGATE_API_KEY',
|
|
32
|
+
publicAppOrigin: 'THUMBGATE_PUBLIC_APP_ORIGIN',
|
|
33
|
+
billingApiBaseUrl: 'THUMBGATE_BILLING_API_BASE_URL',
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
function normalizeText(value) {
|
|
37
|
+
if (value === undefined || value === null) return null;
|
|
38
|
+
const text = String(value).trim();
|
|
39
|
+
return text || null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function readJsonFile(filePath) {
|
|
43
|
+
try {
|
|
44
|
+
if (!filePath || !fs.existsSync(filePath)) return null;
|
|
45
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
46
|
+
} catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function applyJsonToEnv(json, env) {
|
|
52
|
+
if (!json || typeof json !== 'object') return [];
|
|
53
|
+
const applied = [];
|
|
54
|
+
for (const [jsonKey, envKey] of Object.entries(JSON_KEY_TO_ENV)) {
|
|
55
|
+
if (normalizeText(env[envKey])) continue;
|
|
56
|
+
const value = normalizeText(json[jsonKey]);
|
|
57
|
+
if (!value) continue;
|
|
58
|
+
env[envKey] = value;
|
|
59
|
+
applied.push(envKey);
|
|
60
|
+
}
|
|
61
|
+
return applied;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function loadObservabilityEnv({
|
|
65
|
+
env = process.env,
|
|
66
|
+
observabilityPath = OBSERVABILITY_CONFIG_PATH,
|
|
67
|
+
operatorPath = OPERATOR_CONFIG_PATH,
|
|
68
|
+
applyStripeManagedFiles = true,
|
|
69
|
+
} = {}) {
|
|
70
|
+
const applied = [];
|
|
71
|
+
|
|
72
|
+
const observability = readJsonFile(observabilityPath);
|
|
73
|
+
applied.push(...applyJsonToEnv(observability, env));
|
|
74
|
+
|
|
75
|
+
const operator = readJsonFile(operatorPath);
|
|
76
|
+
if (operator) {
|
|
77
|
+
if (!normalizeText(env.THUMBGATE_OPERATOR_KEY) && normalizeText(operator.operatorKey)) {
|
|
78
|
+
env.THUMBGATE_OPERATOR_KEY = String(operator.operatorKey).trim();
|
|
79
|
+
applied.push('THUMBGATE_OPERATOR_KEY');
|
|
80
|
+
}
|
|
81
|
+
if (!normalizeText(env.THUMBGATE_BILLING_API_BASE_URL) && normalizeText(operator.baseUrl)) {
|
|
82
|
+
env.THUMBGATE_BILLING_API_BASE_URL = String(operator.baseUrl).trim();
|
|
83
|
+
applied.push('THUMBGATE_BILLING_API_BASE_URL');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (applyStripeManagedFiles && !normalizeText(env.STRIPE_SECRET_KEY)) {
|
|
88
|
+
try {
|
|
89
|
+
const { resolveStripeSecretKey } = require('./stripe-credentials');
|
|
90
|
+
const resolved = resolveStripeSecretKey({ env });
|
|
91
|
+
if (resolved.secretKey) {
|
|
92
|
+
env.STRIPE_SECRET_KEY = resolved.secretKey;
|
|
93
|
+
applied.push('STRIPE_SECRET_KEY');
|
|
94
|
+
}
|
|
95
|
+
} catch {
|
|
96
|
+
// stripe-credentials optional at load time
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Product-primary Plausible domain is always part of the registered set for
|
|
101
|
+
// doctor/automation unless the operator explicitly overrides site id.
|
|
102
|
+
if (!normalizeText(env.PLAUSIBLE_SITE_ID) && !normalizeText(env.THUMBGATE_PLAUSIBLE_REGISTERED_DOMAINS)) {
|
|
103
|
+
env.THUMBGATE_PLAUSIBLE_REGISTERED_DOMAINS = 'thumbgate.ai,thumbgate-production.up.railway.app';
|
|
104
|
+
applied.push('THUMBGATE_PLAUSIBLE_REGISTERED_DOMAINS');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
applied: [...new Set(applied)],
|
|
109
|
+
observabilityPath,
|
|
110
|
+
operatorPath,
|
|
111
|
+
hasStripe: Boolean(normalizeText(env.STRIPE_SECRET_KEY)),
|
|
112
|
+
hasPlausible: Boolean(normalizeText(env.PLAUSIBLE_API_KEY) && normalizeText(env.PLAUSIBLE_SITE_ID)),
|
|
113
|
+
hasPosthog: Boolean(normalizeText(env.POSTHOG_PERSONAL_API_KEY) && normalizeText(env.POSTHOG_PROJECT_ID)),
|
|
114
|
+
hasOperator: Boolean(normalizeText(env.THUMBGATE_OPERATOR_KEY) || normalizeText(env.THUMBGATE_API_KEY)),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function observabilityConfigTemplate() {
|
|
119
|
+
return {
|
|
120
|
+
stripeSecretKey: '',
|
|
121
|
+
plausibleApiKey: '',
|
|
122
|
+
plausibleSiteId: 'thumbgate.ai',
|
|
123
|
+
plausibleRegisteredDomains: 'thumbgate.ai,thumbgate-production.up.railway.app',
|
|
124
|
+
posthogPersonalApiKey: '',
|
|
125
|
+
posthogProjectId: '',
|
|
126
|
+
publicAppOrigin: 'https://thumbgate.ai',
|
|
127
|
+
billingApiBaseUrl: 'https://thumbgate-production.up.railway.app',
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
module.exports = {
|
|
132
|
+
OBSERVABILITY_CONFIG_PATH,
|
|
133
|
+
OPERATOR_CONFIG_PATH,
|
|
134
|
+
JSON_KEY_TO_ENV,
|
|
135
|
+
loadObservabilityEnv,
|
|
136
|
+
observabilityConfigTemplate,
|
|
137
|
+
readJsonFile,
|
|
138
|
+
normalizeText,
|
|
139
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const os = require('node:os');
|
|
7
|
+
const {
|
|
8
|
+
OBSERVABILITY_CONFIG_PATH,
|
|
9
|
+
observabilityConfigTemplate,
|
|
10
|
+
loadObservabilityEnv,
|
|
11
|
+
} = require('./observability-env');
|
|
12
|
+
|
|
13
|
+
function ensureDir(filePath) {
|
|
14
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function main(argv = process.argv.slice(2)) {
|
|
18
|
+
const write = argv.includes('--write');
|
|
19
|
+
const print = argv.includes('--print') || !write;
|
|
20
|
+
const template = observabilityConfigTemplate();
|
|
21
|
+
|
|
22
|
+
if (print && !write) {
|
|
23
|
+
process.stdout.write(`${JSON.stringify(template, null, 2)}\n`);
|
|
24
|
+
process.stdout.write(
|
|
25
|
+
`\n# Write to ${OBSERVABILITY_CONFIG_PATH} with --write after filling secrets.\n` +
|
|
26
|
+
'# Never commit this file. Doctor/revenue tools load it automatically.\n'
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (write) {
|
|
31
|
+
ensureDir(OBSERVABILITY_CONFIG_PATH);
|
|
32
|
+
if (fs.existsSync(OBSERVABILITY_CONFIG_PATH)) {
|
|
33
|
+
process.stderr.write(`Refusing to overwrite existing ${OBSERVABILITY_CONFIG_PATH}\n`);
|
|
34
|
+
process.exitCode = 2;
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
fs.writeFileSync(OBSERVABILITY_CONFIG_PATH, `${JSON.stringify(template, null, 2)}\n`, { mode: 0o600 });
|
|
38
|
+
process.stdout.write(`Wrote template ${OBSERVABILITY_CONFIG_PATH}\n`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const status = loadObservabilityEnv({ env: { ...process.env } });
|
|
42
|
+
process.stdout.write(JSON.stringify({
|
|
43
|
+
configPath: OBSERVABILITY_CONFIG_PATH,
|
|
44
|
+
hasStripe: status.hasStripe,
|
|
45
|
+
hasPlausible: status.hasPlausible,
|
|
46
|
+
hasPosthog: status.hasPosthog,
|
|
47
|
+
hasOperator: status.hasOperator,
|
|
48
|
+
}, null, 2) + '\n');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (require('node:path').resolve(process.argv[1] || '') === require('node:path').resolve(__filename)) {
|
|
52
|
+
main();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = { main };
|
|
@@ -30,7 +30,11 @@ function getConfiguredRegisteredDomains(env = process.env) {
|
|
|
30
30
|
...splitDomains(env.PLAUSIBLE_REGISTERED_DOMAINS),
|
|
31
31
|
].map(normalizeDomain).filter(Boolean);
|
|
32
32
|
|
|
33
|
+
// Both product surfaces are first-class Plausible site ids for ThumbGate.
|
|
34
|
+
// Emitting data-domain=thumbgate.ai while only registering the Railway host
|
|
35
|
+
// previously made primary-domain traffic invisible to automation.
|
|
33
36
|
return [...new Set([
|
|
37
|
+
PRIMARY_PLAUSIBLE_DOMAIN,
|
|
34
38
|
FALLBACK_REGISTERED_PLAUSIBLE_DOMAIN,
|
|
35
39
|
...configured,
|
|
36
40
|
])];
|
package/scripts/prompt-eval.js
CHANGED
|
@@ -143,6 +143,8 @@ function handleRejectExpectation(checks, result, expected) {
|
|
|
143
143
|
|
|
144
144
|
const wasRejected = result.accepted === false
|
|
145
145
|
|| result.status === 'rejected'
|
|
146
|
+
|| result.status === 'clarification_required'
|
|
147
|
+
|| result.needsClarification === true
|
|
146
148
|
|| result.actionType === 'no-action';
|
|
147
149
|
checks.push({
|
|
148
150
|
criterion: 'shouldReject',
|
|
@@ -466,8 +468,9 @@ function runSuiteObject(suite, options = {}) {
|
|
|
466
468
|
const skipped = results.filter((r) => r.status === 'skip').length;
|
|
467
469
|
const totalScore = results.length > 0
|
|
468
470
|
? Math.round(results.reduce((s, r) => s + r.score, 0) / results.length)
|
|
469
|
-
:
|
|
471
|
+
: 0;
|
|
470
472
|
const minScore = options.minScore ?? 80;
|
|
473
|
+
const insufficientEvidence = results.length === 0;
|
|
471
474
|
|
|
472
475
|
return {
|
|
473
476
|
suite: suite.name,
|
|
@@ -478,8 +481,9 @@ function runSuiteObject(suite, options = {}) {
|
|
|
478
481
|
skipped,
|
|
479
482
|
score: totalScore,
|
|
480
483
|
minScore,
|
|
481
|
-
pass: totalScore >= minScore,
|
|
482
|
-
noCases:
|
|
484
|
+
pass: !insufficientEvidence && totalScore >= minScore,
|
|
485
|
+
noCases: insufficientEvidence,
|
|
486
|
+
evidenceStatus: insufficientEvidence ? 'insufficient_evidence' : 'measured',
|
|
483
487
|
feedbackDerived: suite.source && suite.source.type === 'feedback-log',
|
|
484
488
|
generatedAt: new Date().toISOString(),
|
|
485
489
|
results,
|
|
@@ -685,6 +689,7 @@ function runSuite(suitePath = DEFAULT_SUITE, options = {}) {
|
|
|
685
689
|
|
|
686
690
|
function compareReports(currentReport, baselineReport) {
|
|
687
691
|
const baselineById = new Map((baselineReport?.results || []).map((result) => [result.id, result]));
|
|
692
|
+
const currentById = new Map((currentReport?.results || []).map((result) => [result.id, result]));
|
|
688
693
|
const regressions = [];
|
|
689
694
|
const improvements = [];
|
|
690
695
|
|
|
@@ -717,12 +722,83 @@ function compareReports(currentReport, baselineReport) {
|
|
|
717
722
|
}
|
|
718
723
|
}
|
|
719
724
|
|
|
725
|
+
for (const baseline of baselineReport?.results || []) {
|
|
726
|
+
if (currentById.has(baseline.id)) continue;
|
|
727
|
+
regressions.push({
|
|
728
|
+
id: baseline.id,
|
|
729
|
+
baselineScore: baseline.score,
|
|
730
|
+
currentScore: null,
|
|
731
|
+
delta: null,
|
|
732
|
+
baselineStatus: baseline.status,
|
|
733
|
+
currentStatus: 'missing',
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
|
|
720
737
|
return {
|
|
721
738
|
baselineSuite: baselineReport?.suite || null,
|
|
722
739
|
baselineScore: Number.isFinite(Number(baselineReport?.score)) ? Number(baselineReport.score) : null,
|
|
723
740
|
scoreDelta: Number.isFinite(Number(baselineReport?.score)) ? currentReport.score - Number(baselineReport.score) : null,
|
|
724
741
|
regressions,
|
|
725
742
|
improvements,
|
|
743
|
+
baselineCases: baselineById.size,
|
|
744
|
+
currentCases: currentById.size,
|
|
745
|
+
baselineCoverageRate: baselineById.size
|
|
746
|
+
? Math.round((Array.from(baselineById.keys()).filter((id) => currentById.has(id)).length / baselineById.size) * 10000) / 10000
|
|
747
|
+
: null,
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
function logSafeResult(result, index) {
|
|
752
|
+
const allowedStatuses = new Set(['pass', 'fail', 'error', 'skip']);
|
|
753
|
+
return {
|
|
754
|
+
case: index + 1,
|
|
755
|
+
status: allowedStatuses.has(result.status) ? result.status : 'error',
|
|
756
|
+
score: Number(result.score || 0),
|
|
757
|
+
passCount: Number(result.passCount || 0),
|
|
758
|
+
totalChecks: Number(result.totalChecks || 0),
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
function logSafeReport(report, suite, fromFeedback) {
|
|
763
|
+
const source = suite?.source || {};
|
|
764
|
+
const comparison = report.comparison
|
|
765
|
+
? {
|
|
766
|
+
baselineScore: Number(report.comparison.baselineScore || 0),
|
|
767
|
+
scoreDelta: Number(report.comparison.scoreDelta || 0),
|
|
768
|
+
regressionCount: Number(report.comparison.regressions?.length || 0),
|
|
769
|
+
improvementCount: Number(report.comparison.improvements?.length || 0),
|
|
770
|
+
baselineCases: Number(report.comparison.baselineCases || 0),
|
|
771
|
+
currentCases: Number(report.comparison.currentCases || 0),
|
|
772
|
+
baselineCoverageRate: report.comparison.baselineCoverageRate,
|
|
773
|
+
}
|
|
774
|
+
: undefined;
|
|
775
|
+
return {
|
|
776
|
+
suiteType: fromFeedback ? 'feedback-derived' : 'configured',
|
|
777
|
+
total: Number(report.total || 0),
|
|
778
|
+
passed: Number(report.passed || 0),
|
|
779
|
+
failed: Number(report.failed || 0),
|
|
780
|
+
errors: Number(report.errors || 0),
|
|
781
|
+
skipped: Number(report.skipped || 0),
|
|
782
|
+
score: Number(report.score || 0),
|
|
783
|
+
minScore: Number(report.minScore || 0),
|
|
784
|
+
pass: report.pass === true,
|
|
785
|
+
noCases: report.noCases === true,
|
|
786
|
+
evidenceStatus: report.evidenceStatus === 'measured' ? 'measured' : 'insufficient_evidence',
|
|
787
|
+
feedbackDerived: report.feedbackDerived === true,
|
|
788
|
+
syntheticCount: Number(report.syntheticCount || 0),
|
|
789
|
+
comparison,
|
|
790
|
+
results: (report.results || []).map(logSafeResult),
|
|
791
|
+
suiteDefinition: fromFeedback
|
|
792
|
+
? {
|
|
793
|
+
version: Number(suite?.version || 0),
|
|
794
|
+
source: {
|
|
795
|
+
type: 'feedback-log',
|
|
796
|
+
totalEntries: Number(source.totalEntries || 0),
|
|
797
|
+
selectedCases: Number(source.selectedCases || 0),
|
|
798
|
+
},
|
|
799
|
+
evaluationCount: Number(suite?.evaluations?.length || 0),
|
|
800
|
+
}
|
|
801
|
+
: undefined,
|
|
726
802
|
};
|
|
727
803
|
}
|
|
728
804
|
|
|
@@ -847,7 +923,7 @@ if (isCliInvocation()) {
|
|
|
847
923
|
}
|
|
848
924
|
|
|
849
925
|
if (json) {
|
|
850
|
-
console.log(JSON.stringify(
|
|
926
|
+
console.log(JSON.stringify(logSafeReport(report, suite, fromFeedback), null, 2));
|
|
851
927
|
} else {
|
|
852
928
|
console.log(`\n${report.suite}`);
|
|
853
929
|
console.log('='.repeat(50));
|
|
@@ -883,6 +959,7 @@ module.exports = {
|
|
|
883
959
|
gradeOutput,
|
|
884
960
|
loadSuite,
|
|
885
961
|
loadReport,
|
|
962
|
+
logSafeReport,
|
|
886
963
|
compareReports,
|
|
887
964
|
readJsonl,
|
|
888
965
|
runEvaluation,
|