thumbgate 1.14.1 → 1.16.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/marketplace.json +6 -6
- package/.claude-plugin/plugin.json +3 -3
- package/.well-known/llms.txt +5 -5
- package/.well-known/mcp/server-card.json +1 -1
- package/README.md +60 -35
- package/adapters/chatgpt/openapi.yaml +118 -2
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/mcp/server-stdio.js +217 -84
- package/adapters/opencode/opencode.json +1 -1
- package/bench/prompt-eval-suite.json +5 -1
- package/bin/cli.js +211 -8
- package/config/enforcement.json +59 -7
- package/config/evals/agent-safety-eval.json +338 -22
- package/config/gates/default.json +33 -0
- package/config/gates/routine.json +43 -0
- package/config/github-about.json +3 -3
- package/config/mcp-allowlists.json +4 -0
- package/config/merge-quality-checks.json +2 -1
- package/config/model-candidates.json +131 -0
- package/openapi/openapi.yaml +118 -2
- package/package.json +70 -51
- package/public/blog.html +7 -7
- package/public/codex-plugin.html +13 -7
- package/public/compare.html +29 -23
- package/public/dashboard.html +105 -12
- package/public/guide.html +28 -28
- package/public/index.html +233 -97
- package/public/learn.html +87 -20
- package/public/lessons.html +26 -2
- package/public/numbers.html +271 -0
- package/public/pro.html +89 -19
- package/scripts/agent-audit-trace.js +55 -0
- package/scripts/agent-memory-lifecycle.js +96 -0
- package/scripts/agent-readiness-plan.js +118 -0
- package/scripts/agentic-data-pipeline.js +21 -1
- package/scripts/agents-sdk-sandbox-plan.js +57 -0
- package/scripts/ai-org-governance.js +98 -0
- package/scripts/ai-search-distribution.js +43 -0
- package/scripts/artifact-agent-plan.js +81 -0
- package/scripts/billing.js +27 -8
- package/scripts/cli-feedback.js +2 -1
- package/scripts/cli-schema.js +60 -5
- package/scripts/code-mode-mcp-plan.js +71 -0
- package/scripts/commercial-offer.js +1 -1
- package/scripts/context-engine.js +1 -2
- package/scripts/context-manager.js +4 -1
- package/scripts/contextfs.js +214 -32
- package/scripts/dashboard-render-spec.js +1 -1
- package/scripts/dashboard.js +275 -9
- package/scripts/decision-journal.js +13 -3
- package/scripts/document-workflow-governance.js +62 -0
- package/scripts/enterprise-agent-rollout.js +34 -0
- package/scripts/experience-replay-governance.js +69 -0
- package/scripts/export-hf-dataset.js +1 -1
- package/scripts/feedback-loop.js +141 -9
- package/scripts/feedback-to-rules.js +17 -23
- package/scripts/gates-engine.js +4 -6
- package/scripts/growth-campaigns.js +49 -0
- package/scripts/harness-selector.js +145 -1
- package/scripts/hybrid-supervisor-agent.js +64 -0
- package/scripts/inference-cache-policy.js +72 -0
- package/scripts/inference-economics.js +53 -0
- package/scripts/internal-agent-bootstrap.js +12 -2
- package/scripts/knowledge-layer-plan.js +108 -0
- package/scripts/lesson-canonical.js +181 -0
- package/scripts/lesson-db.js +71 -10
- package/scripts/lesson-inference.js +183 -44
- package/scripts/lesson-search.js +4 -1
- package/scripts/lesson-synthesis.js +23 -2
- package/scripts/llm-client.js +157 -26
- package/scripts/mailer/resend-mailer.js +112 -1
- package/scripts/mcp-transport-strategy.js +66 -0
- package/scripts/memory-store-governance.js +60 -0
- package/scripts/meta-agent-loop.js +7 -13
- package/scripts/model-access-eligibility.js +38 -0
- package/scripts/model-migration-readiness.js +55 -0
- package/scripts/native-messaging-audit.js +514 -0
- package/scripts/operational-integrity.js +96 -3
- package/scripts/otel-declarative-config.js +56 -0
- package/scripts/perplexity-client.js +1 -1
- package/scripts/post-training-governance.js +34 -0
- package/scripts/pr-manager.js +47 -7
- package/scripts/private-core-boundary.js +72 -0
- package/scripts/production-agent-readiness.js +40 -0
- package/scripts/profile-router.js +16 -1
- package/scripts/prompt-eval.js +564 -32
- package/scripts/prompt-programs.js +93 -0
- package/scripts/provider-action-normalizer.js +585 -0
- package/scripts/rule-validator.js +285 -0
- package/scripts/scaling-law-claims.js +60 -0
- package/scripts/security-scanner.js +1 -1
- package/scripts/self-distill-agent.js +7 -32
- package/scripts/seo-gsd.js +400 -43
- package/scripts/skill-rag-router.js +53 -0
- package/scripts/spec-gate.js +1 -1
- package/scripts/student-consistent-training.js +73 -0
- package/scripts/synthetic-data-provenance.js +98 -0
- package/scripts/task-context-result.js +81 -0
- package/scripts/telemetry-analytics.js +149 -0
- package/scripts/thompson-sampling.js +2 -2
- package/scripts/token-savings.js +7 -6
- package/scripts/token-tco.js +46 -0
- package/scripts/tool-registry.js +75 -3
- package/scripts/verification-loop.js +10 -1
- package/scripts/verifier-scoring.js +71 -0
- package/scripts/workflow-sentinel.js +284 -28
- package/scripts/workspace-agent-routines.js +118 -0
- package/skills/thumbgate/SKILL.md +1 -1
- package/src/api/server.js +434 -120
- package/.claude-plugin/README.md +0 -170
- package/adapters/README.md +0 -12
- package/scripts/analytics-report.js +0 -328
- package/scripts/autonomous-workflow.js +0 -377
- package/scripts/billing-setup.js +0 -109
- package/scripts/creator-campaigns.js +0 -239
- package/scripts/cross-encoder-reranker.js +0 -235
- package/scripts/daemon-manager.js +0 -108
- package/scripts/decision-trace.js +0 -354
- package/scripts/delegation-runtime.js +0 -896
- package/scripts/dispatch-brief.js +0 -159
- package/scripts/distribution-surfaces.js +0 -110
- package/scripts/feedback-history-distiller.js +0 -382
- package/scripts/funnel-analytics.js +0 -35
- package/scripts/history-distiller.js +0 -200
- package/scripts/hosted-job-launcher.js +0 -256
- package/scripts/intent-router.js +0 -392
- package/scripts/lesson-reranker.js +0 -263
- package/scripts/lesson-retrieval.js +0 -148
- package/scripts/managed-lesson-agent.js +0 -183
- package/scripts/operational-dashboard.js +0 -103
- package/scripts/operational-summary.js +0 -129
- package/scripts/operator-artifacts.js +0 -608
- package/scripts/optimize-context.js +0 -17
- package/scripts/org-dashboard.js +0 -206
- package/scripts/partner-orchestration.js +0 -146
- package/scripts/predictive-insights.js +0 -356
- package/scripts/pulse.js +0 -80
- package/scripts/reflector-agent.js +0 -221
- package/scripts/sales-pipeline.js +0 -681
- package/scripts/session-episode-store.js +0 -329
- package/scripts/session-health-sensor.js +0 -242
- package/scripts/session-report.js +0 -120
- package/scripts/swarm-coordinator.js +0 -81
- package/scripts/tool-kpi-tracker.js +0 -12
- package/scripts/webhook-delivery.js +0 -62
- package/scripts/workflow-sprint-intake.js +0 -475
- package/skills/agent-memory/SKILL.md +0 -97
- package/skills/solve-architecture-autonomy/SKILL.md +0 -17
- package/skills/solve-architecture-autonomy/tool.js +0 -33
- package/skills/thumbgate-feedback/SKILL.md +0 -49
|
@@ -1,356 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
function toNumber(value) {
|
|
5
|
-
const numeric = Number(value || 0);
|
|
6
|
-
return Number.isFinite(numeric) ? numeric : 0;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
function safeRate(numerator, denominator, precision = 4) {
|
|
10
|
-
if (!denominator) return 0;
|
|
11
|
-
return Number((toNumber(numerator) / toNumber(denominator)).toFixed(precision));
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function clamp01(value) {
|
|
15
|
-
return Math.max(0, Math.min(1, toNumber(value)));
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function normalizeCapped(value, cap) {
|
|
19
|
-
const capped = toNumber(cap);
|
|
20
|
-
if (!capped || capped <= 0) return 0;
|
|
21
|
-
return clamp01(toNumber(value) / capped);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function sumCounter(counter = {}) {
|
|
25
|
-
return Object.values(counter).reduce((sum, value) => sum + toNumber(value), 0);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function summarizeSeverity(anomalies = []) {
|
|
29
|
-
if (anomalies.some((entry) => entry.severity === 'critical')) return 'critical';
|
|
30
|
-
if (anomalies.some((entry) => entry.severity === 'warning')) return 'warning';
|
|
31
|
-
return 'healthy';
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function toBand(score) {
|
|
35
|
-
if (score >= 0.8) return 'very_high';
|
|
36
|
-
if (score >= 0.6) return 'high';
|
|
37
|
-
if (score >= 0.4) return 'medium';
|
|
38
|
-
if (score >= 0.2) return 'low';
|
|
39
|
-
return 'very_low';
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function weightedAverage(entries = []) {
|
|
43
|
-
const active = entries.filter((entry) => entry && Number.isFinite(entry.value) && entry.weight > 0);
|
|
44
|
-
const totalWeight = active.reduce((sum, entry) => sum + entry.weight, 0);
|
|
45
|
-
if (!totalWeight) return 0;
|
|
46
|
-
return active.reduce((sum, entry) => sum + (entry.value * entry.weight), 0) / totalWeight;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function smoothedRate(successes, trials, priorRate = 0, priorWeight = 8) {
|
|
50
|
-
const trialCount = Math.max(0, toNumber(trials));
|
|
51
|
-
const successCount = Math.max(0, toNumber(successes));
|
|
52
|
-
const boundedPrior = clamp01(priorRate);
|
|
53
|
-
return safeRate(successCount + (boundedPrior * priorWeight), trialCount + priorWeight, 6);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function countQualifiedCreators(rows = []) {
|
|
57
|
-
return rows.filter((row) => {
|
|
58
|
-
return toNumber(row.workflowSprintLeads) > 0
|
|
59
|
-
|| toNumber(row.qualifiedWorkflowSprintLeads) > 0
|
|
60
|
-
|| toNumber(row.bookedRevenueCents) > 0
|
|
61
|
-
|| toNumber(row.checkoutStarts) > 0;
|
|
62
|
-
}).length;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function buildBenchmarks({ telemetryAnalytics = {}, billingSummary = {}, stagingModel = {} } = {}) {
|
|
66
|
-
const uniqueVisitors = toNumber(telemetryAnalytics.visitors && telemetryAnalytics.visitors.uniqueVisitors);
|
|
67
|
-
const checkoutStarts = toNumber(telemetryAnalytics.ctas && telemetryAnalytics.ctas.checkoutStarts);
|
|
68
|
-
const acquisitionLeads = toNumber(billingSummary.signups && billingSummary.signups.uniqueLeads);
|
|
69
|
-
const paidCustomers = toNumber(billingSummary.revenue && billingSummary.revenue.paidCustomers);
|
|
70
|
-
const bookedRevenueCents = toNumber(billingSummary.revenue && billingSummary.revenue.bookedRevenueCents);
|
|
71
|
-
const revenuePerPaidCents = paidCustomers > 0
|
|
72
|
-
? Math.max(1, Math.round(bookedRevenueCents / paidCustomers))
|
|
73
|
-
: 1900;
|
|
74
|
-
const creatorRows = Array.isArray(stagingModel.dims && stagingModel.dims.creators)
|
|
75
|
-
? stagingModel.dims.creators
|
|
76
|
-
: [];
|
|
77
|
-
|
|
78
|
-
return {
|
|
79
|
-
uniqueVisitors,
|
|
80
|
-
checkoutStarts,
|
|
81
|
-
acquisitionLeads,
|
|
82
|
-
paidCustomers,
|
|
83
|
-
bookedRevenueCents,
|
|
84
|
-
revenuePerPaidCents,
|
|
85
|
-
visitorToCheckoutRate: safeRate(checkoutStarts, uniqueVisitors, 6),
|
|
86
|
-
visitorToLeadRate: safeRate(acquisitionLeads, uniqueVisitors, 6),
|
|
87
|
-
visitorToPaidRate: safeRate(paidCustomers, uniqueVisitors, 6),
|
|
88
|
-
checkoutToLeadRate: safeRate(acquisitionLeads, checkoutStarts, 6),
|
|
89
|
-
checkoutToPaidRate: safeRate(paidCustomers, checkoutStarts, 6),
|
|
90
|
-
leadToPaidRate: safeRate(paidCustomers, acquisitionLeads, 6),
|
|
91
|
-
creatorCount: creatorRows.length,
|
|
92
|
-
qualifiedCreatorCount: countQualifiedCreators(creatorRows),
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function buildOpportunityForecast(row, benchmarks, labelKey) {
|
|
97
|
-
const pageViews = toNumber(row.pageViews);
|
|
98
|
-
const checkoutStarts = toNumber(row.checkoutStarts);
|
|
99
|
-
const acquisitionLeads = toNumber(row.acquisitionLeads);
|
|
100
|
-
const paidCustomers = toNumber(row.paidCustomers);
|
|
101
|
-
const bookedRevenueCents = toNumber(row.bookedRevenueCents);
|
|
102
|
-
const workflowSprintLeads = toNumber(row.workflowSprintLeads);
|
|
103
|
-
const qualifiedWorkflowSprintLeads = toNumber(row.qualifiedWorkflowSprintLeads);
|
|
104
|
-
|
|
105
|
-
const visitorToPaid = smoothedRate(
|
|
106
|
-
paidCustomers,
|
|
107
|
-
pageViews,
|
|
108
|
-
benchmarks.visitorToPaidRate,
|
|
109
|
-
12
|
|
110
|
-
);
|
|
111
|
-
const checkoutToPaid = smoothedRate(
|
|
112
|
-
paidCustomers,
|
|
113
|
-
checkoutStarts,
|
|
114
|
-
benchmarks.checkoutToPaidRate,
|
|
115
|
-
8
|
|
116
|
-
);
|
|
117
|
-
const leadToPaid = smoothedRate(
|
|
118
|
-
paidCustomers,
|
|
119
|
-
acquisitionLeads,
|
|
120
|
-
benchmarks.leadToPaidRate,
|
|
121
|
-
6
|
|
122
|
-
);
|
|
123
|
-
|
|
124
|
-
const predictedPaidCustomers = Math.max(
|
|
125
|
-
paidCustomers,
|
|
126
|
-
weightedAverage([
|
|
127
|
-
{ value: pageViews * visitorToPaid, weight: pageViews > 0 ? 0.2 : 0 },
|
|
128
|
-
{ value: checkoutStarts * checkoutToPaid, weight: checkoutStarts > 0 ? 0.45 : 0 },
|
|
129
|
-
{ value: acquisitionLeads * leadToPaid, weight: acquisitionLeads > 0 ? 0.35 : 0 },
|
|
130
|
-
])
|
|
131
|
-
);
|
|
132
|
-
const predictedBookedRevenueCents = Math.round(predictedPaidCustomers * benchmarks.revenuePerPaidCents);
|
|
133
|
-
const opportunityRevenueCents = Math.max(0, predictedBookedRevenueCents - bookedRevenueCents);
|
|
134
|
-
const sampleVolume = pageViews + checkoutStarts + (acquisitionLeads * 2) + (workflowSprintLeads * 3);
|
|
135
|
-
const confidence = clamp01(Math.log1p(sampleVolume) / Math.log1p(40));
|
|
136
|
-
const momentumScore = clamp01(weightedAverage([
|
|
137
|
-
{ value: normalizeCapped(pageViews, 50), weight: 0.2 },
|
|
138
|
-
{ value: normalizeCapped(checkoutStarts, 12), weight: 0.35 },
|
|
139
|
-
{ value: normalizeCapped(acquisitionLeads, 6), weight: 0.25 },
|
|
140
|
-
{ value: normalizeCapped(qualifiedWorkflowSprintLeads, 3), weight: 0.2 },
|
|
141
|
-
]));
|
|
142
|
-
|
|
143
|
-
return {
|
|
144
|
-
key: row.key || 'unknown',
|
|
145
|
-
label: row[labelKey] || row.key || 'unknown',
|
|
146
|
-
pageViews,
|
|
147
|
-
checkoutStarts,
|
|
148
|
-
acquisitionLeads,
|
|
149
|
-
paidCustomers,
|
|
150
|
-
bookedRevenueCents,
|
|
151
|
-
workflowSprintLeads,
|
|
152
|
-
qualifiedWorkflowSprintLeads,
|
|
153
|
-
predictedPaidCustomers: Number(predictedPaidCustomers.toFixed(2)),
|
|
154
|
-
predictedBookedRevenueCents,
|
|
155
|
-
opportunityRevenueCents,
|
|
156
|
-
confidence: Number(confidence.toFixed(4)),
|
|
157
|
-
momentumScore: Number(momentumScore.toFixed(4)),
|
|
158
|
-
band: toBand(clamp01((confidence * 0.45) + (momentumScore * 0.55))),
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function scoreDimensionForecasts(rows = [], benchmarks = {}, labelKey = 'key') {
|
|
163
|
-
return rows
|
|
164
|
-
.map((row) => buildOpportunityForecast(row, benchmarks, labelKey))
|
|
165
|
-
.filter((row) => row.pageViews > 0 || row.checkoutStarts > 0 || row.acquisitionLeads > 0 || row.bookedRevenueCents > 0)
|
|
166
|
-
.sort((left, right) => {
|
|
167
|
-
if (right.opportunityRevenueCents !== left.opportunityRevenueCents) {
|
|
168
|
-
return right.opportunityRevenueCents - left.opportunityRevenueCents;
|
|
169
|
-
}
|
|
170
|
-
if (right.predictedBookedRevenueCents !== left.predictedBookedRevenueCents) {
|
|
171
|
-
return right.predictedBookedRevenueCents - left.predictedBookedRevenueCents;
|
|
172
|
-
}
|
|
173
|
-
return String(left.key).localeCompare(String(right.key));
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function summarizeDrivers(drivers = []) {
|
|
178
|
-
return drivers
|
|
179
|
-
.sort((left, right) => right.impact - left.impact)
|
|
180
|
-
.filter((driver) => driver.impact > 0)
|
|
181
|
-
.slice(0, 3)
|
|
182
|
-
.map((driver) => ({
|
|
183
|
-
key: driver.key,
|
|
184
|
-
label: driver.label,
|
|
185
|
-
impact: Number(driver.impact.toFixed(4)),
|
|
186
|
-
rawValue: driver.rawValue,
|
|
187
|
-
}));
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function scoreUpgradePropensity({ telemetryAnalytics = {}, billingSummary = {}, stagingModel = {}, gateStats = {}, team = {} } = {}) {
|
|
191
|
-
const benchmarks = buildBenchmarks({ telemetryAnalytics, billingSummary, stagingModel });
|
|
192
|
-
const pricingSignals = toNumber(telemetryAnalytics.pricing && telemetryAnalytics.pricing.pricingInterestEvents);
|
|
193
|
-
const tooExpensiveSignals = toNumber(telemetryAnalytics.buyerLoss && telemetryAnalytics.buyerLoss.reasonsByCode && telemetryAnalytics.buyerLoss.reasonsByCode.too_expensive);
|
|
194
|
-
const workflowSprintLeads = toNumber(billingSummary.pipeline && billingSummary.pipeline.workflowSprintLeads && billingSummary.pipeline.workflowSprintLeads.total);
|
|
195
|
-
const qualifiedWorkflowSprintLeads = toNumber(billingSummary.pipeline && billingSummary.pipeline.qualifiedWorkflowSprintLeads && billingSummary.pipeline.qualifiedWorkflowSprintLeads.total);
|
|
196
|
-
const activeProKeys = toNumber(billingSummary.keys && billingSummary.keys.active);
|
|
197
|
-
const totalUsage = toNumber(billingSummary.keys && billingSummary.keys.totalUsage);
|
|
198
|
-
const blockedActions = toNumber(gateStats.blocked);
|
|
199
|
-
const activeAgents = toNumber(team.activeAgents);
|
|
200
|
-
|
|
201
|
-
const proDrivers = [
|
|
202
|
-
{ key: 'checkoutStarts', label: 'checkout starts', impact: normalizeCapped(benchmarks.checkoutStarts, 12) * 0.28, rawValue: benchmarks.checkoutStarts },
|
|
203
|
-
{ key: 'acquisitionLeads', label: 'captured leads', impact: normalizeCapped(benchmarks.acquisitionLeads, 6) * 0.2, rawValue: benchmarks.acquisitionLeads },
|
|
204
|
-
{ key: 'visitorToCheckoutRate', label: 'visitor → checkout rate', impact: normalizeCapped(benchmarks.visitorToCheckoutRate, 0.08) * 0.18, rawValue: benchmarks.visitorToCheckoutRate },
|
|
205
|
-
{ key: 'pricingInterest', label: 'pricing intent', impact: normalizeCapped(pricingSignals, 8) * 0.14, rawValue: pricingSignals },
|
|
206
|
-
{ key: 'totalUsage', label: 'usage depth', impact: normalizeCapped(totalUsage, 300) * 0.12, rawValue: totalUsage },
|
|
207
|
-
{ key: 'blockedActions', label: 'blocked mistakes', impact: normalizeCapped(blockedActions, 30) * 0.08, rawValue: blockedActions },
|
|
208
|
-
];
|
|
209
|
-
const proPenalty = normalizeCapped(tooExpensiveSignals, 6) * 0.12;
|
|
210
|
-
const proScore = clamp01(proDrivers.reduce((sum, driver) => sum + driver.impact, 0) - proPenalty);
|
|
211
|
-
|
|
212
|
-
const teamDrivers = [
|
|
213
|
-
{ key: 'qualifiedWorkflowSprintLeads', label: 'qualified workflow sprint leads', impact: normalizeCapped(qualifiedWorkflowSprintLeads, 3) * 0.3, rawValue: qualifiedWorkflowSprintLeads },
|
|
214
|
-
{ key: 'workflowSprintLeads', label: 'workflow sprint leads', impact: normalizeCapped(workflowSprintLeads, 5) * 0.22, rawValue: workflowSprintLeads },
|
|
215
|
-
{ key: 'activeProKeys', label: 'active Pro keys', impact: normalizeCapped(activeProKeys, 5) * 0.16, rawValue: activeProKeys },
|
|
216
|
-
{ key: 'qualifiedCreators', label: 'qualified creators/channels', impact: normalizeCapped(benchmarks.qualifiedCreatorCount, 4) * 0.12, rawValue: benchmarks.qualifiedCreatorCount },
|
|
217
|
-
{ key: 'activeAgents', label: 'active agents', impact: normalizeCapped(activeAgents, 6) * 0.12, rawValue: activeAgents },
|
|
218
|
-
{ key: 'leadToPaidRate', label: 'lead → paid rate', impact: normalizeCapped(benchmarks.leadToPaidRate, 0.5) * 0.08, rawValue: benchmarks.leadToPaidRate },
|
|
219
|
-
];
|
|
220
|
-
const teamScore = clamp01(teamDrivers.reduce((sum, driver) => sum + driver.impact, 0));
|
|
221
|
-
|
|
222
|
-
return {
|
|
223
|
-
pro: {
|
|
224
|
-
score: Number(proScore.toFixed(4)),
|
|
225
|
-
band: toBand(proScore),
|
|
226
|
-
confidence: Number(clamp01(Math.log1p(benchmarks.uniqueVisitors + benchmarks.checkoutStarts + pricingSignals) / Math.log1p(80)).toFixed(4)),
|
|
227
|
-
drivers: summarizeDrivers(proDrivers),
|
|
228
|
-
pricingResistanceSignals: tooExpensiveSignals,
|
|
229
|
-
},
|
|
230
|
-
team: {
|
|
231
|
-
score: Number(teamScore.toFixed(4)),
|
|
232
|
-
band: toBand(teamScore),
|
|
233
|
-
confidence: Number(clamp01(Math.log1p(workflowSprintLeads + qualifiedWorkflowSprintLeads + activeProKeys + activeAgents) / Math.log1p(24)).toFixed(4)),
|
|
234
|
-
drivers: summarizeDrivers(teamDrivers),
|
|
235
|
-
},
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
function detectPredictiveAnomalies({ telemetryAnalytics = {}, billingSummary = {}, creatorForecasts = [], sourceForecasts = [] } = {}) {
|
|
240
|
-
const anomalies = [];
|
|
241
|
-
const uniqueVisitors = toNumber(telemetryAnalytics.visitors && telemetryAnalytics.visitors.uniqueVisitors);
|
|
242
|
-
const checkoutStarts = toNumber(telemetryAnalytics.ctas && telemetryAnalytics.ctas.checkoutStarts);
|
|
243
|
-
const paidCustomers = toNumber(billingSummary.revenue && billingSummary.revenue.paidCustomers);
|
|
244
|
-
const attributionCoverageRate = toNumber(telemetryAnalytics.visitors && telemetryAnalytics.visitors.attributionCoverageRate);
|
|
245
|
-
const unreconciledPaidEvents = toNumber(billingSummary.dataQuality && billingSummary.dataQuality.unreconciledPaidEvents);
|
|
246
|
-
const tooExpensiveSignals = toNumber(telemetryAnalytics.buyerLoss && telemetryAnalytics.buyerLoss.reasonsByCode && telemetryAnalytics.buyerLoss.reasonsByCode.too_expensive);
|
|
247
|
-
const buyerLossSignals = toNumber(telemetryAnalytics.buyerLoss && telemetryAnalytics.buyerLoss.totalSignals);
|
|
248
|
-
|
|
249
|
-
if (checkoutStarts >= 3 && paidCustomers === 0) {
|
|
250
|
-
anomalies.push({
|
|
251
|
-
type: 'conversion_stall',
|
|
252
|
-
severity: checkoutStarts >= 8 ? 'critical' : 'warning',
|
|
253
|
-
message: 'Checkout starts are arriving without paid conversions.',
|
|
254
|
-
evidence: `checkoutStarts=${checkoutStarts}, paidCustomers=${paidCustomers}`,
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
if (uniqueVisitors >= 10 && attributionCoverageRate < 0.6) {
|
|
259
|
-
anomalies.push({
|
|
260
|
-
type: 'attribution_blindspot',
|
|
261
|
-
severity: attributionCoverageRate < 0.35 ? 'critical' : 'warning',
|
|
262
|
-
message: 'Attribution coverage is too low for reliable predictive routing.',
|
|
263
|
-
evidence: `uniqueVisitors=${uniqueVisitors}, attributionCoverageRate=${attributionCoverageRate}`,
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
if (unreconciledPaidEvents > 0) {
|
|
268
|
-
anomalies.push({
|
|
269
|
-
type: 'billing_reconciliation',
|
|
270
|
-
severity: unreconciledPaidEvents >= 3 ? 'critical' : 'warning',
|
|
271
|
-
message: 'Paid events are waiting on reconciliation, which weakens revenue forecasts.',
|
|
272
|
-
evidence: `unreconciledPaidEvents=${unreconciledPaidEvents}`,
|
|
273
|
-
});
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
if (buyerLossSignals >= 3 && safeRate(tooExpensiveSignals, buyerLossSignals, 4) >= 0.5) {
|
|
277
|
-
anomalies.push({
|
|
278
|
-
type: 'pricing_resistance',
|
|
279
|
-
severity: 'warning',
|
|
280
|
-
message: 'Price sensitivity dominates current loss reasons.',
|
|
281
|
-
evidence: `tooExpensiveSignals=${tooExpensiveSignals}, buyerLossSignals=${buyerLossSignals}`,
|
|
282
|
-
});
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
const underperformingCreator = creatorForecasts.find((row) => row.checkoutStarts >= 2 && row.bookedRevenueCents === 0 && row.opportunityRevenueCents >= 1500);
|
|
286
|
-
if (underperformingCreator) {
|
|
287
|
-
anomalies.push({
|
|
288
|
-
type: 'creator_underperformance',
|
|
289
|
-
severity: 'warning',
|
|
290
|
-
message: `Creator ${underperformingCreator.key} is generating intent without revenue conversion.`,
|
|
291
|
-
evidence: `checkouts=${underperformingCreator.checkoutStarts}, opportunityRevenueCents=${underperformingCreator.opportunityRevenueCents}`,
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
const underperformingSource = sourceForecasts.find((row) => row.checkoutStarts >= 3 && row.bookedRevenueCents === 0 && row.opportunityRevenueCents >= 1900);
|
|
296
|
-
if (underperformingSource) {
|
|
297
|
-
anomalies.push({
|
|
298
|
-
type: 'channel_underperformance',
|
|
299
|
-
severity: 'warning',
|
|
300
|
-
message: `Channel ${underperformingSource.key} is leaking revenue between checkout and paid.`,
|
|
301
|
-
evidence: `checkouts=${underperformingSource.checkoutStarts}, opportunityRevenueCents=${underperformingSource.opportunityRevenueCents}`,
|
|
302
|
-
});
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
return anomalies;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
function buildPredictiveInsights({ telemetryAnalytics = {}, billingSummary = {}, stagingModel = {}, gateStats = {}, team = {} } = {}) {
|
|
309
|
-
const benchmarks = buildBenchmarks({ telemetryAnalytics, billingSummary, stagingModel });
|
|
310
|
-
const creators = scoreDimensionForecasts(stagingModel.dims && stagingModel.dims.creators ? stagingModel.dims.creators : [], benchmarks);
|
|
311
|
-
const sources = scoreDimensionForecasts(stagingModel.dims && stagingModel.dims.sources ? stagingModel.dims.sources : [], benchmarks);
|
|
312
|
-
const upgradePropensity = scoreUpgradePropensity({ telemetryAnalytics, billingSummary, stagingModel, gateStats, team });
|
|
313
|
-
const anomalies = detectPredictiveAnomalies({
|
|
314
|
-
telemetryAnalytics,
|
|
315
|
-
billingSummary,
|
|
316
|
-
creatorForecasts: creators,
|
|
317
|
-
sourceForecasts: sources,
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
const aggregatePredictedBookedRevenueCents = Math.max(
|
|
321
|
-
benchmarks.bookedRevenueCents,
|
|
322
|
-
Math.round(weightedAverage([
|
|
323
|
-
{ value: benchmarks.uniqueVisitors * smoothedRate(benchmarks.paidCustomers, benchmarks.uniqueVisitors, benchmarks.visitorToPaidRate, 12) * benchmarks.revenuePerPaidCents, weight: benchmarks.uniqueVisitors > 0 ? 0.2 : 0 },
|
|
324
|
-
{ value: benchmarks.checkoutStarts * smoothedRate(benchmarks.paidCustomers, benchmarks.checkoutStarts, benchmarks.checkoutToPaidRate, 8) * benchmarks.revenuePerPaidCents, weight: benchmarks.checkoutStarts > 0 ? 0.45 : 0 },
|
|
325
|
-
{ value: benchmarks.acquisitionLeads * smoothedRate(benchmarks.paidCustomers, benchmarks.acquisitionLeads, benchmarks.leadToPaidRate, 6) * benchmarks.revenuePerPaidCents, weight: benchmarks.acquisitionLeads > 0 ? 0.35 : 0 },
|
|
326
|
-
]))
|
|
327
|
-
);
|
|
328
|
-
|
|
329
|
-
return {
|
|
330
|
-
generatedAt: new Date().toISOString(),
|
|
331
|
-
modelVersion: 'predictive-insights-v1',
|
|
332
|
-
benchmarks,
|
|
333
|
-
upgradePropensity,
|
|
334
|
-
revenueForecast: {
|
|
335
|
-
predictedBookedRevenueCents: aggregatePredictedBookedRevenueCents,
|
|
336
|
-
incrementalOpportunityCents: Math.max(0, aggregatePredictedBookedRevenueCents - benchmarks.bookedRevenueCents),
|
|
337
|
-
confidence: Number(clamp01((upgradePropensity.pro.confidence + upgradePropensity.team.confidence) / 2).toFixed(4)),
|
|
338
|
-
band: toBand(clamp01((upgradePropensity.pro.score * 0.55) + (upgradePropensity.team.score * 0.45))),
|
|
339
|
-
},
|
|
340
|
-
topCreators: creators.slice(0, 5),
|
|
341
|
-
topSources: sources.slice(0, 5),
|
|
342
|
-
anomalies,
|
|
343
|
-
anomalySummary: {
|
|
344
|
-
count: anomalies.length,
|
|
345
|
-
severity: summarizeSeverity(anomalies),
|
|
346
|
-
},
|
|
347
|
-
};
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
module.exports = {
|
|
351
|
-
buildBenchmarks,
|
|
352
|
-
buildPredictiveInsights,
|
|
353
|
-
detectPredictiveAnomalies,
|
|
354
|
-
scoreDimensionForecasts,
|
|
355
|
-
scoreUpgradePropensity,
|
|
356
|
-
};
|
package/scripts/pulse.js
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
const { getOperationalBillingSummary } = require('./operational-summary');
|
|
3
|
-
|
|
4
|
-
function getPulseSnapshot(summary, now = new Date()) {
|
|
5
|
-
const funnel = summary.funnel || {};
|
|
6
|
-
const revenue = summary.revenue || {};
|
|
7
|
-
const signups = summary.signups || {};
|
|
8
|
-
const trafficMetrics = summary.trafficMetrics || {};
|
|
9
|
-
const pipeline = summary.pipeline || {};
|
|
10
|
-
const operatorGeneratedAcquisition = summary.operatorGeneratedAcquisition || {};
|
|
11
|
-
const dataQuality = summary.dataQuality || {};
|
|
12
|
-
const leadCount = signups.uniqueLeads || 0;
|
|
13
|
-
const activeCount = funnel.stageCounts ? funnel.stageCounts.activation || 0 : 0;
|
|
14
|
-
const sprintLeads = pipeline.workflowSprintLeads ? pipeline.workflowSprintLeads.total || 0 : 0;
|
|
15
|
-
const qualifiedSprintLeads = pipeline.qualifiedWorkflowSprintLeads ? pipeline.qualifiedWorkflowSprintLeads.total || 0 : 0;
|
|
16
|
-
const paidProviderEvents = revenue.paidProviderEvents || 0;
|
|
17
|
-
const paidOrders = revenue.paidOrders || 0;
|
|
18
|
-
const bookedRevenueCents = revenue.bookedRevenueCents || 0;
|
|
19
|
-
const conversionRate = leadCount > 0 ? ((paidOrders / leadCount) * 100).toFixed(2) : '0.00';
|
|
20
|
-
const health = bookedRevenueCents > 0
|
|
21
|
-
? '🟢 BOOKED REVENUE ACTIVE'
|
|
22
|
-
: ((leadCount > 0 || sprintLeads > 0) ? '🟡 PIPELINE ACTIVE' : '🔴 BLIND / COLD');
|
|
23
|
-
|
|
24
|
-
let eta = 'N/A';
|
|
25
|
-
if (bookedRevenueCents === 0 && (leadCount > 0 || sprintLeads > 0)) {
|
|
26
|
-
const hoursRemaining = 4;
|
|
27
|
-
const etaDate = new Date(now.getTime() + hoursRemaining * 60 * 60 * 1000);
|
|
28
|
-
eta = `${etaDate.toLocaleTimeString()} (Decision Window)`;
|
|
29
|
-
} else if (bookedRevenueCents > 0) {
|
|
30
|
-
eta = 'SUCCESS';
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return {
|
|
34
|
-
leadCount,
|
|
35
|
-
activeCount,
|
|
36
|
-
visitors: trafficMetrics.visitors || 0,
|
|
37
|
-
ctaClicks: trafficMetrics.ctaClicks || 0,
|
|
38
|
-
checkoutStarts: trafficMetrics.checkoutStarts || 0,
|
|
39
|
-
sprintLeads,
|
|
40
|
-
qualifiedSprintLeads,
|
|
41
|
-
paidProviderEvents,
|
|
42
|
-
paidOrders,
|
|
43
|
-
bookedRevenueCents,
|
|
44
|
-
conversionRate,
|
|
45
|
-
health,
|
|
46
|
-
eta,
|
|
47
|
-
operatorGeneratedAcquisitionEvents: operatorGeneratedAcquisition.totalEvents || 0,
|
|
48
|
-
operatorGeneratedUniqueLeads: operatorGeneratedAcquisition.uniqueLeads || 0,
|
|
49
|
-
unreconciledPaidEvents: dataQuality.unreconciledPaidEvents || 0,
|
|
50
|
-
topAcquisitionChannels: Object.entries(funnel.eventCounts || {})
|
|
51
|
-
.filter(([key]) => key.startsWith('acquisition:'))
|
|
52
|
-
.sort((a, b) => b[1] - a[1])
|
|
53
|
-
.slice(0, 3),
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
async function showPulse() {
|
|
58
|
-
const now = new Date();
|
|
59
|
-
const { source, summary, fallbackReason } = await getOperationalBillingSummary();
|
|
60
|
-
const snapshot = getPulseSnapshot(summary, now);
|
|
61
|
-
console.log('📡 [MISSION CONTROL] MISSION PULSE — ' + now.toLocaleTimeString());
|
|
62
|
-
console.log('─'.repeat(60));
|
|
63
|
-
console.log(`🛰️ SOURCE: ${source.toUpperCase()}${fallbackReason ? ` (${fallbackReason})` : ''}`);
|
|
64
|
-
console.log(`🚀 TRAFFIC: ${snapshot.visitors} Visitors | ${snapshot.ctaClicks} CTA Clicks | ${snapshot.checkoutStarts} Checkout Starts | ${snapshot.leadCount} Unique Leads`);
|
|
65
|
-
console.log(`🧪 PIPELINE: ${snapshot.sprintLeads} Sprint Leads | ${snapshot.qualifiedSprintLeads} Qualified Sprint Leads | ${snapshot.activeCount} Activations`);
|
|
66
|
-
console.log(`🤖 OPERATOR ACQ: ${snapshot.operatorGeneratedAcquisitionEvents} Events | ${snapshot.operatorGeneratedUniqueLeads} Unique Leads`);
|
|
67
|
-
console.log(`💳 REVENUE FLOW: ${snapshot.paidProviderEvents} Paid Provider Events | ${snapshot.paidOrders} Paid Orders`);
|
|
68
|
-
console.log(`💵 BOOKED REVENUE: $${(snapshot.bookedRevenueCents / 100).toFixed(2)}`);
|
|
69
|
-
console.log(`📈 HEALTH: ${snapshot.health} (${snapshot.conversionRate}% lead-to-paid conversion)`);
|
|
70
|
-
console.log(`⏱️ FIRST DOLLAR ETA: ${snapshot.eta}`);
|
|
71
|
-
console.log(`🧹 DATA QUALITY: ${snapshot.unreconciledPaidEvents} unreconciled paid event(s)`);
|
|
72
|
-
console.log('─'.repeat(60));
|
|
73
|
-
console.log('📊 TOP ACQUISITION CHANNELS:');
|
|
74
|
-
snapshot.topAcquisitionChannels.forEach(([key, count]) => {
|
|
75
|
-
const name = key.split(':')[1];
|
|
76
|
-
console.log(` - ${name.padEnd(25)} : ${count} events`);
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
if (require.main === module) showPulse().catch(console.error);
|
|
80
|
-
module.exports = { showPulse, getPulseSnapshot };
|
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Reflector Agent — Self-Healing Brain
|
|
3
|
-
*
|
|
4
|
-
* On negative feedback, analyzes the conversation window to:
|
|
5
|
-
* 1. Identify what the assistant did wrong
|
|
6
|
-
* 2. Check if this is a recurring mistake
|
|
7
|
-
* 3. Propose a specific, actionable rule
|
|
8
|
-
* 4. Return the proposal for user confirmation
|
|
9
|
-
*
|
|
10
|
-
* This transforms ThumbGate from "Manual Guardrail" to "Self-Healing Brain"
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
'use strict';
|
|
14
|
-
|
|
15
|
-
const { retrieveWithRerankingSync } = require('./cross-encoder-reranker');
|
|
16
|
-
const {
|
|
17
|
-
extractFilePaths,
|
|
18
|
-
extractToolCalls,
|
|
19
|
-
extractErrors,
|
|
20
|
-
normalizeConversationWindow,
|
|
21
|
-
} = require('./conversation-context');
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Run a post-mortem analysis on a negative feedback event.
|
|
25
|
-
* @param {object} params
|
|
26
|
-
* @param {Array} params.conversationWindow - Last N conversation turns
|
|
27
|
-
* @param {string} params.context - One-line context from the caller
|
|
28
|
-
* @param {string} params.whatWentWrong - What the caller said went wrong
|
|
29
|
-
* @param {object} params.structuredRule - IF/THEN rule from lesson-inference
|
|
30
|
-
* @param {object} params.feedbackEvent - The stored feedback event
|
|
31
|
-
* @returns {object} Reflection result with proposed rule and recurrence info
|
|
32
|
-
*/
|
|
33
|
-
function reflect(params) {
|
|
34
|
-
const { conversationWindow, context, whatWentWrong, structuredRule, feedbackEvent } = params;
|
|
35
|
-
|
|
36
|
-
// 1. Extract what happened from the conversation
|
|
37
|
-
const analysis = analyzeConversation(conversationWindow || []);
|
|
38
|
-
|
|
39
|
-
// 2. Check for recurrence — has this mistake happened before?
|
|
40
|
-
const recurrence = checkRecurrence(analysis, feedbackEvent);
|
|
41
|
-
|
|
42
|
-
// 3. Generate a human-readable proposed rule
|
|
43
|
-
const proposedRule = generateProposedRule(analysis, structuredRule, recurrence, whatWentWrong);
|
|
44
|
-
|
|
45
|
-
// 4. Determine severity based on recurrence
|
|
46
|
-
const severity = recurrence.count >= 3 ? 'critical' : recurrence.count >= 1 ? 'warning' : 'info';
|
|
47
|
-
|
|
48
|
-
return {
|
|
49
|
-
status: 'reflection_complete',
|
|
50
|
-
analysis: {
|
|
51
|
-
userIntent: analysis.userIntent,
|
|
52
|
-
assistantAction: analysis.assistantAction,
|
|
53
|
-
errorDetected: analysis.errorDetected,
|
|
54
|
-
toolsUsed: analysis.toolsUsed,
|
|
55
|
-
filesInvolved: analysis.filesInvolved,
|
|
56
|
-
},
|
|
57
|
-
recurrence: {
|
|
58
|
-
isRecurring: recurrence.count > 0,
|
|
59
|
-
count: recurrence.count,
|
|
60
|
-
previousLessons: recurrence.previousLessons.map(l => ({
|
|
61
|
-
id: l.id,
|
|
62
|
-
title: l.title,
|
|
63
|
-
timestamp: l.timestamp,
|
|
64
|
-
})),
|
|
65
|
-
},
|
|
66
|
-
proposedRule: proposedRule,
|
|
67
|
-
severity: severity,
|
|
68
|
-
message: formatReflectionMessage(proposedRule, recurrence, analysis),
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function analyzeConversation(window) {
|
|
73
|
-
const normalizedWindow = normalizeConversationWindow(window);
|
|
74
|
-
const userMsgs = normalizedWindow.filter(m => m.role === 'user');
|
|
75
|
-
const assistantMsgs = normalizedWindow.filter(m => m.role === 'assistant');
|
|
76
|
-
|
|
77
|
-
const lastUser = userMsgs[userMsgs.length - 1]?.content || '';
|
|
78
|
-
const lastAssistant = assistantMsgs[assistantMsgs.length - 1]?.content || '';
|
|
79
|
-
const corrections = extractCorrections(userMsgs);
|
|
80
|
-
const toolsUsed = extractToolCalls(normalizedWindow);
|
|
81
|
-
const filesInvolved = extractFilePaths(normalizedWindow).slice(0, 10);
|
|
82
|
-
const errors = extractErrors(normalizedWindow).slice(0, 5);
|
|
83
|
-
|
|
84
|
-
return {
|
|
85
|
-
userIntent: lastUser.slice(0, 300),
|
|
86
|
-
assistantAction: lastAssistant.slice(0, 300),
|
|
87
|
-
corrections,
|
|
88
|
-
errorDetected: errors.length > 0,
|
|
89
|
-
errors,
|
|
90
|
-
toolsUsed,
|
|
91
|
-
filesInvolved,
|
|
92
|
-
messageCount: normalizedWindow.length,
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function checkRecurrence(analysis, feedbackEvent) {
|
|
97
|
-
// Search existing lessons for similar mistakes
|
|
98
|
-
let previousLessons = [];
|
|
99
|
-
try {
|
|
100
|
-
const context = `${analysis.userIntent} ${analysis.assistantAction} ${analysis.corrections.join(' ')}`;
|
|
101
|
-
const toolName = analysis.toolsUsed[0] || 'unknown';
|
|
102
|
-
previousLessons = retrieveWithRerankingSync(toolName, context, { candidateCount: 20, maxResults: 5 });
|
|
103
|
-
// Filter to only negative lessons
|
|
104
|
-
previousLessons = previousLessons.filter(l => l.signal === 'negative');
|
|
105
|
-
} catch (_err) {
|
|
106
|
-
// Non-critical — recurrence check is best-effort
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return {
|
|
110
|
-
count: previousLessons.length,
|
|
111
|
-
previousLessons,
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function generateProposedRule(analysis, structuredRule, recurrence, whatWentWrong) {
|
|
116
|
-
// Build the most specific rule we can from available data
|
|
117
|
-
const parts = [];
|
|
118
|
-
|
|
119
|
-
// Use corrections from conversation as the strongest signal
|
|
120
|
-
if (analysis.corrections.length > 0) {
|
|
121
|
-
const correction = analysis.corrections[0];
|
|
122
|
-
parts.push({
|
|
123
|
-
type: 'constraint',
|
|
124
|
-
rule: `NEVER ${correction}`,
|
|
125
|
-
source: 'user-correction',
|
|
126
|
-
confidence: 0.95,
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Use structured rule if available
|
|
131
|
-
if (structuredRule?.trigger && structuredRule?.action) {
|
|
132
|
-
parts.push({
|
|
133
|
-
type: structuredRule.action.type === 'avoid' ? 'constraint' : 'preference',
|
|
134
|
-
rule: `IF ${structuredRule.trigger.condition} THEN ${structuredRule.action.description}`,
|
|
135
|
-
source: 'inferred',
|
|
136
|
-
confidence: structuredRule.confidence || 0.7,
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Use whatWentWrong as fallback
|
|
141
|
-
if (parts.length === 0 && whatWentWrong) {
|
|
142
|
-
parts.push({
|
|
143
|
-
type: 'lesson',
|
|
144
|
-
rule: `AVOID: ${whatWentWrong}`,
|
|
145
|
-
source: 'user-provided',
|
|
146
|
-
confidence: 0.8,
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Use analysis as last resort
|
|
151
|
-
if (parts.length === 0) {
|
|
152
|
-
parts.push({
|
|
153
|
-
type: 'observation',
|
|
154
|
-
rule: `Review approach when: ${analysis.userIntent.slice(0, 80)}`,
|
|
155
|
-
source: 'conversation-analysis',
|
|
156
|
-
confidence: 0.5,
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// Pick highest confidence rule
|
|
161
|
-
const best = parts.sort((a, b) => b.confidence - a.confidence)[0];
|
|
162
|
-
|
|
163
|
-
return {
|
|
164
|
-
...best,
|
|
165
|
-
isRecurring: recurrence.count > 0,
|
|
166
|
-
recurrenceCount: recurrence.count,
|
|
167
|
-
scope: analysis.filesInvolved.length > 0 ? 'project' : 'global',
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
function formatReflectionMessage(proposedRule, recurrence, analysis) {
|
|
172
|
-
const prefix = recurrence.count > 0
|
|
173
|
-
? `I've made this mistake ${recurrence.count + 1} time(s) now. `
|
|
174
|
-
: '';
|
|
175
|
-
|
|
176
|
-
const ruleText = proposedRule.rule;
|
|
177
|
-
|
|
178
|
-
const correction = analysis.corrections.length > 0
|
|
179
|
-
? ` I noticed you corrected me: "${analysis.corrections[0].slice(0, 80)}".`
|
|
180
|
-
: '';
|
|
181
|
-
|
|
182
|
-
const fileContext = analysis.filesInvolved.length > 0
|
|
183
|
-
? ` (in ${analysis.filesInvolved.slice(0, 3).join(', ')})`
|
|
184
|
-
: '';
|
|
185
|
-
|
|
186
|
-
return `${prefix}${correction} I've recorded a rule${fileContext}: "${ruleText}". Correct?`;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function extractCorrections(userMessages) {
|
|
190
|
-
const results = [];
|
|
191
|
-
const phraseSets = [
|
|
192
|
-
['don\'t ', 'do not ', 'never ', 'stop '],
|
|
193
|
-
['wrong ', 'incorrect ', 'that\'s not ', 'no, '],
|
|
194
|
-
['i said ', 'i told you ', 'i already '],
|
|
195
|
-
['use ', 'switch to ', 'change to '],
|
|
196
|
-
];
|
|
197
|
-
|
|
198
|
-
for (const message of userMessages) {
|
|
199
|
-
const content = String(message.content || '').trim();
|
|
200
|
-
const lower = content.toLowerCase();
|
|
201
|
-
if (!lower) continue;
|
|
202
|
-
|
|
203
|
-
for (const phrases of phraseSets) {
|
|
204
|
-
for (const phrase of phrases) {
|
|
205
|
-
const index = lower.indexOf(phrase);
|
|
206
|
-
if (index === -1) continue;
|
|
207
|
-
let detail = content.slice(index + phrase.length).trim();
|
|
208
|
-
const insteadIndex = detail.toLowerCase().indexOf(' instead');
|
|
209
|
-
if (insteadIndex >= 0) {
|
|
210
|
-
detail = detail.slice(0, insteadIndex).trim();
|
|
211
|
-
}
|
|
212
|
-
if (detail) results.push(detail.slice(0, 100));
|
|
213
|
-
break;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
return results;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
module.exports = { reflect, analyzeConversation, checkRecurrence, generateProposedRule, formatReflectionMessage };
|