@lynxflow/seo-engine 2.5.0 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/article-strategic-planner.d.ts +35 -0
- package/dist/content-quality-scorer.d.ts +40 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +549 -0
- package/dist/index.mjs +549 -0
- package/dist/landing-channel-scorer.d.ts +41 -0
- package/dist/landing-cro-checker.d.ts +36 -0
- package/dist/social-community-insights.d.ts +27 -0
- package/dist/topic-cluster-architect.d.ts +39 -0
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -11049,6 +11049,543 @@ class KeywordDensityGuard {
|
|
|
11049
11049
|
}
|
|
11050
11050
|
}
|
|
11051
11051
|
|
|
11052
|
+
// src/content-quality-scorer.ts
|
|
11053
|
+
class ContentQualityScorer {
|
|
11054
|
+
static VAGUE_WORDS = [
|
|
11055
|
+
/\b(?:many|some|various|numerous|several|often|sometimes|usually|generally|typically|significantly|substantial|greatly|very|really|quite|relatively)\b/gi
|
|
11056
|
+
];
|
|
11057
|
+
static SPECIFICITY_DATA_PATTERNS = [
|
|
11058
|
+
/\b\d{1,3}%\b/g,
|
|
11059
|
+
/(?:\$|€|£)\s*[\d,]+(?:\.\d{2})?\b/g,
|
|
11060
|
+
/\b\d{4}\b/g,
|
|
11061
|
+
/\b\d+(?:,\d{3})*\s*(?:users?|customers?|businesses?|downloads?|hours?|minutes?)\b/gi,
|
|
11062
|
+
/\"[^\"]{10,120}\"/g
|
|
11063
|
+
];
|
|
11064
|
+
static CONVERSATIONAL_DEVICES = [
|
|
11065
|
+
/\([^)]{5,40}\)/g,
|
|
11066
|
+
/\?(?:\s|$)/g,
|
|
11067
|
+
/\b(?:don't|can't|won't|you're|you've|it's|that's|here's|let's|we've|we're|I've)\b/gi,
|
|
11068
|
+
/(?:^|\.\s+)(?:Look|Here['’]s the thing|The truth is|Sound familiar|Trust me|Regardez|En clair)/gi
|
|
11069
|
+
];
|
|
11070
|
+
static scoreContent(content, metadata) {
|
|
11071
|
+
if (!content || content.trim().length === 0) {
|
|
11072
|
+
return this.emptyReport();
|
|
11073
|
+
}
|
|
11074
|
+
const words = content.split(/\s+/).filter(Boolean);
|
|
11075
|
+
const totalWords = words.length;
|
|
11076
|
+
let conversationalHits = 0;
|
|
11077
|
+
for (const pat of this.CONVERSATIONAL_DEVICES) {
|
|
11078
|
+
conversationalHits += (content.match(pat) || []).length;
|
|
11079
|
+
}
|
|
11080
|
+
const humanityRaw = Math.min(100, Math.round(conversationalHits / Math.max(1, totalWords / 80) * 60) + 30);
|
|
11081
|
+
const humanityScore = Math.max(20, Math.min(100, humanityRaw));
|
|
11082
|
+
let dataPointsCount = 0;
|
|
11083
|
+
for (const pat of this.SPECIFICITY_DATA_PATTERNS) {
|
|
11084
|
+
dataPointsCount += (content.match(pat) || []).length;
|
|
11085
|
+
}
|
|
11086
|
+
let vagueWordsCount = 0;
|
|
11087
|
+
for (const pat of this.VAGUE_WORDS) {
|
|
11088
|
+
vagueWordsCount += (content.match(pat) || []).length;
|
|
11089
|
+
}
|
|
11090
|
+
const specificityRatio = dataPointsCount / Math.max(1, vagueWordsCount * 0.5 + 2);
|
|
11091
|
+
const specificityScore = Math.max(25, Math.min(100, Math.round(specificityRatio * 35 + 40)));
|
|
11092
|
+
const lines = content.split(`
|
|
11093
|
+
`).map((l) => l.trim()).filter(Boolean);
|
|
11094
|
+
const listLines = lines.filter((l) => l.startsWith("-") || l.startsWith("*") || l.startsWith("1.") || l.startsWith("2.")).length;
|
|
11095
|
+
const proseLines = lines.filter((l) => !l.startsWith("#") && !l.startsWith("-") && !l.startsWith("*") && !l.startsWith("1.")).length;
|
|
11096
|
+
const totalContentLines = Math.max(1, listLines + proseLines);
|
|
11097
|
+
const proseRatio = Math.round(proseLines / totalContentLines * 100) / 100;
|
|
11098
|
+
let structureScore = 85;
|
|
11099
|
+
const structureIssues = [];
|
|
11100
|
+
if (proseRatio < 0.4) {
|
|
11101
|
+
structureScore = 50;
|
|
11102
|
+
structureIssues.push("Too list-heavy (> 60% bullet points). Add explanatory narrative prose paragraphs between lists.");
|
|
11103
|
+
} else if (proseRatio > 0.85) {
|
|
11104
|
+
structureScore = 65;
|
|
11105
|
+
structureIssues.push("Too prose-heavy (> 85% text walls). Break up long sections with bullet lists, comparison tables, or callouts.");
|
|
11106
|
+
}
|
|
11107
|
+
let seoScore = 80;
|
|
11108
|
+
const seoIssues = [];
|
|
11109
|
+
const hasH1 = lines.some((l) => l.startsWith("# "));
|
|
11110
|
+
const h2Count = lines.filter((l) => l.startsWith("## ")).length;
|
|
11111
|
+
if (!hasH1) {
|
|
11112
|
+
seoScore -= 25;
|
|
11113
|
+
seoIssues.push("Missing H1 title heading.");
|
|
11114
|
+
}
|
|
11115
|
+
if (h2Count < 2) {
|
|
11116
|
+
seoScore -= 20;
|
|
11117
|
+
seoIssues.push("Fewer than 2 H2 subheadings found. Structure content with clear sub-topics.");
|
|
11118
|
+
}
|
|
11119
|
+
if (metadata?.primaryKeyword && !content.toLowerCase().includes(metadata.primaryKeyword.toLowerCase())) {
|
|
11120
|
+
seoScore -= 20;
|
|
11121
|
+
seoIssues.push(`Target keyword "${metadata.primaryKeyword}" is missing from body text.`);
|
|
11122
|
+
}
|
|
11123
|
+
const avgSentenceLength = Math.round(totalWords / Math.max(1, content.split(/[.?!]/).length) * 10) / 10;
|
|
11124
|
+
let readabilityScore = 85;
|
|
11125
|
+
const readIssues = [];
|
|
11126
|
+
if (avgSentenceLength > 20) {
|
|
11127
|
+
readabilityScore = 55;
|
|
11128
|
+
readIssues.push(`Average sentence length is ${avgSentenceLength} words (Target: 13-17 words).`);
|
|
11129
|
+
}
|
|
11130
|
+
const composite = Math.round(humanityScore * 0.3 + specificityScore * 0.25 + structureScore * 0.2 + seoScore * 0.15 + readabilityScore * 0.1);
|
|
11131
|
+
const passes = composite >= 70;
|
|
11132
|
+
let grade = "F";
|
|
11133
|
+
if (composite >= 90)
|
|
11134
|
+
grade = "A+";
|
|
11135
|
+
else if (composite >= 80)
|
|
11136
|
+
grade = "A";
|
|
11137
|
+
else if (composite >= 70)
|
|
11138
|
+
grade = "B";
|
|
11139
|
+
else if (composite >= 55)
|
|
11140
|
+
grade = "C";
|
|
11141
|
+
const topFixes = [
|
|
11142
|
+
...structureIssues,
|
|
11143
|
+
...seoIssues,
|
|
11144
|
+
...readIssues
|
|
11145
|
+
];
|
|
11146
|
+
if (specificityScore < 70) {
|
|
11147
|
+
topFixes.push("Inject verified data points (specific dollar amounts, percentage improvements, dates) instead of vague generalities.");
|
|
11148
|
+
}
|
|
11149
|
+
if (humanityScore < 70) {
|
|
11150
|
+
topFixes.push("Adopt a conversational tone: add contractions (you're, it's), rhetorical questions, and casual transition words.");
|
|
11151
|
+
}
|
|
11152
|
+
return {
|
|
11153
|
+
compositeScore: composite,
|
|
11154
|
+
passesQualityThreshold: passes,
|
|
11155
|
+
grade,
|
|
11156
|
+
dimensions: {
|
|
11157
|
+
humanity: {
|
|
11158
|
+
score: humanityScore,
|
|
11159
|
+
weight: 0.3,
|
|
11160
|
+
weightedScore: Math.round(humanityScore * 0.3),
|
|
11161
|
+
strengths: humanityScore >= 75 ? ["Conversational tone", "Natural pacing"] : [],
|
|
11162
|
+
issues: humanityScore < 75 ? ["Sounds slightly robotic or formal"] : []
|
|
11163
|
+
},
|
|
11164
|
+
specificity: {
|
|
11165
|
+
score: specificityScore,
|
|
11166
|
+
weight: 0.25,
|
|
11167
|
+
weightedScore: Math.round(specificityScore * 0.25),
|
|
11168
|
+
strengths: specificityScore >= 75 ? ["High hard-data density", "Concrete benchmarks"] : [],
|
|
11169
|
+
issues: specificityScore < 75 ? ["Excessive vague words (many, several, often)"] : []
|
|
11170
|
+
},
|
|
11171
|
+
structureBalance: {
|
|
11172
|
+
score: structureScore,
|
|
11173
|
+
weight: 0.2,
|
|
11174
|
+
weightedScore: Math.round(structureScore * 0.2),
|
|
11175
|
+
strengths: structureScore >= 75 ? ["Balanced mix of prose and scannable lists"] : [],
|
|
11176
|
+
issues: structureIssues
|
|
11177
|
+
},
|
|
11178
|
+
seoCompliance: {
|
|
11179
|
+
score: seoScore,
|
|
11180
|
+
weight: 0.15,
|
|
11181
|
+
weightedScore: Math.round(seoScore * 0.15),
|
|
11182
|
+
strengths: seoScore >= 75 ? ["Proper H1/H2 hierarchy"] : [],
|
|
11183
|
+
issues: seoIssues
|
|
11184
|
+
},
|
|
11185
|
+
readability: {
|
|
11186
|
+
score: readabilityScore,
|
|
11187
|
+
weight: 0.1,
|
|
11188
|
+
weightedScore: Math.round(readabilityScore * 0.1),
|
|
11189
|
+
strengths: readabilityScore >= 75 ? ["Punchy sentence lengths"] : [],
|
|
11190
|
+
issues: readIssues
|
|
11191
|
+
}
|
|
11192
|
+
},
|
|
11193
|
+
proseToListRatio: proseRatio,
|
|
11194
|
+
topPriorityFixes: topFixes
|
|
11195
|
+
};
|
|
11196
|
+
}
|
|
11197
|
+
static emptyReport() {
|
|
11198
|
+
return {
|
|
11199
|
+
compositeScore: 0,
|
|
11200
|
+
passesQualityThreshold: false,
|
|
11201
|
+
grade: "F",
|
|
11202
|
+
dimensions: {
|
|
11203
|
+
humanity: { score: 0, weight: 0.3, weightedScore: 0, strengths: [], issues: [] },
|
|
11204
|
+
specificity: { score: 0, weight: 0.25, weightedScore: 0, strengths: [], issues: [] },
|
|
11205
|
+
structureBalance: { score: 0, weight: 0.2, weightedScore: 0, strengths: [], issues: [] },
|
|
11206
|
+
seoCompliance: { score: 0, weight: 0.15, weightedScore: 0, strengths: [], issues: [] },
|
|
11207
|
+
readability: { score: 0, weight: 0.1, weightedScore: 0, strengths: [], issues: [] }
|
|
11208
|
+
},
|
|
11209
|
+
proseToListRatio: 0,
|
|
11210
|
+
topPriorityFixes: ["Provide text content to score."]
|
|
11211
|
+
};
|
|
11212
|
+
}
|
|
11213
|
+
}
|
|
11214
|
+
|
|
11215
|
+
// src/landing-cro-checker.ts
|
|
11216
|
+
class LandingCroChecker {
|
|
11217
|
+
static auditLandingPage(content, config = {}) {
|
|
11218
|
+
const checks = [];
|
|
11219
|
+
const lines = content.split(`
|
|
11220
|
+
`).map((l) => l.trim()).filter(Boolean);
|
|
11221
|
+
const h1 = lines.find((l) => l.startsWith("# "))?.replace("# ", "") || "";
|
|
11222
|
+
const h1Words = h1.split(/\s+/).filter(Boolean).length;
|
|
11223
|
+
const isH1Clear = h1Words >= 4 && h1Words <= 14;
|
|
11224
|
+
checks.push({
|
|
11225
|
+
id: "h1_length",
|
|
11226
|
+
name: "Headline Length & Outcome Focus",
|
|
11227
|
+
category: "headline",
|
|
11228
|
+
passed: isH1Clear,
|
|
11229
|
+
importance: "critical",
|
|
11230
|
+
feedback: isH1Clear ? "H1 is clear, outcome-focused, and scannable in < 3s." : "H1 is missing or too long/vague."
|
|
11231
|
+
});
|
|
11232
|
+
const hasValueProp = /save|boost|cut|grow|automate|increase|réduire|augmenter|gagner/i.test(content);
|
|
11233
|
+
checks.push({
|
|
11234
|
+
id: "value_prop_benefit",
|
|
11235
|
+
name: "Concrete Benefit & Outcome",
|
|
11236
|
+
category: "value_prop",
|
|
11237
|
+
passed: hasValueProp,
|
|
11238
|
+
importance: "critical",
|
|
11239
|
+
feedback: hasValueProp ? "Explicit outcome/benefit articulated in content." : "Lacks explicit outcome/benefit statements."
|
|
11240
|
+
});
|
|
11241
|
+
const hasReviews = /rated|reviews?|avis|clients?|customers?|businesses?|\d{1,3}(?:,\d{3})*\+/i.test(content);
|
|
11242
|
+
checks.push({
|
|
11243
|
+
id: "social_proof_ratings",
|
|
11244
|
+
name: "Customer Reviews & Numbers",
|
|
11245
|
+
category: "social_proof",
|
|
11246
|
+
passed: hasReviews,
|
|
11247
|
+
importance: "important",
|
|
11248
|
+
feedback: hasReviews ? "Social proof count or star rating present." : "No social proof numbers found."
|
|
11249
|
+
});
|
|
11250
|
+
const hasActionCta = /(?:start|try|get|claim|démarrer|essayer|profiter|obtenir)\s+(?:free|trial|now|today|gratuit)/i.test(content);
|
|
11251
|
+
checks.push({
|
|
11252
|
+
id: "cta_action_verb",
|
|
11253
|
+
name: "High-Contrast Outcome CTA",
|
|
11254
|
+
category: "ctas",
|
|
11255
|
+
passed: hasActionCta,
|
|
11256
|
+
importance: "critical",
|
|
11257
|
+
feedback: hasActionCta ? "High-converting action verb present in CTA." : "CTA is generic or missing action verb."
|
|
11258
|
+
});
|
|
11259
|
+
const hasFaq = /faq|frequently asked|questions|foire aux questions/i.test(content);
|
|
11260
|
+
checks.push({
|
|
11261
|
+
id: "faq_objection_handling",
|
|
11262
|
+
name: "Objection Handling & FAQ Section",
|
|
11263
|
+
category: "objection_handling",
|
|
11264
|
+
passed: hasFaq,
|
|
11265
|
+
importance: "important",
|
|
11266
|
+
feedback: hasFaq ? "FAQ section handles user hesitations." : "Missing FAQ / objection handling section."
|
|
11267
|
+
});
|
|
11268
|
+
const hasRiskReversal = /no credit card|cancel anytime|money-back|sans carte|sans engagement|garantie/i.test(content);
|
|
11269
|
+
checks.push({
|
|
11270
|
+
id: "risk_reversal_guarantee",
|
|
11271
|
+
name: "Zero-Risk Micro-Copy",
|
|
11272
|
+
category: "risk_reversal",
|
|
11273
|
+
passed: hasRiskReversal,
|
|
11274
|
+
importance: "important",
|
|
11275
|
+
feedback: hasRiskReversal ? "Risk reversal micro-copy found near CTA." : "No risk reversal guarantee found."
|
|
11276
|
+
});
|
|
11277
|
+
const hasUrgency = /today|now|instant|limited|aujourd'hui|maintenant|immédiat/i.test(content);
|
|
11278
|
+
checks.push({
|
|
11279
|
+
id: "urgency_trigger",
|
|
11280
|
+
name: "Immediate Access & Momentum",
|
|
11281
|
+
category: "urgency",
|
|
11282
|
+
passed: hasUrgency,
|
|
11283
|
+
importance: "enhancement",
|
|
11284
|
+
feedback: hasUrgency ? "Presents immediate access momentum." : "Lacks urgency or immediacy trigger."
|
|
11285
|
+
});
|
|
11286
|
+
const hasSubheadings = lines.filter((l) => l.startsWith("## ")).length >= 3;
|
|
11287
|
+
checks.push({
|
|
11288
|
+
id: "structure_scannability",
|
|
11289
|
+
name: "Visual Hierarchy & H2 Subheadings",
|
|
11290
|
+
category: "structure",
|
|
11291
|
+
passed: hasSubheadings,
|
|
11292
|
+
importance: "important",
|
|
11293
|
+
feedback: hasSubheadings ? "Clean visual hierarchy with 3+ H2 sections." : "Fewer than 3 H2 subheadings found."
|
|
11294
|
+
});
|
|
11295
|
+
const totalChecks = checks.length;
|
|
11296
|
+
const passedChecks = checks.filter((c) => c.passed).length;
|
|
11297
|
+
const criticalFailures = checks.filter((c) => !c.passed && c.importance === "critical");
|
|
11298
|
+
const warnings = checks.filter((c) => !c.passed && c.importance === "important");
|
|
11299
|
+
const overallCroScore = Math.round(passedChecks / totalChecks * 100);
|
|
11300
|
+
const passes = overallCroScore >= 70 && criticalFailures.length === 0;
|
|
11301
|
+
let grade = "F";
|
|
11302
|
+
if (overallCroScore >= 90)
|
|
11303
|
+
grade = "A+";
|
|
11304
|
+
else if (overallCroScore >= 80)
|
|
11305
|
+
grade = "A";
|
|
11306
|
+
else if (overallCroScore >= 70)
|
|
11307
|
+
grade = "B";
|
|
11308
|
+
else if (overallCroScore >= 55)
|
|
11309
|
+
grade = "C";
|
|
11310
|
+
const categoryScores = {};
|
|
11311
|
+
for (const check of checks) {
|
|
11312
|
+
if (!categoryScores[check.category]) {
|
|
11313
|
+
categoryScores[check.category] = { score: 0, passedCount: 0, totalCount: 0 };
|
|
11314
|
+
}
|
|
11315
|
+
categoryScores[check.category].totalCount++;
|
|
11316
|
+
if (check.passed)
|
|
11317
|
+
categoryScores[check.category].passedCount++;
|
|
11318
|
+
}
|
|
11319
|
+
for (const cat in categoryScores) {
|
|
11320
|
+
categoryScores[cat].score = Math.round(categoryScores[cat].passedCount / categoryScores[cat].totalCount * 100);
|
|
11321
|
+
}
|
|
11322
|
+
const roadmap = checks.filter((c) => !c.passed).map((c) => `[${c.importance.toUpperCase()}] ${c.category}: ${c.feedback}`);
|
|
11323
|
+
return {
|
|
11324
|
+
overallCroScore,
|
|
11325
|
+
passesCroAudit: passes,
|
|
11326
|
+
grade,
|
|
11327
|
+
criticalFailuresCount: criticalFailures.length,
|
|
11328
|
+
warningsCount: warnings.length,
|
|
11329
|
+
checks,
|
|
11330
|
+
categoryScores,
|
|
11331
|
+
conversionRoadmap: roadmap
|
|
11332
|
+
};
|
|
11333
|
+
}
|
|
11334
|
+
}
|
|
11335
|
+
|
|
11336
|
+
// src/social-community-insights.ts
|
|
11337
|
+
class SocialCommunityInsightsAggregator {
|
|
11338
|
+
static aggregate(topic, rawInsights) {
|
|
11339
|
+
const painPoints = [];
|
|
11340
|
+
const realLanguage = [];
|
|
11341
|
+
const quotables = [];
|
|
11342
|
+
for (const item of rawInsights) {
|
|
11343
|
+
if (item.type === "pain_point" || item.type === "complaint") {
|
|
11344
|
+
if (!painPoints.includes(item.quotableText)) {
|
|
11345
|
+
painPoints.push(item.quotableText);
|
|
11346
|
+
}
|
|
11347
|
+
}
|
|
11348
|
+
if (item.quotableText) {
|
|
11349
|
+
quotables.push(`"${item.quotableText}" (${item.source})`);
|
|
11350
|
+
}
|
|
11351
|
+
for (const phrase of item.userVocabulary) {
|
|
11352
|
+
if (!realLanguage.includes(phrase)) {
|
|
11353
|
+
realLanguage.push(phrase);
|
|
11354
|
+
}
|
|
11355
|
+
}
|
|
11356
|
+
}
|
|
11357
|
+
const recommendedAngle = painPoints.length > 0 ? `Address the #1 community frustration: "${painPoints[0]}" by demonstrating immediate resolution in the hero hook.` : `Highlight community-tested best practices and back every recommendation with verified user metrics.`;
|
|
11358
|
+
return {
|
|
11359
|
+
topic,
|
|
11360
|
+
totalInsights: rawInsights.length,
|
|
11361
|
+
painPoints,
|
|
11362
|
+
realLanguagePhrases: realLanguage,
|
|
11363
|
+
quotableExcerpts: quotables,
|
|
11364
|
+
recommendedAngleForCopy: recommendedAngle
|
|
11365
|
+
};
|
|
11366
|
+
}
|
|
11367
|
+
}
|
|
11368
|
+
|
|
11369
|
+
// src/landing-channel-scorer.ts
|
|
11370
|
+
class LandingChannelScorer {
|
|
11371
|
+
static CONFIGS = {
|
|
11372
|
+
seo: { minWords: 1200, optimalWords: 2000, maxWords: 3000, minCtas: 3, optimalCtas: 5, maxDistractions: 6 },
|
|
11373
|
+
ppc: { minWords: 350, optimalWords: 600, maxWords: 900, minCtas: 2, optimalCtas: 3, maxDistractions: 1 }
|
|
11374
|
+
};
|
|
11375
|
+
static scorePage(content, options) {
|
|
11376
|
+
const config = this.CONFIGS[options.channel] || this.CONFIGS.seo;
|
|
11377
|
+
const words = content.split(/\s+/).filter(Boolean);
|
|
11378
|
+
const totalWords = words.length;
|
|
11379
|
+
const ctaMatches = content.match(/(?:start|try|get|claim|demo|free trial|sign up|démarrer|essayer|profiter|obtenir)\s+(?:free|trial|now|today|gratuit|demo)?/gi) || [];
|
|
11380
|
+
const ctaCount = ctaMatches.length;
|
|
11381
|
+
const linkMatches = content.match(/\[.*?\]\(.*?\)/g) || [];
|
|
11382
|
+
const distractionLinksCount = linkMatches.filter((l) => !l.toLowerCase().includes("pricing") && !l.toLowerCase().includes("signup")).length;
|
|
11383
|
+
const hasH1 = /^#\s+.+/m.test(content);
|
|
11384
|
+
const aboveFoldScore = hasH1 && ctaCount >= 1 ? 25 : 12;
|
|
11385
|
+
let ctaScore = 15;
|
|
11386
|
+
if (ctaCount >= config.minCtas && ctaCount <= config.optimalCtas + 2) {
|
|
11387
|
+
ctaScore = 25;
|
|
11388
|
+
} else if (ctaCount === 0) {
|
|
11389
|
+
ctaScore = 0;
|
|
11390
|
+
}
|
|
11391
|
+
const hasProof = /rated|reviews?|avis|clients?|customers?|\d{1,3}(?:,\d{3})*\+/i.test(content);
|
|
11392
|
+
const trustScore = hasProof ? 20 : 5;
|
|
11393
|
+
const hasH2s = (content.match(/^##\s+.+/gm) || []).length >= 2;
|
|
11394
|
+
const structureScore = hasH2s ? 15 : 8;
|
|
11395
|
+
let channelFitScore = 15;
|
|
11396
|
+
const recommendations = [];
|
|
11397
|
+
if (options.channel === "seo") {
|
|
11398
|
+
if (totalWords < config.minWords) {
|
|
11399
|
+
channelFitScore -= 8;
|
|
11400
|
+
recommendations.push(`SEO landing pages need depth (${totalWords}/${config.minWords} words). Expand with FAQ, feature breakdown, and case studies.`);
|
|
11401
|
+
}
|
|
11402
|
+
} else {
|
|
11403
|
+
if (totalWords > config.maxWords) {
|
|
11404
|
+
channelFitScore -= 8;
|
|
11405
|
+
recommendations.push(`PPC landing page is too long (${totalWords} words). Cut text walls down to ~${config.optimalWords} words to maximize instant conversions.`);
|
|
11406
|
+
}
|
|
11407
|
+
if (distractionLinksCount > config.maxDistractions) {
|
|
11408
|
+
channelFitScore -= 7;
|
|
11409
|
+
recommendations.push(`Remove ${distractionLinksCount} navigation/leak links. PPC pages should only offer ONE path to conversion.`);
|
|
11410
|
+
}
|
|
11411
|
+
}
|
|
11412
|
+
if (ctaCount < config.minCtas) {
|
|
11413
|
+
recommendations.push(`Add ${config.minCtas - ctaCount} additional CTA button(s) distributed across the page.`);
|
|
11414
|
+
}
|
|
11415
|
+
if (!hasProof) {
|
|
11416
|
+
recommendations.push("Add social proof indicators (reviews, user counts, client logos).");
|
|
11417
|
+
}
|
|
11418
|
+
const totalScore = Math.max(10, Math.min(100, aboveFoldScore + ctaScore + trustScore + structureScore + channelFitScore));
|
|
11419
|
+
const passed = totalScore >= 70;
|
|
11420
|
+
return {
|
|
11421
|
+
channel: options.channel,
|
|
11422
|
+
score: totalScore,
|
|
11423
|
+
passed,
|
|
11424
|
+
wordCount: totalWords,
|
|
11425
|
+
wordCountTarget: { min: config.minWords, optimal: config.optimalWords, max: config.maxWords },
|
|
11426
|
+
ctaCount,
|
|
11427
|
+
ctaTarget: { min: config.minCtas, optimal: config.optimalWords ? config.optimalCtas : 3 },
|
|
11428
|
+
distractionLinksCount,
|
|
11429
|
+
categoryBreakdown: {
|
|
11430
|
+
aboveTheFold: aboveFoldScore,
|
|
11431
|
+
ctaAlignment: ctaScore,
|
|
11432
|
+
trustSignals: trustScore,
|
|
11433
|
+
structure: structureScore,
|
|
11434
|
+
channelFit: channelFitScore
|
|
11435
|
+
},
|
|
11436
|
+
recommendations
|
|
11437
|
+
};
|
|
11438
|
+
}
|
|
11439
|
+
}
|
|
11440
|
+
|
|
11441
|
+
// src/article-strategic-planner.ts
|
|
11442
|
+
class ArticleStrategicPlanner {
|
|
11443
|
+
static planArticle(topic, primaryKeyword, depth = "standard") {
|
|
11444
|
+
const totalWords = depth === "exhaustive_pillar" ? 3200 : 2200;
|
|
11445
|
+
const readingTime = Math.round(totalWords / 200);
|
|
11446
|
+
const sections = [
|
|
11447
|
+
{
|
|
11448
|
+
sectionNumber: 1,
|
|
11449
|
+
type: "intro",
|
|
11450
|
+
heading: `Introduction & Executive Direct Answer`,
|
|
11451
|
+
targetWordCount: 250,
|
|
11452
|
+
strategicObjective: "Hook the reader with a thought-provoking metric and provide a 40-word Direct Answer for SearchGPT & Gemini AI Overviews.",
|
|
11453
|
+
hasFeaturedSnippetTarget: true,
|
|
11454
|
+
internalLinkAnchors: ["Product Overview", "Platform Features"]
|
|
11455
|
+
},
|
|
11456
|
+
{
|
|
11457
|
+
sectionNumber: 2,
|
|
11458
|
+
type: "explanation",
|
|
11459
|
+
heading: `Why ${primaryKeyword} Matters in 2026`,
|
|
11460
|
+
targetWordCount: depth === "exhaustive_pillar" ? 600 : 450,
|
|
11461
|
+
strategicObjective: "Establish market context, operational bottlenecks of manual workflows, and revenue impact.",
|
|
11462
|
+
hasFeaturedSnippetTarget: false,
|
|
11463
|
+
ctaIntensity: "soft",
|
|
11464
|
+
internalLinkAnchors: ["Pricing & Plans"]
|
|
11465
|
+
},
|
|
11466
|
+
{
|
|
11467
|
+
sectionNumber: 3,
|
|
11468
|
+
type: "comparison",
|
|
11469
|
+
heading: `Top Approaches & Benchmarks Matrix`,
|
|
11470
|
+
targetWordCount: depth === "exhaustive_pillar" ? 750 : 500,
|
|
11471
|
+
strategicObjective: "Present an objective comparison table with speed, latency, feature availability, and pricing.",
|
|
11472
|
+
hasFeaturedSnippetTarget: true,
|
|
11473
|
+
ctaIntensity: "medium",
|
|
11474
|
+
internalLinkAnchors: ["Interactive Calculator", "Case Studies"]
|
|
11475
|
+
},
|
|
11476
|
+
{
|
|
11477
|
+
sectionNumber: 4,
|
|
11478
|
+
type: "how_to",
|
|
11479
|
+
heading: `Step-by-Step Implementation Blueprint`,
|
|
11480
|
+
targetWordCount: depth === "exhaustive_pillar" ? 800 : 550,
|
|
11481
|
+
strategicObjective: "Provide actionable step-by-step tutorial with code snippets, configuration files, and verification criteria.",
|
|
11482
|
+
hasFeaturedSnippetTarget: false,
|
|
11483
|
+
ctaIntensity: "strong",
|
|
11484
|
+
internalLinkAnchors: ["Documentation Hub", "API Reference"]
|
|
11485
|
+
},
|
|
11486
|
+
{
|
|
11487
|
+
sectionNumber: 5,
|
|
11488
|
+
type: "faq",
|
|
11489
|
+
heading: `Frequently Asked Questions about ${primaryKeyword}`,
|
|
11490
|
+
targetWordCount: 350,
|
|
11491
|
+
strategicObjective: "Address top 4 'People Also Ask' search queries to qualify for Google FAQ rich snippet.",
|
|
11492
|
+
hasFeaturedSnippetTarget: true,
|
|
11493
|
+
internalLinkAnchors: []
|
|
11494
|
+
},
|
|
11495
|
+
{
|
|
11496
|
+
sectionNumber: 6,
|
|
11497
|
+
type: "conclusion",
|
|
11498
|
+
heading: `Final Takeaways & Next Steps`,
|
|
11499
|
+
targetWordCount: 150,
|
|
11500
|
+
strategicObjective: "Summarize key findings and present final primary action button.",
|
|
11501
|
+
hasFeaturedSnippetTarget: false,
|
|
11502
|
+
ctaIntensity: "strong",
|
|
11503
|
+
internalLinkAnchors: ["Start Free Trial"]
|
|
11504
|
+
}
|
|
11505
|
+
];
|
|
11506
|
+
return {
|
|
11507
|
+
topic,
|
|
11508
|
+
targetTotalWords: totalWords,
|
|
11509
|
+
estimatedReadingTimeMinutes: readingTime,
|
|
11510
|
+
meta: {
|
|
11511
|
+
titleOptions: [
|
|
11512
|
+
`${topic}: Complete Guide for 2026`,
|
|
11513
|
+
`How to Master ${topic} (Step-by-Step)`,
|
|
11514
|
+
`The Ultimate ${topic} Blueprint: Speed & Accuracy`
|
|
11515
|
+
],
|
|
11516
|
+
metaDescription: `Discover how to master ${topic} in 2026. Explore step-by-step implementation, benchmarks, comparison matrices, and FAQ.`,
|
|
11517
|
+
targetKeywords: [primaryKeyword, `${primaryKeyword} guide`, `best ${primaryKeyword} tools`]
|
|
11518
|
+
},
|
|
11519
|
+
sections,
|
|
11520
|
+
ctaMap: {
|
|
11521
|
+
soft: 2,
|
|
11522
|
+
medium: 3,
|
|
11523
|
+
strong: 4
|
|
11524
|
+
}
|
|
11525
|
+
};
|
|
11526
|
+
}
|
|
11527
|
+
}
|
|
11528
|
+
|
|
11529
|
+
// src/topic-cluster-architect.ts
|
|
11530
|
+
class TopicClusterArchitect {
|
|
11531
|
+
static buildCluster(pillarTopic, subTopics) {
|
|
11532
|
+
const pillarSlug = `/${pillarTopic.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "")}`;
|
|
11533
|
+
let totalVolume = 0;
|
|
11534
|
+
const spokes = subTopics.map((st) => {
|
|
11535
|
+
const slug = `${pillarSlug}/${st.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "")}`;
|
|
11536
|
+
const vol = st.volume || 1200;
|
|
11537
|
+
totalVolume += vol;
|
|
11538
|
+
return {
|
|
11539
|
+
subTopic: st.name,
|
|
11540
|
+
slug,
|
|
11541
|
+
searchIntent: st.intent || "informational",
|
|
11542
|
+
monthlySearchVolume: vol,
|
|
11543
|
+
keywordDifficulty: st.difficulty || 35,
|
|
11544
|
+
targetWordCount: 1800,
|
|
11545
|
+
inboundLinks: [pillarSlug],
|
|
11546
|
+
outboundLinks: [pillarSlug]
|
|
11547
|
+
};
|
|
11548
|
+
});
|
|
11549
|
+
const crossLinks = [];
|
|
11550
|
+
for (const spoke of spokes) {
|
|
11551
|
+
crossLinks.push({
|
|
11552
|
+
fromSlug: spoke.slug,
|
|
11553
|
+
toSlug: pillarSlug,
|
|
11554
|
+
anchorText: `${pillarTopic} Complete Overview`
|
|
11555
|
+
});
|
|
11556
|
+
crossLinks.push({
|
|
11557
|
+
fromSlug: pillarSlug,
|
|
11558
|
+
toSlug: spoke.slug,
|
|
11559
|
+
anchorText: `Deep dive on ${spoke.subTopic}`
|
|
11560
|
+
});
|
|
11561
|
+
}
|
|
11562
|
+
for (let i = 0;i < spokes.length; i++) {
|
|
11563
|
+
const nextSpoke = spokes[(i + 1) % spokes.length];
|
|
11564
|
+
crossLinks.push({
|
|
11565
|
+
fromSlug: spokes[i].slug,
|
|
11566
|
+
toSlug: nextSpoke.slug,
|
|
11567
|
+
anchorText: `Related: ${nextSpoke.subTopic}`
|
|
11568
|
+
});
|
|
11569
|
+
spokes[i].outboundLinks.push(nextSpoke.slug);
|
|
11570
|
+
nextSpoke.inboundLinks.push(spokes[i].slug);
|
|
11571
|
+
}
|
|
11572
|
+
let authority = "emerging";
|
|
11573
|
+
if (spokes.length >= 6 && totalVolume > 5000)
|
|
11574
|
+
authority = "dominant";
|
|
11575
|
+
else if (spokes.length >= 3)
|
|
11576
|
+
authority = "strong";
|
|
11577
|
+
return {
|
|
11578
|
+
pillarTopic,
|
|
11579
|
+
pillarSlug,
|
|
11580
|
+
totalSubSpokes: spokes.length,
|
|
11581
|
+
totalEstimatedSearchVolume: totalVolume,
|
|
11582
|
+
spokes,
|
|
11583
|
+
crossLinkMatrix: crossLinks,
|
|
11584
|
+
clusterAuthorityPotential: authority
|
|
11585
|
+
};
|
|
11586
|
+
}
|
|
11587
|
+
}
|
|
11588
|
+
|
|
11052
11589
|
// src/index.ts
|
|
11053
11590
|
function createLynxSeoEngine(config) {
|
|
11054
11591
|
return new LynxSeoEngine(config);
|
|
@@ -11074,6 +11611,12 @@ var LynxSeo = {
|
|
|
11074
11611
|
competitorGap: CompetitorGapBlueprintEngine,
|
|
11075
11612
|
engagementHook: EngagementHookAnalyzer,
|
|
11076
11613
|
keywordDensity: KeywordDensityGuard,
|
|
11614
|
+
contentQuality: ContentQualityScorer,
|
|
11615
|
+
landingCroChecker: LandingCroChecker,
|
|
11616
|
+
socialInsights: SocialCommunityInsightsAggregator,
|
|
11617
|
+
channelScorer: LandingChannelScorer,
|
|
11618
|
+
articlePlanner: ArticleStrategicPlanner,
|
|
11619
|
+
topicClusters: TopicClusterArchitect,
|
|
11077
11620
|
inspectMeta: SiteAuditor.inspectMeta,
|
|
11078
11621
|
crawlDomain: DeepCrawlerAuditor.crawlAndAuditDomain,
|
|
11079
11622
|
inspectHtmlSnapshot: DeepCrawlerAuditor.inspectHtmlSnapshot,
|
|
@@ -11134,12 +11677,14 @@ export {
|
|
|
11134
11677
|
UrlyticsEngine,
|
|
11135
11678
|
UI_ICONS,
|
|
11136
11679
|
TrustSignalsExtractor,
|
|
11680
|
+
TopicClusterArchitect,
|
|
11137
11681
|
TokenQuotaManager,
|
|
11138
11682
|
TechnicalRulesAuditor,
|
|
11139
11683
|
TeamRbacEngine,
|
|
11140
11684
|
SocialVideoSeoAnalyticsEngine,
|
|
11141
11685
|
SocialTrendSeoGenerator,
|
|
11142
11686
|
SocialGrowthSuite,
|
|
11687
|
+
SocialCommunityInsightsAggregator,
|
|
11143
11688
|
SocialAdsSocialSeoEngine,
|
|
11144
11689
|
SiteAuditor,
|
|
11145
11690
|
SerpRankHistoryEngine,
|
|
@@ -11177,6 +11722,8 @@ export {
|
|
|
11177
11722
|
LynxAnalyticsClient,
|
|
11178
11723
|
LlmContentCleaner,
|
|
11179
11724
|
LegalDisclaimerEngine,
|
|
11725
|
+
LandingCroChecker,
|
|
11726
|
+
LandingChannelScorer,
|
|
11180
11727
|
LagoTokenMeter,
|
|
11181
11728
|
KnowledgeGraphLinker,
|
|
11182
11729
|
KnowledgeBankBuilder,
|
|
@@ -11204,12 +11751,14 @@ export {
|
|
|
11204
11751
|
CroCopywritingEngine,
|
|
11205
11752
|
CopywritingFrameworksMaster,
|
|
11206
11753
|
ContextTemplatesGenerator,
|
|
11754
|
+
ContentQualityScorer,
|
|
11207
11755
|
CompetitorGapBlueprintEngine,
|
|
11208
11756
|
CONTENT_AI_40_TOOLS,
|
|
11209
11757
|
BrandDnaCalendarEngine,
|
|
11210
11758
|
BacklinksClient,
|
|
11211
11759
|
BUILT_IN_LOCATIONS_DATABASE,
|
|
11212
11760
|
BRAND_ICONS,
|
|
11761
|
+
ArticleStrategicPlanner,
|
|
11213
11762
|
ApiKeyGuardian,
|
|
11214
11763
|
AiCopilotClient,
|
|
11215
11764
|
AiBotsLogAnalyzer,
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🎯 Dual-Channel Landing Page Scorer (SEO vs. PPC)
|
|
3
|
+
* Ported & optimized from Python landing_page_scorer.py (28 KB).
|
|
4
|
+
* Evaluates landing pages with tailored benchmarks for SEO (1,500-2,500 words) vs PPC (400-800 words, zero leak links).
|
|
5
|
+
*/
|
|
6
|
+
export interface ChannelScoringConfig {
|
|
7
|
+
channel: "seo" | "ppc";
|
|
8
|
+
conversionGoal?: "trial" | "demo" | "lead";
|
|
9
|
+
}
|
|
10
|
+
export interface ChannelScoringReport {
|
|
11
|
+
channel: "seo" | "ppc";
|
|
12
|
+
score: number;
|
|
13
|
+
passed: boolean;
|
|
14
|
+
wordCount: number;
|
|
15
|
+
wordCountTarget: {
|
|
16
|
+
min: number;
|
|
17
|
+
optimal: number;
|
|
18
|
+
max: number;
|
|
19
|
+
};
|
|
20
|
+
ctaCount: number;
|
|
21
|
+
ctaTarget: {
|
|
22
|
+
min: number;
|
|
23
|
+
optimal: number;
|
|
24
|
+
};
|
|
25
|
+
distractionLinksCount: number;
|
|
26
|
+
categoryBreakdown: {
|
|
27
|
+
aboveTheFold: number;
|
|
28
|
+
ctaAlignment: number;
|
|
29
|
+
trustSignals: number;
|
|
30
|
+
structure: number;
|
|
31
|
+
channelFit: number;
|
|
32
|
+
};
|
|
33
|
+
recommendations: string[];
|
|
34
|
+
}
|
|
35
|
+
export declare class LandingChannelScorer {
|
|
36
|
+
private static readonly CONFIGS;
|
|
37
|
+
/**
|
|
38
|
+
* Scores landing page according to the specific acquisition channel.
|
|
39
|
+
*/
|
|
40
|
+
static scorePage(content: string, options: ChannelScoringConfig): ChannelScoringReport;
|
|
41
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 📊 Comprehensive Landing Page CRO Checker
|
|
3
|
+
* Ported & optimized from Python cro_checker.py (23 KB).
|
|
4
|
+
* Evaluates 8 landing page conversion categories with pass/fail criteria and grade rating.
|
|
5
|
+
*/
|
|
6
|
+
export interface CroCheckItem {
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
category: "headline" | "value_prop" | "social_proof" | "ctas" | "objection_handling" | "risk_reversal" | "urgency" | "structure";
|
|
10
|
+
passed: boolean;
|
|
11
|
+
importance: "critical" | "important" | "enhancement";
|
|
12
|
+
feedback: string;
|
|
13
|
+
}
|
|
14
|
+
export interface LandingCroAuditResult {
|
|
15
|
+
overallCroScore: number;
|
|
16
|
+
passesCroAudit: boolean;
|
|
17
|
+
grade: "A+" | "A" | "B" | "C" | "F";
|
|
18
|
+
criticalFailuresCount: number;
|
|
19
|
+
warningsCount: number;
|
|
20
|
+
checks: CroCheckItem[];
|
|
21
|
+
categoryScores: Record<string, {
|
|
22
|
+
score: number;
|
|
23
|
+
passedCount: number;
|
|
24
|
+
totalCount: number;
|
|
25
|
+
}>;
|
|
26
|
+
conversionRoadmap: string[];
|
|
27
|
+
}
|
|
28
|
+
export declare class LandingCroChecker {
|
|
29
|
+
/**
|
|
30
|
+
* Runs the 8-category CRO checklist against landing page content or markup.
|
|
31
|
+
*/
|
|
32
|
+
static auditLandingPage(content: string, config?: {
|
|
33
|
+
pageType?: "seo" | "ppc";
|
|
34
|
+
conversionGoal?: "trial" | "demo" | "lead";
|
|
35
|
+
}): LandingCroAuditResult;
|
|
36
|
+
}
|