@lynxflow/seo-engine 2.5.0 → 2.6.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 CHANGED
@@ -4,7 +4,7 @@ High-Performance Universal Programmatic SEO & Structured Data Engine for Modern
4
4
 
5
5
  [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)
6
6
  [![License](https://img.shields.io/badge/License-Proprietary-green.svg)](LICENSE)
7
- [![Version](https://img.shields.io/badge/Version-2.5.0-orange.svg)](https://www.npmjs.com/package/@lynxflow/seo-engine)
7
+ [![Version](https://img.shields.io/badge/Version-2.6.0-orange.svg)](https://www.npmjs.com/package/@lynxflow/seo-engine)
8
8
 
9
9
  ---
10
10
 
@@ -0,0 +1,40 @@
1
+ /**
2
+ * 💎 Multi-Dimensional Content Quality Scorer
3
+ * Ported & optimized from Python content_scorer.py (31 KB).
4
+ * Evaluates 5 dimensions: Humanity/Voice (30%), Specificity (25%), Structure Balance (20%), SEO Compliance (15%), Readability (10%).
5
+ * Composite quality threshold: >= 70 to pass.
6
+ */
7
+ export interface DimensionScoreBreakdown {
8
+ score: number;
9
+ weight: number;
10
+ weightedScore: number;
11
+ strengths: string[];
12
+ issues: string[];
13
+ }
14
+ export interface ContentQualityReport {
15
+ compositeScore: number;
16
+ passesQualityThreshold: boolean;
17
+ grade: "A+" | "A" | "B" | "C" | "F";
18
+ dimensions: {
19
+ humanity: DimensionScoreBreakdown;
20
+ specificity: DimensionScoreBreakdown;
21
+ structureBalance: DimensionScoreBreakdown;
22
+ seoCompliance: DimensionScoreBreakdown;
23
+ readability: DimensionScoreBreakdown;
24
+ };
25
+ proseToListRatio: number;
26
+ topPriorityFixes: string[];
27
+ }
28
+ export declare class ContentQualityScorer {
29
+ private static readonly VAGUE_WORDS;
30
+ private static readonly SPECIFICITY_DATA_PATTERNS;
31
+ private static readonly CONVERSATIONAL_DEVICES;
32
+ /**
33
+ * Scores content across all 5 dimensions.
34
+ */
35
+ static scoreContent(content: string, metadata?: {
36
+ metaTitle?: string;
37
+ primaryKeyword?: string;
38
+ }): ContentQualityReport;
39
+ private static emptyReport;
40
+ }
package/dist/index.d.ts CHANGED
@@ -125,6 +125,9 @@ import { TrustSignalsExtractor } from "./trust-signals-extractor";
125
125
  import { CompetitorGapBlueprintEngine } from "./competitor-gap-blueprint";
126
126
  import { EngagementHookAnalyzer } from "./engagement-hook-analyzer";
127
127
  import { KeywordDensityGuard } from "./keyword-density-guard";
128
+ import { ContentQualityScorer } from "./content-quality-scorer";
129
+ import { LandingCroChecker } from "./landing-cro-checker";
130
+ import { SocialCommunityInsightsAggregator } from "./social-community-insights";
128
131
  export declare function createLynxSeoEngine(config: EngineConfig): LynxSeoEngine;
129
132
  export declare const LynxSeo: {
130
133
  createEngine: typeof createLynxSeoEngine;
@@ -147,6 +150,9 @@ export declare const LynxSeo: {
147
150
  competitorGap: typeof CompetitorGapBlueprintEngine;
148
151
  engagementHook: typeof EngagementHookAnalyzer;
149
152
  keywordDensity: typeof KeywordDensityGuard;
153
+ contentQuality: typeof ContentQualityScorer;
154
+ landingCroChecker: typeof LandingCroChecker;
155
+ socialInsights: typeof SocialCommunityInsightsAggregator;
150
156
  inspectMeta: typeof SiteAuditor.inspectMeta;
151
157
  crawlDomain: typeof DeepCrawlerAuditor.crawlAndAuditDomain;
152
158
  inspectHtmlSnapshot: typeof DeepCrawlerAuditor.inspectHtmlSnapshot;
@@ -188,6 +194,9 @@ export declare const LynxSeo: {
188
194
  marketingSkills: typeof MarketingSkillsEngine;
189
195
  regenerationTracker: typeof PageRegenerationTracker;
190
196
  };
197
+ export * from "./content-quality-scorer";
198
+ export * from "./landing-cro-checker";
199
+ export * from "./social-community-insights";
191
200
  export * from "./competitor-gap-blueprint";
192
201
  export * from "./engagement-hook-analyzer";
193
202
  export * from "./keyword-density-guard";
package/dist/index.js CHANGED
@@ -11049,6 +11049,323 @@ 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
+
11052
11369
  // src/index.ts
11053
11370
  function createLynxSeoEngine(config) {
11054
11371
  return new LynxSeoEngine(config);
@@ -11074,6 +11391,9 @@ var LynxSeo = {
11074
11391
  competitorGap: CompetitorGapBlueprintEngine,
11075
11392
  engagementHook: EngagementHookAnalyzer,
11076
11393
  keywordDensity: KeywordDensityGuard,
11394
+ contentQuality: ContentQualityScorer,
11395
+ landingCroChecker: LandingCroChecker,
11396
+ socialInsights: SocialCommunityInsightsAggregator,
11077
11397
  inspectMeta: SiteAuditor.inspectMeta,
11078
11398
  crawlDomain: DeepCrawlerAuditor.crawlAndAuditDomain,
11079
11399
  inspectHtmlSnapshot: DeepCrawlerAuditor.inspectHtmlSnapshot,
@@ -11140,6 +11460,7 @@ export {
11140
11460
  SocialVideoSeoAnalyticsEngine,
11141
11461
  SocialTrendSeoGenerator,
11142
11462
  SocialGrowthSuite,
11463
+ SocialCommunityInsightsAggregator,
11143
11464
  SocialAdsSocialSeoEngine,
11144
11465
  SiteAuditor,
11145
11466
  SerpRankHistoryEngine,
@@ -11177,6 +11498,7 @@ export {
11177
11498
  LynxAnalyticsClient,
11178
11499
  LlmContentCleaner,
11179
11500
  LegalDisclaimerEngine,
11501
+ LandingCroChecker,
11180
11502
  LagoTokenMeter,
11181
11503
  KnowledgeGraphLinker,
11182
11504
  KnowledgeBankBuilder,
@@ -11204,6 +11526,7 @@ export {
11204
11526
  CroCopywritingEngine,
11205
11527
  CopywritingFrameworksMaster,
11206
11528
  ContextTemplatesGenerator,
11529
+ ContentQualityScorer,
11207
11530
  CompetitorGapBlueprintEngine,
11208
11531
  CONTENT_AI_40_TOOLS,
11209
11532
  BrandDnaCalendarEngine,
package/dist/index.mjs CHANGED
@@ -11049,6 +11049,323 @@ 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
+
11052
11369
  // src/index.ts
11053
11370
  function createLynxSeoEngine(config) {
11054
11371
  return new LynxSeoEngine(config);
@@ -11074,6 +11391,9 @@ var LynxSeo = {
11074
11391
  competitorGap: CompetitorGapBlueprintEngine,
11075
11392
  engagementHook: EngagementHookAnalyzer,
11076
11393
  keywordDensity: KeywordDensityGuard,
11394
+ contentQuality: ContentQualityScorer,
11395
+ landingCroChecker: LandingCroChecker,
11396
+ socialInsights: SocialCommunityInsightsAggregator,
11077
11397
  inspectMeta: SiteAuditor.inspectMeta,
11078
11398
  crawlDomain: DeepCrawlerAuditor.crawlAndAuditDomain,
11079
11399
  inspectHtmlSnapshot: DeepCrawlerAuditor.inspectHtmlSnapshot,
@@ -11140,6 +11460,7 @@ export {
11140
11460
  SocialVideoSeoAnalyticsEngine,
11141
11461
  SocialTrendSeoGenerator,
11142
11462
  SocialGrowthSuite,
11463
+ SocialCommunityInsightsAggregator,
11143
11464
  SocialAdsSocialSeoEngine,
11144
11465
  SiteAuditor,
11145
11466
  SerpRankHistoryEngine,
@@ -11177,6 +11498,7 @@ export {
11177
11498
  LynxAnalyticsClient,
11178
11499
  LlmContentCleaner,
11179
11500
  LegalDisclaimerEngine,
11501
+ LandingCroChecker,
11180
11502
  LagoTokenMeter,
11181
11503
  KnowledgeGraphLinker,
11182
11504
  KnowledgeBankBuilder,
@@ -11204,6 +11526,7 @@ export {
11204
11526
  CroCopywritingEngine,
11205
11527
  CopywritingFrameworksMaster,
11206
11528
  ContextTemplatesGenerator,
11529
+ ContentQualityScorer,
11207
11530
  CompetitorGapBlueprintEngine,
11208
11531
  CONTENT_AI_40_TOOLS,
11209
11532
  BrandDnaCalendarEngine,
@@ -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
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * 👥 Social Community Insights & Real-Voice Vocabulary Aggregator
3
+ * Ported & optimized from Python social_research_aggregator.py.
4
+ * Aggregates pain points, success stories, and authentic user vocabulary from Reddit, YouTube, and community forums.
5
+ */
6
+ export interface CommunityInsightItem {
7
+ source: "reddit" | "youtube" | "community_forum";
8
+ type: "pain_point" | "success_story" | "complaint" | "recommendation" | "real_slang";
9
+ titleOrThread: string;
10
+ quotableText: string;
11
+ userVocabulary: string[];
12
+ upvotesOrViews?: number;
13
+ }
14
+ export interface CommunityResearchSummary {
15
+ topic: string;
16
+ totalInsights: number;
17
+ painPoints: string[];
18
+ realLanguagePhrases: string[];
19
+ quotableExcerpts: string[];
20
+ recommendedAngleForCopy: string;
21
+ }
22
+ export declare class SocialCommunityInsightsAggregator {
23
+ /**
24
+ * Aggregates social insights and structures high-converting copywriting angles.
25
+ */
26
+ static aggregate(topic: string, rawInsights: CommunityInsightItem[]): CommunityResearchSummary;
27
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lynxflow/seo-engine",
3
- "version": "2.5.0",
3
+ "version": "2.6.0",
4
4
  "description": "High-Performance Universal Programmatic SEO & AI Search Engine SDK",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",