recker 1.0.30 → 1.0.32-next.02f2bae

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 (46) hide show
  1. package/dist/cli/index.js +2653 -197
  2. package/dist/cli/tui/shell-search.js +10 -8
  3. package/dist/cli/tui/shell.d.ts +29 -0
  4. package/dist/cli/tui/shell.js +1733 -9
  5. package/dist/mcp/search/hybrid-search.js +4 -2
  6. package/dist/seo/analyzer.d.ts +7 -0
  7. package/dist/seo/analyzer.js +200 -4
  8. package/dist/seo/rules/ai-search.d.ts +2 -0
  9. package/dist/seo/rules/ai-search.js +423 -0
  10. package/dist/seo/rules/canonical.d.ts +12 -0
  11. package/dist/seo/rules/canonical.js +249 -0
  12. package/dist/seo/rules/crawl.js +113 -0
  13. package/dist/seo/rules/cwv.js +0 -95
  14. package/dist/seo/rules/i18n.js +27 -0
  15. package/dist/seo/rules/images.js +23 -27
  16. package/dist/seo/rules/index.js +14 -0
  17. package/dist/seo/rules/internal-linking.js +6 -6
  18. package/dist/seo/rules/links.js +321 -0
  19. package/dist/seo/rules/meta.js +24 -0
  20. package/dist/seo/rules/mobile.js +0 -20
  21. package/dist/seo/rules/performance.js +124 -0
  22. package/dist/seo/rules/redirects.d.ts +16 -0
  23. package/dist/seo/rules/redirects.js +193 -0
  24. package/dist/seo/rules/resources.d.ts +2 -0
  25. package/dist/seo/rules/resources.js +373 -0
  26. package/dist/seo/rules/security.js +290 -0
  27. package/dist/seo/rules/technical-advanced.d.ts +10 -0
  28. package/dist/seo/rules/technical-advanced.js +283 -0
  29. package/dist/seo/rules/technical.js +74 -18
  30. package/dist/seo/rules/types.d.ts +103 -3
  31. package/dist/seo/seo-spider.d.ts +2 -0
  32. package/dist/seo/seo-spider.js +47 -2
  33. package/dist/seo/types.d.ts +48 -28
  34. package/dist/seo/utils/index.d.ts +1 -0
  35. package/dist/seo/utils/index.js +1 -0
  36. package/dist/seo/utils/similarity.d.ts +47 -0
  37. package/dist/seo/utils/similarity.js +273 -0
  38. package/dist/seo/validators/index.d.ts +3 -0
  39. package/dist/seo/validators/index.js +3 -0
  40. package/dist/seo/validators/llms-txt.d.ts +57 -0
  41. package/dist/seo/validators/llms-txt.js +317 -0
  42. package/dist/seo/validators/robots.d.ts +54 -0
  43. package/dist/seo/validators/robots.js +382 -0
  44. package/dist/seo/validators/sitemap.d.ts +69 -0
  45. package/dist/seo/validators/sitemap.js +424 -0
  46. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
1
  import Fuse from 'fuse.js';
2
- import { cosineSimilarity, combineScores, levenshtein } from './math.js';
2
+ import { cosineSimilarity, levenshtein } from './math.js';
3
3
  import { loadEmbeddings } from '../embeddings-loader.js';
4
4
  import { StateError } from '../../core/errors.js';
5
5
  let cachedEmbeddings = null;
