@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,182 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { readJson } from '../shared/io.js';
4
+
5
+ /**
6
+ * Generate a prioritized A/B experiment backlog from CRO audit findings.
7
+ */
8
+ export function generateExperimentBacklog(findings = [], { dailyVisitors = 500, baselineRate = 0.02, defaultMde = 0.12 } = {}) {
9
+ const croFindings = findings.filter((f) => (f.detector_id || '').startsWith('CRO-') || f.discipline?.includes('cro'));
10
+
11
+ const backlog = [];
12
+
13
+ for (let i = 0; i < croFindings.length; i++) {
14
+ const f = croFindings[i];
15
+ const ns = f.detector_id;
16
+ const pageUrl = f.subject?.identifier || f.subject?.url || '/';
17
+
18
+ let title = '';
19
+ let change = '';
20
+ let rationale = f.observation?.summary || 'Friction detected on conversion pathway';
21
+ let expectedMde = defaultMde;
22
+ let primaryMetric = 'conversion_rate';
23
+ let secondaryMetrics = ['bounce_rate', 'form_completion_rate'];
24
+
25
+ switch (ns) {
26
+ case 'CRO-001':
27
+ title = 'Prominent Interactive Hero CTA Deployment';
28
+ change = 'Deploy prominent, high-contrast primary CTA button matching declared conversion intent';
29
+ expectedMde = 0.15;
30
+ break;
31
+ case 'CRO-002':
32
+ title = 'Form Friction Reduction & Progressive Profiling';
33
+ change = 'Condense lead capture form from >6 fields to 3 essential fields with progressive disclosure';
34
+ expectedMde = 0.20;
35
+ primaryMetric = 'lead_submission_rate';
36
+ break;
37
+ case 'CRO-005':
38
+ title = 'Ad-to-Landing Scent Match Alignment';
39
+ change = 'Align above-the-fold H1 headline and value proposition directly with title and ad intent';
40
+ expectedMde = 0.12;
41
+ break;
42
+ case 'CRO-006':
43
+ title = 'Proximate Trust Proof & Security Badges';
44
+ change = 'Position verified customer logo bar and security certification badges immediately below primary CTA';
45
+ expectedMde = 0.10;
46
+ break;
47
+ case 'CRO-007':
48
+ title = 'HTML5 Autocomplete Form Optimization';
49
+ change = 'Inject standard HTML5 autocomplete attributes on all identity, email, and telephone fields';
50
+ expectedMde = 0.08;
51
+ primaryMetric = 'form_completion_speed';
52
+ break;
53
+ case 'CRO-010':
54
+ title = 'Hero Choice Overload Elimination';
55
+ change = 'Consolidate competing hero buttons into exactly ONE dominant primary action and one text link';
56
+ expectedMde = 0.14;
57
+ break;
58
+ case 'CRO-012':
59
+ title = 'High-Intent Value CTA Microcopy';
60
+ change = 'Replace generic "Submit" or "Learn More" with action-oriented value copy ("Get My Free Analysis")';
61
+ expectedMde = 0.10;
62
+ break;
63
+ case 'CRO-013':
64
+ title = 'Enclosed Distraction-Free Checkout Funnel';
65
+ change = 'Remove global header navigation and external outbound links on checkout/pricing steps';
66
+ expectedMde = 0.15;
67
+ break;
68
+ case 'CRO-021':
69
+ title = 'Express Payment Wallet Integration';
70
+ change = 'Implement 1-click Apple Pay, Google Pay, and PayPal express wallet buttons';
71
+ expectedMde = 0.18;
72
+ primaryMetric = 'checkout_conversion_rate';
73
+ break;
74
+ default:
75
+ title = `Remediation for ${f.name || ns}`;
76
+ change = f.remediation?.preferred || 'Implement recommended conversion design system component';
77
+ expectedMde = defaultMde;
78
+ }
79
+
80
+ // Falsifiable hypothesis formulation
81
+ const hypothesis = `If we ${change} on ${pageUrl}, then ${primaryMetric} will increase by at least ${Math.round(expectedMde * 100)}%, because ${rationale.toLowerCase()}.`;
82
+
83
+ // Sample size calculation (two-tailed alpha=0.05, power=0.80)
84
+ const p1 = baselineRate;
85
+ const p2 = p1 * (1 + expectedMde);
86
+ const pBar = (p1 + p2) / 2;
87
+ const zAlpha = 1.96;
88
+ const zBeta = 0.8416;
89
+ const num = Math.pow(zAlpha * Math.sqrt(2 * pBar * (1 - pBar)) + zBeta * Math.sqrt(p1 * (1 - p1) + p2 * (1 - p2)), 2);
90
+ const den = Math.pow(p2 - p1, 2);
91
+ const nPerVariant = Math.ceil(num / den);
92
+ const totalSample = nPerVariant * 2;
93
+ const estDays = Math.max(7, Math.ceil(totalSample / dailyVisitors));
94
+
95
+ // Impact, Effort, Confidence (ICE)
96
+ const impact = Math.min(10, Math.max(4, Math.round(expectedMde * 50)));
97
+ const effort = ['CRO-007', 'CRO-012', 'CRO-005'].includes(ns) ? 2 : ['CRO-001', 'CRO-006', 'CRO-010'].includes(ns) ? 4 : 6;
98
+ const confidence = f.classification?.deterministic ? 9 : 7;
99
+ const iceScore = Math.round(((impact * confidence) / effort) * 10) / 10;
100
+
101
+ backlog.push({
102
+ experiment_id: `EXP-CRO-${String(i + 1).padStart(3, '0')}`,
103
+ title,
104
+ detector_id: ns,
105
+ target_page: pageUrl,
106
+ hypothesis,
107
+ discipline: ['cro'],
108
+ primary_metric: primaryMetric,
109
+ secondary_metrics: secondaryMetrics,
110
+ statistical_setup: {
111
+ baseline_rate_pct: Math.round(baselineRate * 1000) / 10,
112
+ expected_mde_pct: Math.round(expectedMde * 100),
113
+ confidence_level_pct: 95,
114
+ power_pct: 80,
115
+ sample_size_per_variant: nPerVariant,
116
+ total_sample_required: totalSample,
117
+ estimated_duration_days: estDays,
118
+ daily_visitors: dailyVisitors,
119
+ },
120
+ guardrails: [
121
+ 'SEO Retention: Variant must preserve all canonical tags, JSON-LD structured data, and meta robots tags',
122
+ 'Performance Guardrail: Mobile LCP must not degrade by > 250ms; CLS must remain <= 0.10',
123
+ 'Secondary Safety Metric: Bounce rate must not increase by > 5% relative to control',
124
+ ],
125
+ stopping_criteria: [
126
+ 'Minimum Duration Lock: Run for at least 7 full days to account for day-of-week seasonality, regardless of early p-values',
127
+ 'Sample Ratio Mismatch (SRM): Terminate immediately if traffic allocation deviates significantly (chi-square p < 0.001)',
128
+ 'Maximum Duration Cap: Stop and conclude as inconclusive if required sample size is not reached within 45 days',
129
+ ],
130
+ ice_prioritization: {
131
+ impact,
132
+ effort,
133
+ confidence,
134
+ ice_score: iceScore,
135
+ quadrant: impact >= 6 && effort <= 4 ? 'quick_wins' : impact >= 6 ? 'strategic_bets' : 'low_hanging_fruit',
136
+ },
137
+ status: 'draft',
138
+ });
139
+ }
140
+
141
+ // Sort by ICE score descending
142
+ backlog.sort((a, b) => b.ice_prioritization.ice_score - a.ice_prioritization.ice_score);
143
+
144
+ return {
145
+ fact_status: 'experiment_backlog_projection',
146
+ total_experiments: backlog.length,
147
+ summary: {
148
+ quick_wins: backlog.filter((e) => e.ice_prioritization.quadrant === 'quick_wins').length,
149
+ strategic_bets: backlog.filter((e) => e.ice_prioritization.quadrant === 'strategic_bets').length,
150
+ low_hanging_fruit: backlog.filter((e) => e.ice_prioritization.quadrant === 'low_hanging_fruit').length,
151
+ },
152
+ experiments: backlog,
153
+ };
154
+ }
155
+
156
+ /**
157
+ * Format Experiment Backlog as Markdown
158
+ */
159
+ export function formatBacklogMarkdown(backlog) {
160
+ const lines = [
161
+ `# Conversion Experimentation Backlog & Hypothesis Register`,
162
+ `=========================================================`,
163
+ `Total Hypotheses: ${backlog.total_experiments} | Quick Wins: ${backlog.summary.quick_wins} | Strategic Bets: ${backlog.summary.strategic_bets}`,
164
+ ``,
165
+ `Methodology: Every experiment requires a falsifiable hypothesis, statistical power calculations, SEO guardrails, and stopping criteria.`,
166
+ ``,
167
+ ];
168
+
169
+ for (const exp of backlog.experiments) {
170
+ lines.push(`## [${exp.ice_prioritization.quadrant.toUpperCase()}] ${exp.experiment_id}: ${exp.title}`);
171
+ lines.push(`- **Target Surface**: \`${exp.target_page}\` (Trigger: \`${exp.detector_id}\`)`);
172
+ lines.push(`- **Falsifiable Hypothesis**: "${exp.hypothesis}"`);
173
+ lines.push(`- **Primary Metric**: \`${exp.primary_metric}\` | **Expected MDE**: +${exp.statistical_setup.expected_mde_pct}%`);
174
+ lines.push(`- **Statistical Blueprint**: ${exp.statistical_setup.sample_size_per_variant.toLocaleString()} visitors/variant (~${exp.statistical_setup.estimated_duration_days} days at ${exp.statistical_setup.daily_visitors} visitors/day, 80% power @ 95% conf)`);
175
+ lines.push(`- **ICE Score**: **${exp.ice_prioritization.ice_score}** (Impact: ${exp.ice_prioritization.impact}/10, Effort: ${exp.ice_prioritization.effort}/10, Conf: ${exp.ice_prioritization.confidence}/10)`);
176
+ lines.push(`- **Guardrails**: ${exp.guardrails.join('; ')}`);
177
+ lines.push(`- **Stopping Criteria**: ${exp.stopping_criteria.join('; ')}`);
178
+ lines.push(``);
179
+ }
180
+
181
+ return lines.join('\n');
182
+ }
@@ -0,0 +1,156 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { buildContext } from './context.js';
4
+ import { selectDetectors } from '../detectors/index.js';
5
+ import { runDetectors, indexTargets, pageSubject, safePath } from '../detectors/framework.js';
6
+ import { auditPageCro } from '../analysis/croAudit.js';
7
+ import { analyzeConversionFunnel } from '../analysis/funnelAnalysis.js';
8
+ import { analyzeBehavioralTelemetry } from '../analysis/behavioral.js';
9
+ import { generateExperimentBacklog, formatBacklogMarkdown } from './croBacklog.js';
10
+ import { buildIceMatrix, formatIceMatrixOutput } from '../analysis/iceMatrix.js';
11
+ import { buildCroRoadmap, formatCroRoadmapMarkdown } from '../analysis/croRoadmap.js';
12
+
13
+ /**
14
+ * Full End-to-End CRO Intelligence Suite Audit
15
+ */
16
+ export async function auditCroSuite(root, { target, baseUrl, refDate, input, funnelId, write = false } = {}) {
17
+ const ctx = await buildContext(root, { target, baseUrl, refDate });
18
+ if (!ctx.site) throw new Error('cro audit requires a site target (built output directory or URL)');
19
+
20
+ // 1. Run CRO detectors across all pages
21
+ const croDetectors = selectDetectors({ namespaces: ['CRO'] });
22
+ const { findings } = runDetectors(croDetectors, ctx);
23
+
24
+ const pages = indexTargets(ctx);
25
+
26
+ // 2. Above-the-fold clarity, trust, cognitive load, offer architecture per page
27
+ const auditedPages = pages.map((page) => auditPageCro(page, ctx));
28
+
29
+ // Average scores across commercial pages
30
+ const commercialPages = auditedPages.filter((p) => {
31
+ const u = (p.url || '').toLowerCase();
32
+ return u.includes('pricing') || u.includes('product') || u.includes('feature') || u.includes('checkout') || u.endsWith('/') || u.includes('landing');
33
+ });
34
+ const targetGroup = commercialPages.length > 0 ? commercialPages : auditedPages;
35
+
36
+ const avgAtfClarity = targetGroup.length > 0
37
+ ? Math.round(targetGroup.reduce((acc, p) => acc + p.atf_clarity.score, 0) / targetGroup.length)
38
+ : 0;
39
+
40
+ const avgTrustScore = targetGroup.length > 0
41
+ ? Math.round(targetGroup.reduce((acc, p) => acc + p.trust_and_credibility.score, 0) / targetGroup.length)
42
+ : 0;
43
+
44
+ const avgConversionReadiness = targetGroup.length > 0
45
+ ? Math.round(targetGroup.reduce((acc, p) => acc + p.conversion_readiness_score, 0) / targetGroup.length)
46
+ : 0;
47
+
48
+ // 3. End-to-end Funnel and Conversion-Path Analysis
49
+ const declaredFunnel = (ctx.registries?.funnels?.entries || []).find((f) => funnelId ? f.funnel_id === funnelId : true) || null;
50
+ const funnelAnalysis = analyzeConversionFunnel(ctx.site.pages, declaredFunnel);
51
+
52
+ // 4. Behavioral Evidence Analysis (if telemetry file provided)
53
+ let behavioralAnalysis = null;
54
+ if (input) {
55
+ const filePath = path.resolve(root, input);
56
+ if (fs.existsSync(filePath)) {
57
+ try {
58
+ const rawTelemetry = JSON.parse(fs.readFileSync(filePath, 'utf8'));
59
+ behavioralAnalysis = analyzeBehavioralTelemetry(rawTelemetry);
60
+ } catch (err) {
61
+ // Non-blocking parse error
62
+ }
63
+ }
64
+ }
65
+
66
+ // 5. Experiment Backlog with Falsifiable Hypotheses
67
+ const experimentBacklog = generateExperimentBacklog(findings);
68
+
69
+ // 6. Impact / Effort / Confidence (ICE) Prioritization Matrix
70
+ const iceMatrix = buildIceMatrix(findings, { type: 'findings' });
71
+
72
+ // 7. 30 / 90 / 180-Day CRO Strategic Roadmap tied to Measurable Conversion Outcomes
73
+ const targetDomain = ctx.site.baseUrl ? new URL(ctx.site.baseUrl).hostname : 'target-site';
74
+ const croRoadmap = buildCroRoadmap({
75
+ findings,
76
+ targetDomain,
77
+ });
78
+
79
+ const result = {
80
+ fact_status: 'modeled_cro_evaluation',
81
+ target: ctx.site.baseUrl || target,
82
+ total_pages_audited: pages.length,
83
+ summary: {
84
+ conversion_readiness_score: avgConversionReadiness,
85
+ atf_clarity_score: avgAtfClarity,
86
+ trust_credibility_score: avgTrustScore,
87
+ funnel_health_score: funnelAnalysis.funnel_health_score,
88
+ total_cro_findings: findings.length,
89
+ planned_experiments: experimentBacklog.total_experiments,
90
+ quick_wins_count: iceMatrix.summary.quick_wins_count,
91
+ },
92
+ funnel_analysis: funnelAnalysis,
93
+ pages: auditedPages,
94
+ behavioral_telemetry: behavioralAnalysis,
95
+ experiment_backlog: experimentBacklog,
96
+ ice_matrix: iceMatrix,
97
+ strategic_roadmap: croRoadmap,
98
+ findings: findings.map((f) => ({
99
+ detector_id: f.detector_id,
100
+ severity: f.classification.severity,
101
+ subject: f.subject.identifier || f.subject.url,
102
+ summary: f.observation.summary,
103
+ remediation: f.remediation?.preferred,
104
+ })),
105
+ };
106
+
107
+ return result;
108
+ }
109
+
110
+ /**
111
+ * Format Human-Readable CRO Suite Terminal Report
112
+ */
113
+ export function formatCroSuiteOutput(r) {
114
+ const lines = [
115
+ `Conversion Rate Optimization (CRO) & Journey Intelligence Suite`,
116
+ `==============================================================`,
117
+ `Target: ${r.target}`,
118
+ `Audited Pages: ${r.total_pages_audited} | Conversion Readiness Score: ${r.summary.conversion_readiness_score}/100`,
119
+ ``,
120
+ `CORE PILLAR SCORES:`,
121
+ ` Above-The-Fold Clarity & Message Match: ${r.summary.atf_clarity_score} / 100`,
122
+ ` Trust, Credibility & Risk-Reversal: ${r.summary.trust_credibility_score} / 100`,
123
+ ` End-to-End Funnel Health: ${r.summary.funnel_health_score} / 100 (${r.funnel_analysis.status.toUpperCase()})`,
124
+ ` Active CRO Findings: ${r.summary.total_cro_findings} defect(s)`,
125
+ ``,
126
+ `END-TO-END CONVERSION PATH ANALYSIS (${r.funnel_analysis.funnel_name}):`,
127
+ ];
128
+
129
+ for (const s of r.funnel_analysis.steps) {
130
+ const status = s.continuity_intact ? 'INTACT' : 'BROKEN';
131
+ lines.push(` Step ${s.step}: [${s.role.toUpperCase()}] ${s.name} (${s.url}) → ${status} (Drop-off risk: ${s.drop_off_risk.toUpperCase()})`);
132
+ }
133
+
134
+ if (r.funnel_analysis.leaks.length) {
135
+ lines.push(``, `Identified Conversion Leaks:`);
136
+ for (const leak of r.funnel_analysis.leaks) {
137
+ lines.push(` - [${leak.severity.toUpperCase()}] (${leak.type}) ${leak.description}`);
138
+ }
139
+ }
140
+
141
+ lines.push(``, `EXPERIMENTATION BACKLOG (${r.experiment_backlog.total_experiments} falsifiable hypotheses generated):`);
142
+ for (const exp of r.experiment_backlog.experiments.slice(0, 5)) {
143
+ lines.push(` [${exp.ice_prioritization.quadrant.toUpperCase()}] ${exp.experiment_id}: ${exp.title} (Expected MDE: +${exp.statistical_setup.expected_mde_pct}%, ICE: ${exp.ice_prioritization.ice_score})`);
144
+ lines.push(` Hypothesis: "${exp.hypothesis}"`);
145
+ }
146
+
147
+ lines.push(``, `30 / 90 / 180-DAY CRO STRATEGIC ROADMAP:`);
148
+ for (const h of r.strategic_roadmap.horizons) {
149
+ lines.push(` ${h.label}:`);
150
+ lines.push(` Goal: ${h.objective}`);
151
+ lines.push(` Target KPI: ${h.measurable_conversion_kpi}`);
152
+ lines.push(` Sequenced Experiments: ${h.experiments.length} experiment(s)`);
153
+ }
154
+
155
+ return lines.join('\n');
156
+ }
@@ -45,6 +45,8 @@ const ROUTES = [
45
45
  { patterns: [/variance|miss|why.did.we/i], command: 'variance', reason: 'Variance analysis' },
46
46
  { patterns: [/prioriti|roadmap|what.to.build|rank/i], command: 'prioritize', reason: 'Initiative prioritization' },
47
47
  { patterns: [/competitor|competitive|market.change/i], command: 'competitive-intel', reason: 'Competitive intelligence' },
48
+ { patterns: [/search|seo|aeo|geo|serp/i], command: 'executive-search', reason: 'Enterprise Search & AEO/GEO Executive Report' },
49
+ { patterns: [/cro|conversion|funnel|journey/i], command: 'executive-cro', reason: 'Enterprise CRO & Journey Intelligence Executive Report' },
48
50
  ];
