@nebulacomponents/citable 1.16.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 +47 -0
- package/README.md +16 -3
- package/dist/universal/.agents/skills/citable/SKILL.md +66 -1
- package/dist/universal/.agents/skills/citable/VERSION +1 -1
- package/dist/universal/.agents/skills/citable/manifest.json +9 -6
- package/dist/universal/.agents/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
- package/dist/universal/.agents/skills/citable/schemas/finding.schema.json +69 -0
- package/dist/universal/.agents/skills/citable/schemas/remediation-verification.schema.json +98 -0
- 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 +66 -1
- package/dist/universal/.claude/skills/citable/VERSION +1 -1
- package/dist/universal/.claude/skills/citable/manifest.json +9 -6
- package/dist/universal/.claude/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
- package/dist/universal/.claude/skills/citable/schemas/finding.schema.json +69 -0
- package/dist/universal/.claude/skills/citable/schemas/remediation-verification.schema.json +98 -0
- package/dist/universal/.claude/skills/citable/schemas/sow.schema.json +92 -0
- package/dist/universal/.cursor/skills/citable/SKILL.md +66 -1
- package/dist/universal/.cursor/skills/citable/VERSION +1 -1
- package/dist/universal/.cursor/skills/citable/manifest.json +9 -6
- package/dist/universal/.cursor/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
- package/dist/universal/.cursor/skills/citable/schemas/finding.schema.json +69 -0
- package/dist/universal/.cursor/skills/citable/schemas/remediation-verification.schema.json +98 -0
- package/dist/universal/.cursor/skills/citable/schemas/sow.schema.json +92 -0
- package/dist/universal/.gemini/skills/citable/SKILL.md +66 -1
- package/dist/universal/.gemini/skills/citable/VERSION +1 -1
- package/dist/universal/.gemini/skills/citable/manifest.json +9 -6
- package/dist/universal/.gemini/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
- package/dist/universal/.gemini/skills/citable/schemas/finding.schema.json +69 -0
- package/dist/universal/.gemini/skills/citable/schemas/remediation-verification.schema.json +98 -0
- package/dist/universal/.gemini/skills/citable/schemas/sow.schema.json +92 -0
- package/dist/universal/.github/skills/citable/SKILL.md +66 -1
- package/dist/universal/.github/skills/citable/VERSION +1 -1
- package/dist/universal/.github/skills/citable/manifest.json +9 -6
- package/dist/universal/.github/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
- package/dist/universal/.github/skills/citable/schemas/finding.schema.json +69 -0
- package/dist/universal/.github/skills/citable/schemas/remediation-verification.schema.json +98 -0
- package/dist/universal/.github/skills/citable/schemas/sow.schema.json +92 -0
- package/dist/universal/.kiro/skills/citable/SKILL.md +66 -1
- package/dist/universal/.kiro/skills/citable/VERSION +1 -1
- package/dist/universal/.kiro/skills/citable/manifest.json +9 -6
- package/dist/universal/.kiro/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
- package/dist/universal/.kiro/skills/citable/schemas/finding.schema.json +69 -0
- package/dist/universal/.kiro/skills/citable/schemas/remediation-verification.schema.json +98 -0
- package/dist/universal/.kiro/skills/citable/schemas/sow.schema.json +92 -0
- package/dist/universal/.opencode/skills/citable/SKILL.md +66 -1
- package/dist/universal/.opencode/skills/citable/VERSION +1 -1
- package/dist/universal/.opencode/skills/citable/manifest.json +9 -6
- package/dist/universal/.opencode/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
- package/dist/universal/.opencode/skills/citable/schemas/finding.schema.json +69 -0
- package/dist/universal/.opencode/skills/citable/schemas/remediation-verification.schema.json +98 -0
- package/dist/universal/.opencode/skills/citable/schemas/sow.schema.json +92 -0
- package/dist/universal/.pi/agent/skills/citable/SKILL.md +66 -1
- package/dist/universal/.pi/agent/skills/citable/VERSION +1 -1
- package/dist/universal/.pi/agent/skills/citable/manifest.json +9 -6
- package/dist/universal/.pi/agent/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/finding.schema.json +69 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/remediation-verification.schema.json +98 -0
- package/dist/universal/.pi/agent/skills/citable/schemas/sow.schema.json +92 -0
- package/dist/universal/.qoder/skills/citable/SKILL.md +66 -1
- package/dist/universal/.qoder/skills/citable/VERSION +1 -1
- package/dist/universal/.qoder/skills/citable/manifest.json +9 -6
- package/dist/universal/.qoder/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
- package/dist/universal/.qoder/skills/citable/schemas/finding.schema.json +69 -0
- package/dist/universal/.qoder/skills/citable/schemas/remediation-verification.schema.json +98 -0
- package/dist/universal/.qoder/skills/citable/schemas/sow.schema.json +92 -0
- package/dist/universal/.rovodev/skills/citable/SKILL.md +66 -1
- package/dist/universal/.rovodev/skills/citable/VERSION +1 -1
- package/dist/universal/.rovodev/skills/citable/manifest.json +9 -6
- package/dist/universal/.rovodev/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
- package/dist/universal/.rovodev/skills/citable/schemas/finding.schema.json +69 -0
- package/dist/universal/.rovodev/skills/citable/schemas/remediation-verification.schema.json +98 -0
- package/dist/universal/.rovodev/skills/citable/schemas/sow.schema.json +92 -0
- package/dist/universal/.trae/skills/citable/SKILL.md +66 -1
- package/dist/universal/.trae/skills/citable/VERSION +1 -1
- package/dist/universal/.trae/skills/citable/manifest.json +9 -6
- package/dist/universal/.trae/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
- package/dist/universal/.trae/skills/citable/schemas/finding.schema.json +69 -0
- package/dist/universal/.trae/skills/citable/schemas/remediation-verification.schema.json +98 -0
- package/dist/universal/.trae/skills/citable/schemas/sow.schema.json +92 -0
- package/dist/universal/.trae-cn/skills/citable/SKILL.md +66 -1
- package/dist/universal/.trae-cn/skills/citable/VERSION +1 -1
- package/dist/universal/.trae-cn/skills/citable/manifest.json +9 -6
- package/dist/universal/.trae-cn/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/finding.schema.json +69 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/remediation-verification.schema.json +98 -0
- package/dist/universal/.trae-cn/skills/citable/schemas/sow.schema.json +92 -0
- package/dist/universal/manifest.json +112 -76
- package/package.json +1 -1
- package/schemas/cli-output-envelope.schema.json +30 -0
- package/schemas/finding.schema.json +69 -0
- package/schemas/remediation-verification.schema.json +98 -0
- package/schemas/sow.schema.json +92 -0
- package/skill/SKILL.md +66 -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/saliency.js +119 -0
- package/src/analysis/schemaRecommendations.js +173 -0
- package/src/analysis/strategicRoadmap.js +148 -0
- package/src/cli/index.js +418 -9
- package/src/commands/answerEngineReadiness.js +48 -0
- package/src/commands/audit.js +2 -2
- package/src/commands/auditBacklinks.js +79 -0
- package/src/commands/ciWorkflow.js +63 -0
- package/src/commands/compatibility.js +205 -0
- package/src/commands/context.js +2 -1
- package/src/commands/croBacklog.js +182 -0
- package/src/commands/croSuite.js +156 -0
- package/src/commands/edgeRules.js +80 -4
- package/src/commands/edgeSecurity.js +192 -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/experimentGuardrails.js +213 -0
- package/src/commands/goldenCorpus.js +155 -0
- package/src/commands/implementationKit.js +202 -0
- package/src/commands/inspectCro.js +67 -0
- package/src/commands/inspectEeat.js +53 -0
- package/src/commands/planExperiment.js +52 -0
- package/src/commands/previewCro.js +241 -0
- package/src/commands/prioritize.js +42 -6
- package/src/commands/remediate.js +461 -0
- 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/commands/verifyRemediation.js +204 -0
- package/src/commands/visualRegression.js +165 -0
- package/src/components/index.js +556 -0
- package/src/detectors/cro.js +48 -0
- package/src/detectors/framework.js +31 -1
- package/src/reporting/executiveCroReport.js +731 -0
- package/src/reporting/executiveExport.js +175 -1
- package/src/reporting/executiveSearchReport.js +704 -0
- package/src/sow/admissibilityGate.js +194 -0
- package/src/sow/generateSow.js +762 -0
|
@@ -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
|
+
}
|