@nebulacomponents/citable 1.17.0 → 1.18.1

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.
Files changed (164) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/dist/universal/.agents/skills/citable/SKILL.md +41 -1
  3. package/dist/universal/.agents/skills/citable/VERSION +1 -1
  4. package/dist/universal/.agents/skills/citable/agents/citable-remediator.md +43 -0
  5. package/dist/universal/.agents/skills/citable/agents/citable-sow-architect.md +43 -0
  6. package/dist/universal/.agents/skills/citable/commands/README.md +13 -0
  7. package/dist/universal/.agents/skills/citable/commands/remediation.md +55 -0
  8. package/dist/universal/.agents/skills/citable/commands/reporting.md +28 -0
  9. package/dist/universal/.agents/skills/citable/commands/sow.md +95 -0
  10. package/dist/universal/.agents/skills/citable/manifest.json +12 -7
  11. package/dist/universal/.agents/skills/citable/schemas/sow.schema.json +409 -0
  12. package/dist/universal/.claude/agents/citable/citable-remediator.md +43 -0
  13. package/dist/universal/.claude/agents/citable/citable-sow-architect.md +43 -0
  14. package/dist/universal/.claude/agents/citable/manifest.json +6 -4
  15. package/dist/universal/.claude/skills/citable/SKILL.md +41 -1
  16. package/dist/universal/.claude/skills/citable/VERSION +1 -1
  17. package/dist/universal/.claude/skills/citable/agents/citable-remediator.md +43 -0
  18. package/dist/universal/.claude/skills/citable/agents/citable-sow-architect.md +43 -0
  19. package/dist/universal/.claude/skills/citable/commands/README.md +13 -0
  20. package/dist/universal/.claude/skills/citable/commands/remediation.md +55 -0
  21. package/dist/universal/.claude/skills/citable/commands/reporting.md +28 -0
  22. package/dist/universal/.claude/skills/citable/commands/sow.md +95 -0
  23. package/dist/universal/.claude/skills/citable/manifest.json +16 -9
  24. package/dist/universal/.claude/skills/citable/schemas/sow.schema.json +409 -0
  25. package/dist/universal/.cursor/skills/citable/SKILL.md +41 -1
  26. package/dist/universal/.cursor/skills/citable/VERSION +1 -1
  27. package/dist/universal/.cursor/skills/citable/agents/citable-remediator.md +43 -0
  28. package/dist/universal/.cursor/skills/citable/agents/citable-sow-architect.md +43 -0
  29. package/dist/universal/.cursor/skills/citable/commands/README.md +13 -0
  30. package/dist/universal/.cursor/skills/citable/commands/remediation.md +55 -0
  31. package/dist/universal/.cursor/skills/citable/commands/reporting.md +28 -0
  32. package/dist/universal/.cursor/skills/citable/commands/sow.md +95 -0
  33. package/dist/universal/.cursor/skills/citable/manifest.json +12 -7
  34. package/dist/universal/.cursor/skills/citable/schemas/sow.schema.json +409 -0
  35. package/dist/universal/.gemini/skills/citable/SKILL.md +41 -1
  36. package/dist/universal/.gemini/skills/citable/VERSION +1 -1
  37. package/dist/universal/.gemini/skills/citable/agents/citable-remediator.md +43 -0
  38. package/dist/universal/.gemini/skills/citable/agents/citable-sow-architect.md +43 -0
  39. package/dist/universal/.gemini/skills/citable/commands/README.md +13 -0
  40. package/dist/universal/.gemini/skills/citable/commands/remediation.md +55 -0
  41. package/dist/universal/.gemini/skills/citable/commands/reporting.md +28 -0
  42. package/dist/universal/.gemini/skills/citable/commands/sow.md +95 -0
  43. package/dist/universal/.gemini/skills/citable/manifest.json +12 -7
  44. package/dist/universal/.gemini/skills/citable/schemas/sow.schema.json +409 -0
  45. package/dist/universal/.github/skills/citable/SKILL.md +41 -1
  46. package/dist/universal/.github/skills/citable/VERSION +1 -1
  47. package/dist/universal/.github/skills/citable/agents/citable-remediator.md +43 -0
  48. package/dist/universal/.github/skills/citable/agents/citable-sow-architect.md +43 -0
  49. package/dist/universal/.github/skills/citable/commands/README.md +13 -0
  50. package/dist/universal/.github/skills/citable/commands/remediation.md +55 -0
  51. package/dist/universal/.github/skills/citable/commands/reporting.md +28 -0
  52. package/dist/universal/.github/skills/citable/commands/sow.md +95 -0
  53. package/dist/universal/.github/skills/citable/manifest.json +12 -7
  54. package/dist/universal/.github/skills/citable/schemas/sow.schema.json +409 -0
  55. package/dist/universal/.kiro/skills/citable/SKILL.md +41 -1
  56. package/dist/universal/.kiro/skills/citable/VERSION +1 -1
  57. package/dist/universal/.kiro/skills/citable/agents/citable-remediator.md +43 -0
  58. package/dist/universal/.kiro/skills/citable/agents/citable-sow-architect.md +43 -0
  59. package/dist/universal/.kiro/skills/citable/commands/README.md +13 -0
  60. package/dist/universal/.kiro/skills/citable/commands/remediation.md +55 -0
  61. package/dist/universal/.kiro/skills/citable/commands/reporting.md +28 -0
  62. package/dist/universal/.kiro/skills/citable/commands/sow.md +95 -0
  63. package/dist/universal/.kiro/skills/citable/manifest.json +12 -7
  64. package/dist/universal/.kiro/skills/citable/schemas/sow.schema.json +409 -0
  65. package/dist/universal/.opencode/skills/citable/SKILL.md +41 -1
  66. package/dist/universal/.opencode/skills/citable/VERSION +1 -1
  67. package/dist/universal/.opencode/skills/citable/agents/citable-remediator.md +43 -0
  68. package/dist/universal/.opencode/skills/citable/agents/citable-sow-architect.md +43 -0
  69. package/dist/universal/.opencode/skills/citable/commands/README.md +13 -0
  70. package/dist/universal/.opencode/skills/citable/commands/remediation.md +55 -0
  71. package/dist/universal/.opencode/skills/citable/commands/reporting.md +28 -0
  72. package/dist/universal/.opencode/skills/citable/commands/sow.md +95 -0
  73. package/dist/universal/.opencode/skills/citable/manifest.json +12 -7
  74. package/dist/universal/.opencode/skills/citable/schemas/sow.schema.json +409 -0
  75. package/dist/universal/.pi/agent/skills/citable/SKILL.md +41 -1
  76. package/dist/universal/.pi/agent/skills/citable/VERSION +1 -1
  77. package/dist/universal/.pi/agent/skills/citable/agents/citable-remediator.md +43 -0
  78. package/dist/universal/.pi/agent/skills/citable/agents/citable-sow-architect.md +43 -0
  79. package/dist/universal/.pi/agent/skills/citable/commands/README.md +13 -0
  80. package/dist/universal/.pi/agent/skills/citable/commands/remediation.md +55 -0
  81. package/dist/universal/.pi/agent/skills/citable/commands/reporting.md +28 -0
  82. package/dist/universal/.pi/agent/skills/citable/commands/sow.md +95 -0
  83. package/dist/universal/.pi/agent/skills/citable/manifest.json +12 -7
  84. package/dist/universal/.pi/agent/skills/citable/schemas/sow.schema.json +409 -0
  85. package/dist/universal/.qoder/skills/citable/SKILL.md +41 -1
  86. package/dist/universal/.qoder/skills/citable/VERSION +1 -1
  87. package/dist/universal/.qoder/skills/citable/agents/citable-remediator.md +43 -0
  88. package/dist/universal/.qoder/skills/citable/agents/citable-sow-architect.md +43 -0
  89. package/dist/universal/.qoder/skills/citable/commands/README.md +13 -0
  90. package/dist/universal/.qoder/skills/citable/commands/remediation.md +55 -0
  91. package/dist/universal/.qoder/skills/citable/commands/reporting.md +28 -0
  92. package/dist/universal/.qoder/skills/citable/commands/sow.md +95 -0
  93. package/dist/universal/.qoder/skills/citable/manifest.json +12 -7
  94. package/dist/universal/.qoder/skills/citable/schemas/sow.schema.json +409 -0
  95. package/dist/universal/.rovodev/skills/citable/SKILL.md +41 -1
  96. package/dist/universal/.rovodev/skills/citable/VERSION +1 -1
  97. package/dist/universal/.rovodev/skills/citable/agents/citable-remediator.md +43 -0
  98. package/dist/universal/.rovodev/skills/citable/agents/citable-sow-architect.md +43 -0
  99. package/dist/universal/.rovodev/skills/citable/commands/README.md +13 -0
  100. package/dist/universal/.rovodev/skills/citable/commands/remediation.md +55 -0
  101. package/dist/universal/.rovodev/skills/citable/commands/reporting.md +28 -0
  102. package/dist/universal/.rovodev/skills/citable/commands/sow.md +95 -0
  103. package/dist/universal/.rovodev/skills/citable/manifest.json +12 -7
  104. package/dist/universal/.rovodev/skills/citable/schemas/sow.schema.json +409 -0
  105. package/dist/universal/.trae/skills/citable/SKILL.md +41 -1
  106. package/dist/universal/.trae/skills/citable/VERSION +1 -1
  107. package/dist/universal/.trae/skills/citable/agents/citable-remediator.md +43 -0
  108. package/dist/universal/.trae/skills/citable/agents/citable-sow-architect.md +43 -0
  109. package/dist/universal/.trae/skills/citable/commands/README.md +13 -0
  110. package/dist/universal/.trae/skills/citable/commands/remediation.md +55 -0
  111. package/dist/universal/.trae/skills/citable/commands/reporting.md +28 -0
  112. package/dist/universal/.trae/skills/citable/commands/sow.md +95 -0
  113. package/dist/universal/.trae/skills/citable/manifest.json +12 -7
  114. package/dist/universal/.trae/skills/citable/schemas/sow.schema.json +409 -0
  115. package/dist/universal/.trae-cn/skills/citable/SKILL.md +41 -1
  116. package/dist/universal/.trae-cn/skills/citable/VERSION +1 -1
  117. package/dist/universal/.trae-cn/skills/citable/agents/citable-remediator.md +43 -0
  118. package/dist/universal/.trae-cn/skills/citable/agents/citable-sow-architect.md +43 -0
  119. package/dist/universal/.trae-cn/skills/citable/commands/README.md +13 -0
  120. package/dist/universal/.trae-cn/skills/citable/commands/remediation.md +55 -0
  121. package/dist/universal/.trae-cn/skills/citable/commands/reporting.md +28 -0
  122. package/dist/universal/.trae-cn/skills/citable/commands/sow.md +95 -0
  123. package/dist/universal/.trae-cn/skills/citable/manifest.json +12 -7
  124. package/dist/universal/.trae-cn/skills/citable/schemas/sow.schema.json +409 -0
  125. package/dist/universal/manifest.json +150 -88
  126. package/package.json +1 -1
  127. package/schemas/sow.schema.json +409 -0
  128. package/skill/SKILL.md +41 -1
  129. package/skill/agents/citable-remediator.md +43 -0
  130. package/skill/agents/citable-sow-architect.md +43 -0
  131. package/skill/commands/README.md +13 -0
  132. package/skill/commands/remediation.md +55 -0
  133. package/skill/commands/reporting.md +28 -0
  134. package/skill/commands/sow.md +95 -0
  135. package/src/analysis/behavioral.js +126 -0
  136. package/src/analysis/croAudit.js +302 -0
  137. package/src/analysis/croRoadmap.js +121 -0
  138. package/src/analysis/eeat.js +300 -0
  139. package/src/analysis/funnelAnalysis.js +171 -0
  140. package/src/analysis/iceMatrix.js +205 -0
  141. package/src/analysis/offpage.js +228 -0
  142. package/src/analysis/readiness.js +253 -0
  143. package/src/analysis/schemaRecommendations.js +173 -0
  144. package/src/analysis/strategicRoadmap.js +148 -0
  145. package/src/cli/index.js +149 -3
  146. package/src/commands/answerEngineReadiness.js +48 -0
  147. package/src/commands/auditBacklinks.js +79 -0
  148. package/src/commands/croBacklog.js +182 -0
  149. package/src/commands/croSuite.js +156 -0
  150. package/src/commands/executive.js +6 -0
  151. package/src/commands/executiveCro.js +30 -0
  152. package/src/commands/executiveSearch.js +28 -0
  153. package/src/commands/inspectCro.js +7 -0
  154. package/src/commands/inspectEeat.js +53 -0
  155. package/src/commands/prioritize.js +42 -6
  156. package/src/commands/roadmapCmd.js +69 -0
  157. package/src/commands/schemaCmd.js +10 -2
  158. package/src/commands/sowCmd.js +84 -0
  159. package/src/commands/sweep.js +357 -0
  160. package/src/reporting/executiveCroReport.js +731 -0
  161. package/src/reporting/executiveExport.js +62 -0
  162. package/src/reporting/executiveSearchReport.js +704 -0
  163. package/src/sow/admissibilityGate.js +364 -0
  164. package/src/sow/generateSow.js +1178 -0