49
51
 
50
52
  const MAX_DEPTH = 1;
@@ -119,6 +121,8 @@ function commandToModule(cmd) {
119
121
  'variance': 'variance',
120
122
  'prioritize': 'prioritize',
121
123
  'competitive-intel': 'competitiveIntel',
124
+ 'executive-search': 'executiveSearch',
125
+ 'executive-cro': 'executiveCro',
122
126
  };
123
127
  return MAP[cmd] ?? cmd;
124
128
  }
@@ -136,6 +140,8 @@ function commandToExport(cmd) {
136
140
  'variance': 'varianceCommand',
137
141
  'prioritize': 'prioritizeCommand',
138
142
  'competitive-intel': 'competitiveIntelCommand',
143
+ 'executive-search': 'executiveSearchCommand',
144
+ 'executive-cro': 'executiveCroCommand',
139
145
  };
140
146
  return MAP[cmd] ?? cmd + 'Command';
141
147
  }
@@ -0,0 +1,30 @@
1
+ import { exportExecutiveCroReport } from '../reporting/executiveCroReport.js';
2
+
3
+ export async function executiveCroCommand(args = [], root = process.cwd()) {
4
+ const target = argVal(args, '--target');
5
+ const baseUrl = argVal(args, '--base-url');
6
+ const runId = argVal(args, '--run');
7
+ const client = argVal(args, '--client');
8
+ const format = args.includes('--json') ? 'json' : argVal(args, '--format') || 'markdown';
9
+ const output = argVal(args, '--output');
10
+ const input = argVal(args, '--input');
11
+ const funnel = argVal(args, '--funnel');
12
+
13
+ const r = await exportExecutiveCroReport(root, {
14
+ target,
15
+ baseUrl,
16
+ runId,
17
+ clientName: client || 'Enterprise Client',
18
+ format,
19
+ output,
20
+ telemetryInput: input,
21
+ funnelId: funnel,
22
+ });
23
+
24
+ return format === 'json' ? r.data : r.content;
25
+ }
26
+
27
+ function argVal(args, flag) {
28
+ const i = args.indexOf(flag);
29
+ return i >= 0 ? args[i + 1] : null;
30
+ }
@@ -0,0 +1,28 @@
1
+ import { exportExecutiveSearchReport } from '../reporting/executiveSearchReport.js';
2
+
3
+ export async function executiveSearchCommand(args = [], root = process.cwd()) {
4
+ const target = argVal(args, '--target');
5
+ const baseUrl = argVal(args, '--base-url');
6
+ const runId = argVal(args, '--run');
7
+ const client = argVal(args, '--client');
8
+ const format = args.includes('--json') ? 'json' : argVal(args, '--format') || 'markdown';
9
+ const output = argVal(args, '--output');
10
+ const input = argVal(args, '--input');
11
+
12
+ const r = await exportExecutiveSearchReport(root, {
13
+ target,
14
+ baseUrl,
15
+ runId,
16
+ clientName: client || 'Enterprise Client',
17
+ format,
18
+ output,
19
+ backlinksInput: input,
20
+ });
21
+
22
+ return format === 'json' ? r.data : r.content;
23
+ }
24
+
25
+ function argVal(args, flag) {
26
+ const i = args.indexOf(flag);
27
+ return i >= 0 ? args[i + 1] : null;
28
+ }
@@ -1,3 +1,4 @@
1
+ import { auditPageCro } from "../analysis/croAudit.js";
1
2
  import { buildContext } from './context.js';
