@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.
Files changed (142) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +16 -3
  3. package/dist/universal/.agents/skills/citable/SKILL.md +66 -1
  4. package/dist/universal/.agents/skills/citable/VERSION +1 -1
  5. package/dist/universal/.agents/skills/citable/manifest.json +9 -6
  6. package/dist/universal/.agents/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
  7. package/dist/universal/.agents/skills/citable/schemas/finding.schema.json +69 -0
  8. package/dist/universal/.agents/skills/citable/schemas/remediation-verification.schema.json +98 -0
  9. package/dist/universal/.agents/skills/citable/schemas/sow.schema.json +92 -0
  10. package/dist/universal/.claude/agents/citable/manifest.json +2 -2
  11. package/dist/universal/.claude/skills/citable/SKILL.md +66 -1
  12. package/dist/universal/.claude/skills/citable/VERSION +1 -1
  13. package/dist/universal/.claude/skills/citable/manifest.json +9 -6
  14. package/dist/universal/.claude/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
  15. package/dist/universal/.claude/skills/citable/schemas/finding.schema.json +69 -0
  16. package/dist/universal/.claude/skills/citable/schemas/remediation-verification.schema.json +98 -0
  17. package/dist/universal/.claude/skills/citable/schemas/sow.schema.json +92 -0
  18. package/dist/universal/.cursor/skills/citable/SKILL.md +66 -1
  19. package/dist/universal/.cursor/skills/citable/VERSION +1 -1
  20. package/dist/universal/.cursor/skills/citable/manifest.json +9 -6
  21. package/dist/universal/.cursor/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
  22. package/dist/universal/.cursor/skills/citable/schemas/finding.schema.json +69 -0
  23. package/dist/universal/.cursor/skills/citable/schemas/remediation-verification.schema.json +98 -0
  24. package/dist/universal/.cursor/skills/citable/schemas/sow.schema.json +92 -0
  25. package/dist/universal/.gemini/skills/citable/SKILL.md +66 -1
  26. package/dist/universal/.gemini/skills/citable/VERSION +1 -1
  27. package/dist/universal/.gemini/skills/citable/manifest.json +9 -6
  28. package/dist/universal/.gemini/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
  29. package/dist/universal/.gemini/skills/citable/schemas/finding.schema.json +69 -0
  30. package/dist/universal/.gemini/skills/citable/schemas/remediation-verification.schema.json +98 -0
  31. package/dist/universal/.gemini/skills/citable/schemas/sow.schema.json +92 -0
  32. package/dist/universal/.github/skills/citable/SKILL.md +66 -1
  33. package/dist/universal/.github/skills/citable/VERSION +1 -1
  34. package/dist/universal/.github/skills/citable/manifest.json +9 -6
  35. package/dist/universal/.github/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
  36. package/dist/universal/.github/skills/citable/schemas/finding.schema.json +69 -0
  37. package/dist/universal/.github/skills/citable/schemas/remediation-verification.schema.json +98 -0
  38. package/dist/universal/.github/skills/citable/schemas/sow.schema.json +92 -0
  39. package/dist/universal/.kiro/skills/citable/SKILL.md +66 -1
  40. package/dist/universal/.kiro/skills/citable/VERSION +1 -1
  41. package/dist/universal/.kiro/skills/citable/manifest.json +9 -6
  42. package/dist/universal/.kiro/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
  43. package/dist/universal/.kiro/skills/citable/schemas/finding.schema.json +69 -0
  44. package/dist/universal/.kiro/skills/citable/schemas/remediation-verification.schema.json +98 -0
  45. package/dist/universal/.kiro/skills/citable/schemas/sow.schema.json +92 -0
  46. package/dist/universal/.opencode/skills/citable/SKILL.md +66 -1
  47. package/dist/universal/.opencode/skills/citable/VERSION +1 -1
  48. package/dist/universal/.opencode/skills/citable/manifest.json +9 -6
  49. package/dist/universal/.opencode/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
  50. package/dist/universal/.opencode/skills/citable/schemas/finding.schema.json +69 -0
  51. package/dist/universal/.opencode/skills/citable/schemas/remediation-verification.schema.json +98 -0
  52. package/dist/universal/.opencode/skills/citable/schemas/sow.schema.json +92 -0
  53. package/dist/universal/.pi/agent/skills/citable/SKILL.md +66 -1
  54. package/dist/universal/.pi/agent/skills/citable/VERSION +1 -1
  55. package/dist/universal/.pi/agent/skills/citable/manifest.json +9 -6
  56. package/dist/universal/.pi/agent/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
  57. package/dist/universal/.pi/agent/skills/citable/schemas/finding.schema.json +69 -0
  58. package/dist/universal/.pi/agent/skills/citable/schemas/remediation-verification.schema.json +98 -0
  59. package/dist/universal/.pi/agent/skills/citable/schemas/sow.schema.json +92 -0
  60. package/dist/universal/.qoder/skills/citable/SKILL.md +66 -1
  61. package/dist/universal/.qoder/skills/citable/VERSION +1 -1
  62. package/dist/universal/.qoder/skills/citable/manifest.json +9 -6
  63. package/dist/universal/.qoder/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
  64. package/dist/universal/.qoder/skills/citable/schemas/finding.schema.json +69 -0
  65. package/dist/universal/.qoder/skills/citable/schemas/remediation-verification.schema.json +98 -0
  66. package/dist/universal/.qoder/skills/citable/schemas/sow.schema.json +92 -0
  67. package/dist/universal/.rovodev/skills/citable/SKILL.md +66 -1
  68. package/dist/universal/.rovodev/skills/citable/VERSION +1 -1
  69. package/dist/universal/.rovodev/skills/citable/manifest.json +9 -6
  70. package/dist/universal/.rovodev/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
  71. package/dist/universal/.rovodev/skills/citable/schemas/finding.schema.json +69 -0
  72. package/dist/universal/.rovodev/skills/citable/schemas/remediation-verification.schema.json +98 -0
  73. package/dist/universal/.rovodev/skills/citable/schemas/sow.schema.json +92 -0
  74. package/dist/universal/.trae/skills/citable/SKILL.md +66 -1
  75. package/dist/universal/.trae/skills/citable/VERSION +1 -1
  76. package/dist/universal/.trae/skills/citable/manifest.json +9 -6
  77. package/dist/universal/.trae/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
  78. package/dist/universal/.trae/skills/citable/schemas/finding.schema.json +69 -0
  79. package/dist/universal/.trae/skills/citable/schemas/remediation-verification.schema.json +98 -0
  80. package/dist/universal/.trae/skills/citable/schemas/sow.schema.json +92 -0
  81. package/dist/universal/.trae-cn/skills/citable/SKILL.md +66 -1
  82. package/dist/universal/.trae-cn/skills/citable/VERSION +1 -1
  83. package/dist/universal/.trae-cn/skills/citable/manifest.json +9 -6
  84. package/dist/universal/.trae-cn/skills/citable/schemas/cli-output-envelope.schema.json +30 -0
  85. package/dist/universal/.trae-cn/skills/citable/schemas/finding.schema.json +69 -0
  86. package/dist/universal/.trae-cn/skills/citable/schemas/remediation-verification.schema.json +98 -0
  87. package/dist/universal/.trae-cn/skills/citable/schemas/sow.schema.json +92 -0
  88. package/dist/universal/manifest.json +112 -76
  89. package/package.json +1 -1
  90. package/schemas/cli-output-envelope.schema.json +30 -0
  91. package/schemas/finding.schema.json +69 -0
  92. package/schemas/remediation-verification.schema.json +98 -0
  93. package/schemas/sow.schema.json +92 -0
  94. package/skill/SKILL.md +66 -1
  95. package/src/analysis/behavioral.js +126 -0
  96. package/src/analysis/croAudit.js +302 -0
  97. package/src/analysis/croRoadmap.js +121 -0
  98. package/src/analysis/eeat.js +300 -0
  99. package/src/analysis/funnelAnalysis.js +171 -0
  100. package/src/analysis/iceMatrix.js +205 -0
  101. package/src/analysis/offpage.js +228 -0
  102. package/src/analysis/readiness.js +253 -0
  103. package/src/analysis/saliency.js +119 -0
  104. package/src/analysis/schemaRecommendations.js +173 -0
  105. package/src/analysis/strategicRoadmap.js +148 -0
  106. package/src/cli/index.js +418 -9
  107. package/src/commands/answerEngineReadiness.js +48 -0
  108. package/src/commands/audit.js +2 -2
  109. package/src/commands/auditBacklinks.js +79 -0
  110. package/src/commands/ciWorkflow.js +63 -0
  111. package/src/commands/compatibility.js +205 -0
  112. package/src/commands/context.js +2 -1
  113. package/src/commands/croBacklog.js +182 -0
  114. package/src/commands/croSuite.js +156 -0
  115. package/src/commands/edgeRules.js +80 -4
  116. package/src/commands/edgeSecurity.js +192 -0
  117. package/src/commands/executive.js +6 -0
  118. package/src/commands/executiveCro.js +30 -0
  119. package/src/commands/executiveSearch.js +28 -0
  120. package/src/commands/experimentGuardrails.js +213 -0
  121. package/src/commands/goldenCorpus.js +155 -0
  122. package/src/commands/implementationKit.js +202 -0
  123. package/src/commands/inspectCro.js +67 -0
  124. package/src/commands/inspectEeat.js +53 -0
  125. package/src/commands/planExperiment.js +52 -0
  126. package/src/commands/previewCro.js +241 -0
  127. package/src/commands/prioritize.js +42 -6
  128. package/src/commands/remediate.js +461 -0
  129. package/src/commands/roadmapCmd.js +69 -0
  130. package/src/commands/schemaCmd.js +10 -2
  131. package/src/commands/sowCmd.js +67 -0
  132. package/src/commands/sweep.js +357 -0
  133. package/src/commands/verifyRemediation.js +204 -0
  134. package/src/commands/visualRegression.js +165 -0
  135. package/src/components/index.js +556 -0
  136. package/src/detectors/cro.js +48 -0
  137. package/src/detectors/framework.js +31 -1
  138. package/src/reporting/executiveCroReport.js +731 -0
  139. package/src/reporting/executiveExport.js +175 -1
  140. package/src/reporting/executiveSearchReport.js +704 -0
  141. package/src/sow/admissibilityGate.js +194 -0
  142. package/src/sow/generateSow.js +762 -0