@@ -99,7 +99,9 @@ export class HybridSearch {
99
99
  for (const result of semanticResults) {
100
100
  const existing = results.get(result.id);
101
101
  if (existing) {
102
- existing.score = combineScores(existing.score, result.score);
102
+ const maxScore = Math.max(existing.score, result.score);
103
+ const bonus = Math.min(existing.score, result.score) * 0.3;
104
+ existing.score = Math.min(1.0, maxScore + bonus);
103
105
  existing.source = 'hybrid';
104
106
  }
105
107
  else {
@@ -26,6 +26,13 @@ export declare class SeoAnalyzer {
26
26
  private analyzeTrustSignals;
27
27
  private calculateTextHtmlRatio;
28
28
  private convertToCheckResults;
29
+ private buildSummary;
30
+ private calculateMetaCompleteness;
31
+ private calculateSocialCompleteness;
32
+ private calculateTechnicalCompleteness;
33
+ private calculateContentCompleteness;
34
+ private calculateImageCompleteness;
35
+ private calculateLinkCompleteness;
29
36
  private analyzeHeadings;
30
37
  private analyzeContent;
31
38
  private buildLinkAnalysis;
@@ -49,11 +49,21 @@ export class SeoAnalyzer {
49
49
  const ruleResults = this.rulesEngine.evaluate(context);
50
50
  const checks = this.convertToCheckResults(ruleResults);
51
51
  const { score, grade } = this.calculateScore(checks);
52
+ const summary = this.buildSummary(ruleResults, checks, {
53
+ content,
54
+ imageAnalysis,
55
+ linkAnalysis,
56
+ meta,
57
+ og,
58
+ twitter,
59
+ technical,
60
+ });
52
61
  return {
53
62
  url,
54
63
  timestamp: new Date(),
55
64
  grade,
56
65
  score,
66
+ summary,
57
67
  checks,
58
68
  title: meta.title ? { text: meta.title, length: meta.title.length } : undefined,
59
69
  metaDescription: meta.description ? { text: meta.description, length: meta.description.length } : undefined,
@@ -72,16 +82,17 @@ export class SeoAnalyzer {
72
82
  image: Array.isArray(twitter.image) ? twitter.image[0] : twitter.image,
73
83
  site: twitter.site,
74
84
  } : undefined,
85
+ structuredData: {
86
+ count: jsonLd.length,
87
+ types: jsonLd.map((j) => j['@type']).filter(Boolean),
88
+ items: jsonLd,
89
+ },
75
90
  headings: headings,
76
91
  content,
77
92
  links: linkAnalysis,
78
93
  images: imageAnalysis,
79
94
  social,
80
95
  technical,
81
- jsonLd: {
82
- count: jsonLd.length,
83
- types: jsonLd.map((j) => j['@type']).filter(Boolean),
84
- },
85
96
  };
86
97
  }
87
98
  buildRuleContext(data) {
@@ -503,6 +514,191 @@ export class SeoAnalyzer {
503
514
  evidence: r.evidence,
504
515
  }));
505
516
  }
517
+ buildSummary(ruleResults, checks, data) {
518
+ const passed = checks.filter(c => c.status === 'pass').length;
519
+ const warnings = checks.filter(c => c.status === 'warn').length;
520
+ const errors = checks.filter(c => c.status === 'fail').length;
521
+ const infos = checks.filter(c => c.status === 'info').length;
522
+ const totalChecks = checks.length;
523
+ const scoringChecks = totalChecks - infos;
524
+ const passRate = scoringChecks > 0 ? Math.round((passed / scoringChecks) * 100) : 100;
525
+ const issuesByCategory = {};
526
+ for (const result of ruleResults) {
527
+ const cat = result.category;
528
+ if (!issuesByCategory[cat]) {
529
+ issuesByCategory[cat] = { passed: 0, warnings: 0, errors: 0 };
530
+ }
531
+ if (result.status === 'pass')
532
+ issuesByCategory[cat].passed++;
533
+ else if (result.status === 'warn')
534
+ issuesByCategory[cat].warnings++;
535
+ else if (result.status === 'fail')
536
+ issuesByCategory[cat].errors++;
537
+ }
538
+ const topIssues = ruleResults
539
+ .filter(r => r.status === 'fail' || r.status === 'warn')
540
+ .sort((a, b) => {
541
+ if (a.status === 'fail' && b.status !== 'fail')
542
+ return -1;
543
+ if (a.status !== 'fail' && b.status === 'fail')
544
+ return 1;
545
+ return 0;
546
+ })
547
+ .slice(0, 5)
548
+ .map(r => ({
549
+ name: r.name,
550
+ message: r.message,
551
+ category: r.category,
552
+ severity: (r.status === 'fail' ? 'error' : 'warning'),
553
+ }));
554
+ const quickWins = [];
555
+ if (!data.meta.title)
556
+ quickWins.push('Add a page title');
557
+ if (!data.meta.description)
558
+ quickWins.push('Add a meta description');
559
+ if (!data.og.title && !data.og.description)
560
+ quickWins.push('Add OpenGraph meta tags for social sharing');
561
+ if (!data.twitter.card)
562
+ quickWins.push('Add Twitter Card meta tags');
563
+ if (!data.technical.hasCanonical)
564
+ quickWins.push('Add a canonical URL');
565
+ if (!data.technical.hasLang)
566
+ quickWins.push('Add lang attribute to <html>');
567
+ if (data.imageAnalysis.withoutAlt > 0)
568
+ quickWins.push(`Add alt text to ${data.imageAnalysis.withoutAlt} image(s)`);
569
+ if (data.linkAnalysis.withoutText > 0)
570
+ quickWins.push(`Add text to ${data.linkAnalysis.withoutText} empty link(s)`);
571
+ const limitedQuickWins = quickWins.slice(0, 5);
572
+ const htmlSize = this.$('html').html()?.length;
573
+ const domElements = this.$('*').length;
574
+ const vitals = {
575
+ htmlSize,
576
+ domElements,
577
+ ttfb: this.options.responseHeaders ? undefined : undefined,
578
+ totalTime: undefined,
579
+ wordCount: data.content.wordCount,
580
+ readingTime: data.content.readingTimeMinutes,
581
+ imageCount: data.imageAnalysis.total,
582
+ linkCount: data.linkAnalysis.total,
583
+ };
584
+ const completeness = {
585
+ meta: this.calculateMetaCompleteness(data.meta),
586
+ social: this.calculateSocialCompleteness(data.og, data.twitter),
587
+ technical: this.calculateTechnicalCompleteness(data.technical),
588
+ content: this.calculateContentCompleteness(data.content),
589
+ images: this.calculateImageCompleteness(data.imageAnalysis),
590
+ links: this.calculateLinkCompleteness(data.linkAnalysis),
591
+ };
592
+ return {
593
+ totalChecks,
594
+ passed,
595
+ warnings,
596
+ errors,
597
+ infos,
598
+ passRate,
599
+ issuesByCategory,
600
+ topIssues,
601
+ quickWins: limitedQuickWins,
602
+ vitals,
603
+ completeness,
604
+ };
605
+ }
606
+ calculateMetaCompleteness(meta) {
607
+ let score = 0;
608
+ const total = 5;
609
+ if (meta.title)
610
+ score++;
611
+ if (meta.description)
612
+ score++;
613
+ if (meta.canonical)
614
+ score++;
615
+ if (meta.viewport)
616
+ score++;
617
+ if (meta.charset)
618
+ score++;
619
+ return Math.round((score / total) * 100);
620
+ }
621
+ calculateSocialCompleteness(og, twitter) {
622
+ let score = 0;
623
+ const total = 8;
624
+ if (og.title)
625
+ score++;
626
+ if (og.description)
627
+ score++;
628
+ if (og.image)
629
+ score++;
630
+ if (og.url)
631
+ score++;
632
+ if (twitter.card)
633
+ score++;
634
+ if (twitter.title)
635
+ score++;
636
+ if (twitter.description)
637
+ score++;
638
+ if (twitter.image)
639
+ score++;
640
+ return Math.round((score / total) * 100);
641
+ }
642
+ calculateTechnicalCompleteness(technical) {
643
+ let score = 0;
644
+ const total = 5;
645
+ if (technical.hasCanonical)
646
+ score++;
647
+ if (technical.hasViewport)
648
+ score++;
649
+ if (technical.hasCharset)
650
+ score++;
651
+ if (technical.hasLang)
652
+ score++;
653
+ if (technical.hasRobotsMeta)
654
+ score++;
655
+ return Math.round((score / total) * 100);
656
+ }
657
+ calculateContentCompleteness(content) {
658
+ let score = 0;
659
+ const total = 5;
660
+ if (content.wordCount >= 300)
661
+ score++;
662
+ if (content.paragraphCount >= 3)
663
+ score++;
664
+ if (content.listCount > 0)
665
+ score++;
666
+ if (content.readingTimeMinutes >= 1)
667
+ score++;
668
+ if (content.strongTagCount > 0 || content.emTagCount > 0)
669
+ score++;
670
+ return Math.round((score / total) * 100);
671
+ }
672
+ calculateImageCompleteness(images) {
673
+ if (images.total === 0)
674
+ return 100;
675
+ let score = 0;
676
+ const total = 4;
677
+ if (images.withoutAlt === 0)
678
+ score++;
679
+ if (images.lazy > 0)
680
+ score++;
681
+ if (images.missingDimensions === 0)
682
+ score++;
683
+ if (images.modernFormats > 0)
684
+ score++;
685
+ return Math.round((score / total) * 100);
686
+ }
687
+ calculateLinkCompleteness(links) {
688
+ if (links.total === 0)
689
+ return 100;
690
+ let score = 0;
691
+ const total = 4;
692
+ if (links.internal > 0)
693
+ score++;
694
+ if (links.external > 0)
695
+ score++;
696
+ if (links.withoutText === 0)
697
+ score++;
698
+ if (links.broken === 0)
699
+ score++;
700
+ return Math.round((score / total) * 100);
701
+ }
506
702
  analyzeHeadings() {
507
703
  const issues = [];
508
704
  const structure = [];
@@ -0,0 +1,2 @@
1
+ import { SeoRule } from './types.js';
2
+ export declare const aiSearchRules: SeoRule[];