@nebulacomponents/citable 1.17.0 → 1.18.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/CHANGELOG.md +22 -0
- package/dist/universal/.agents/skills/citable/SKILL.md +20 -1
- package/dist/universal/.agents/skills/citable/VERSION +1 -1
- package/dist/universal/.agents/skills/citable/manifest.json +6 -5
- package/dist/universal/.agents/skills/citable/schemas/sow.schema.json +92 -0
- package/dist/universal/.claude/agents/citable/manifest.json +2 -2
- package/dist/universal/.claude/skills/citable/SKILL.md +20 -1
- package/dist/universal/.claude/skills/citable/VERSION +1 -1
- package/dist/universal/.claude/skills/citable/manifest.json +6 -5
- package/dist/universal/.claude/skills/citable/schemas/sow.schema.json +92 -0
- package/dist/universal/.cursor/skills/citable/SKILL.md +20 -1
- package/dist/universal/.cursor/skills/citable/VERSION +1 -1
- package/dist/universal/.cursor/skills/citable/manifest.json +6 -5
- package/dist/universal/.cursor/skills/citable/schemas/sow.schema.json +92 -0
- package/dist/universal/.gemini/skills/citable/SKILL.md +20 -1
- package/dist/universal/.gemini/skills/citable/VERSION +1 -1
- package/dist/universal/.gemini/skills/citable/manifest.json +6 -5
- package/dist/universal/.gemini/skills/citable/schemas/sow.schema.json +92 -0
- package/dist/universal/.github/skills/citable/SKILL.md +20 -1
- package/dist/universal/.github/skills/citable/VERSION +1 -1
- package/dist/universal/.github/skills/citable/manifest.json +6 -5
- package/dist/universal/.github/skills/citable/schemas/sow.schema.json +92 -0
- package/dist/universal/.kiro/skills/citable/SKILL.md +20 -1
- package/dist/universal/.kiro/skills/citable/VERSION +1 -1
- package/dist/universal/.kiro/skills/citable/manifest.json +6 -5
- package/dist/universal/.kiro/skills/citable/schemas/sow.schema.json +92 -0
- package/dist/universal/.opencode/skills/citable/SKILL.md +20 -1
- package/dist/universal/.opencode/skills/citable/VERSION +1 -1
- package/dist/universal/.opencode/skills/citable/manifest.json +6 -5
- package/dist/universal/.opencode/skills/citable/schemas/sow.schema.json +92 -0
- package/dist/universal/.pi/agent/skills/citable/SKILL.md +20 -1
- package/dist/universal/.pi/agent/skills/citable/VERSION +1 -1
- package/dist/universal/.pi/agent/skills/citable/manifest.json +6 -5
- package/dist/universal/.pi/agent/skills/citable/schemas/sow.schema.json +92 -0
- package/dist/universal/.qoder/skills/citable/SKILL.md +20 -1
- package/dist/universal/.qoder/skills/citable/VERSION +1 -1
- package/dist/universal/.qoder/skills/citable/manifest.json +6 -5
- package/dist/universal/.qoder/skills/citable/schemas/sow.schema.json +92 -0
- package/dist/universal/.rovodev/skills/citable/SKILL.md +20 -1
- package/dist/universal/.rovodev/skills/citable/VERSION +1 -1
- package/dist/universal/.rovodev/skills/citable/manifest.json +6 -5
- package/dist/universal/.rovodev/skills/citable/schemas/sow.schema.json +92 -0
- package/dist/universal/.trae/skills/citable/SKILL.md +20 -1
- package/dist/universal/.trae/skills/citable/VERSION +1 -1
- package/dist/universal/.trae/skills/citable/manifest.json +6 -5
- package/dist/universal/.trae/skills/citable/schemas/sow.schema.json +92 -0
- package/dist/universal/.trae-cn/skills/citable/SKILL.md +20 -1
- package/dist/universal/.trae-cn/skills/citable/VERSION +1 -1
- package/dist/universal/.trae-cn/skills/citable/manifest.json +6 -5
- package/dist/universal/.trae-cn/skills/citable/schemas/sow.schema.json +92 -0
- package/dist/universal/manifest.json +76 -64
- package/package.json +1 -1
- package/schemas/sow.schema.json +92 -0
- package/skill/SKILL.md +20 -1
- package/src/analysis/behavioral.js +126 -0
- package/src/analysis/croAudit.js +302 -0
- package/src/analysis/croRoadmap.js +121 -0
- package/src/analysis/eeat.js +300 -0
- package/src/analysis/funnelAnalysis.js +171 -0
- package/src/analysis/iceMatrix.js +205 -0
- package/src/analysis/offpage.js +228 -0
- package/src/analysis/readiness.js +253 -0
- package/src/analysis/schemaRecommendations.js +173 -0
- package/src/analysis/strategicRoadmap.js +148 -0
- package/src/cli/index.js +149 -3
- package/src/commands/answerEngineReadiness.js +48 -0
- package/src/commands/auditBacklinks.js +79 -0
- package/src/commands/croBacklog.js +182 -0
- package/src/commands/croSuite.js +156 -0
- package/src/commands/executive.js +6 -0
- package/src/commands/executiveCro.js +30 -0
- package/src/commands/executiveSearch.js +28 -0
- package/src/commands/inspectCro.js +7 -0
- package/src/commands/inspectEeat.js +53 -0
- package/src/commands/prioritize.js +42 -6
- package/src/commands/roadmapCmd.js +69 -0
- package/src/commands/schemaCmd.js +10 -2
- package/src/commands/sowCmd.js +67 -0
- package/src/commands/sweep.js +357 -0
- package/src/reporting/executiveCroReport.js +731 -0
- package/src/reporting/executiveExport.js +62 -0
- package/src/reporting/executiveSearchReport.js +704 -0
- package/src/sow/admissibilityGate.js +194 -0
- package/src/sow/generateSow.js +762 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Analyze behavioral interaction evidence across funnels, sessions, cohorts, and interaction signals.
|
|
3
|
+
*/
|
|
4
|
+
export function analyzeBehavioralTelemetry(telemetryData = {}) {
|
|
5
|
+
const sessions = telemetryData.sessions || telemetryData.metrics || {};
|
|
6
|
+
const cohorts = telemetryData.cohorts || {};
|
|
7
|
+
const interactions = telemetryData.interactions || {};
|
|
8
|
+
const funnelSteps = telemetryData.funnel || [];
|
|
9
|
+
|
|
10
|
+
const findings = [];
|
|
11
|
+
const frictionIndicators = [];
|
|
12
|
+
|
|
13
|
+
// 1. Mobile vs Desktop Cohort Divergence
|
|
14
|
+
const mobileCr = cohorts.mobile?.conversion_rate ?? null;
|
|
15
|
+
const desktopCr = cohorts.desktop?.conversion_rate ?? null;
|
|
16
|
+
|
|
17
|
+
let cohortDivergence = null;
|
|
18
|
+
if (mobileCr !== null && desktopCr !== null && desktopCr > 0) {
|
|
19
|
+
const ratio = mobileCr / desktopCr;
|
|
20
|
+
cohortDivergence = {
|
|
21
|
+
mobile_cr: mobileCr,
|
|
22
|
+
desktop_cr: desktopCr,
|
|
23
|
+
mobile_to_desktop_ratio: Math.round(ratio * 100) / 100,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
if (ratio < 0.5) {
|
|
27
|
+
frictionIndicators.push({
|
|
28
|
+
type: 'mobile_conversion_gap',
|
|
29
|
+
severity: 'high',
|
|
30
|
+
evidence: `Mobile conversion rate (${(mobileCr * 100).toFixed(1)}%) is under half of desktop (${(desktopCr * 100).toFixed(1)}%)`,
|
|
31
|
+
hypothesis: 'Mobile friction (virtual keyboard effort, diminutive tap targets, or slow paint) impedes mobile buyers',
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 2. Scroll Depth vs CTA Placement
|
|
37
|
+
const p50Scroll = interactions.scroll_depth_p50_pct ?? interactions.median_scroll_depth ?? null;
|
|
38
|
+
const ctaPosition = interactions.primary_cta_vertical_pct ?? null;
|
|
39
|
+
|
|
40
|
+
if (p50Scroll !== null && ctaPosition !== null) {
|
|
41
|
+
if (ctaPosition > p50Scroll) {
|
|
42
|
+
frictionIndicators.push({
|
|
43
|
+
type: 'buried_cta',
|
|
44
|
+
severity: 'high',
|
|
45
|
+
evidence: `Primary CTA is positioned at ${ctaPosition}% page depth, but median user scroll depth is only ${p50Scroll}%`,
|
|
46
|
+
hypothesis: 'Over 50% of visitors never view the primary conversion CTA before abandoning',
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 3. Rage Clicks and Dead Clicks
|
|
52
|
+
const rageClicks = interactions.rage_click_rate_pct ?? interactions.rage_clicks_count ?? 0;
|
|
53
|
+
const deadClicks = interactions.dead_click_rate_pct ?? interactions.dead_clicks_count ?? 0;
|
|
54
|
+
|
|
55
|
+
if (rageClicks > 3 || rageClicks > 50) {
|
|
56
|
+
frictionIndicators.push({
|
|
57
|
+
type: 'rage_clicks',
|
|
58
|
+
severity: 'high',
|
|
59
|
+
evidence: `Elevated rage click frequency (${rageClicks}) indicates user frustration with unresponsive or broken interactive elements`,
|
|
60
|
+
hypothesis: 'Interactive components fail to provide immediate visual feedback or fail on tap',
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (deadClicks > 5 || deadClicks > 80) {
|
|
65
|
+
frictionIndicators.push({
|
|
66
|
+
type: 'dead_clicks',
|
|
67
|
+
severity: 'medium',
|
|
68
|
+
evidence: `High dead click count (${deadClicks}): users attempt to click static elements perceived as interactive`,
|
|
69
|
+
hypothesis: 'Visual affordances mislead users into clicking non-actionable elements',
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 4. Funnel Drop-off Analysis
|
|
74
|
+
const funnelDropOffs = [];
|
|
75
|
+
if (Array.isArray(funnelSteps) && funnelSteps.length >= 2) {
|
|
76
|
+
for (let i = 0; i < funnelSteps.length - 1; i++) {
|
|
77
|
+
const curr = funnelSteps[i];
|
|
78
|
+
const next = funnelSteps[i + 1];
|
|
79
|
+
const dropOffRate = curr.visitors > 0 ? Math.round(((curr.visitors - next.visitors) / curr.visitors) * 100) : 0;
|
|
80
|
+
funnelDropOffs.push({
|
|
81
|
+
from_step: curr.name || `Step ${i + 1}`,
|
|
82
|
+
to_step: next.name || `Step ${i + 2}`,
|
|
83
|
+
drop_off_pct: dropOffRate,
|
|
84
|
+
retained_visitors: next.visitors,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
if (dropOffRate > 70) {
|
|
88
|
+
frictionIndicators.push({
|
|
89
|
+
type: 'steep_funnel_leak',
|
|
90
|
+
severity: 'critical',
|
|
91
|
+
evidence: `Severe drop-off between ${curr.name} and ${next.name} (${dropOffRate}% abandon rate)`,
|
|
92
|
+
hypothesis: `Significant cognitive load, price shock, or friction barrier encountered at ${curr.name}`,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 5. Form Field Abandonment Peak
|
|
99
|
+
const fieldAbandons = interactions.form_field_abandonment || [];
|
|
100
|
+
let peakAbandonField = null;
|
|
101
|
+
if (fieldAbandons.length > 0) {
|
|
102
|
+
fieldAbandons.sort((a, b) => b.abandon_count - a.abandon_count);
|
|
103
|
+
peakAbandonField = fieldAbandons[0];
|
|
104
|
+
frictionIndicators.push({
|
|
105
|
+
type: 'form_field_bottleneck',
|
|
106
|
+
severity: 'high',
|
|
107
|
+
evidence: `Peak form abandonment occurs on field "${peakAbandonField.field_name}" (${peakAbandonField.abandon_count} drop-offs)`,
|
|
108
|
+
hypothesis: `Field "${peakAbandonField.field_name}" creates high friction or privacy resistance; consider making optional or eliminating`,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
fact_status: 'observed_behavioral_telemetry',
|
|
114
|
+
summary: {
|
|
115
|
+
total_friction_indicators: frictionIndicators.length,
|
|
116
|
+
critical_indicators: frictionIndicators.filter((i) => i.severity === 'critical').length,
|
|
117
|
+
high_indicators: frictionIndicators.filter((i) => i.severity === 'high').length,
|
|
118
|
+
cohort_divergence: cohortDivergence,
|
|
119
|
+
},
|
|
120
|
+
funnel_drop_offs: funnelDropOffs,
|
|
121
|
+
friction_indicators: frictionIndicators,
|
|
122
|
+
limitations: [
|
|
123
|
+
'Behavioral signals indicate correlation and user interaction bottlenecks; causal mechanisms must be verified via controlled A/B experiments.',
|
|
124
|
+
],
|
|
125
|
+
};
|
|
126
|
+
}
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { parse as parseHtml } from 'node-html-parser';
|
|
2
|
+
import { calculateVisualSaliency } from './saliency.js';
|
|
3
|
+
import { registryPageFor, safePath } from '../detectors/framework.js';
|
|
4
|
+
|
|
5
|
+
const GENERIC_CTA_RX = /^(?:submit|click\s*here|continue|next|send|ok|go|learn\s*more|more)$/i;
|
|
6
|
+
const VALUE_CTA_RX = /\b(get|start|try|claim|unlock|explore|join|download|access|calculate|book|schedule|request)\b/i;
|
|
7
|
+
const RISK_REVERSAL_RX = /\b(no\s*credit\s*card\s*required|cancel\s*anytime|money-back\s*guarantee|\d+-day\s*guarantee|risk-free|free\s*(?:\d+-day\s*)?trial|no\s*commitment|guaranteed)\b/i;
|
|
8
|
+
const SOCIAL_PROOF_RX = /\b(trusted\s*by|over\s*\d+[\d,]*\s*(?:users|customers|teams|companies)|customer\s*reviews?|what\s*(?:our\s*)?customers\s*say|case\s*studies|testimonials?)\b/i;
|
|
9
|
+
const OBJECTION_RX = /\b(frequently\s*asked\s*questions|faq|common\s*questions|how\s*much|pricing\s*details|cancel\s*policy|security\s*standards)\b/i;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Perform comprehensive CRO audit on a page model
|
|
13
|
+
*/
|
|
14
|
+
export function auditPageCro(page, ctx = {}) {
|
|
15
|
+
const html = page.html || '';
|
|
16
|
+
const root = parseHtml(html);
|
|
17
|
+
const text = (page.text || root.textContent || '').replace(/\s+/g, ' ').trim();
|
|
18
|
+
const ctas = page.ctas || [];
|
|
19
|
+
const forms = page.forms || [];
|
|
20
|
+
const trustBadges = page.trustBadges || [];
|
|
21
|
+
const headings = page.headings || [];
|
|
22
|
+
|
|
23
|
+
// -------------------------------------------------------------
|
|
24
|
+
// 1. ABOVE-THE-FOLD (ATF) CLARITY & MESSAGE-MATCH (0-100)
|
|
25
|
+
// -------------------------------------------------------------
|
|
26
|
+
let atfScore = 0;
|
|
27
|
+
const atfSignals = [];
|
|
28
|
+
const atfGaps = [];
|
|
29
|
+
|
|
30
|
+
// Scent match: Title to H1 alignment
|
|
31
|
+
const titleStem = page.title ? page.title.split(/[|—–-]/)[0].trim().toLowerCase() : '';
|
|
32
|
+
const h1Text = (headings.find((h) => h.level === 1)?.text || '').trim();
|
|
33
|
+
const h1Lower = h1Text.toLowerCase();
|
|
34
|
+
|
|
35
|
+
let scentMatch = false;
|
|
36
|
+
if (titleStem && h1Lower) {
|
|
37
|
+
const titleWords = titleStem.split(/\s+/).filter((w) => w.length > 3);
|
|
38
|
+
const matchedWords = titleWords.filter((w) => h1Lower.includes(w));
|
|
39
|
+
if (matchedWords.length >= 1 || titleStem.includes(h1Lower) || h1Lower.includes(titleStem)) {
|
|
40
|
+
scentMatch = true;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (scentMatch) {
|
|
45
|
+
atfScore += 30;
|
|
46
|
+
atfSignals.push(`Strong information scent: Title and primary H1 corroborate core value proposition ("${h1Text.slice(0, 50)}")`);
|
|
47
|
+
} else if (h1Text) {
|
|
48
|
+
atfScore += 15;
|
|
49
|
+
atfGaps.push(`Scent gap: Primary H1 ("${h1Text.slice(0, 40)}") diverges from title tag ("${titleStem.slice(0, 40)}")`);
|
|
50
|
+
} else {
|
|
51
|
+
atfGaps.push('Missing H1 headline above the fold to establish page purpose');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Hero CTA presence & conspicuity
|
|
55
|
+
const heroCtas = ctas.filter((c) => c.inHero);
|
|
56
|
+
const primaryHeroCta = heroCtas.find((c) => c.isPrimary) || heroCtas[0];
|
|
57
|
+
|
|
58
|
+
const saliency = calculateVisualSaliency({
|
|
59
|
+
ctas: ctas.map((c) => ({ ...c, isPrimary: c.isPrimary })),
|
|
60
|
+
headings: headings.map((h) => ({ level: h.level || 1, text: h.text })),
|
|
61
|
+
forms: forms.map((f) => ({ fieldCount: f.fieldCount })),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const pci = saliency.primary_cta_conspicuity_index || 0;
|
|
65
|
+
|
|
66
|
+
if (primaryHeroCta && pci >= 0.60) {
|
|
67
|
+
atfScore += 30;
|
|
68
|
+
atfSignals.push(`Prominent hero CTA with optimal conspicuity (${Math.round(pci * 100)}% PCI score)`);
|
|
69
|
+
} else if (primaryHeroCta) {
|
|
70
|
+
atfScore += 20;
|
|
71
|
+
atfSignals.push(`Hero CTA present ("${primaryHeroCta.text}") but visual conspicuity is diluted (${Math.round(pci * 100)}% PCI score)`);
|
|
72
|
+
} else {
|
|
73
|
+
atfGaps.push('No primary CTA visible above the fold in the hero viewport');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Choice overload penalty
|
|
77
|
+
if (heroCtas.filter((c) => c.isPrimary).length > 1) {
|
|
78
|
+
atfScore -= 10;
|
|
79
|
+
atfGaps.push(`Choice overload: ${heroCtas.filter((c) => c.isPrimary).length} competing primary CTAs in hero zone induce decision paralysis`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Value proposition lead copy (opening 80 words)
|
|
83
|
+
const firstP = page.paragraphs?.[0] || '';
|
|
84
|
+
if (firstP.length >= 40 && firstP.length <= 400) {
|
|
85
|
+
atfScore += 25;
|
|
86
|
+
atfSignals.push('Concise value proposition paragraph immediately supports hero headline');
|
|
87
|
+
} else if (firstP.length > 400) {
|
|
88
|
+
atfScore += 15;
|
|
89
|
+
atfGaps.push('Opening hero lead text is too verbose (>400 chars); condense into clear benefit bullets');
|
|
90
|
+
} else {
|
|
91
|
+
atfGaps.push('Missing explanatory sub-headline or lead benefit description above the fold');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Supporting micro-signals (risk reversal near hero)
|
|
95
|
+
const heroText = (h1Text + ' ' + firstP + ' ' + (primaryHeroCta?.text || '')).toLowerCase();
|
|
96
|
+
if (RISK_REVERSAL_RX.test(heroText)) {
|
|
97
|
+
atfScore += 15;
|
|
98
|
+
atfSignals.push('Friction-reducing risk reversal (e.g. "No credit card required") placed directly proximate to hero CTA');
|
|
99
|
+
} else {
|
|
100
|
+
atfGaps.push('Add an explicit risk-reversal micro-copy badge under hero CTA (e.g. "Free 14-day trial • No credit card required")');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
atfScore = Math.min(100, Math.max(0, atfScore));
|
|
104
|
+
|
|
105
|
+
// -------------------------------------------------------------
|
|
106
|
+
// 2. UX FRICTION, COGNITIVE LOAD & ABANDONMENT ANALYSIS
|
|
107
|
+
// -------------------------------------------------------------
|
|
108
|
+
const frictionIssues = [];
|
|
109
|
+
let frictionPoints = 0;
|
|
110
|
+
|
|
111
|
+
// Form field count & Keystroke Effort Index (KEI)
|
|
112
|
+
const totalInputs = forms.reduce((sum, f) => sum + (f.fieldCount || 0), 0);
|
|
113
|
+
const inputsWithAutocomplete = forms.reduce((sum, f) => sum + f.inputs.filter((inp) => Boolean(inp.autocomplete)).length, 0);
|
|
114
|
+
const manualKeystrokes = totalInputs * 14;
|
|
115
|
+
const autofillKeystrokes = totalInputs > 0 && inputsWithAutocomplete > 0
|
|
116
|
+
? (totalInputs - inputsWithAutocomplete) * 14 + 1
|
|
117
|
+
: manualKeystrokes;
|
|
118
|
+
const keystrokeReductionPct = manualKeystrokes > 0
|
|
119
|
+
? Math.round(((manualKeystrokes - autofillKeystrokes) / manualKeystrokes) * 100)
|
|
120
|
+
: 0;
|
|
121
|
+
|
|
122
|
+
for (const f of forms) {
|
|
123
|
+
if (f.fieldCount > 6) {
|
|
124
|
+
frictionPoints += 20;
|
|
125
|
+
frictionIssues.push({
|
|
126
|
+
severity: 'high',
|
|
127
|
+
type: 'form_length',
|
|
128
|
+
issue: `Form contains ${f.fieldCount} fields (recommend <= 4 fields for top-of-funnel lead capture)`,
|
|
129
|
+
remediation: 'Progressive profiling: reduce initial form fields to work email and name; capture details on subsequent step',
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const missingAuto = f.inputs.filter((inp) => !inp.autocomplete && ['email', 'tel', 'text', 'name'].includes(inp.type));
|
|
134
|
+
if (missingAuto.length > 0) {
|
|
135
|
+
frictionPoints += 10;
|
|
136
|
+
frictionIssues.push({
|
|
137
|
+
severity: 'medium',
|
|
138
|
+
type: 'autocomplete_missing',
|
|
139
|
+
issue: `${missingAuto.length} input field(s) lack HTML5 autocomplete attributes, increasing mobile entry friction`,
|
|
140
|
+
remediation: 'Add standard autocomplete attributes (e.g. autocomplete="email", autocomplete="name", autocomplete="tel")',
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (!f.hasSubmit) {
|
|
145
|
+
frictionPoints += 30;
|
|
146
|
+
frictionIssues.push({
|
|
147
|
+
severity: 'critical',
|
|
148
|
+
type: 'missing_submit',
|
|
149
|
+
issue: 'Form has no explicit submit button or trigger mechanism',
|
|
150
|
+
remediation: 'Add an accessible <button type="submit"> element with clear action microcopy',
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Navigation distraction on conversion surfaces (leaks)
|
|
156
|
+
const reg = registryPageFor(ctx, page);
|
|
157
|
+
const isCheckoutOrCapture = ['checkout', 'pricing', 'signup', 'demo', 'cart'].some((t) => (page.url || '').toLowerCase().includes(t));
|
|
158
|
+
const navLinks = page.navLinksCount || root.querySelectorAll('header nav a, footer a').length;
|
|
159
|
+
|
|
160
|
+
if (isCheckoutOrCapture && navLinks > 15) {
|
|
161
|
+
frictionPoints += 15;
|
|
162
|
+
frictionIssues.push({
|
|
163
|
+
severity: 'medium',
|
|
164
|
+
type: 'funnel_leak',
|
|
165
|
+
issue: `High-intent conversion page contains ${navLinks} external/header navigation links (funnel leak)`,
|
|
166
|
+
remediation: 'Enclose conversion funnel: remove non-essential header navigation and social links during checkout/signup',
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Cognitive load index
|
|
171
|
+
let cognitiveLoad = 'minimal';
|
|
172
|
+
if (frictionPoints >= 40) cognitiveLoad = 'critical_friction';
|
|
173
|
+
else if (frictionPoints >= 25) cognitiveLoad = 'high_cognitive_load';
|
|
174
|
+
else if (frictionPoints >= 10) cognitiveLoad = 'moderate';
|
|
175
|
+
|
|
176
|
+
// -------------------------------------------------------------
|
|
177
|
+
// 3. TRUST, CREDIBILITY, OBJECTIONS & RISK-REVERSAL (0-100)
|
|
178
|
+
// -------------------------------------------------------------
|
|
179
|
+
let trustScore = 0;
|
|
180
|
+
const trustSignals = [];
|
|
181
|
+
const trustGaps = [];
|
|
182
|
+
|
|
183
|
+
// Social proof
|
|
184
|
+
const hasSocialProof = SOCIAL_PROOF_RX.test(text) || trustBadges.some((b) => /rating|review|g2|capterra|trustpilot/i.test(b.signal || ''));
|
|
185
|
+
if (hasSocialProof) {
|
|
186
|
+
trustScore += 25;
|
|
187
|
+
trustSignals.push('Customer social proof and review signals detected');
|
|
188
|
+
} else {
|
|
189
|
+
trustGaps.push('Add customer social proof (client logos, quantitative adoption stats, or verified review quotes)');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Security credentials & badges
|
|
193
|
+
const hasSecurityBadge = trustBadges.some((b) => /security|ssl|soc-?2|iso|hipaa|pci|verified/i.test(b.signal || '')) ||
|
|
194
|
+
Boolean(root.querySelector('[class*="security"], [class*="trust"], [class*="badge"]'));
|
|
195
|
+
if (hasSecurityBadge) {
|
|
196
|
+
trustScore += 25;
|
|
197
|
+
trustSignals.push('Security compliance, SSL, or verification badges present');
|
|
198
|
+
} else {
|
|
199
|
+
trustGaps.push('Display enterprise security credentials (SOC-2, ISO, HIPAA, or encryption badges) proximate to conversion action');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Risk reversal
|
|
203
|
+
const hasRiskReversal = RISK_REVERSAL_RX.test(text);
|
|
204
|
+
if (hasRiskReversal) {
|
|
205
|
+
trustScore += 25;
|
|
206
|
+
trustSignals.push('Explicit risk-reversal guarantees present (money-back guarantee, free trial, or cancel anytime)');
|
|
207
|
+
} else {
|
|
208
|
+
trustGaps.push('Eliminate buyer perceived risk with explicit guarantees ("Cancel anytime", "30-day money back guarantee")');
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Objection handling (FAQ or comparison)
|
|
212
|
+
const hasObjectionHandling = OBJECTION_RX.test(text) || root.querySelectorAll('details, .faq, #faq').length > 0;
|
|
213
|
+
if (hasObjectionHandling) {
|
|
214
|
+
trustScore += 25;
|
|
215
|
+
trustSignals.push('Pre-purchase objection handling and structured FAQ content detected');
|
|
216
|
+
} else {
|
|
217
|
+
trustGaps.push('Include a pre-purchase FAQ addressing common pricing, onboarding, and contract objections');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
trustScore = Math.min(100, Math.max(0, trustScore));
|
|
221
|
+
|
|
222
|
+
// -------------------------------------------------------------
|
|
223
|
+
// 4. OFFER ARCHITECTURE & CTA HIERARCHY
|
|
224
|
+
// -------------------------------------------------------------
|
|
225
|
+
const offerFindings = [];
|
|
226
|
+
let primaryCtaCount = 0;
|
|
227
|
+
let genericCtaCount = 0;
|
|
228
|
+
|
|
229
|
+
for (const cta of ctas) {
|
|
230
|
+
if (cta.isPrimary) primaryCtaCount++;
|
|
231
|
+
if (GENERIC_CTA_RX.test(cta.text)) {
|
|
232
|
+
genericCtaCount++;
|
|
233
|
+
offerFindings.push({
|
|
234
|
+
cta: cta.text,
|
|
235
|
+
type: 'generic_microcopy',
|
|
236
|
+
remediation: `Replace low-intent generic copy "${cta.text}" with high-value benefit action copy (e.g. "Get Started Free", "Claim Your Audit")`,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const ctaHierarchy = {
|
|
242
|
+
total_ctas: ctas.length,
|
|
243
|
+
primary_ctas: primaryCtaCount,
|
|
244
|
+
secondary_ctas: ctas.length - primaryCtaCount,
|
|
245
|
+
generic_microcopy_count: genericCtaCount,
|
|
246
|
+
hierarchy_status: primaryCtaCount === 1 ? 'optimal' : primaryCtaCount > 1 ? 'choice_overload' : 'missing_primary',
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
// Express checkout
|
|
250
|
+
const walletRx = /apple\s*pay|google\s*pay|paypal|one-click/i;
|
|
251
|
+
const hasExpressPayment = ctas.some((c) => walletRx.test(c.text || '')) ||
|
|
252
|
+
trustBadges.some((t) => walletRx.test(t.signal || ''));
|
|
253
|
+
|
|
254
|
+
// -------------------------------------------------------------
|
|
255
|
+
// OVERALL CONVERSION READINESS SCORE (0-100)
|
|
256
|
+
// -------------------------------------------------------------
|
|
257
|
+
const conversionReadiness = Math.round(
|
|
258
|
+
(atfScore * 0.35) +
|
|
259
|
+
(trustScore * 0.30) +
|
|
260
|
+
(Math.max(0, 100 - frictionPoints) * 0.25) +
|
|
261
|
+
(ctaHierarchy.hierarchy_status === 'optimal' ? 10 : 5)
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
return {
|
|
265
|
+
url: page.url,
|
|
266
|
+
fact_status: 'modeled_cro_evaluation',
|
|
267
|
+
conversion_readiness_score: conversionReadiness,
|
|
268
|
+
atf_clarity: {
|
|
269
|
+
score: atfScore,
|
|
270
|
+
scent_match: scentMatch,
|
|
271
|
+
title_stem: titleStem,
|
|
272
|
+
h1_headline: h1Text,
|
|
273
|
+
pci_score_pct: Math.round(pci * 100),
|
|
274
|
+
signals: atfSignals,
|
|
275
|
+
gaps: atfGaps,
|
|
276
|
+
},
|
|
277
|
+
cognitive_load_and_friction: {
|
|
278
|
+
load_assessment: cognitiveLoad,
|
|
279
|
+
friction_points: frictionPoints,
|
|
280
|
+
keystroke_effort: {
|
|
281
|
+
total_inputs: totalInputs,
|
|
282
|
+
inputs_with_autocomplete: inputsWithAutocomplete,
|
|
283
|
+
keystroke_reduction_pct: keystrokeReductionPct,
|
|
284
|
+
},
|
|
285
|
+
issues: frictionIssues,
|
|
286
|
+
},
|
|
287
|
+
trust_and_credibility: {
|
|
288
|
+
score: trustScore,
|
|
289
|
+
signals: trustSignals,
|
|
290
|
+
gaps: trustGaps,
|
|
291
|
+
social_proof_detected: hasSocialProof,
|
|
292
|
+
security_badges_detected: hasSecurityBadge,
|
|
293
|
+
risk_reversal_detected: hasRiskReversal,
|
|
294
|
+
objection_handling_detected: hasObjectionHandling,
|
|
295
|
+
},
|
|
296
|
+
offer_architecture: {
|
|
297
|
+
cta_hierarchy: ctaHierarchy,
|
|
298
|
+
express_checkout_supported: hasExpressPayment,
|
|
299
|
+
findings: offerFindings,
|
|
300
|
+
},
|
|
301
|
+
};
|
|
302
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { nowIso } from '../shared/io.js';
|
|
2
|
+
import { generateExperimentBacklog } from '../commands/croBacklog.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Generate a 30 / 90 / 180-Day CRO Roadmap tied to measurable conversion outcomes.
|
|
6
|
+
*/
|
|
7
|
+
export function buildCroRoadmap({ findings = [], targetDomain = 'example.com', dailyVisitors = 500, baselineRate = 0.02 } = {}) {
|
|
8
|
+
const backlog = generateExperimentBacklog(findings, { dailyVisitors, baselineRate });
|
|
9
|
+
|
|
10
|
+
const horizon30 = {
|
|
11
|
+
horizon: '30_days',
|
|
12
|
+
label: 'Days 1 - 30: Immediate Mechanical Friction Removal (Quick Wins)',
|
|
13
|
+
objective: 'Eradicate mechanical conversion leaks, keystroke friction, and title-to-H1 scent divergence.',
|
|
14
|
+
focus_areas: [
|
|
15
|
+
'Funnel Continuity: Fix broken conversion progression links and attribution parameter loss (CRO-017, CRO-018)',
|
|
16
|
+
'Keystroke Friction: Inject HTML5 autocomplete attributes on all form fields (CRO-007) to reduce manual typing by up to 70%',
|
|
17
|
+
'Information Scent: Align above-the-fold H1 headlines directly with title and ad intent to close scent gaps (CRO-005)',
|
|
18
|
+
'Mobile Tap Sizing: Expand diminutive mobile CTA touch targets to >= 48px standard (CRO-015)',
|
|
19
|
+
],
|
|
20
|
+
target_outcomes: [
|
|
21
|
+
'100% conversion funnel continuity intact across all declared paths',
|
|
22
|
+
'Keystroke Effort Index: Form completion friction reduced by >= 40%',
|
|
23
|
+
'Above-the-fold information scent match verified across all primary landing pages',
|
|
24
|
+
'Zero mobile touch target defects under 48px',
|
|
25
|
+
],
|
|
26
|
+
measurable_conversion_kpi: 'Form Completion Rate (+8% to +15% expected relative lift)',
|
|
27
|
+
experiments: backlog.experiments.filter((e) => ['CRO-007', 'CRO-005', 'CRO-015', 'CRO-017', 'CRO-018'].includes(e.detector_id) || e.ice_prioritization.effort <= 3),
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const horizon90 = {
|
|
31
|
+
horizon: '90_days',
|
|
32
|
+
label: 'Days 31 - 90: Offer Architecture, Credibility & Governed A/B Testing',
|
|
33
|
+
objective: 'Overhaul value proposition hierarchy, deploy proximate social proof and risk-reversals, and run governed A/B experiments.',
|
|
34
|
+
focus_areas: [
|
|
35
|
+
'Choice Overload: Consolidate competing hero buttons into exactly ONE dominant primary action and one secondary link (CRO-010)',
|
|
36
|
+
'Microcopy Elevation: Replace generic "Submit" / "Click Here" with high-intent benefit microcopy (CRO-012)',
|
|
37
|
+
'Credibility & Proof: Position enterprise client logos and security badges immediately adjacent to conversion buttons (CRO-006)',
|
|
38
|
+
'Objection Reversal: Deploy pre-purchase FAQ accordions directly addressing pricing, security, and contract terms',
|
|
39
|
+
'Governed Experimentation: Launch first wave of A/B tests with statistical power and SRM sample-ratio mismatch guardrails',
|
|
40
|
+
],
|
|
41
|
+
target_outcomes: [
|
|
42
|
+
'Primary CTA Conspicuity Index (PCI) >= 70% with zero choice overload',
|
|
43
|
+
'Trust & Credibility score >= 75/100 across commercial surfaces',
|
|
44
|
+
'Statistically validated A/B experiment win with zero SRM violations (p >= 0.001)',
|
|
45
|
+
'At least 2 high-confidence Nebula Component remediations verified in production',
|
|
46
|
+
],
|
|
47
|
+
measurable_conversion_kpi: 'Lead Capture / Trial Signup Rate (+12% to +20% expected relative lift)',
|
|
48
|
+
experiments: backlog.experiments.filter((e) => ['CRO-001', 'CRO-006', 'CRO-010', 'CRO-012', 'CRO-002'].includes(e.detector_id)),
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const horizon180 = {
|
|
52
|
+
horizon: '180_days',
|
|
53
|
+
label: 'Days 91 - 180: Payment Acceleration, Funnel Enclosure & Continuous Optimization',
|
|
54
|
+
objective: 'Accelerate checkout velocity with express payment wallets, enclose high-intent funnels, and automate regression monitoring.',
|
|
55
|
+
focus_areas: [
|
|
56
|
+
'Express Checkout: Integrate 1-click Apple Pay, Google Pay, and PayPal express wallet buttons on purchase steps (CRO-021)',
|
|
57
|
+
'Distraction-Free Funnels: Enclose checkout steps by stripping non-essential navigation and external outbound links (CRO-013)',
|
|
58
|
+
'AI-Scent Landing Personalization: Ground landing pages cited by AI answer engines with immediate claim corroboration (CRO-019)',
|
|
59
|
+
'Automated Governance: Schedule weekly continuous CRO audits with automated webhook regression alerts',
|
|
60
|
+
],
|
|
61
|
+
target_outcomes: [
|
|
62
|
+
'Checkout completion rate lift of +15% to +25%',
|
|
63
|
+
'Zero distraction leaks on checkout and demo request steps',
|
|
64
|
+
'AI answer engine referral conversion parity with organic search',
|
|
65
|
+
'Fully automated, version-pinned continuous conversion governance active',
|
|
66
|
+
],
|
|
67
|
+
measurable_conversion_kpi: 'Checkout / Revenue Conversion Rate (+15% to +25% expected relative lift)',
|
|
68
|
+
experiments: backlog.experiments.filter((e) => ['CRO-021', 'CRO-013', 'CRO-019', 'CRO-014', 'CRO-020'].includes(e.detector_id)),
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
fact_status: 'cro_roadmap_projection',
|
|
73
|
+
target_domain: targetDomain,
|
|
74
|
+
generated_at: nowIso(),
|
|
75
|
+
total_planned_experiments: backlog.total_experiments,
|
|
76
|
+
methodology: 'Horizon Phasing: 30-Day (Mechanical Friction) -> 90-Day (Offer & Trust) -> 180-Day (Payment & Scale)',
|
|
77
|
+
horizons: [horizon30, horizon90, horizon180],
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Format CRO Roadmap as GitHub-flavored Markdown
|
|
83
|
+
*/
|
|
84
|
+
export function formatCroRoadmapMarkdown(roadmap) {
|
|
85
|
+
const lines = [
|
|
86
|
+
`# 30 / 90 / 180-Day Conversion Rate Optimization (CRO) Roadmap`,
|
|
87
|
+
`=============================================================`,
|
|
88
|
+
`- Target: \`${roadmap.target_domain}\``,
|
|
89
|
+
`- Generated: ${roadmap.generated_at}`,
|
|
90
|
+
`- Planned Experiments: ${roadmap.total_planned_experiments}`,
|
|
91
|
+
`- Strategic Framework: ${roadmap.methodology}`,
|
|
92
|
+
``,
|
|
93
|
+
`> **Operating Premise Reminder**: This roadmap sequences verified conversion design remediations and A/B experiments. It eliminates friction and elevates decision clarity, but never guarantees conversion or revenue outcomes without empirical testing.`,
|
|
94
|
+
``,
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
for (const h of roadmap.horizons) {
|
|
98
|
+
lines.push(`## ${h.label}`);
|
|
99
|
+
lines.push(``);
|
|
100
|
+
lines.push(`**Core Objective**: ${h.objective}`);
|
|
101
|
+
lines.push(`**Measurable Target KPI**: **${h.measurable_conversion_kpi}**`);
|
|
102
|
+
lines.push(``);
|
|
103
|
+
lines.push(`### Strategic Focus Areas:`);
|
|
104
|
+
for (const f of h.focus_areas) lines.push(`- ${f}`);
|
|
105
|
+
lines.push(``);
|
|
106
|
+
lines.push(`### Target Outcomes & Success Milestones:`);
|
|
107
|
+
for (const o of h.target_outcomes) lines.push(`- [ ] ${o}`);
|
|
108
|
+
lines.push(``);
|
|
109
|
+
if (h.experiments.length) {
|
|
110
|
+
lines.push(`### Sequenced A/B Experiments (${h.experiments.length}):`);
|
|
111
|
+
lines.push(`| Experiment ID | Title | Expected Lift | Sample Size | Est. Days | ICE Score |`);
|
|
112
|
+
lines.push(`| :--- | :--- | :--- | :--- | :--- | :--- |`);
|
|
113
|
+
for (const e of h.experiments) {
|
|
114
|
+
lines.push(`| **${e.experiment_id}** | ${e.title.slice(0, 35)} | +${e.statistical_setup.expected_mde_pct}% | ${e.statistical_setup.sample_size_per_variant.toLocaleString()}/var | ~${e.statistical_setup.estimated_duration_days}d | **${e.ice_prioritization.ice_score}** |`);
|
|
115
|
+
}
|
|
116
|
+
lines.push(``);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return lines.join('\n');
|
|
121
|
+
}
|