@@ -0,0 +1,173 @@
1
+ import { parse as parseHtml } from 'node-html-parser';
2
+
3
+ const QUESTION_RX = /^(?:how|what|why|when|where|who|can|is|does|which|should)\b|\?$/i;
4
+ const HOWTO_TITLE_RX = /^(?:how\s+to|how\s+do\s+i|step-by-step|guide\s+to)\b/i;
5
+
6
+ /**
7
+ * Scan audited site pages for FAQPage, Speakable, and HowTo markup opportunities.
8
+ */
9
+ export function recommendSchemaForPages(pages = []) {
10
+ const recommendations = {
11
+ faq: [],
12
+ speakable: [],
13
+ howto: [],
14
+ };
15
+
16
+ for (const page of pages) {
17
+ if (!page || page.status !== 200) continue;
18
+ const html = page.html || '';
19
+ const root = parseHtml(html);
20
+ const existingTypes = new Set(
21
+ (page.jsonLd || []).flatMap((j) => j.blocks || []).flatMap((b) => [].concat(b['@type'] || []))
22
+ );
23
+
24
+ // -------------------------------------------------------------
25
+ // 1. FAQPage Recommendation
26
+ // -------------------------------------------------------------
27
+ if (!existingTypes.has('FAQPage')) {
28
+ const qas = [];
29
+
30
+ // Pattern A: <details><summary>Q</summary><p>A</p></details>
31
+ for (const details of root.querySelectorAll('details')) {
32
+ const summary = details.querySelector('summary');
33
+ if (summary) {
34
+ const questionText = summary.text.trim();
35
+ const clone = parseHtml(details.innerHTML);
36
+ const s = clone.querySelector('summary');
37
+ if (s) s.remove();
38
+ const answerText = clone.text.replace(/\s+/g, ' ').trim();
39
+ if (questionText && answerText) {
40
+ qas.push({ question: questionText, answer: answerText });
41
+ }
42
+ }
43
+ }
44
+
45
+ // Pattern B: Question headings followed by paragraphs
46
+ if (qas.length < 2) {
47
+ const headings = root.querySelectorAll('h2, h3');
48
+ for (const h of headings) {
49
+ const hText = h.text.trim();
50
+ if (QUESTION_RX.test(hText)) {
51
+ // Find next sibling paragraph
52
+ let next = h.nextElementSibling;
53
+ let answerText = '';
54
+ while (next && !['H1', 'H2', 'H3', 'H4'].includes(next.tagName)) {
55
+ if (['P', 'UL', 'OL', 'DIV'].includes(next.tagName)) {
56
+ answerText += (answerText ? ' ' : '') + next.text.replace(/\s+/g, ' ').trim();
57
+ if (answerText.length > 60) break;
58
+ }
59
+ next = next.nextElementSibling;
60
+ }
61
+ if (hText && answerText) {
62
+ qas.push({ question: hText, answer: answerText });
63
+ }
64
+ }
65
+ }
66
+ }
67
+
68
+ if (qas.length >= 2) {
69
+ recommendations.faq.push({
70
+ target_url: page.url,
71
+ source_file: page.sourceFile || null,
72
+ schema_type: 'FAQPage',
73
+ rationale: `Detected ${qas.length} visible question-and-answer pairs without FAQPage schema.`,
74
+ jsonld: {
75
+ '@context': 'https://schema.org',
76
+ '@type': 'FAQPage',
77
+ mainEntity: qas.slice(0, 10).map((qa) => ({
78
+ '@type': 'Question',
79
+ name: qa.question,
80
+ acceptedAnswer: {
81
+ '@type': 'Answer',
82
+ text: qa.answer,
83
+ },
84
+ })),
85
+ },
86
+ });
87
+ }
88
+ }
89
+
90
+ // -------------------------------------------------------------
91
+ // 2. Speakable Recommendation (SpeakableSpecification)
92
+ // -------------------------------------------------------------
93
+ const hasExistingSpeakable = (page.jsonLd || []).some((j) =>
94
+ (j.blocks || []).some((b) => b.speakable)
95
+ );
96
+
97
+ if (!hasExistingSpeakable) {
98
+ const speakableSelectors = [];
99
+ if (root.querySelector('h1')) speakableSelectors.push('h1');
100
+ if (root.querySelector('#summary, .executive-summary, .summary, .key-takeaways')) {
101
+ speakableSelectors.push(root.querySelector('#summary') ? '#summary' : '.executive-summary');
102
+ } else if (root.querySelector('article > p:first-of-type, main > p:first-of-type, p:first-of-type')) {
103
+ speakableSelectors.push('article > p:first-of-type');
104
+ }
105
+
106
+ if (speakableSelectors.length >= 2) {
107
+ recommendations.speakable.push({
108
+ target_url: page.url,
109
+ source_file: page.sourceFile || null,
110
+ schema_type: 'SpeakableSpecification',
111
+ rationale: 'Page has clear title and executive lead passage suitable for smart speaker / assistant audio playback.',
112
+ jsonld: {
113
+ '@context': 'https://schema.org',
114
+ '@type': 'WebPage',
115
+ '@id': `${page.url}#webpage`,
116
+ url: page.url,
117
+ name: page.title || 'Page',
118
+ speakable: {
119
+ '@type': 'SpeakableSpecification',
120
+ cssSelector: speakableSelectors,
121
+ },
122
+ },
123
+ });
124
+ }
125
+ }
126
+
127
+ // -------------------------------------------------------------
128
+ // 3. HowTo Recommendation
129
+ // -------------------------------------------------------------
130
+ if (!existingTypes.has('HowTo')) {
131
+ const pageTitle = page.title || '';
132
+ const h1s = (page.headings || []).filter((h) => h.level === 1).map((h) => h.text);
133
+ const isHowToPage = HOWTO_TITLE_RX.test(pageTitle) || h1s.some((h) => HOWTO_TITLE_RX.test(h));
134
+
135
+ if (isHowToPage) {
136
+ const steps = [];
137
+ // Look for ordered list items or step subheadings
138
+ const olItems = root.querySelectorAll('ol > li');
139
+ if (olItems.length >= 2) {
140
+ olItems.forEach((li, idx) => {
141
+ const stepText = li.text.replace(/\s+/g, ' ').trim();
142
+ if (stepText) {
143
+ steps.push({
144
+ '@type': 'HowToStep',
145
+ position: idx + 1,
146
+ name: `Step ${idx + 1}`,
147
+ text: stepText,
148
+ });
149
+ }
150
+ });
151
+ }
152
+
153
+ if (steps.length >= 2) {
154
+ recommendations.howto.push({
155
+ target_url: page.url,
156
+ source_file: page.sourceFile || null,
157
+ schema_type: 'HowTo',
158
+ rationale: `Procedural instructional guide detected with ${steps.length} ordered steps.`,
159
+ jsonld: {
160
+ '@context': 'https://schema.org',
161
+ '@type': 'HowTo',
162
+ name: h1s[0] || pageTitle,
163
+ description: page.paragraphs?.[0] || 'Procedural guide and instructions.',
164
+ step: steps,
165
+ },
166
+ });
167
+ }
168
+ }
169
+ }
170
+ }
171
+
172
+ return recommendations;
173
+ }
@@ -0,0 +1,148 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { readJson, writeJson, nowIso } from '../shared/io.js';
4
+ import { scoreFinding, scoreInitiative } from './iceMatrix.js';
5
+
6
+ /**
7
+ * Generate a 30 / 90 / 180-day Strategic Roadmap from audit findings and initiatives.
8
+ */
9
+ export function buildStrategicRoadmap({ findings = [], initiatives = [], targetDomain = 'example.com' } = {}) {
10
+ // Score all findings and initiatives with ICE
11
+ const scoredFindings = findings.map(scoreFinding);
12
+ const scoredInitiatives = initiatives.map(scoreInitiative);
13
+
14
+ // Group into horizons
15
+ const horizon30 = {
16
+ horizon: '30_days',
17
+ label: 'Days 1 - 30: Foundation & Unblock',
18
+ objective: 'Eliminate catastrophic crawl, index, and Core Web Vitals blockers; establish foundational crawler access and entity identity.',
19
+ focus_areas: [
20
+ 'Technical SEO: Resolve HTTP non-200 responses, accidental noindex, and canonical contradictions',
21
+ 'Core Web Vitals: Eliminate render-blocking scripts, specify image dimensions (CLS <= 0.1), preload LCP hero images',
22
+ 'Answer Engine Access: Open robots.txt permissions for PerplexityBot, Bingbot, and OAI-SearchBot',
23
+ 'Entity Identity: Deploy baseline Organization and primary Person JSON-LD schemas',
24
+ ],
25
+ exit_criteria: [
26
+ 'Zero critical-severity technical findings in citable audit technical',
27
+ 'Static CLS readiness: 100% of body images have explicit dimensions',
28
+ 'All major answer engine bots verified allowed in robots directives',
29
+ 'Baseline Organization schema live with verified @id',
30
+ ],
31
+ actions: [],
32
+ };
33
+
34
+ const horizon90 = {
35
+ horizon: '90_days',
36
+ label: 'Days 31 - 90: Governance, E-E-A-T & Extractability',
37
+ objective: 'Elevate on-page E-E-A-T, deploy rich structured data (FAQPage, Speakable, HowTo), audit backlinks, and automate fast indexing.',
38
+ focus_areas: [
39
+ 'On-Page E-E-A-T: Attribute all content to verified authors, add credentials, visible update dates, and editorial transparency links',
40
+ 'Schema Enrichment: Deploy FAQPage Q&A markup, SpeakableSpecification for audio extraction, and HowTo step schemas',
41
+ 'Fast Indexing: Configure and verify IndexNow protocol connector for instant Bing / search engine notification',
42
+ 'Off-Page Governance: Audit inbound backlink profile, flag toxic PBN/spam TLDs, and compile GSC disavow candidate file',
43
+ 'Claim Substantiation: Map material business claims to evidence registry records',
44
+ ],
45
+ exit_criteria: [
46
+ 'Composite E-E-A-T score >= 4.0 / 5.0 across money and content pages',
47
+ 'FAQPage and Speakable schema deployed on all eligible guide/product pages',
48
+ 'IndexNow connection tested and submitting on content updates',
49
+ 'Google Disavow file reviewed and archived in evidence package',
50
+ 'Zero unverified material claims in citable substantiate',
51
+ ],
52
+ actions: [],
53
+ };
54
+
55
+ const horizon180 = {
56
+ horizon: '180_days',
57
+ label: 'Days 91 - 180: GEO Dominance, Corroboration & Scale',
58
+ objective: 'Dominate generative search citations across Perplexity, Copilot, and ChatGPT; build digital PR authority; govern conversion experiments.',
59
+ focus_areas: [
60
+ 'Generative Engine Optimization (GEO): Optimize RAG chunk boundaries (150-350 words) and direct copular definitions ("X is Y")',
61
+ 'Competitive Share-of-Voice: Benchmark brand citation share and sentiment stance across AI engines vs key competitors',
62
+ 'Authority & Corroboration: Earn high-authority primary citations (.edu, .gov, standards bodies, industry reports)',
63
+ 'Conversion Rate Optimization (CRO): Implement accessible Nebula UI components and govern A/B experiment guardrails (SRM, stopping)',
64
+ 'Continuous Governance: Schedule automated version-pinned audit runs with regression alerts',
65
+ ],
66
+ exit_criteria: [
67
+ 'Answer-Engine readiness score >= 85/100 across Perplexity, Bing Copilot, and ChatGPT',
68
+ 'Weekly automated continuous audit pipeline with regression monitoring active',
69
+ 'Dominant citation share on primary category prompts in AI search engine probes',
70
+ 'Zero statistically invalid experiment decisions (SRM < 0.001)',
71
+ ],
72
+ actions: [],
73
+ };
74
+
75
+ // Map findings into horizons based on urgency and discipline
76
+ for (const f of scoredFindings) {
77
+ const ns = (f.id || '').split('-')[0];
78
+ if (f.severity === 'critical' || ['TECH', 'CRAWL', 'STATUS'].includes(ns)) {
79
+ horizon30.actions.push(f);
80
+ } else if (f.severity === 'high' || ['CWV', 'SCHEMA', 'PAGE', 'LINK'].includes(ns)) {
81
+ if (['CWV-001', 'CWV-004'].includes(f.id)) horizon30.actions.push(f);
82
+ else horizon90.actions.push(f);
83
+ } else if (['ANS', 'ENTITY', 'CLAIM', 'EVD'].includes(ns)) {
84
+ horizon90.actions.push(f);
85
+ } else {
86
+ horizon180.actions.push(f);
87
+ }
88
+ }
89
+
90
+ // Map initiatives into horizons
91
+ for (const i of scoredInitiatives) {
92
+ if (i.impact >= 8 && i.effort <= 4) {
93
+ horizon30.actions.push(i);
94
+ } else if (i.impact >= 6) {
95
+ horizon90.actions.push(i);
96
+ } else {
97
+ horizon180.actions.push(i);
98
+ }
99
+ }
100
+
101
+ return {
102
+ fact_status: 'strategic_roadmap_projection',
103
+ target_domain: targetDomain,
104
+ generated_at: nowIso(),
105
+ methodology: 'Horizon Phasing: 30-Day (Critical Unblock & Core CWV) -> 90-Day (E-E-A-T, Schema & Off-Page) -> 180-Day (GEO Dominance & PR)',
106
+ horizons: [horizon30, horizon90, horizon180],
107
+ };
108
+ }
109
+
110
+ /**
111
+ * Format Strategic Roadmap as GitHub-flavored Markdown
112
+ */
113
+ export function formatRoadmapMarkdown(roadmap) {
114
+ const lines = [
115
+ `# 30 / 90 / 180-Day Strategic Roadmap`,
116
+ ``,
117
+ `- Target Property: \`${roadmap.target_domain}\``,
118
+ `- Generated: ${roadmap.generated_at}`,
119
+ `- Governance Methodology: ${roadmap.methodology}`,
120
+ ``,
121
+ `> **Operating Premise Reminder**: This roadmap sequences verified remediation priorities and strategic enhancements. It establishes technical eligibility and extractability, but never guarantees ranking, traffic, or citation outcomes.`,
122
+ ``,
123
+ ];
124
+
125
+ for (const h of roadmap.horizons) {
126
+ lines.push(`## ${h.label}`);
127
+ lines.push(``);
128
+ lines.push(`**Objective**: ${h.objective}`);
129
+ lines.push(``);
130
+ lines.push(`### Strategic Focus Areas:`);
131
+ for (const f of h.focus_areas) lines.push(`- ${f}`);
132
+ lines.push(``);
133
+ lines.push(`### Exit Criteria & Success Milestones:`);
134
+ for (const c of h.exit_criteria) lines.push(`- [ ] ${c}`);
135
+ lines.push(``);
136
+ if (h.actions.length) {
137
+ lines.push(`### Prioritized Actions (${h.actions.length}):`);
138
+ lines.push(`| Action / Initiative | Impact | Effort | Conf | ICE Score | Quadrant |`);
139
+ lines.push(`| :--- | :--- | :--- | :--- | :--- | :--- |`);
140
+ for (const a of h.actions.slice(0, 10)) {
141
+ lines.push(`| **${a.id}**: ${a.title.slice(0, 45)} | ${a.impact}/10 | ${a.effort}/10 | ${a.confidence}/10 | **${a.ice_score}** | ${a.quadrant} |`);
142
+ }
143
+ lines.push(``);
144
+ }
145
+ }
146
+
147
+ return lines.join('\n');
148
+ }