@@ -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
+ }
@@ -0,0 +1,300 @@
1
+ import { parse as parseHtml } from 'node-html-parser';
2
+ import { registryPageFor, safePath } from '../detectors/framework.js';
3
+
4
+ /**
5
+ * Evaluates on-page content against Google's E-E-A-T rubric on a 0.0 - 5.0 scale.
6
+ *
7
+ * Premise 2 compliance:
8
+ * - fact_status: 'modeled_rubric_evaluation'
9
+ * - E-E-A-T is an evaluative framework from the Search Quality Rater Guidelines,
10
+ * NOT a direct algorithmic ranking factor or search engine API metric.
11
+ */
12
+ export function evaluateEeat(page, ctx = {}) {
13
+ const html = page.html || '';
14
+ const root = parseHtml(html);
15
+ const text = (page.text || root.textContent || '').replace(/\s+/g, ' ').trim();
16
+ const lowerText = text.toLowerCase();
17
+ const reg = registryPageFor(ctx, page);
18
+
19
+ // -------------------------------------------------------------
20
+ // 1. EXPERIENCE (0.0 - 5.0)
21
+ // -------------------------------------------------------------
22
+ const expSignals = [];
23
+ const expMissing = [];
24
+ let expScore = 1.0;
25
+
26
+ // First-person practitioner indicators
27
+ const FIRST_PERSON_RX = /\b(?:we tested|in our testing|we evaluated|our evaluation|we measured|our analysis|we observed|our findings|in our experience|our team found|hands-on|case study|we verified|we deployed)\b/i;
28
+ if (FIRST_PERSON_RX.test(text)) {
29
+ expScore += 1.2;
30
+ expSignals.push('First-person practitioner observations and testing language detected');
31
+ } else {
32
+ expMissing.push('Add first-hand testing observations, case studies, or practitioner findings');
33
+ }
34
+
35
+ // Original media / demonstrations / code artifacts
36
+ const codeBlocks = root.querySelectorAll('pre, code, table');
37
+ if (codeBlocks.length >= 2) {
38
+ expScore += 1.0;
39
+ expSignals.push(`Structured demonstration artifacts present (${codeBlocks.length} code/table blocks)`);
40
+ } else {
41
+ expMissing.push('Include original demonstration tables, code snippets, or walkthroughs');
42
+ }
43
+
44
+ // Original images with descriptive captions/alt text
45
+ const images = page.images || root.querySelectorAll('img');
46
+ const validImages = images.filter((img) => {
47
+ const alt = typeof img === 'object' && img.getAttribute ? img.getAttribute('alt') : img.alt;
48
+ return Boolean(alt && alt.length > 5);
49
+ });
50
+ if (validImages.length >= 1) {
51
+ expScore += 0.8;
52
+ expSignals.push(`Descriptive contextual images present (${validImages.length} with detailed alt text)`);
53
+ }
54
+
55
+ // Quantitative measurement / benchmark data
56
+ const METRIC_DATA_RX = /(?:\b\d+(?:\.\d+)?%|\b\d+\s*ms\b|\b\d+\s*s\b|\$\d+(?:,\d{3})*|\bbenchmark\b|\bresults showed\b)/i;
57
+ if (METRIC_DATA_RX.test(text)) {
58
+ expScore += 1.0;
59
+ expSignals.push('Quantitative experimental or benchmark metrics presented');
60
+ } else {
61
+ expMissing.push('Include verifiable quantitative benchmark data or empirical results');
62
+ }
63
+
64
+ expScore = Math.min(5.0, Math.max(0.0, Math.round(expScore * 10) / 10));
65
+
66
+ // -------------------------------------------------------------
67
+ // 2. EXPERTISE (0.0 - 5.0)
68
+ // -------------------------------------------------------------
69
+ const expertSignals = [];
70
+ const expertMissing = [];
71
+ let expertScore = 1.0;
72
+
73
+ // Author attribution in page or schema
74
+ let authorName = null;
75
+ let authorSameAs = null;
76
+ for (const j of (page.jsonLd || [])) {
77
+ for (const b of (j.blocks || [])) {
78
+ if (b.author) {
79
+ const authorObj = Array.isArray(b.author) ? b.author[0] : b.author;
80
+ authorName = authorObj?.name || (typeof authorObj === 'string' ? authorObj : null);
81
+ authorSameAs = authorObj?.sameAs || null;
82
+ }
83
+ }
84
+ }
85
+
86
+ // Visible byline check
87
+ const BYLINE_RX = /\b(?:written by|authored by|author:|by)\s+([A-Z][a-z]+(?:\s+[A-Z][a-z]+)+)/i;
88
+ const bylineMatch = text.match(BYLINE_RX);
89
+ if (!authorName && bylineMatch) {
90
+ authorName = bylineMatch[1];
91
+ }
92
+
93
+ if (authorName) {
94
+ expertScore += 1.5;
95
+ expertSignals.push(`Attributed author identified: "${authorName}"`);
96
+ } else {
97
+ expertMissing.push('Add explicit author byline with credentials or Person schema');
98
+ }
99
+
100
+ // Author credentials / biography / authoritative profiles
101
+ const BIO_RX = /\b(?:ph\.?d|m\.?d|engineer|architect|researcher|director|specialist|consultant|analyst|professor|founder|lead)\b/i;
102
+ if (authorSameAs || BIO_RX.test(text)) {
103
+ expertScore += 1.2;
104
+ expertSignals.push('Author credentials, professional title, or external disambiguation profiles verified');
105
+ } else {
106
+ expertMissing.push('Link author to authoritative profile (sameAs: LinkedIn, ORCID, GitHub) and list credentials');
107
+ }
108
+
109
+ // Content depth & semantic heading hierarchy
110
+ const wordCount = page.rawVisibleWordCount || page.wordCount || text.split(/\s+/).filter(Boolean).length;
111
+ const headings = page.headings || [];
112
+ if (wordCount >= 600 && headings.length >= 3) {
113
+ expertScore += 1.3;
114
+ expertSignals.push(`Substantive technical depth (${wordCount} words, ${headings.length} headings)`);
115
+ } else if (wordCount < 300) {
116
+ expertMissing.push('Expand content depth to address topic comprehensively (currently thin <300 words)');
117
+ }
118
+
119
+ expertScore = Math.min(5.0, Math.max(0.0, Math.round(expertScore * 10) / 10));
120
+
121
+ // -------------------------------------------------------------
122
+ // 3. AUTHORITATIVENESS (0.0 - 5.0)
123
+ // -------------------------------------------------------------
124
+ const authSignals = [];
125
+ const authMissing = [];
126
+ let authScore = 1.0;
127
+
128
+ // External primary source citations (.edu, .gov, standards, docs)
129
+ const links = page.links || root.querySelectorAll('a').map((a) => ({
130
+ href: a.getAttribute('href') || '',
131
+ text: a.text.trim(),
132
+ }));
133
+
134
+ const externalLinks = links.filter((l) => /^https?:\/\//i.test(l.href));
135
+ const authoritativeDomains = externalLinks.filter((l) => {
136
+ const u = l.href.toLowerCase();
137
+ return u.includes('.edu') || u.includes('.gov') || u.includes('.org') ||
138
+ u.includes('w3.org') || u.includes('rfc-editor.org') || u.includes('github.com') ||
139
+ u.includes('arxiv.org') || u.includes('developer.mozilla.org') || u.includes('web.dev');
140
+ });
141
+
142
+ if (authoritativeDomains.length >= 2) {
143
+ authScore += 1.5;
144
+ authSignals.push(`Primary authority citations present (${authoritativeDomains.length} citations to academic/standard/industry domains)`);
145
+ } else if (externalLinks.length >= 1) {
146
+ authScore += 0.8;
147
+ authSignals.push(`External citations present (${externalLinks.length} external links)`);
148
+ authMissing.push('Cite recognized primary sources (standards organizations, academic research, official documentation)');
149
+ } else {
150
+ authMissing.push('Add outbound citations to primary sources and industry standards');
151
+ }
152
+
153
+ // Registry claim grounding
154
+ const claims = ctx.registries?.claims?.entries || [];
155
+ const pageClaims = claims.filter((c) => c.canonical_url === page.url || c.claim_id);
156
+ const verifiedClaims = pageClaims.filter((c) => c.status === 'verified');
157
+ if (verifiedClaims.length >= 1) {
158
+ authScore += 1.5;
159
+ authSignals.push(`Page claims backed by verified evidence records (${verifiedClaims.length} verified claims)`);
160
+ } else {
161
+ authMissing.push('Ground material claims in evidence registry with primary citations');
162
+ }
163
+
164
+ // Publisher / Organization entity graph
165
+ let hasOrgSchema = false;
166
+ for (const j of (page.jsonLd || [])) {
167
+ for (const b of (j.blocks || [])) {
168
+ const type = [].concat(b['@type'] || []).join(',');
169
+ const pubType = [].concat(b.publisher?.["@type"] || []).join(",");
170
+ if (/Organization|Corporation/i.test(type) || /Organization|Corporation/i.test(pubType)) hasOrgSchema = true;
171
+ }
172
+ }
173
+ if (hasOrgSchema) {
174
+ authScore += 1.0;
175
+ authSignals.push('Publisher Organization schema graph present');
176
+ } else {
177
+ authMissing.push('Add Organization schema with publisher credentials and sameAs properties');
178
+ }
179
+
180
+ authScore = Math.min(5.0, Math.max(0.0, Math.round(authScore * 10) / 10));
181
+
182
+ // -------------------------------------------------------------
183
+ // 4. TRUSTWORTHINESS (0.0 - 5.0) — The foundational pillar
184
+ // -------------------------------------------------------------
185
+ const trustSignals = [];
186
+ const trustMissing = [];
187
+ let trustScore = 1.0;
188
+
189
+ // Editorial freshness / visible timestamps
190
+ const DATE_RX = /\b(?:published|updated|last modified|reviewed|date):\s*([a-zA-Z]+\s+\d{1,2},\s*\d{4}|\d{4}-\d{2}-\d{2})\b/i;
191
+ const hasVisibleDate = DATE_RX.test(text);
192
+ let hasSchemaDate = false;
193
+ for (const j of (page.jsonLd || [])) {
194
+ for (const b of (j.blocks || [])) {
195
+ if (b.datePublished || b.dateModified) hasSchemaDate = true;
196
+ }
197
+ }
198
+ if (hasVisibleDate || hasSchemaDate) {
199
+ trustScore += 1.0;
200
+ trustSignals.push('Transparent publication and modification dates disclosed');
201
+ } else {
202
+ trustMissing.push('Disclose explicit publication and last-updated dates');
203
+ }
204
+
205
+ // Contact / About / Support transparency
206
+ const hasContact = links.some((l) => /contact|about|support|team|company/i.test(l.href) || /contact|about|support/i.test(l.text));
207
+ if (hasContact) {
208
+ trustScore += 1.0;
209
+ trustSignals.push('Direct contact, about, or support channels accessible');
210
+ } else {
211
+ trustMissing.push('Provide clear navigation links to About Us, Contact, and Support pages');
212
+ }
213
+
214
+ // Legal transparency: Privacy policy & Terms
215
+ const hasPrivacy = links.some((l) => /privacy/i.test(l.href) || /privacy/i.test(l.text));
216
+ const hasTerms = links.some((l) => /terms/i.test(l.href) || /terms/i.test(l.text));
217
+ if (hasPrivacy && hasTerms) {
218
+ trustScore += 1.0;
219
+ trustSignals.push('Privacy policy and terms of service disclosed');
220
+ } else {
221
+ trustMissing.push('Provide clear links to Privacy Policy and Terms of Service');
222
+ }
223
+
224
+ // Security & protocol
225
+ if (page.url && page.url.startsWith('https://')) {
226
+ trustScore += 1.0;
227
+ trustSignals.push('Secure HTTPS transport confirmed');
228
+ } else if (!page.url.startsWith('http://')) {
229
+ trustScore += 0.5; // relative / local test
230
+ }
231
+
232
+ // Commercial / sponsorship transparency
233
+ const sponsoredLinks = links.filter((l) => (l.rel || '').includes('sponsored') || (l.rel || '').includes('nofollow'));
234
+ if (sponsoredLinks.length > 0) {
235
+ trustScore += 0.5;
236
+ trustSignals.push('Commercial and affiliate links properly attributed with rel="sponsored"');
237
+ }
238
+
239
+ trustScore = Math.min(5.0, Math.max(0.0, Math.round(trustScore * 10) / 10));
240
+
241
+ // -------------------------------------------------------------
242
+ // COMPOSITE E-E-A-T SCORE (0.0 - 5.0)
243
+ // In Google Quality Rater Guidelines, Trust is the foundational base:
244
+ // untrusted content cannot achieve a high overall rating.
245
+ // -------------------------------------------------------------
246
+ let composite = (trustScore * 0.40) + (expertScore * 0.25) + (expScore * 0.20) + (authScore * 0.15);
247
+ if (trustScore < 2.0) {
248
+ composite = Math.min(composite, trustScore); // Trust cap
249
+ }
250
+ composite = Math.round(composite * 10) / 10;
251
+
252
+ // Grade rating
253
+ let rating = 'medium';
254
+ if (composite >= 4.0) rating = 'very_high';
255
+ else if (composite >= 3.0) rating = 'high';
256
+ else if (composite >= 2.0) rating = 'medium';
257
+ else rating = 'low';
258
+
259
+ return {
260
+ url: page.url,
261
+ fact_status: 'modeled_rubric_evaluation',
262
+ methodology: 'Google Search Quality Rater Guidelines (E-E-A-T Evaluative Rubric)',
263
+ disclaimer: 'E-E-A-T is an evaluative heuristic rubric modeled on Quality Rater Guidelines, not an algorithmic ranking score. Google does not calculate a single E-E-A-T metric.',
264
+ composite_score: composite,
265
+ overall_rating: rating,
266
+ dimensions: {
267
+ experience: {
268
+ score: expScore,
269
+ scale: '0.0 - 5.0',
270
+ signals: expSignals,
271
+ gaps: expMissing,
272
+ },
273
+ expertise: {
274
+ score: expertScore,
275
+ scale: '0.0 - 5.0',
276
+ author: authorName,
277
+ signals: expertSignals,
278
+ gaps: expertMissing,
279
+ },
280
+ authoritativeness: {
281
+ score: authScore,
282
+ scale: '0.0 - 5.0',
283
+ signals: authSignals,
284
+ gaps: authMissing,
285
+ },
286
+ trustworthiness: {
287
+ score: trustScore,
288
+ scale: '0.0 - 5.0',
289
+ signals: trustSignals,
290
+ gaps: trustMissing,
291
+ },
292
+ },
293
+ actionable_improvements: [
294
+ ...expMissing.map((g) => ({ dimension: 'experience', recommendation: g })),
295
+ ...expertMissing.map((g) => ({ dimension: 'expertise', recommendation: g })),
296
+ ...authMissing.map((g) => ({ dimension: 'authoritativeness', recommendation: g })),
297
+ ...trustMissing.map((g) => ({ dimension: 'trustworthiness', recommendation: g })),
298
+ ],
299
+ };
300
+ }
@@ -0,0 +1,171 @@
1
+ import { safePath } from '../detectors/framework.js';
2
+
3
+ /**
4
+ * Perform End-to-End Funnel and Conversion-Path Analysis across site pages.
5
+ */
6
+ export function analyzeConversionFunnel(sitePages = [], declaredFunnel = null) {
7
+ let steps = [];
8
+
9
+ if (declaredFunnel?.steps?.length) {
10
+ steps = declaredFunnel.steps;
11
+ } else {
12
+ // Auto-discover standard conversion path from site pages
13
+ const roleOrder = ['landing', 'product', 'pricing', 'checkout', 'confirmation'];
14
+ const discovered = [];
15
+
16
+ for (const role of roleOrder) {
17
+ const p = sitePages.find((page) => {
18
+ const u = (page.url || '').toLowerCase();
19
+ if (role === 'landing' && (u.endsWith('/') || u.includes('home') || u.includes('landing'))) return true;
20
+ if (role === 'product' && (u.includes('product') || u.includes('feature') || u.includes('solution'))) return true;
21
+ if (role === 'pricing' && (u.includes('pricing') || u.includes('plan'))) return true;
22
+ if (role === 'checkout' && (u.includes('checkout') || u.includes('signup') || u.includes('demo') || u.includes('contact'))) return true;
23
+ if (role === 'confirmation' && (u.includes('thank-you') || u.includes('success') || u.includes('confirmed'))) return true;
24
+ return false;
25
+ });
26
+ if (p) {
27
+ discovered.push({
28
+ step_id: `STEP-${role.toUpperCase()}`,
29
+ name: `${role[0].toUpperCase()}${role.slice(1)} Page`,
30
+ url_pattern: p.url,
31
+ role,
32
+ });
33
+ }
34
+ }
35
+ steps = discovered.length >= 2 ? discovered : [];
36
+ }
37
+
38
+ if (steps.length === 0) {
39
+ return {
40
+ funnel_id: declaredFunnel?.funnel_id || 'AUTO-DISCOVERED',
41
+ funnel_name: declaredFunnel?.name || 'Conversion Funnel',
42
+ status: 'insufficient_steps',
43
+ message: 'Could not establish conversion funnel: declare steps in .citable/funnels.yaml or provide multi-step pages.',
44
+ steps: [],
45
+ funnel_health_score: 0,
46
+ leaks: [],
47
+ };
48
+ }
49
+
50
+ const stepEvaluations = [];
51
+ const leaks = [];
52
+ let continuityScore = 100;
53
+
54
+ for (let i = 0; i < steps.length; i++) {
55
+ const step = steps[i];
56
+ const page = sitePages.find((p) => (p.url || '').includes(step.url_pattern) || safePath(p.url) === safePath(step.url_pattern));
57
+
58
+ if (!page) {
59
+ continuityScore -= 25;
60
+ leaks.push({
61
+ step_id: step.step_id,
62
+ severity: 'critical',
63
+ type: 'missing_page',
64
+ description: `Funnel step page matching "${step.url_pattern}" was not found in audited site output`,
65
+ });
66
+ stepEvaluations.push({
67
+ step: i + 1,
68
+ step_id: step.step_id,
69
+ name: step.name,
70
+ url: step.url_pattern,
71
+ role: step.role,
72
+ status_code: null,
73
+ continuity_intact: false,
74
+ drop_off_risk: 'critical',
75
+ });
76
+ continue;
77
+ }
78
+
79
+ const ctas = page.ctas || [];
80
+ const forms = page.forms || [];
81
+ let nextStepLinked = true;
82
+ let nextStepTarget = null;
83
+ let paramPreserved = true;
84
+
85
+ if (i < steps.length - 1) {
86
+ const nextStep = steps[i + 1];
87
+ const linkToNext = ctas.find((c) => c.target && c.target.includes(nextStep.url_pattern));
88
+ if (!linkToNext) {
89
+ nextStepLinked = false;
90
+ continuityScore -= 20;
91
+ leaks.push({
92
+ step_id: step.step_id,
93
+ severity: 'high',
94
+ type: 'missing_next_step_cta',
95
+ description: `Step ${i + 1} (${step.name}) has no interactive CTA linking directly to step ${i + 2} (${nextStep.name})`,
96
+ });
97
+ } else {
98
+ nextStepTarget = linkToNext.target;
99
+ // Check attribution parameter preservation
100
+ if (linkToNext.target.includes('?') && !linkToNext.target.includes('utm_')) {
101
+ paramPreserved = false;
102
+ continuityScore -= 10;
103
+ leaks.push({
104
+ step_id: step.step_id,
105
+ severity: 'medium',
106
+ type: 'attribution_parameter_loss',
107
+ description: `CTA link to "${nextStepTarget}" strips campaign attribution query parameters`,
108
+ });
109
+ }
110
+ }
111
+ }
112
+
113
+ // Post-conversion confirmation page indexing check
114
+ if (step.role === 'confirmation' || i === steps.length - 1) {
115
+ const robots = Array.from(page.robotsDirectives || []);
116
+ const hasNoindex = robots.some((r) => r.includes('noindex'));
117
+ if (!hasNoindex && (step.role === 'confirmation' || page.url.includes('thank-you') || page.url.includes('success'))) {
118
+ continuityScore -= 10;
119
+ leaks.push({
120
+ step_id: step.step_id,
121
+ severity: 'medium',
122
+ type: 'unprotected_conversion_page',
123
+ description: 'Post-conversion thank-you page lacks a noindex directive, risking conversion goal contamination in analytics',
124
+ });
125
+ }
126
+ }
127
+
128
+ // Distraction leaks on checkout / high-intent steps
129
+ if (['checkout', 'pricing', 'signup'].includes(step.role) && (page.navLinksCount || 0) > 12) {
130
+ continuityScore -= 15;
131
+ leaks.push({
132
+ step_id: step.step_id,
133
+ severity: 'medium',
134
+ type: 'enclosed_checkout_leak',
135
+ description: `Conversion step "${step.name}" contains ${page.navLinksCount} header/footer links, leaking visitors out of the funnel`,
136
+ });
137
+ }
138
+
139
+ // Drop-off risk assessment
140
+ let dropOffRisk = 'low';
141
+ if (!nextStepLinked || page.status >= 400) dropOffRisk = 'critical';
142
+ else if (forms.some((f) => f.fieldCount > 6)) dropOffRisk = 'high';
143
+ else if ((page.navLinksCount || 0) > 12) dropOffRisk = 'moderate';
144
+
145
+ stepEvaluations.push({
146
+ step: i + 1,
147
+ step_id: step.step_id,
148
+ name: step.name,
149
+ url: page.url,
150
+ role: step.role,
151
+ status_code: page.status,
152
+ continuity_intact: nextStepLinked && page.status === 200,
153
+ next_step_target: nextStepTarget,
154
+ drop_off_risk: dropOffRisk,
155
+ form_count: forms.length,
156
+ cta_count: ctas.length,
157
+ });
158
+ }
159
+
160
+ const finalScore = Math.min(100, Math.max(0, continuityScore));
161
+
162
+ return {
163
+ funnel_id: declaredFunnel?.funnel_id || 'DISCOVERED-FUNNEL',
164
+ funnel_name: declaredFunnel?.name || 'Primary Conversion Path',
165
+ funnel_health_score: finalScore,
166
+ status: finalScore >= 80 ? 'healthy' : finalScore >= 50 ? 'needs_optimization' : 'critical_leaks',
167
+ steps: stepEvaluations,
168
+ total_steps: steps.length,
169
+ leaks,
170
+ };
171
+ }