2
3
  import { registryPageFor, safePath } from '../detectors/framework.js';
3
4
  import { selectDetectors } from '../detectors/index.js';
@@ -74,8 +75,14 @@ export async function inspectCro(root, pageRef, { target, baseUrl, refDate } = {
74
75
  return acc + w;
75
76
  }, 0);
76
77
 
78
+ const pageCroAudit = auditPageCro(page, ctx);
79
+
77
80
  return {
78
81
  url: page.url,
82
+ conversion_readiness_score: pageCroAudit.conversion_readiness_score,
83
+ atf_clarity: pageCroAudit.atf_clarity,
84
+ trust_and_credibility: pageCroAudit.trust_and_credibility,
85
+ offer_architecture: pageCroAudit.offer_architecture,
79
86
  sourceFile: page.sourceFile,
80
87
  status: page.status,
81
88
  declared_conversion_action: declaredAction,
@@ -0,0 +1,53 @@
1
+ import { buildContext } from './context.js';
2
+ import { safePath } from '../detectors/framework.js';
3
+ import { evaluateEeat } from '../analysis/eeat.js';
4
+
5
+ /**
6
+ * `citable inspect eeat <page>` — evaluate on-page content against the E-E-A-T rubric (0-5 scale).
7
+ */
8
+ export async function inspectEeat(root, pageRef, { target, baseUrl, refDate } = {}) {
9
+ const ctx = await buildContext(root, { target, baseUrl, refDate });
10
+ if (!ctx.site) throw new Error('inspect eeat requires a site target (built output directory or URL)');
11
+ const wanted = safePath(pageRef, ctx.site.baseUrl);
12
+ const page = ctx.site.pages.find((p) => safePath(p.url) === wanted || p.sourceFile === pageRef);
13
+ if (!page) throw new Error(`page not found in audited output: ${pageRef}`);
14
+
15
+ return evaluateEeat(page, ctx);
16
+ }
17
+
18
+ /**
19
+ * Format terminal output for `citable inspect eeat <page>`
20
+ */
21
+ export function formatEeatOutput(r) {
22
+ const lines = [
23
+ `Inspect E-E-A-T: ${r.url}`,
24
+ `========================================`,
25
+ `Composite Score: ${r.composite_score} / 5.0 [${r.overall_rating.toUpperCase()}]`,
26
+ `Status: ${r.fact_status} (Google Search Quality Rater Guidelines rubric)`,
27
+ ``,
28
+ `DIMENSIONS (0.0 - 5.0 scale):`,
29
+ ` Experience: ${r.dimensions.experience.score} / 5.0`,
30
+ ` Expertise: ${r.dimensions.expertise.score} / 5.0 (Author: ${r.dimensions.expertise.author || 'unattributed'})`,
31
+ ` Authoritativeness: ${r.dimensions.authoritativeness.score} / 5.0`,
32
+ ` Trustworthiness: ${r.dimensions.trustworthiness.score} / 5.0 (Foundational Base)`,
33
+ ``,
34
+ `OBSERVED POSITIVE SIGNALS:`,
35
+ ];
36
+
37
+ for (const [dim, val] of Object.entries(r.dimensions)) {
38
+ if (val.signals.length) {
39
+ lines.push(` ${dim.toUpperCase()}:`);
40
+ for (const s of val.signals) lines.push(` ✓ ${s}`);
41
+ }
42
+ }
43
+
44
+ if (r.actionable_improvements.length) {
45
+ lines.push(``, `ACTIONABLE IMPROVEMENTS:`);
46
+ for (const item of r.actionable_improvements) {
47
+ lines.push(` - [${item.dimension.toUpperCase()}] ${item.recommendation}`);
48
+ }
49
+ }
50
+
51
+ lines.push(``, `Note: ${r.disclaimer}`);
52
+ return lines.join('\n');
53
+ }
@@ -1,19 +1,21 @@
1
1
  /**
2
- * prioritize command — Initiative Prioritization
2
+ * prioritize command — Initiative and Finding Prioritization (ICE Matrix)
3
3
  *
4
- * Ranks roadmap and investment choices. Weights must be visible when
5
- * weighted scoring is used — no opaque scores.
4
+ * Ranks roadmap, audit findings, and investment choices.
5
+ * Weights and factors must be visible when weighted scoring is used — no opaque scores.
6
6
  *
7
7
  * Usage:
8
8
  * citable prioritize [--status proposed|approved]
9
9
  * citable prioritize show <initiative_id>
10
10
  * citable prioritize validate
11
- * citable prioritize rank — ranked list with explicit criteria
11
+ * citable prioritize rank — ranked list with explicit criteria
12
+ * citable prioritize matrix — Impact/Effort/Confidence (ICE) scoring matrix
12
13
  */
14
+ import fs from 'node:fs';
15
+ import path from 'node:path';
13
16
  import { contextDir, loadRegistryFile, registryLoadProblems } from '../registries/index.js';
14
17
  import { validateAgainst } from '../shared/schemaValidator.js';
15
- import path from 'node:path';
16
-
18
+ import { buildIceMatrix, formatIceMatrixOutput } from '../analysis/iceMatrix.js';
17
19
 
18
20
  const SCALE = { none: 0, low: 1, medium: 2, high: 3, critical: 4, validated: 4, transformative: 4, trivial: 0, very_high: 4, negligible: 0, 'n/a': 0 };
19
21
 
@@ -25,6 +27,7 @@ export async function prioritizeCommand(args, root = process.cwd()) {
25
27
  case 'show': return initiativeShow(file, rest[0]);
26
28
  case 'validate': return initiativeValidate(file);
27
29
  case 'rank': return initiativeRank(file);
30
+ case 'matrix': return initiativeMatrix(file, root, rest);
28
31
  case 'list':
29
32
  default: {
30
33
  const statusFilter = rest[rest.indexOf('--status') + 1] ?? null;
@@ -86,6 +89,39 @@ function initiativeRank(file) {
86
89
  };
87
90
  }
88
91
 
92
+ function initiativeMatrix(file, root, rest = []) {
93
+ const runIdx = rest.indexOf('--run');
94
+ const runId = runIdx !== -1 ? rest[runIdx + 1] : null;
95
+
96
+ if (runId) {
97
+ const findingsPath = path.join(root, '.citable', 'runs', runId, 'findings.json');
98
+ if (!fs.existsSync(findingsPath)) throw new Error(`run ${runId} findings.json not found`);
99
+ const findings = JSON.parse(fs.readFileSync(findingsPath, 'utf8'));
100
+ const matrix = buildIceMatrix(findings, { type: 'findings' });
101
+ return { ...matrix, formatted: formatIceMatrixOutput(matrix) };
102
+ }
103
+
104
+ // Check if findings.json exists in latest run
105
+ const runsDir = path.join(root, '.citable', 'runs');
106
+ if (fs.existsSync(runsDir)) {
107
+ const runs = fs.readdirSync(runsDir).filter((r) => fs.existsSync(path.join(runsDir, r, 'findings.json'))).sort().reverse();
108
+ if (runs.length > 0 && rest.includes('--findings')) {
109
+ const findings = JSON.parse(fs.readFileSync(path.join(runsDir, runs[0], 'findings.json'), 'utf8'));
110
+ const matrix = buildIceMatrix(findings, { type: 'findings' });
111
+ return { ...matrix, formatted: formatIceMatrixOutput(matrix) };
112
+ }
113
+ }
114
+
115
+ // Default: score initiatives from initiatives.yaml
116
+ if (fs.existsSync(file)) {
117
+ const data = load(file);
118
+ const matrix = buildIceMatrix(data.entries || [], { type: 'initiatives' });
119
+ return { ...matrix, formatted: formatIceMatrixOutput(matrix) };
120
+ }
121
+
122
+ throw new Error('prioritize matrix requires initiatives.yaml or an audit run (--run <run-id>)');
123
+ }
124
+
89
125
  function initiativeValidate(file) {
90
126
  const data = load(file);
91
127
  const problems = [...registryLoadProblems(data)];
@@ -0,0 +1,69 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { contextDir, loadRegistryFile } from '../registries/index.js';
4
+ import { readJson, writeJson } from '../shared/io.js';
5
+ import { buildStrategicRoadmap, formatRoadmapMarkdown } from '../analysis/strategicRoadmap.js';
6
+
7
+ /**
8
+ * `citable roadmap [generate|show]` — generate or view the 30/90/180-day strategic roadmap.
9
+ */
10
+ export async function roadmapCommand(root, { runId, target, write = true } = {}) {
11
+ let findings = [];
12
+ let initiatives = [];
13
+
14
+ // 1. Load findings from specified or latest run
15
+ const runsDir = path.join(root, '.citable', 'runs');
16
+ let sourceRun = runId;
17
+ if (!sourceRun && fs.existsSync(runsDir)) {
18
+ const runs = fs.readdirSync(runsDir).filter((r) => fs.existsSync(path.join(runsDir, r, 'findings.json'))).sort();
19
+ if (runs.length) sourceRun = runs.at(-1);
20
+ }
21
+
22
+ if (sourceRun) {
23
+ const findingsFile = path.join(runsDir, sourceRun, 'findings.json');
24
+ if (fs.existsSync(findingsFile)) {
25
+ findings = JSON.parse(fs.readFileSync(findingsFile, 'utf8'));
26
+ }
27
+ }
28
+
29
+ // 2. Load initiatives if present
30
+ const initFile = path.join(contextDir(root), 'initiatives.yaml');
31
+ if (fs.existsSync(initFile)) {
32
+ try {
33
+ const data = loadRegistryFile(initFile, 'initiatives');
34
+ initiatives = data.entries || [];
35
+ } catch {
36
+ // Ignore non-fatal registry load problems
37
+ }
38
+ }
39
+
40
+ // Target domain resolution
41
+ let domain = target || 'nebulacomponents.com';
42
+ if (sourceRun) {
43
+ const manifestFile = path.join(runsDir, sourceRun, 'manifest.json');
44
+ if (fs.existsSync(manifestFile)) {
45
+ try {
46
+ const manifest = readJson(manifestFile);
47
+ if (manifest.target?.location) domain = manifest.target.location;
48
+ } catch {}
49
+ }
50
+ }
51
+
52
+ const roadmap = buildStrategicRoadmap({ findings, initiatives, targetDomain: domain });
53
+ const markdown = formatRoadmapMarkdown(roadmap);
54
+
55
+ let artifactPath = null;
56
+ if (write) {
57
+ const outDir = path.join(root, '.citable', 'roadmap');
58
+ fs.mkdirSync(outDir, { recursive: true });
59
+ artifactPath = path.join(outDir, 'strategic-roadmap.md');
60
+ fs.writeFileSync(artifactPath, markdown, 'utf8');
61
+ writeJson(path.join(outDir, 'strategic-roadmap.json'), roadmap);
62
+ }
63
+
64
+ return {
65
+ ...roadmap,
66
+ markdown,
67
+ artifact_path: artifactPath,
68
+ };
69
+ }
@@ -2,9 +2,11 @@ import { buildContext } from './context.js';
2
2
  import { selectDetectors } from '../detectors/index.js';
3
3
  import { runDetectors, sitePageFor } from '../detectors/framework.js';
4
4
  import { checklistItem, toStringArray } from '../shared/checklist.js';
5
+ import { recommendSchemaForPages } from '../analysis/schemaRecommendations.js';
5
6
 
6
7
  /**
7
- * `citable schema` — derive JSON-LD proposals from registry data and validate deployed schema.
8
+ * `citable schema` — derive JSON-LD proposals from registry data, recommend FAQPage, Speakable,
9
+ * and HowTo markup from visible page content, and validate deployed schema.
8
10
  *
9
11
  * Generation is registry-driven: schema is only proposed for entities that exist in the
10
12
  * entity registry with a canonical URL. Nothing is fabricated; missing fields are listed
@@ -71,5 +73,11 @@ export async function schemaCommand(root, { target, baseUrl, refDate } = {}) {
71
73
  }
72
74
  }
73
75
 
74
- return { findings, proposals, blocked, missingDeployment };
76
+ // 4. Recommend structural schema markup (FAQPage, Speakable, HowTo) from visible content
77
+ let recommendations = { faq: [], speakable: [], howto: [] };
78
+ if (ctx.site?.pages) {
79
+ recommendations = recommendSchemaForPages(ctx.site.pages);
80
+ }
81
+
82
+ return { findings, proposals, blocked, missingDeployment, recommendations };
75
83
  }