@ijonis/geo-lint 0.1.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/CHANGELOG.md +17 -0
- package/LICENSE +21 -0
- package/README.md +692 -0
- package/dist/cli.cjs +2716 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +2689 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +2691 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +267 -0
- package/dist/index.d.ts +267 -0
- package/dist/index.js +2646 -0
- package/dist/index.js.map +1 -0
- package/package.json +77 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../node_modules/tsup/assets/cjs_shims.js","../src/config/loader.ts","../src/config/defaults.ts","../src/adapters/mdx.ts","../src/utils/link-extractor.ts","../src/utils/slug-resolver.ts","../src/utils/display-path.ts","../src/rules/title-rules.ts","../src/rules/description-rules.ts","../src/rules/duplicate-rules.ts","../src/utils/heading-extractor.ts","../src/rules/heading-rules.ts","../src/utils/image-extractor.ts","../src/rules/image-rules.ts","../src/utils/word-counter.ts","../src/utils/readability.ts","../src/rules/content-rules.ts","../src/rules/og-rules.ts","../src/rules/performance-rules.ts","../src/rules/robots-rules.ts","../src/rules/slug-rules.ts","../src/rules/i18n-rules.ts","../src/rules/date-rules.ts","../src/rules/schema-rules.ts","../src/rules/keyword-coherence-rules.ts","../src/rules/link-rules.ts","../src/rules/external-link-rules.ts","../src/rules/orphan-rules.ts","../src/utils/geo-analyzer.ts","../src/rules/geo-rules.ts","../src/rules/category-rules.ts","../src/rules/canonical-rules.ts","../src/rules/index.ts","../src/reporter.ts","../src/adapters/types.ts"],"sourcesContent":["/**\n * @ijonis/geo-lint\n * SEO and GEO (Generative Engine Optimization) linter for Markdown/MDX content\n *\n * The first open-source linter that checks your content for AI search visibility.\n */\n\nimport { resolve } from 'node:path';\nimport { loadConfig, mergeWithDefaults, defineConfig } from './config/loader.js';\nimport type { GeoLintConfig, GeoLintUserConfig } from './config/types.js';\nimport { loadContentItems } from './adapters/mdx.js';\nimport type { ContentAdapter } from './adapters/types.js';\nimport { createLinkExtractor } from './utils/link-extractor.js';\nimport { buildSlugRegistry, buildImageRegistry } from './utils/slug-resolver.js';\nimport { buildRules, runAllRules } from './rules/index.js';\nimport { formatResults, formatResultsJson, printProgress } from './reporter.js';\nimport type { LintResult, Rule, ContentItem, RuleContext, GlobalRule } from './types.js';\n\n/** Options for the lint() function */\nexport interface LintOptions {\n /** Project root directory (defaults to cwd) */\n projectRoot?: string;\n /** Explicit config (skips file loading) */\n config?: GeoLintUserConfig;\n /** Custom content adapter (skips default MDX scanning) */\n adapter?: ContentAdapter;\n /** Global rules to run after per-item rules */\n globalRules?: GlobalRule[];\n /** Output format: 'pretty' for terminal, 'json' for machine consumption */\n format?: 'pretty' | 'json';\n}\n\n/**\n * Run the complete GEO lint process.\n * Returns exit code: 0 for success (no errors), 1 for errors found.\n */\nexport async function lint(options: LintOptions = {}): Promise<number> {\n const projectRoot = resolve(options.projectRoot ?? process.cwd());\n\n // Load config\n const config: GeoLintConfig = options.config\n ? mergeWithDefaults(options.config)\n : await loadConfig(projectRoot);\n\n const isPretty = (options.format ?? 'pretty') === 'pretty';\n\n if (isPretty) printProgress('Loading content...');\n\n // Load content items\n const contentItems = options.adapter\n ? await options.adapter.loadItems(projectRoot)\n : loadContentItems(config.contentPaths, projectRoot);\n\n // Separate excluded items (still included in context for link validation)\n const excludeSlugs = new Set(config.excludeSlugs);\n const excludeCategories = new Set(config.excludeCategories);\n\n const isExcluded = (item: ContentItem): boolean => {\n if (item.category && excludeCategories.has(item.category)) return true;\n return excludeSlugs.has(item.slug);\n };\n\n const excludedItems = contentItems.filter(isExcluded);\n const lintableItems = contentItems.filter(item => !isExcluded(item));\n\n if (isPretty) {\n printProgress(`Loaded ${contentItems.length} content files (${excludedItems.length} excluded)`);\n printProgress('Building validation context...');\n }\n\n // Build validation context with ALL items (excluded are still valid link targets)\n const linkExtractor = createLinkExtractor(config.siteUrl);\n const context: RuleContext = {\n allContent: contentItems,\n validSlugs: buildSlugRegistry(contentItems, config.staticRoutes, config.contentPaths),\n validImages: buildImageRegistry(config.imageDirectories),\n };\n\n if (isPretty) {\n printProgress(`Found ${context.validSlugs.size} valid URLs, ${context.validImages.size} images`);\n printProgress('Running validation rules...');\n }\n\n // Build and run rules\n const rules = buildRules(config, linkExtractor);\n const results: LintResult[] = runAllRules(lintableItems, context, rules);\n\n // Run global rules (user-provided, e.g., service registry checks)\n if (options.globalRules) {\n for (const globalRule of options.globalRules) {\n try {\n results.push(...globalRule.run());\n } catch (error) {\n if (isPretty) console.error(`Global rule ${globalRule.name} failed:`, error);\n }\n }\n }\n\n // Output results\n if (options.format === 'json') {\n console.log(formatResultsJson(results));\n } else {\n formatResults(results, lintableItems.length, excludedItems.length);\n }\n\n const errorCount = results.filter(r => r.severity === 'error').length;\n return errorCount > 0 ? 1 : 0;\n}\n\n/**\n * Run the linter and return raw results (no console output).\n * Useful for programmatic integration.\n */\nexport async function lintQuiet(options: LintOptions = {}): Promise<LintResult[]> {\n const projectRoot = resolve(options.projectRoot ?? process.cwd());\n\n const config: GeoLintConfig = options.config\n ? mergeWithDefaults(options.config)\n : await loadConfig(projectRoot);\n\n const contentItems = options.adapter\n ? await options.adapter.loadItems(projectRoot)\n : loadContentItems(config.contentPaths, projectRoot);\n\n const excludeSlugs = new Set(config.excludeSlugs);\n const excludeCategories = new Set(config.excludeCategories);\n\n const isExcluded = (item: ContentItem): boolean => {\n if (item.category && excludeCategories.has(item.category)) return true;\n return excludeSlugs.has(item.slug);\n };\n\n const lintableItems = contentItems.filter(item => !isExcluded(item));\n const linkExtractor = createLinkExtractor(config.siteUrl);\n\n const context: RuleContext = {\n allContent: contentItems,\n validSlugs: buildSlugRegistry(contentItems, config.staticRoutes, config.contentPaths),\n validImages: buildImageRegistry(config.imageDirectories),\n };\n\n const rules = buildRules(config, linkExtractor);\n return runAllRules(lintableItems, context, rules);\n}\n\n// Re-export public API\nexport { defineConfig } from './config/loader.js';\nexport type {\n GeoLintUserConfig,\n GeoLintConfig,\n GeoConfig,\n ContentPathConfig,\n ThresholdConfig,\n} from './config/types.js';\nexport type {\n LintResult,\n Rule,\n GlobalRule,\n ContentItem,\n RuleContext,\n ContentType,\n Severity,\n Heading,\n ExtractedLink,\n ExtractedImage,\n} from './types.js';\nexport type { ContentAdapter } from './adapters/types.js';\nexport { createAdapter } from './adapters/types.js';\nexport { loadContentItems } from './adapters/mdx.js';\n","// Shim globals in cjs bundle\n// There's a weird bug that esbuild will always inject importMetaUrl\n// if we export it as `const importMetaUrl = ... __filename ...`\n// But using a function will not cause this issue\n\nconst getImportMetaUrl = () => \n typeof document === \"undefined\" \n ? new URL(`file:${__filename}`).href \n : (document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT') \n ? document.currentScript.src \n : new URL(\"main.js\", document.baseURI).href;\n\nexport const importMetaUrl = /* @__PURE__ */ getImportMetaUrl()\n","/**\n * @ijonis/geo-lint Configuration Loader\n */\n\nimport { existsSync } from 'node:fs';\nimport { readFileSync } from 'node:fs';\nimport { join, resolve } from 'node:path';\nimport type { GeoLintUserConfig, GeoLintConfig } from './types.js';\nimport { DEFAULT_CONFIG } from './defaults.js';\n\nconst CONFIG_FILENAMES = [\n 'geo-lint.config.ts',\n 'geo-lint.config.mts',\n 'geo-lint.config.mjs',\n 'geo-lint.config.js',\n];\n\n/**\n * Helper for TypeScript-aware config files. Provides autocomplete in user configs.\n */\nexport function defineConfig(config: GeoLintUserConfig): GeoLintUserConfig {\n return config;\n}\n\n/**\n * Try to load a config file from the project root using jiti for TS support\n */\nasync function tryLoadConfigFile(projectRoot: string): Promise<GeoLintUserConfig | null> {\n for (const filename of CONFIG_FILENAMES) {\n const configPath = join(projectRoot, filename);\n if (!existsSync(configPath)) continue;\n\n try {\n // Use jiti for TypeScript config loading (works without tsx/ts-node)\n const { createJiti } = await import('jiti');\n const jiti = createJiti(import.meta.url);\n const mod = await jiti.import(configPath) as Record<string, unknown>;\n const config = (mod.default ?? mod) as GeoLintUserConfig;\n\n if (config && typeof config === 'object' && 'siteUrl' in config) {\n return config;\n }\n } catch {\n // Try next file\n }\n }\n return null;\n}\n\n/**\n * Try to load config from package.json geoLint key\n */\nfunction tryLoadPackageJsonConfig(projectRoot: string): GeoLintUserConfig | null {\n try {\n const pkgPath = join(projectRoot, 'package.json');\n if (!existsSync(pkgPath)) return null;\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));\n const config = pkg.geoLint;\n if (config && typeof config === 'object' && 'siteUrl' in config) {\n return config as GeoLintUserConfig;\n }\n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * Merge user config with defaults\n */\nexport function mergeWithDefaults(user: GeoLintUserConfig): GeoLintConfig {\n return {\n siteUrl: user.siteUrl,\n contentPaths: user.contentPaths ?? DEFAULT_CONFIG.contentPaths,\n staticRoutes: user.staticRoutes ?? DEFAULT_CONFIG.staticRoutes,\n imageDirectories: user.imageDirectories ?? DEFAULT_CONFIG.imageDirectories,\n categories: user.categories ?? DEFAULT_CONFIG.categories,\n excludeSlugs: user.excludeSlugs ?? DEFAULT_CONFIG.excludeSlugs,\n excludeCategories: user.excludeCategories ?? DEFAULT_CONFIG.excludeCategories,\n geo: {\n brandName: user.geo?.brandName ?? DEFAULT_CONFIG.geo.brandName,\n brandCity: user.geo?.brandCity ?? DEFAULT_CONFIG.geo.brandCity,\n keywordsPath: user.geo?.keywordsPath ?? DEFAULT_CONFIG.geo.keywordsPath,\n },\n rules: { ...DEFAULT_CONFIG.rules, ...(user.rules ?? {}) },\n thresholds: {\n title: { ...DEFAULT_CONFIG.thresholds.title, ...(user.thresholds?.title ?? {}) },\n description: { ...DEFAULT_CONFIG.thresholds.description, ...(user.thresholds?.description ?? {}) },\n slug: { ...DEFAULT_CONFIG.thresholds.slug, ...(user.thresholds?.slug ?? {}) },\n content: { ...DEFAULT_CONFIG.thresholds.content, ...(user.thresholds?.content ?? {}) },\n },\n };\n}\n\n/**\n * Load configuration from the project root.\n * Searches for geo-lint.config.{ts,mjs,js} or package.json#geoLint\n */\nexport async function loadConfig(projectRoot?: string): Promise<GeoLintConfig> {\n const root = resolve(projectRoot ?? process.cwd());\n\n const userConfig = await tryLoadConfigFile(root) ?? tryLoadPackageJsonConfig(root);\n\n if (!userConfig) {\n throw new Error(\n 'geo-lint: No configuration found.\\n' +\n 'Create a geo-lint.config.ts in your project root with at least:\\n\\n' +\n ' import { defineConfig } from \"@ijonis/geo-lint\";\\n' +\n ' export default defineConfig({ siteUrl: \"https://example.com\" });\\n\\n' +\n 'See https://github.com/ijonis/geo-lint#configuration'\n );\n }\n\n return mergeWithDefaults(userConfig);\n}\n","/**\n * @ijonis/geo-lint Default Configuration\n */\n\nimport type { GeoLintConfig } from './types.js';\n\n/** Default configuration values (no project-specific data) */\nexport const DEFAULT_CONFIG: Omit<GeoLintConfig, 'siteUrl'> = {\n contentPaths: [\n { dir: 'content/blog', type: 'blog', urlPrefix: '/blog/' },\n { dir: 'content/pages', type: 'page', urlPrefix: '/' },\n { dir: 'content/projects', type: 'project', urlPrefix: '/projects/' },\n ],\n staticRoutes: [],\n imageDirectories: ['public/images'],\n categories: [],\n excludeSlugs: [],\n excludeCategories: ['legal'],\n geo: {\n brandName: '',\n brandCity: '',\n keywordsPath: '',\n },\n rules: {},\n thresholds: {\n title: { minLength: 30, maxLength: 60, warnLength: 55 },\n description: { minLength: 70, maxLength: 160, warnLength: 150 },\n slug: { maxLength: 75 },\n content: { minWordCount: 300, minReadabilityScore: 30 },\n },\n};\n","/**\n * @ijonis/geo-lint MDX Content Adapter\n * Scans directories for MDX/Markdown files and parses them with gray-matter\n */\n\nimport { readdirSync, statSync, existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport matter from 'gray-matter';\nimport type { ContentItem, ContentType } from '../types.js';\nimport type { ContentPathConfig } from '../config/types.js';\n\n/**\n * Derive permalink from slug, locale, and URL prefix config\n */\nfunction derivePermalink(\n slug: string,\n locale: string | undefined,\n pathConfig: ContentPathConfig,\n): string {\n const prefix = pathConfig.urlPrefix ?? '/';\n\n // Pages use root-level URLs\n if (pathConfig.type === 'page') {\n return `/${slug}`.replace(/\\/\\/+/g, '/');\n }\n\n // Non-default locale gets locale prefix\n const defaultLocale = pathConfig.defaultLocale ?? 'de';\n if (locale && locale !== defaultLocale) {\n return `/${locale}${prefix}${slug}`.replace(/\\/\\/+/g, '/');\n }\n\n return `${prefix}${slug}`.replace(/\\/\\/+/g, '/');\n}\n\n/**\n * Recursively find all MDX/Markdown files in a directory\n */\nfunction findContentFiles(dir: string): string[] {\n const files: string[] = [];\n if (!existsSync(dir)) return files;\n\n for (const entry of readdirSync(dir)) {\n const fullPath = join(dir, entry);\n try {\n const stat = statSync(fullPath);\n if (stat.isDirectory()) {\n files.push(...findContentFiles(fullPath));\n } else if (entry.endsWith('.mdx') || entry.endsWith('.md')) {\n files.push(fullPath);\n }\n } catch {\n // Skip files that can't be read\n }\n }\n return files;\n}\n\n/**\n * Parse a single content file into a ContentItem\n */\nfunction parseContentFile(\n filePath: string,\n pathConfig: ContentPathConfig,\n): ContentItem | null {\n try {\n const { data: fm, content: body, orig } = matter.read(filePath);\n\n const slug = fm.slug as string | undefined;\n if (!slug) return null;\n\n // Skip drafts\n if (fm.draft === true) return null;\n\n const locale = fm.locale as string | undefined;\n const permalink =\n (fm.permalink as string | undefined) ??\n derivePermalink(slug, locale, pathConfig);\n\n return {\n title: (fm.title as string) ?? '',\n slug,\n description: (fm.description as string) ?? '',\n permalink,\n image: (fm.image as string | undefined) ?? (fm.thumbnail as string | undefined),\n imageAlt: fm.imageAlt as string | undefined,\n categories: fm.categories as string[] | undefined,\n date: fm.date ? String(fm.date) : undefined,\n category: fm.category as string | undefined,\n locale,\n translationKey: fm.translationKey as string | undefined,\n updatedAt: fm.updatedAt ? String(fm.updatedAt) : undefined,\n noindex: fm.noindex as boolean | undefined,\n draft: fm.draft as boolean | undefined,\n contentType: pathConfig.type as ContentType,\n filePath,\n rawContent: orig?.toString() ?? '',\n body,\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Load all content items from configured content paths.\n * Uses gray-matter for robust YAML frontmatter parsing.\n */\nexport function loadContentItems(\n contentPaths: ContentPathConfig[],\n projectRoot: string,\n): ContentItem[] {\n const items: ContentItem[] = [];\n\n for (const pathConfig of contentPaths) {\n const fullDir = join(projectRoot, pathConfig.dir);\n const files = findContentFiles(fullDir);\n\n for (const filePath of files) {\n const item = parseContentFile(filePath, pathConfig);\n if (item) items.push(item);\n }\n }\n\n return items;\n}\n","/**\n * Link Extractor Utility\n * Extracts and classifies links from MDX content\n */\n\nimport type { ExtractedLink } from '../types.js';\n\n/** Link extraction utilities bound to a specific site URL */\nexport interface LinkExtractor {\n isInternalUrl: (url: string) => boolean;\n normalizeInternalUrl: (url: string) => string;\n isAbsoluteInternalLink: (url: string) => boolean;\n extractLinks: (mdxBody: string) => ExtractedLink[];\n getInternalLinks: (links: ExtractedLink[]) => ExtractedLink[];\n}\n\n/**\n * Create a link extractor bound to a specific site URL.\n * All internal URL detection derives from the provided siteUrl.\n */\nexport function createLinkExtractor(siteUrl: string): LinkExtractor {\n const { hostname } = new URL(siteUrl);\n const escapedDomain = hostname.replace(/\\./g, '\\\\.');\n\n const internalPatterns = [\n new RegExp(`^https?:\\\\/\\\\/(www\\\\.)?${escapedDomain}(\\\\/|$)`, 'i'),\n new RegExp(`^\\\\/\\\\/${escapedDomain}(\\\\/|$)`, 'i'),\n /^\\/(?!\\/)/, // Relative paths starting with single /\n ];\n\n function isInternalUrl(url: string): boolean {\n return internalPatterns.some(pattern => pattern.test(url));\n }\n\n function normalizeInternalUrl(url: string): string {\n let normalized = url;\n normalized = normalized.replace(new RegExp(`^https?:\\\\/\\\\/(www\\\\.)?${escapedDomain}`, 'i'), '');\n normalized = normalized.replace(new RegExp(`^\\\\/\\\\/${escapedDomain}`, 'i'), '');\n if (!normalized.startsWith('/')) {\n normalized = '/' + normalized;\n }\n normalized = normalized.split('?')[0].split('#')[0];\n if (normalized.length > 1 && normalized.endsWith('/')) {\n normalized = normalized.slice(0, -1);\n }\n return normalized;\n }\n\n function isAbsoluteInternalLink(url: string): boolean {\n return new RegExp(`^https?:\\\\/\\\\/(www\\\\.)?${escapedDomain}`, 'i').test(url);\n }\n\n function isInCodeBlock(lines: string[], lineIndex: number): boolean {\n let inCodeBlock = false;\n for (let i = 0; i < lineIndex; i++) {\n const line = lines[i].trim();\n if (line.startsWith('```') || line.startsWith('~~~')) {\n inCodeBlock = !inCodeBlock;\n }\n }\n return inCodeBlock;\n }\n\n function extractLinks(mdxBody: string): ExtractedLink[] {\n const links: ExtractedLink[] = [];\n const lines = mdxBody.split('\\n');\n const markdownLinkRegex = /(?<!!)\\[([^\\]]*)\\]\\(([^)]+)\\)/g;\n const hrefRegex = /href=[\"']([^\"']+)[\"']/g;\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n if (isInCodeBlock(lines, i)) continue;\n\n let match;\n while ((match = markdownLinkRegex.exec(line)) !== null) {\n const [, text, url] = match;\n if (url.startsWith('mailto:') || url.startsWith('tel:') || url === '#') continue;\n const internal = isInternalUrl(url);\n links.push({\n text: text.trim(),\n url: internal ? normalizeInternalUrl(url) : url,\n originalUrl: url,\n line: i + 1,\n isInternal: internal,\n });\n }\n markdownLinkRegex.lastIndex = 0;\n\n while ((match = hrefRegex.exec(line)) !== null) {\n const [, url] = match;\n if (url.startsWith('mailto:') || url.startsWith('tel:') || url === '#') continue;\n const normalized = isInternalUrl(url) ? normalizeInternalUrl(url) : url;\n const alreadyCaptured = links.some(\n l => l.line === i + 1 && (l.url === normalized || l.originalUrl === url)\n );\n if (!alreadyCaptured) {\n const internal = isInternalUrl(url);\n links.push({\n text: '',\n url: internal ? normalizeInternalUrl(url) : url,\n originalUrl: url,\n line: i + 1,\n isInternal: internal,\n });\n }\n }\n hrefRegex.lastIndex = 0;\n }\n return links;\n }\n\n function getInternalLinks(links: ExtractedLink[]): ExtractedLink[] {\n return links.filter(l => l.isInternal);\n }\n\n return { isInternalUrl, normalizeInternalUrl, isAbsoluteInternalLink, extractLinks, getInternalLinks };\n}\n","/**\n * Slug Resolver Utility\n * Builds a registry of valid internal URLs for link validation\n */\n\nimport { readdirSync, readFileSync, statSync, existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { ContentItem } from '../types.js';\nimport type { ContentPathConfig } from '../config/types.js';\n\n/**\n * Extract frontmatter slug and locale from a raw MDX file\n * Returns null if the file has no valid frontmatter\n */\nfunction extractSlugFromFile(filePath: string): { slug: string; locale: string } | null {\n try {\n const content = readFileSync(filePath, 'utf-8');\n if (!content.startsWith('---')) return null;\n\n const endIndex = content.indexOf('---', 3);\n if (endIndex === -1) return null;\n\n const frontmatter = content.slice(3, endIndex);\n\n const slugMatch = frontmatter.match(/^slug:\\s*[\"']?([^\"'\\n]+)[\"']?\\s*$/m);\n if (!slugMatch) return null;\n\n const localeMatch = frontmatter.match(/^locale:\\s*[\"']?([^\"'\\n]+)[\"']?\\s*$/m);\n const draftMatch = frontmatter.match(/^draft:\\s*true\\s*$/m);\n\n if (draftMatch) return null;\n\n return {\n slug: slugMatch[1].trim(),\n locale: localeMatch ? localeMatch[1].trim() : 'de',\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Find all files with a given extension in a directory (recursive)\n */\nfunction findFilesInDir(dir: string, ext: string): string[] {\n const results: string[] = [];\n try {\n for (const entry of readdirSync(dir)) {\n const fullPath = join(dir, entry);\n const stat = statSync(fullPath);\n if (stat.isDirectory()) {\n results.push(...findFilesInDir(fullPath, ext));\n } else if (entry.endsWith(ext)) {\n results.push(fullPath);\n }\n }\n } catch {\n // Directory doesn't exist or can't be read\n }\n return results;\n}\n\n/**\n * Scan raw MDX files to find permalinks not covered by content adapter output.\n * Handles cases where the content adapter fails to process certain files.\n *\n * @param knownSlugs - Set of already-known permalink slugs\n * @param contentPaths - Content directory configurations to scan\n */\nexport function scanRawContentPermalinks(\n knownSlugs: Set<string>,\n contentPaths: ContentPathConfig[],\n): string[] {\n const projectRoot = process.cwd();\n const additionalPermalinks: string[] = [];\n\n for (const pathConfig of contentPaths) {\n const contentDir = join(projectRoot, pathConfig.dir);\n if (!existsSync(contentDir)) continue;\n\n const urlPrefix = pathConfig.urlPrefix ?? '/';\n\n for (const file of findFilesInDir(contentDir, '.mdx')) {\n const meta = extractSlugFromFile(file);\n if (!meta) continue;\n\n // Build permalink based on locale and URL prefix\n let permalink: string;\n const defaultLocale = pathConfig.defaultLocale ?? 'de';\n\n if (meta.locale !== defaultLocale && meta.locale !== 'de') {\n // Non-default locale: prefix with locale\n permalink = `/${meta.locale}${urlPrefix}${meta.slug}`.replace(/\\/+/g, '/');\n } else {\n permalink = `${urlPrefix}${meta.slug}`.replace(/\\/+/g, '/');\n }\n\n // Ensure leading slash\n if (!permalink.startsWith('/')) {\n permalink = '/' + permalink;\n }\n\n // Remove trailing slash (except root)\n if (permalink.length > 1 && permalink.endsWith('/')) {\n permalink = permalink.slice(0, -1);\n }\n\n if (!knownSlugs.has(permalink)) {\n additionalPermalinks.push(permalink);\n }\n }\n }\n\n return additionalPermalinks;\n}\n\n/**\n * Build a set of all valid internal URL paths.\n * Combines content permalinks, static routes, and raw MDX file scan.\n *\n * @param allContent - Content items from the content adapter\n * @param staticRoutes - Array of static route paths (e.g. ['/about', '/contact'])\n * @param contentPaths - Content directory configurations for raw MDX fallback scan\n */\nexport function buildSlugRegistry(\n allContent: ContentItem[],\n staticRoutes: string[],\n contentPaths: ContentPathConfig[],\n): Set<string> {\n const slugs = new Set<string>();\n\n // Add static routes\n for (const route of staticRoutes) {\n slugs.add(route);\n }\n\n // Add content permalinks from adapter output\n for (const item of allContent) {\n slugs.add(item.permalink);\n\n // Also add without trailing slash if present\n if (item.permalink.endsWith('/') && item.permalink.length > 1) {\n slugs.add(item.permalink.slice(0, -1));\n }\n }\n\n // Scan raw MDX files for any permalinks the adapter missed\n const additional = scanRawContentPermalinks(slugs, contentPaths);\n for (const permalink of additional) {\n slugs.add(permalink);\n }\n\n return slugs;\n}\n\n/**\n * Check if a normalized internal URL is valid\n */\nexport function isValidInternalLink(url: string, validSlugs: Set<string>): boolean {\n // Direct match\n if (validSlugs.has(url)) {\n return true;\n }\n\n // Try with trailing slash\n if (validSlugs.has(url + '/')) {\n return true;\n }\n\n // Try without trailing slash\n if (url.endsWith('/') && validSlugs.has(url.slice(0, -1))) {\n return true;\n }\n\n return false;\n}\n\n/**\n * Recursively scan directory for files\n */\nfunction scanDirectory(dir: string): string[] {\n const files: string[] = [];\n\n try {\n const entries = readdirSync(dir);\n\n for (const entry of entries) {\n const fullPath = join(dir, entry);\n const stat = statSync(fullPath);\n\n if (stat.isDirectory()) {\n files.push(...scanDirectory(fullPath));\n } else {\n files.push(fullPath);\n }\n }\n } catch {\n // Directory doesn't exist or can't be read\n }\n\n return files;\n}\n\n/**\n * Build a set of all valid image paths.\n * Scans the provided image directories.\n *\n * @param imageDirectories - Array of image directory paths relative to project root\n * (e.g. ['public/images'])\n */\nexport function buildImageRegistry(imageDirectories: string[]): Set<string> {\n const images = new Set<string>();\n const projectRoot = process.cwd();\n\n for (const imageDir of imageDirectories) {\n const fullDir = join(projectRoot, imageDir);\n\n if (!existsSync(fullDir)) {\n continue;\n }\n\n const files = scanDirectory(fullDir);\n\n for (const file of files) {\n // Get path relative to public directory\n const relativePath = file.replace(join(projectRoot, 'public'), '');\n // Normalize to forward slashes and ensure leading slash\n const normalizedPath = relativePath.replace(/\\\\/g, '/');\n images.add(normalizedPath.startsWith('/') ? normalizedPath : '/' + normalizedPath);\n }\n }\n\n return images;\n}\n\n/**\n * Check if an image path exists\n */\nexport function isValidImagePath(imagePath: string, validImages: Set<string>): boolean {\n // Normalize the path\n let normalized = imagePath;\n\n // Handle paths without leading slash\n if (!normalized.startsWith('/')) {\n normalized = '/' + normalized;\n }\n\n // Direct match\n if (validImages.has(normalized)) {\n return true;\n }\n\n // Try URL-decoded version\n try {\n const decoded = decodeURIComponent(normalized);\n if (validImages.has(decoded)) {\n return true;\n }\n } catch {\n // Invalid URL encoding, ignore\n }\n\n return false;\n}\n","import type { ContentItem } from '../types.js';\n\n/** Get the relative file path for display in lint results */\nexport function getDisplayPath(item: ContentItem): string {\n return `${item.contentType}/${item.slug}`;\n}\n","/**\n * Title Validation Rules\n * Validates title presence and length\n */\n\nimport type { Rule, ContentItem, LintResult } from '../types.js';\nimport { getDisplayPath } from '../utils/display-path.js';\n\n/** Default title length thresholds */\nconst TITLE_MIN_LENGTH = 30;\nconst TITLE_MAX_LENGTH = 60;\nconst TITLE_WARN_LENGTH = 55;\n\n/**\n * Rule: Title must be present\n */\nexport const titleMissing: Rule = {\n name: 'title-missing',\n severity: 'error',\n category: 'seo',\n fixStrategy: 'Add a title field to the frontmatter',\n run: (item: ContentItem): LintResult[] => {\n if (!item.title || item.title.trim().length === 0) {\n return [{\n file: getDisplayPath(item),\n field: 'title',\n rule: 'title-missing',\n severity: 'error',\n message: 'Missing title',\n suggestion: 'Add a title field to the frontmatter',\n }];\n }\n return [];\n },\n};\n\n/**\n * Rule: Title should not be too short\n */\nexport const titleTooShort: Rule = {\n name: 'title-too-short',\n severity: 'warning',\n category: 'seo',\n fixStrategy: 'Expand the title to meet minimum length',\n run: (item: ContentItem): LintResult[] => {\n if (!item.title) return [];\n\n const length = item.title.length;\n if (length > 0 && length < TITLE_MIN_LENGTH) {\n return [{\n file: getDisplayPath(item),\n field: 'title',\n rule: 'title-too-short',\n severity: 'warning',\n message: `Title is too short (${length}/${TITLE_MIN_LENGTH} chars minimum)`,\n suggestion: `Expand the title to at least ${TITLE_MIN_LENGTH} characters for better SEO`,\n }];\n }\n return [];\n },\n};\n\n/**\n * Rule: Title must not exceed maximum length\n */\nexport const titleTooLong: Rule = {\n name: 'title-too-long',\n severity: 'error',\n category: 'seo',\n fixStrategy: 'Shorten the title to avoid truncation in search results',\n run: (item: ContentItem): LintResult[] => {\n if (!item.title) return [];\n\n const length = item.title.length;\n if (length > TITLE_MAX_LENGTH) {\n return [{\n file: getDisplayPath(item),\n field: 'title',\n rule: 'title-too-long',\n severity: 'error',\n message: `Title is too long (${length}/${TITLE_MAX_LENGTH} chars)`,\n suggestion: `Shorten the title to ${TITLE_MAX_LENGTH} characters or less to avoid truncation in search results`,\n }];\n }\n return [];\n },\n};\n\n/**\n * Rule: Warn when title is approaching maximum length\n */\nexport const titleApproachingLimit: Rule = {\n name: 'title-approaching-limit',\n severity: 'warning',\n category: 'seo',\n fixStrategy: 'Consider shortening to leave room for site name suffix',\n run: (item: ContentItem): LintResult[] => {\n if (!item.title) return [];\n\n const length = item.title.length;\n // Only warn if it's close to but not over the limit\n if (length > TITLE_WARN_LENGTH && length <= TITLE_MAX_LENGTH) {\n return [{\n file: getDisplayPath(item),\n field: 'title',\n rule: 'title-approaching-limit',\n severity: 'warning',\n message: `Title is approaching maximum length (${length}/${TITLE_MAX_LENGTH} chars)`,\n suggestion: 'Consider shortening to leave room for site name suffix in search results',\n }];\n }\n return [];\n },\n};\n\n/**\n * All title rules\n */\nexport const titleRules: Rule[] = [\n titleMissing,\n titleTooShort,\n titleTooLong,\n titleApproachingLimit,\n];\n","/**\n * Description Validation Rules\n * Validates meta description presence and length\n */\n\nimport type { Rule, ContentItem, LintResult } from '../types.js';\nimport { getDisplayPath } from '../utils/display-path.js';\n\n/** Default description length thresholds */\nconst DESC_MIN_LENGTH = 70;\nconst DESC_MAX_LENGTH = 160;\nconst DESC_WARN_LENGTH = 150;\n\n/**\n * Rule: Description must be present\n */\nexport const descriptionMissing: Rule = {\n name: 'description-missing',\n severity: 'error',\n category: 'seo',\n fixStrategy: 'Add a description field to the frontmatter (max 160 chars)',\n run: (item: ContentItem): LintResult[] => {\n if (!item.description || item.description.trim().length === 0) {\n return [{\n file: getDisplayPath(item),\n field: 'description',\n rule: 'description-missing',\n severity: 'error',\n message: 'Missing description',\n suggestion: 'Add a description field to the frontmatter (max 160 chars)',\n }];\n }\n return [];\n },\n};\n\n/**\n * Rule: Description must not exceed maximum length\n */\nexport const descriptionTooLong: Rule = {\n name: 'description-too-long',\n severity: 'error',\n category: 'seo',\n fixStrategy: 'Shorten the description to avoid truncation in search results',\n run: (item: ContentItem): LintResult[] => {\n if (!item.description) return [];\n\n const length = item.description.length;\n if (length > DESC_MAX_LENGTH) {\n return [{\n file: getDisplayPath(item),\n field: 'description',\n rule: 'description-too-long',\n severity: 'error',\n message: `Description is too long (${length}/${DESC_MAX_LENGTH} chars)`,\n suggestion: `Shorten to ${DESC_MAX_LENGTH} characters to avoid truncation in search results`,\n }];\n }\n return [];\n },\n};\n\n/**\n * Rule: Warn when description is approaching maximum length\n */\nexport const descriptionApproachingLimit: Rule = {\n name: 'description-approaching-limit',\n severity: 'warning',\n category: 'seo',\n fixStrategy: 'Consider shortening to ensure full display in search results',\n run: (item: ContentItem): LintResult[] => {\n if (!item.description) return [];\n\n const length = item.description.length;\n // Only warn if it's close to but not over the limit\n if (length > DESC_WARN_LENGTH && length <= DESC_MAX_LENGTH) {\n return [{\n file: getDisplayPath(item),\n field: 'description',\n rule: 'description-approaching-limit',\n severity: 'warning',\n message: `Description is approaching maximum length (${length}/${DESC_MAX_LENGTH} chars)`,\n suggestion: 'Consider shortening to ensure full display in search results',\n }];\n }\n return [];\n },\n};\n\n/**\n * Rule: Description should meet minimum length for effective SEO\n */\nexport const descriptionTooShort: Rule = {\n name: 'description-too-short',\n severity: 'warning',\n category: 'seo',\n fixStrategy: 'Expand the description for better search result snippets',\n run: (item: ContentItem): LintResult[] => {\n // Skip if description is missing entirely (caught by description-missing)\n if (!item.description || item.description.trim().length === 0) return [];\n\n const length = item.description.length;\n if (length < DESC_MIN_LENGTH) {\n return [{\n file: getDisplayPath(item),\n field: 'description',\n rule: 'description-too-short',\n severity: 'warning',\n message: `Description is too short (${length}/${DESC_MIN_LENGTH} chars minimum)`,\n suggestion: `Expand the description to at least ${DESC_MIN_LENGTH} characters for better search result snippets`,\n }];\n }\n return [];\n },\n};\n\n/**\n * All description rules\n */\nexport const descriptionRules: Rule[] = [\n descriptionMissing,\n descriptionTooLong,\n descriptionApproachingLimit,\n descriptionTooShort,\n];\n","/**\n * Duplicate Detection Rules\n * Validates that titles and descriptions are unique across all content\n */\n\nimport type { Rule, ContentItem, RuleContext, LintResult } from '../types.js';\nimport { getDisplayPath } from '../utils/display-path.js';\n\n/**\n * Build a map of value -> list of files using that value\n */\nfunction buildDuplicateMap(\n items: ContentItem[],\n getValue: (item: ContentItem) => string | undefined\n): Map<string, string[]> {\n const map = new Map<string, string[]>();\n\n for (const item of items) {\n const value = getValue(item);\n if (!value || value.trim().length === 0) continue;\n\n // Normalize for comparison (lowercase, trim)\n const normalized = value.toLowerCase().trim();\n const files = map.get(normalized) || [];\n files.push(getDisplayPath(item));\n map.set(normalized, files);\n }\n\n return map;\n}\n\n/**\n * Rule: Titles must be unique across all content\n */\nexport const duplicateTitle: Rule = {\n name: 'duplicate-title',\n severity: 'error',\n category: 'seo',\n fixStrategy: 'Use a unique title that differentiates this content from others',\n run: (item: ContentItem, context: RuleContext): LintResult[] => {\n const titleMap = buildDuplicateMap(context.allContent, c => c.title);\n const normalizedTitle = item.title?.toLowerCase().trim();\n\n if (!normalizedTitle) return [];\n\n const filesWithSameTitle = titleMap.get(normalizedTitle);\n if (!filesWithSameTitle || filesWithSameTitle.length <= 1) return [];\n\n // Filter out the current file\n const otherFiles = filesWithSameTitle.filter(f => f !== getDisplayPath(item));\n if (otherFiles.length === 0) return [];\n\n return [{\n file: getDisplayPath(item),\n field: 'title',\n rule: 'duplicate-title',\n severity: 'error',\n message: `Duplicate title found`,\n suggestion: `Also used by: ${otherFiles.join(', ')}`,\n }];\n },\n};\n\n/**\n * Rule: Descriptions must be unique across all content\n */\nexport const duplicateDescription: Rule = {\n name: 'duplicate-description',\n severity: 'error',\n category: 'seo',\n fixStrategy: 'Write a unique description that differentiates this content',\n run: (item: ContentItem, context: RuleContext): LintResult[] => {\n const descMap = buildDuplicateMap(context.allContent, c => c.description);\n const normalizedDesc = item.description?.toLowerCase().trim();\n\n if (!normalizedDesc) return [];\n\n const filesWithSameDesc = descMap.get(normalizedDesc);\n if (!filesWithSameDesc || filesWithSameDesc.length <= 1) return [];\n\n // Filter out the current file\n const otherFiles = filesWithSameDesc.filter(f => f !== getDisplayPath(item));\n if (otherFiles.length === 0) return [];\n\n return [{\n file: getDisplayPath(item),\n field: 'description',\n rule: 'duplicate-description',\n severity: 'error',\n message: `Duplicate description found`,\n suggestion: `Also used by: ${otherFiles.join(', ')}`,\n }];\n },\n};\n\n/**\n * All duplicate rules\n */\nexport const duplicateRules: Rule[] = [\n duplicateTitle,\n duplicateDescription,\n];\n","/**\n * Heading Extractor Utility\n * Extracts and analyzes heading structure from MDX content\n */\n\nimport type { Heading } from '../types.js';\n\n/**\n * Check if a line is inside a code block\n * Tracks ``` and ~~~ fenced code blocks\n */\nfunction isInCodeBlock(lines: string[], lineIndex: number): boolean {\n let inCodeBlock = false;\n\n for (let i = 0; i < lineIndex; i++) {\n const line = lines[i].trim();\n if (line.startsWith('```') || line.startsWith('~~~')) {\n inCodeBlock = !inCodeBlock;\n }\n }\n\n return inCodeBlock;\n}\n\n/**\n * Extract all headings from MDX body content\n * Handles markdown-style headings (# H1, ## H2, etc.)\n */\nexport function extractHeadings(mdxBody: string): Heading[] {\n const headings: Heading[] = [];\n const lines = mdxBody.split('\\n');\n\n // Regex for markdown headings: # to ######\n const headingRegex = /^(#{1,6})\\s+(.+)$/;\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n const match = line.match(headingRegex);\n\n if (match && !isInCodeBlock(lines, i)) {\n const [, hashes, text] = match;\n headings.push({\n level: hashes.length,\n text: text.trim(),\n line: i + 1, // 1-indexed for display\n });\n }\n }\n\n return headings;\n}\n\n/**\n * Count H1 headings in the content\n */\nexport function countH1s(headings: Heading[]): number {\n return headings.filter(h => h.level === 1).length;\n}\n\n/**\n * Find H1 headings (for error reporting)\n */\nexport function findH1s(headings: Heading[]): Heading[] {\n return headings.filter(h => h.level === 1);\n}\n\n/**\n * Check for heading hierarchy violations\n * A violation occurs when a heading level is skipped (e.g., H1 -> H3 without H2)\n */\nexport function findHierarchyViolations(headings: Heading[]): Array<{\n heading: Heading;\n previousLevel: number;\n expectedMaxLevel: number;\n}> {\n const violations: Array<{\n heading: Heading;\n previousLevel: number;\n expectedMaxLevel: number;\n }> = [];\n\n let previousLevel = 0;\n\n for (const heading of headings) {\n // A heading should be at most 1 level deeper than previous\n // e.g., H1 can be followed by H1 or H2, not H3+\n if (heading.level > previousLevel + 1 && previousLevel > 0) {\n violations.push({\n heading,\n previousLevel,\n expectedMaxLevel: previousLevel + 1,\n });\n }\n\n previousLevel = heading.level;\n }\n\n return violations;\n}\n\n/**\n * Get a summary of heading structure for debugging\n */\nexport function getHeadingSummary(headings: Heading[]): string {\n if (headings.length === 0) {\n return 'No headings found';\n }\n\n const counts: Record<number, number> = {};\n for (const h of headings) {\n counts[h.level] = (counts[h.level] || 0) + 1;\n }\n\n return Object.entries(counts)\n .sort(([a], [b]) => Number(a) - Number(b))\n .map(([level, count]) => `H${level}: ${count}`)\n .join(', ');\n}\n","/**\n * Heading Structure Rules\n * Validates H1 presence, count, and heading hierarchy\n */\n\nimport type { Rule, ContentItem, LintResult } from '../types.js';\nimport { getDisplayPath } from '../utils/display-path.js';\nimport {\n extractHeadings,\n countH1s,\n findH1s,\n findHierarchyViolations,\n} from '../utils/heading-extractor.js';\n\n/**\n * Rule: Content must have exactly one H1 heading\n * Exceptions:\n * - Pages with category 'legal' or 'service' use ServicePageHeader (H1 via title prop)\n * - Blog posts use BlogHeader component which renders the H1 from frontmatter title\n */\nexport const missingH1: Rule = {\n name: 'missing-h1',\n severity: 'warning',\n category: 'seo',\n fixStrategy: 'Add an H1 heading (# Heading) at the start of the content',\n run: (item: ContentItem): LintResult[] => {\n // Skip H1 check for blog posts — BlogHeader renders the H1 from frontmatter title\n if (item.contentType === 'blog') {\n return [];\n }\n\n // Skip H1 check for pages that use ServicePageHeader component\n // These pages get their H1 from the title prop in the header\n if (item.category === 'legal' || item.category === 'service') {\n return [];\n }\n\n const headings = extractHeadings(item.body);\n const h1Count = countH1s(headings);\n\n if (h1Count === 0) {\n return [{\n file: getDisplayPath(item),\n field: 'body',\n rule: 'missing-h1',\n severity: 'warning',\n message: 'No H1 heading found in content',\n suggestion: 'Add an H1 heading (# Heading) at the start of your content',\n }];\n }\n return [];\n },\n};\n\n/**\n * Rule: Content must not have multiple H1 headings\n * Blog posts are exempt — BlogHeader provides the page H1,\n * and MDX # headings are rendered as <h2> via mdx-components.tsx.\n */\nexport const multipleH1: Rule = {\n name: 'multiple-h1',\n severity: 'error',\n category: 'seo',\n fixStrategy: 'Convert extra H1s to H2s',\n run: (item: ContentItem): LintResult[] => {\n if (item.contentType === 'blog') {\n return [];\n }\n\n const headings = extractHeadings(item.body);\n const h1s = findH1s(headings);\n\n if (h1s.length > 1) {\n const locations = h1s.map(h => `line ${h.line}`).join(', ');\n return [{\n file: getDisplayPath(item),\n field: 'body',\n rule: 'multiple-h1',\n severity: 'error',\n message: `Found ${h1s.length} H1 headings (expected 1)`,\n suggestion: `H1 headings at: ${locations}. Convert extra H1s to H2s.`,\n }];\n }\n return [];\n },\n};\n\n/**\n * Rule: Heading hierarchy should not skip levels\n * e.g., H1 -> H3 without H2 is a violation\n */\nexport const headingHierarchySkip: Rule = {\n name: 'heading-hierarchy-skip',\n severity: 'warning',\n category: 'seo',\n fixStrategy: 'Adjust heading levels to avoid skipping (e.g., H2 before H3)',\n run: (item: ContentItem): LintResult[] => {\n const headings = extractHeadings(item.body);\n const violations = findHierarchyViolations(headings);\n\n return violations.map(v => ({\n file: getDisplayPath(item),\n field: 'body',\n rule: 'heading-hierarchy-skip',\n severity: 'warning' as const,\n message: `Heading level skipped: H${v.previousLevel} → H${v.heading.level}`,\n suggestion: `At line ${v.heading.line}: \"${v.heading.text}\". Expected H${v.expectedMaxLevel} or lower.`,\n line: v.heading.line,\n }));\n },\n};\n\n/**\n * Rule: Heading text should not be duplicated within the same page\n * Duplicate headings confuse readers and dilute SEO signal\n */\nexport const duplicateHeadingText: Rule = {\n name: 'duplicate-heading-text',\n severity: 'warning',\n category: 'seo',\n fixStrategy: 'Use unique heading text for each section',\n run: (item: ContentItem): LintResult[] => {\n const results: LintResult[] = [];\n const headings = extractHeadings(item.body);\n\n const seen = new Map<string, number>();\n\n for (const heading of headings) {\n const normalized = heading.text.toLowerCase();\n const previousLine = seen.get(normalized);\n\n if (previousLine !== undefined) {\n results.push({\n file: getDisplayPath(item),\n field: 'body',\n rule: 'duplicate-heading-text',\n severity: 'warning',\n message: `Duplicate heading text: \"${heading.text}\"`,\n suggestion: `Same heading appears at line ${previousLine} and line ${heading.line}. Use unique headings for better structure.`,\n line: heading.line,\n });\n } else {\n seen.set(normalized, heading.line);\n }\n }\n\n return results;\n },\n};\n\n/**\n * All heading rules\n */\nexport const headingRules: Rule[] = [\n missingH1,\n multipleH1,\n headingHierarchySkip,\n duplicateHeadingText,\n];\n","/**\n * Image Extractor Utility\n * Extracts images and their alt text from MDX content\n */\n\nimport type { ExtractedImage } from '../types.js';\n\n/**\n * Check if a line is inside a code block\n */\nfunction isInCodeBlock(lines: string[], lineIndex: number): boolean {\n let inCodeBlock = false;\n\n for (let i = 0; i < lineIndex; i++) {\n const line = lines[i].trim();\n if (line.startsWith('```') || line.startsWith('~~~')) {\n inCodeBlock = !inCodeBlock;\n }\n }\n\n return inCodeBlock;\n}\n\n/**\n * Extract all images from MDX body content\n * Handles markdown images  and JSX/HTML img tags\n */\nexport function extractImages(mdxBody: string): ExtractedImage[] {\n const images: ExtractedImage[] = [];\n const lines = mdxBody.split('\\n');\n\n // Regex patterns\n // Markdown:  or \n const markdownImageRegex = /!\\[([^\\]]*)\\]\\(([^)\\s]+)(?:\\s+\"[^\"]*\")?\\)/g;\n\n // JSX/HTML: <img src=\"...\" alt=\"...\" /> or <Image src=\"...\" alt=\"...\" />\n const jsxImageRegex = /<(?:img|Image)\\s+[^>]*src=[\"']([^\"']+)[\"'][^>]*>/gi;\n const altAttrRegex = /alt=[\"']([^\"']*)[\"']/i;\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n\n // Skip lines inside code blocks\n if (isInCodeBlock(lines, i)) {\n continue;\n }\n\n // Find markdown images: \n let match;\n while ((match = markdownImageRegex.exec(line)) !== null) {\n const [, alt, src] = match;\n\n images.push({\n src,\n alt: alt.trim(),\n line: i + 1,\n hasAlt: alt.trim().length > 0,\n source: 'inline',\n });\n }\n\n // Reset regex lastIndex for reuse\n markdownImageRegex.lastIndex = 0;\n\n // Find JSX/HTML images: <img src=\"...\" /> or <Image src=\"...\" />\n while ((match = jsxImageRegex.exec(line)) !== null) {\n const [fullMatch, src] = match;\n\n // Extract alt attribute if present\n const altMatch = fullMatch.match(altAttrRegex);\n const alt = altMatch ? altMatch[1] : '';\n\n // Skip if this src was already captured by markdown regex\n const alreadyCaptured = images.some(\n img => img.line === i + 1 && img.src === src\n );\n\n if (!alreadyCaptured) {\n images.push({\n src,\n alt: alt.trim(),\n line: i + 1,\n hasAlt: alt.trim().length > 0,\n source: 'inline',\n });\n }\n }\n\n // Reset regex lastIndex for reuse\n jsxImageRegex.lastIndex = 0;\n }\n\n return images;\n}\n\n/**\n * Get images missing alt text\n */\nexport function getImagesMissingAlt(images: ExtractedImage[]): ExtractedImage[] {\n return images.filter(img => !img.hasAlt);\n}\n\n/**\n * Create an ExtractedImage from frontmatter data\n */\nexport function createFrontmatterImage(\n imagePath: string | undefined,\n imageAlt: string | undefined\n): ExtractedImage | null {\n if (!imagePath) {\n return null;\n }\n\n return {\n src: imagePath,\n alt: imageAlt || '',\n line: 0, // Frontmatter doesn't have a specific line\n hasAlt: !!imageAlt && imageAlt.trim().length > 0,\n source: 'frontmatter',\n };\n}\n\n/**\n * Normalize image path for comparison\n */\nexport function normalizeImagePath(src: string): string {\n let normalized = src;\n\n // Handle relative paths\n if (!normalized.startsWith('/') && !normalized.startsWith('http')) {\n normalized = '/' + normalized;\n }\n\n // Remove query strings\n normalized = normalized.split('?')[0];\n\n return normalized;\n}\n","/**\n * Image Validation Rules\n * Validates image alt text presence and image file existence\n */\n\nimport type { Rule, ContentItem, RuleContext, LintResult } from '../types.js';\nimport { getDisplayPath } from '../utils/display-path.js';\nimport {\n extractImages,\n createFrontmatterImage,\n normalizeImagePath,\n} from '../utils/image-extractor.js';\nimport { isValidImagePath } from '../utils/slug-resolver.js';\n\n/**\n * Rule: Inline images must have alt text\n */\nexport const inlineImageMissingAlt: Rule = {\n name: 'inline-image-missing-alt',\n severity: 'error',\n category: 'seo',\n fixStrategy: 'Add descriptive alt text for accessibility and SEO',\n run: (item: ContentItem): LintResult[] => {\n const results: LintResult[] = [];\n const images = extractImages(item.body);\n\n for (const img of images) {\n if (!img.hasAlt) {\n results.push({\n file: getDisplayPath(item),\n field: 'body',\n rule: 'inline-image-missing-alt',\n severity: 'error',\n message: `Image missing alt text: ${img.src}`,\n suggestion: 'Add descriptive alt text for accessibility and SEO',\n line: img.line,\n });\n }\n }\n\n return results;\n },\n};\n\n/**\n * Rule: Featured image in frontmatter should have alt text\n */\nexport const frontmatterImageMissingAlt: Rule = {\n name: 'frontmatter-image-missing-alt',\n severity: 'warning',\n category: 'seo',\n fixStrategy: 'Add an imageAlt field to the frontmatter for the featured image',\n run: (item: ContentItem): LintResult[] => {\n // Only check if there's an image but no imageAlt\n if (item.image && (!item.imageAlt || item.imageAlt.trim().length === 0)) {\n return [{\n file: getDisplayPath(item),\n field: 'imageAlt',\n rule: 'frontmatter-image-missing-alt',\n severity: 'warning',\n message: 'Featured image missing alt text',\n suggestion: 'Add an imageAlt field to the frontmatter for the featured image',\n }];\n }\n return [];\n },\n};\n\n/**\n * Rule: Referenced images should exist in public directory\n */\nexport const imageNotFound: Rule = {\n name: 'image-not-found',\n severity: 'warning',\n category: 'technical',\n fixStrategy: 'Check that the image path is correct and the file exists in public/',\n run: (item: ContentItem, context: RuleContext): LintResult[] => {\n const results: LintResult[] = [];\n\n // Check frontmatter image\n if (item.image) {\n const normalizedPath = normalizeImagePath(item.image);\n // Only check local images, not external URLs\n if (!item.image.startsWith('http') && !isValidImagePath(normalizedPath, context.validImages)) {\n results.push({\n file: getDisplayPath(item),\n field: 'image',\n rule: 'image-not-found',\n severity: 'warning',\n message: `Featured image not found: ${item.image}`,\n suggestion: 'Check that the image path is correct and the file exists in public/',\n });\n }\n }\n\n // Check inline images\n const images = extractImages(item.body);\n for (const img of images) {\n // Only check local images\n if (!img.src.startsWith('http')) {\n const normalizedPath = normalizeImagePath(img.src);\n if (!isValidImagePath(normalizedPath, context.validImages)) {\n results.push({\n file: getDisplayPath(item),\n field: 'body',\n rule: 'image-not-found',\n severity: 'warning',\n message: `Inline image not found: ${img.src}`,\n suggestion: 'Check that the image path is correct and the file exists in public/',\n line: img.line,\n });\n }\n }\n }\n\n return results;\n },\n};\n\n/**\n * All image rules\n */\nexport const imageRules: Rule[] = [\n inlineImageMissingAlt,\n frontmatterImageMissingAlt,\n imageNotFound,\n];\n","/**\n * Word Counter Utility\n * Counts words in MDX content, stripping markdown syntax\n */\n\n/**\n * Strip markdown syntax to get plain text\n */\nexport function stripMarkdown(text: string): string {\n let result = text;\n\n // Remove code blocks (``` ... ```)\n result = result.replace(/```[\\s\\S]*?```/g, '');\n\n // Remove inline code (`code`)\n result = result.replace(/`[^`]+`/g, '');\n\n // Remove images \n result = result.replace(/!\\[[^\\]]*\\]\\([^)]+\\)/g, '');\n\n // Remove links but keep text [text](url) -> text\n result = result.replace(/\\[([^\\]]+)\\]\\([^)]+\\)/g, '$1');\n\n // Remove HTML/JSX tags\n result = result.replace(/<[^>]+>/g, '');\n\n // Remove headings markers (#)\n result = result.replace(/^#{1,6}\\s+/gm, '');\n\n // Remove bold/italic markers\n result = result.replace(/\\*\\*([^*]+)\\*\\*/g, '$1');\n result = result.replace(/\\*([^*]+)\\*/g, '$1');\n result = result.replace(/__([^_]+)__/g, '$1');\n result = result.replace(/_([^_]+)_/g, '$1');\n\n // Remove blockquotes\n result = result.replace(/^>\\s+/gm, '');\n\n // Remove horizontal rules\n result = result.replace(/^[-*_]{3,}$/gm, '');\n\n // Remove list markers\n result = result.replace(/^[\\s]*[-*+]\\s+/gm, '');\n result = result.replace(/^[\\s]*\\d+\\.\\s+/gm, '');\n\n // Remove frontmatter-style content (import statements, etc.)\n result = result.replace(/^import\\s+.*$/gm, '');\n result = result.replace(/^export\\s+.*$/gm, '');\n\n // Remove JSX component tags (like <CalloutBox>...</CalloutBox>)\n result = result.replace(/<\\w+[^>]*>[\\s\\S]*?<\\/\\w+>/g, (match) => {\n // Try to extract text content from JSX\n return match.replace(/<[^>]+>/g, ' ');\n });\n\n return result;\n}\n\n/**\n * Count words in text\n * Splits on whitespace and counts non-empty tokens\n */\nexport function countWords(text: string): number {\n const stripped = stripMarkdown(text);\n\n // Split on whitespace and filter empty strings\n const words = stripped\n .split(/\\s+/)\n .filter(word => word.length > 0)\n // Filter out remaining non-word characters\n .filter(word => /\\w/.test(word));\n\n return words.length;\n}\n\n/**\n * Count sentences in text\n * Looks for sentence-ending punctuation\n */\nexport function countSentences(text: string): number {\n const stripped = stripMarkdown(text);\n\n // Count sentence-ending punctuation\n // This is a simplified approach - handles . ! ? followed by space or end\n const sentences = stripped.match(/[.!?]+(?:\\s|$)/g);\n\n return sentences ? sentences.length : 0;\n}\n\n/**\n * Get word count statistics for content\n */\nexport function getWordStats(mdxBody: string): {\n wordCount: number;\n sentenceCount: number;\n avgWordsPerSentence: number;\n} {\n const wordCount = countWords(mdxBody);\n const sentenceCount = countSentences(mdxBody);\n\n return {\n wordCount,\n sentenceCount,\n avgWordsPerSentence: sentenceCount > 0 ? wordCount / sentenceCount : 0,\n };\n}\n","/**\n * Readability Utility\n * Calculates a simplified readability score for content\n *\n * Note: This is a German-adjusted readability indicator, not strict Flesch-Kincaid.\n * German has longer words on average, so we use modified thresholds.\n */\n\nimport { stripMarkdown, countWords, countSentences } from './word-counter.js';\n\n/**\n * Estimate syllable count for a word\n * This is a simplified heuristic based on vowel patterns\n * Works reasonably well for German text\n */\nfunction estimateSyllables(word: string): number {\n // Lowercase for comparison\n const lower = word.toLowerCase();\n\n // Count vowel groups (including umlauts)\n const vowelPattern = /[aeiouyäöü]+/gi;\n const matches = lower.match(vowelPattern);\n\n if (!matches) {\n return 1; // Assume at least 1 syllable\n }\n\n let count = matches.length;\n\n // Adjust for common patterns\n // Silent e at end of word (English pattern, less common in German)\n if (lower.endsWith('e') && count > 1) {\n count -= 0.5;\n }\n\n // German compound word adjustment - longer words tend to have more syllables\n if (word.length > 12) {\n count = Math.max(count, Math.ceil(word.length / 4));\n }\n\n return Math.max(1, Math.round(count));\n}\n\n/**\n * Count total syllables in text\n */\nfunction countSyllables(text: string): number {\n const words = text\n .split(/\\s+/)\n .filter(word => word.length > 0)\n .filter(word => /\\w/.test(word));\n\n return words.reduce((total, word) => total + estimateSyllables(word), 0);\n}\n\n/**\n * Calculate average word length in characters\n */\nfunction averageWordLength(text: string): number {\n const words = text\n .split(/\\s+/)\n .filter(word => word.length > 0)\n .filter(word => /\\w/.test(word));\n\n if (words.length === 0) return 0;\n\n const totalLength = words.reduce((sum, word) => sum + word.length, 0);\n return totalLength / words.length;\n}\n\n/**\n * Calculate readability score\n *\n * Uses a modified Flesch formula adjusted for German:\n * - German text naturally has longer words\n * - Score range: 0-100 (higher = easier to read)\n *\n * Formula (German adaptation):\n * 180 - ASL - (58.5 * ASW)\n *\n * Where:\n * - ASL = Average Sentence Length (words per sentence)\n * - ASW = Average Syllables per Word\n */\nexport function calculateReadability(mdxBody: string): {\n score: number;\n avgSentenceLength: number;\n avgSyllablesPerWord: number;\n avgWordLength: number;\n interpretation: string;\n} {\n const plainText = stripMarkdown(mdxBody);\n\n const wordCount = countWords(mdxBody);\n const sentenceCount = countSentences(mdxBody);\n const syllableCount = countSyllables(plainText);\n\n // Avoid division by zero\n if (wordCount === 0 || sentenceCount === 0) {\n return {\n score: 0,\n avgSentenceLength: 0,\n avgSyllablesPerWord: 0,\n avgWordLength: 0,\n interpretation: 'No content to analyze',\n };\n }\n\n const avgSentenceLength = wordCount / sentenceCount;\n const avgSyllablesPerWord = syllableCount / wordCount;\n const avgWordLen = averageWordLength(plainText);\n\n // German Flesch formula\n const score = Math.round(180 - avgSentenceLength - (58.5 * avgSyllablesPerWord));\n\n // Clamp to 0-100 range\n const clampedScore = Math.max(0, Math.min(100, score));\n\n return {\n score: clampedScore,\n avgSentenceLength: Math.round(avgSentenceLength * 10) / 10,\n avgSyllablesPerWord: Math.round(avgSyllablesPerWord * 100) / 100,\n avgWordLength: Math.round(avgWordLen * 10) / 10,\n interpretation: getInterpretation(clampedScore),\n };\n}\n\n/**\n * Get human-readable interpretation of readability score\n */\nfunction getInterpretation(score: number): string {\n if (score >= 70) return 'Very easy to read';\n if (score >= 60) return 'Easy to read';\n if (score >= 50) return 'Fairly easy to read';\n if (score >= 40) return 'Standard';\n if (score >= 30) return 'Fairly difficult';\n if (score >= 20) return 'Difficult';\n return 'Very difficult';\n}\n\n/**\n * Check if readability score meets minimum threshold\n */\nexport function isReadable(score: number, threshold: number): boolean {\n return score >= threshold;\n}\n","/**\n * Content Quality Rules\n * Validates word count and readability\n */\n\nimport type { Rule, ContentItem, LintResult } from '../types.js';\nimport { getDisplayPath } from '../utils/display-path.js';\nimport { countWords } from '../utils/word-counter.js';\nimport { calculateReadability, isReadable } from '../utils/readability.js';\n\n/** Default content thresholds */\nconst MIN_WORD_COUNT = 300;\nconst MIN_READABILITY_SCORE = 30;\n\n/**\n * Rule: Content should have minimum word count\n */\nexport const contentTooShort: Rule = {\n name: 'content-too-short',\n severity: 'warning',\n category: 'content',\n fixStrategy: 'Expand the content for better SEO performance',\n run: (item: ContentItem): LintResult[] => {\n const wordCount = countWords(item.body);\n\n if (wordCount < MIN_WORD_COUNT) {\n return [{\n file: getDisplayPath(item),\n field: 'body',\n rule: 'content-too-short',\n severity: 'warning',\n message: `Content is too short (${wordCount} words, minimum ${MIN_WORD_COUNT})`,\n suggestion: 'Consider expanding the content for better SEO performance',\n }];\n }\n return [];\n },\n};\n\n/**\n * Rule: Content should meet minimum readability threshold\n */\nexport const lowReadability: Rule = {\n name: 'low-readability',\n severity: 'warning',\n category: 'content',\n fixStrategy: 'Use shorter sentences for easier reading',\n run: (item: ContentItem): LintResult[] => {\n const wordCount = countWords(item.body);\n\n // Skip readability check for very short content\n if (wordCount < 100) {\n return [];\n }\n\n const readability = calculateReadability(item.body);\n\n if (!isReadable(readability.score, MIN_READABILITY_SCORE)) {\n return [{\n file: getDisplayPath(item),\n field: 'body',\n rule: 'low-readability',\n severity: 'warning',\n message: `Low readability score (${readability.score}/100 - ${readability.interpretation})`,\n suggestion: `Avg sentence length: ${readability.avgSentenceLength} words. Consider shorter sentences for easier reading.`,\n }];\n }\n return [];\n },\n};\n\n/**\n * All content rules\n */\nexport const contentRules: Rule[] = [\n contentTooShort,\n lowReadability,\n];\n","/**\n * Open Graph Rules\n * Validates Open Graph / social sharing metadata\n */\n\nimport type { Rule, ContentItem, LintResult } from '../types.js';\nimport { getDisplayPath } from '../utils/display-path.js';\n\n/**\n * Rule: Blog posts should have a featured image for social sharing\n */\nexport const blogMissingOgImage: Rule = {\n name: 'blog-missing-og-image',\n severity: 'warning',\n category: 'seo',\n fixStrategy: 'Add an image field to the frontmatter for social sharing previews',\n run: (item: ContentItem): LintResult[] => {\n // Only apply to blog posts\n if (item.contentType !== 'blog') {\n return [];\n }\n\n if (!item.image || item.image.trim().length === 0) {\n return [{\n file: getDisplayPath(item),\n field: 'image',\n rule: 'blog-missing-og-image',\n severity: 'warning',\n message: 'Blog post missing featured image',\n suggestion: 'Add an image field for better social media sharing previews.',\n }];\n }\n return [];\n },\n};\n\n/**\n * Rule: Project pages should have a thumbnail/featured image for social sharing\n */\nexport const projectMissingOgImage: Rule = {\n name: 'project-missing-og-image',\n severity: 'warning',\n category: 'seo',\n fixStrategy: 'Add a thumbnail image for social media sharing previews',\n run: (item: ContentItem): LintResult[] => {\n if (item.contentType !== 'project') {\n return [];\n }\n\n if (!item.image || item.image.trim().length === 0) {\n return [{\n file: getDisplayPath(item),\n field: 'image',\n rule: 'project-missing-og-image',\n severity: 'warning',\n message: 'Project missing thumbnail/featured image',\n suggestion: 'Add a thumbnail image for better social media sharing previews.',\n }];\n }\n return [];\n },\n};\n\n/**\n * All Open Graph rules\n */\nexport const ogRules: Rule[] = [\n blogMissingOgImage,\n projectMissingOgImage,\n];\n","/**\n * Performance Validation Rules\n * Checks image file sizes and other performance-related concerns\n */\n\nimport { statSync } from 'fs';\nimport { join } from 'path';\nimport type { Rule, ContentItem, LintResult } from '../types.js';\nimport { getDisplayPath } from '../utils/display-path.js';\nimport { extractImages } from '../utils/image-extractor.js';\n\n/** Default max image size in KB */\nconst MAX_IMAGE_SIZE_KB = 500;\n\n/**\n * Rule: Image files should not exceed the configured size limit\n * Large images degrade page load performance and Core Web Vitals\n */\nexport const imageFileTooLarge: Rule = {\n name: 'image-file-too-large',\n severity: 'warning',\n category: 'technical',\n fixStrategy: 'Compress the image or convert to WebP/AVIF format',\n run: (item: ContentItem): LintResult[] => {\n const results: LintResult[] = [];\n const maxBytes = MAX_IMAGE_SIZE_KB * 1024;\n const imagesToCheck: Array<{ src: string; line?: number; source: string }> = [];\n\n // Check featured image from frontmatter\n if (item.image && !item.image.startsWith('http')) {\n imagesToCheck.push({ src: item.image, source: 'frontmatter' });\n }\n\n // Check inline images from body\n const inlineImages = extractImages(item.body);\n for (const img of inlineImages) {\n if (!img.src.startsWith('http')) {\n imagesToCheck.push({ src: img.src, line: img.line, source: 'inline' });\n }\n }\n\n for (const image of imagesToCheck) {\n try {\n const imagePath = join(process.cwd(), 'public', image.src);\n const stats = statSync(imagePath);\n const sizeKB = Math.round(stats.size / 1024);\n\n if (stats.size > maxBytes) {\n results.push({\n file: getDisplayPath(item),\n field: image.source === 'frontmatter' ? 'image' : 'body',\n rule: 'image-file-too-large',\n severity: 'warning',\n message: `Image file too large: ${image.src} (${sizeKB}KB > ${MAX_IMAGE_SIZE_KB}KB)`,\n suggestion: 'Compress the image or convert to WebP/AVIF format to improve page load speed',\n line: image.line,\n });\n }\n } catch {\n // File not found is handled by image-not-found rule\n }\n }\n\n return results;\n },\n};\n\n/**\n * All performance rules\n */\nexport const performanceRules: Rule[] = [\n imageFileTooLarge,\n];\n","/**\n * Robots / Indexing Validation Rules\n * Validates noindex and robots-related settings\n */\n\nimport type { Rule, ContentItem, LintResult } from '../types.js';\nimport { getDisplayPath } from '../utils/display-path.js';\n\n/**\n * Rule: Published (non-draft) content with noindex may be unintentional\n * Warns when content is publicly available but hidden from search engines\n */\nexport const publishedNoindex: Rule = {\n name: 'published-noindex',\n severity: 'warning',\n category: 'seo',\n fixStrategy: 'Remove noindex if content should be discoverable, or set draft: true to hide it',\n run: (item: ContentItem): LintResult[] => {\n if (!item.draft && item.noindex === true) {\n return [{\n file: getDisplayPath(item),\n field: 'noindex',\n rule: 'published-noindex',\n severity: 'warning',\n message: 'Published content has noindex set — it will not appear in search results',\n suggestion: 'Remove noindex if this content should be discoverable, or set draft: true if it should be hidden entirely',\n }];\n }\n return [];\n },\n};\n\n/**\n * All robots/indexing rules\n */\nexport const robotsRules: Rule[] = [\n publishedNoindex,\n];\n","/**\n * Slug Validation Rules\n * Validates slug format and length for SEO-friendly URLs\n */\n\nimport type { Rule, ContentItem, LintResult } from '../types.js';\nimport { getDisplayPath } from '../utils/display-path.js';\n\n/** Default slug thresholds */\nconst SLUG_MAX_LENGTH = 75;\nconst SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\n\n/**\n * Rule: Slug must contain only valid characters (lowercase alphanumeric + hyphens)\n */\nexport const slugInvalidCharacters: Rule = {\n name: 'slug-invalid-characters',\n severity: 'error',\n category: 'seo',\n fixStrategy: 'Use lowercase alphanumeric characters with hyphens only (e.g., \"my-blog-post\")',\n run: (item: ContentItem): LintResult[] => {\n if (!item.slug) return [];\n\n const hasUppercase = /[A-Z]/.test(item.slug);\n const matchesPattern = SLUG_PATTERN.test(item.slug);\n\n if (hasUppercase || !matchesPattern) {\n return [{\n file: getDisplayPath(item),\n field: 'slug',\n rule: 'slug-invalid-characters',\n severity: 'error',\n message: `Slug \"${item.slug}\" contains invalid characters`,\n suggestion: 'Slugs must be lowercase alphanumeric with hyphens only (e.g., \"my-blog-post\")',\n }];\n }\n return [];\n },\n};\n\n/**\n * Rule: Slug should not exceed maximum length\n */\nexport const slugTooLong: Rule = {\n name: 'slug-too-long',\n severity: 'warning',\n category: 'seo',\n fixStrategy: 'Shorten the slug for better URL readability',\n run: (item: ContentItem): LintResult[] => {\n if (!item.slug) return [];\n\n const length = item.slug.length;\n if (length > SLUG_MAX_LENGTH) {\n return [{\n file: getDisplayPath(item),\n field: 'slug',\n rule: 'slug-too-long',\n severity: 'warning',\n message: `Slug is too long (${length}/${SLUG_MAX_LENGTH} chars)`,\n suggestion: `Shorten the slug to ${SLUG_MAX_LENGTH} characters or less for better URL readability`,\n }];\n }\n return [];\n },\n};\n\n/**\n * All slug rules\n */\nexport const slugRules: Rule[] = [\n slugInvalidCharacters,\n slugTooLong,\n];\n","/**\n * Internationalization Validation Rules\n * Validates translation pairs and locale metadata for bilingual content\n */\n\nimport type { Rule, ContentItem, RuleContext, LintResult } from '../types.js';\nimport { getDisplayPath } from '../utils/display-path.js';\n\n/**\n * Rule: Blog/project content with translationKey must have both DE and EN versions\n */\nexport const translationPairMissing: Rule = {\n name: 'translation-pair-missing',\n severity: 'warning',\n category: 'i18n',\n fixStrategy: 'Create the missing translation version with the same translationKey',\n run: (item: ContentItem, context: RuleContext): LintResult[] => {\n // Only applies to blog and project content\n if (item.contentType === 'page') return [];\n if (!item.translationKey) return [];\n\n const siblings = context.allContent.filter(\n c => c.translationKey === item.translationKey && c.slug !== item.slug\n );\n\n const locales = new Set([item.locale, ...siblings.map(s => s.locale)]);\n const results: LintResult[] = [];\n\n if (item.locale === 'de' && !locales.has('en')) {\n results.push({\n file: getDisplayPath(item),\n field: 'translationKey',\n rule: 'translation-pair-missing',\n severity: 'warning',\n message: `Missing English translation for translationKey \"${item.translationKey}\"`,\n suggestion: 'Create an English (.en.mdx) version with the same translationKey',\n });\n }\n\n if (item.locale === 'en' && !locales.has('de')) {\n results.push({\n file: getDisplayPath(item),\n field: 'translationKey',\n rule: 'translation-pair-missing',\n severity: 'warning',\n message: `Missing German translation for translationKey \"${item.translationKey}\"`,\n suggestion: 'Create a German (.de.mdx) version with the same translationKey',\n });\n }\n\n return results;\n },\n};\n\n/**\n * Rule: Blog/project content should have a locale field\n */\nexport const missingLocale: Rule = {\n name: 'missing-locale',\n severity: 'warning',\n category: 'i18n',\n fixStrategy: 'Add a locale field (\"de\" or \"en\") to the frontmatter',\n run: (item: ContentItem): LintResult[] => {\n // Only applies to blog and project content\n if (item.contentType === 'page') return [];\n\n if (!item.locale) {\n return [{\n file: getDisplayPath(item),\n field: 'locale',\n rule: 'missing-locale',\n severity: 'warning',\n message: 'Missing locale field',\n suggestion: 'Add a locale field (\"de\" or \"en\") to the frontmatter for proper i18n support',\n }];\n }\n return [];\n },\n};\n\n/**\n * All i18n rules\n */\nexport const i18nRules: Rule[] = [\n translationPairMissing,\n missingLocale,\n];\n","/**\n * Date Validation Rules\n * Validates date presence and correctness for blog and project content\n */\n\nimport type { Rule, ContentItem, LintResult } from '../types.js';\nimport { getDisplayPath } from '../utils/display-path.js';\n\n/**\n * Rule: Blog and project content must have a date field\n */\nexport const missingDate: Rule = {\n name: 'missing-date',\n severity: 'error',\n category: 'content',\n fixStrategy: 'Add a date field (e.g., \"2025-01-15\") to the frontmatter',\n run: (item: ContentItem): LintResult[] => {\n if (item.contentType === 'page') return [];\n\n if (!item.date) {\n return [{\n file: getDisplayPath(item),\n field: 'date',\n rule: 'missing-date',\n severity: 'error',\n message: 'Missing date field',\n suggestion: 'Add a date field (e.g., \"2025-01-15\") to the frontmatter',\n }];\n }\n return [];\n },\n};\n\n/**\n * Rule: Date should not be in the future\n */\nexport const futureDate: Rule = {\n name: 'future-date',\n severity: 'warning',\n category: 'content',\n fixStrategy: 'Verify the publish date is correct or set it to today',\n run: (item: ContentItem): LintResult[] => {\n if (!item.date) return [];\n\n const dateValue = new Date(item.date);\n const now = new Date();\n\n if (dateValue > now) {\n return [{\n file: getDisplayPath(item),\n field: 'date',\n rule: 'future-date',\n severity: 'warning',\n message: `Date \"${item.date}\" is in the future`,\n suggestion: 'Verify the publish date is correct or set it to today',\n }];\n }\n return [];\n },\n};\n\n/**\n * Rule: Blog and project content should have an updatedAt field\n */\nexport const missingUpdatedAt: Rule = {\n name: 'missing-updated-at',\n severity: 'warning',\n category: 'content',\n fixStrategy: 'Add an updatedAt field to help search engines identify fresh content',\n run: (item: ContentItem): LintResult[] => {\n if (item.contentType === 'page') return [];\n\n if (!item.updatedAt) {\n return [{\n file: getDisplayPath(item),\n field: 'updatedAt',\n rule: 'missing-updated-at',\n severity: 'warning',\n message: 'Missing updatedAt field',\n suggestion: 'Add an updatedAt field to help search engines identify fresh content',\n }];\n }\n return [];\n },\n};\n\n/**\n * All date rules\n */\nexport const dateRules: Rule[] = [\n missingDate,\n futureDate,\n missingUpdatedAt,\n];\n","/**\n * Schema Rules\n * Validates structured data (schema.org) readiness for content\n */\n\nimport type { Rule, ContentItem, LintResult } from '../types.js';\nimport { getDisplayPath } from '../utils/display-path.js';\n\n/**\n * Rule: Blog posts should have fields required for rich BlogPosting schema\n * Checks for updatedAt (dateModified) and image (schema image)\n */\nexport const blogMissingSchemaFields: Rule = {\n name: 'blog-missing-schema-fields',\n severity: 'warning',\n category: 'seo',\n fixStrategy: 'Add updatedAt and image fields to frontmatter for complete schema.org data',\n run: (item: ContentItem): LintResult[] => {\n // Only apply to blog posts\n if (item.contentType !== 'blog') {\n return [];\n }\n\n const results: LintResult[] = [];\n\n if (!item.updatedAt) {\n results.push({\n file: getDisplayPath(item),\n field: 'updatedAt',\n rule: 'blog-missing-schema-fields',\n severity: 'warning',\n message: 'Missing updatedAt — BlogPosting schema dateModified will fall back to datePublished',\n suggestion: 'Add updatedAt field to frontmatter for accurate schema.org dateModified.',\n });\n }\n\n if (!item.image || item.image.trim().length === 0) {\n results.push({\n file: getDisplayPath(item),\n field: 'image',\n rule: 'blog-missing-schema-fields',\n severity: 'warning',\n message: 'Missing featured image — BlogPosting schema image will be empty',\n suggestion: 'Add an image field for complete BlogPosting structured data.',\n });\n }\n\n return results;\n },\n};\n\n/**\n * All schema rules\n */\nexport const schemaRules: Rule[] = [\n blogMissingSchemaFields,\n];\n","/**\n * Keyword Coherence Rules\n * Validates that title keywords appear in description and headings\n */\n\nimport type { Rule, ContentItem, LintResult } from '../types.js';\nimport { getDisplayPath } from '../utils/display-path.js';\nimport { extractHeadings } from '../utils/heading-extractor.js';\nimport { countWords } from '../utils/word-counter.js';\n\n/** Keyword coherence thresholds */\nconst MIN_SIGNIFICANT_WORDS = 2;\nconst MIN_WORD_LENGTH = 3;\nconst MIN_CONTENT_WORDS = 300;\n\n/**\n * Combined DE + EN stopwords (articles, prepositions, conjunctions, pronouns)\n * These are filtered out when extracting significant keywords from titles\n */\nconst STOPWORDS = new Set([\n // German\n 'der', 'die', 'das', 'ein', 'eine', 'einer', 'eines', 'einem', 'einen',\n 'und', 'oder', 'aber', 'als', 'auch', 'auf', 'aus', 'bei', 'bis',\n 'für', 'mit', 'nach', 'über', 'von', 'vor', 'wie', 'zum', 'zur',\n 'den', 'dem', 'des', 'ist', 'sind', 'war', 'hat', 'ihr', 'wir',\n 'sie', 'ich', 'nicht', 'sich', 'man', 'nur', 'noch', 'mehr',\n // English\n 'the', 'and', 'for', 'are', 'but', 'not', 'you', 'all', 'can',\n 'her', 'was', 'one', 'our', 'out', 'has', 'had', 'its', 'his',\n 'how', 'who', 'what', 'why', 'when', 'where', 'which', 'with',\n 'this', 'that', 'from', 'they', 'been', 'have', 'will', 'your',\n 'into', 'than', 'them', 'then', 'some', 'each', 'make',\n]);\n\n/**\n * Extract significant words from text\n * Filters out stopwords and short words, normalizes to lowercase\n */\nfunction extractSignificantWords(text: string): string[] {\n return text\n .toLowerCase()\n .replace(/[^\\p{L}\\p{N}\\s-]/gu, ' ')\n .split(/\\s+/)\n .filter(word =>\n word.length >= MIN_WORD_LENGTH && !STOPWORDS.has(word)\n );\n}\n\n/**\n * Check if any keyword appears in a target text (case-insensitive)\n */\nfunction findMatchingKeywords(keywords: string[], targetText: string): string[] {\n const normalizedTarget = targetText.toLowerCase();\n return keywords.filter(keyword => normalizedTarget.includes(keyword));\n}\n\n/**\n * Rule: At least one title keyword should appear in the meta description\n * Improves click-through rate by ensuring title/description alignment\n */\nexport const keywordNotInDescription: Rule = {\n name: 'keyword-not-in-description',\n severity: 'warning',\n category: 'seo',\n fixStrategy: 'Include at least one title keyword in the meta description',\n run: (item: ContentItem): LintResult[] => {\n if (!item.title || !item.description) return [];\n\n const keywords = extractSignificantWords(item.title);\n if (keywords.length < MIN_SIGNIFICANT_WORDS) return [];\n\n const matches = findMatchingKeywords(keywords, item.description);\n if (matches.length === 0) {\n return [{\n file: getDisplayPath(item),\n field: 'description',\n rule: 'keyword-not-in-description',\n severity: 'warning',\n message: `Description contains none of the title keywords: ${keywords.join(', ')}`,\n suggestion: 'Include at least one keyword from the title in the meta description for better SEO coherence',\n }];\n }\n return [];\n },\n};\n\n/**\n * Rule: At least one title keyword should appear in H2/H3 headings\n * Only applies to blog/project content with sufficient length\n */\nexport const keywordNotInHeadings: Rule = {\n name: 'keyword-not-in-headings',\n severity: 'warning',\n category: 'seo',\n fixStrategy: 'Include at least one title keyword in subheadings',\n run: (item: ContentItem): LintResult[] => {\n if (item.contentType !== 'blog' && item.contentType !== 'project') return [];\n if (!item.title || !item.body) return [];\n\n const wordCount = countWords(item.body);\n if (wordCount < MIN_CONTENT_WORDS) return [];\n\n const keywords = extractSignificantWords(item.title);\n if (keywords.length < MIN_SIGNIFICANT_WORDS) return [];\n\n const headings = extractHeadings(item.body);\n const subHeadings = headings.filter(h => h.level === 2 || h.level === 3);\n if (subHeadings.length === 0) return [];\n\n const allHeadingText = subHeadings.map(h => h.text).join(' ');\n const matches = findMatchingKeywords(keywords, allHeadingText);\n\n if (matches.length === 0) {\n return [{\n file: getDisplayPath(item),\n field: 'body',\n rule: 'keyword-not-in-headings',\n severity: 'warning',\n message: `No title keywords found in H2/H3 headings: ${keywords.join(', ')}`,\n suggestion: 'Include at least one title keyword in your subheadings to reinforce topical relevance',\n }];\n }\n return [];\n },\n};\n\n/**\n * Rule: Title and description must share at least one significant word\n * Catches completely disconnected metadata that confuses search engines\n */\nexport const titleDescriptionNoOverlap: Rule = {\n name: 'title-description-no-overlap',\n severity: 'warning',\n category: 'seo',\n fixStrategy: 'Ensure title and description share at least one keyword',\n run: (item: ContentItem): LintResult[] => {\n if (!item.title || !item.description) return [];\n\n const titleWords = extractSignificantWords(item.title);\n const descWords = new Set(extractSignificantWords(item.description));\n\n if (titleWords.length < MIN_SIGNIFICANT_WORDS) return [];\n if (descWords.size < MIN_SIGNIFICANT_WORDS) return [];\n\n const overlap = titleWords.filter(word => descWords.has(word));\n if (overlap.length === 0) {\n return [{\n file: getDisplayPath(item),\n field: 'description',\n rule: 'title-description-no-overlap',\n severity: 'warning',\n message: 'Title and description share no significant words',\n suggestion: 'Title and description should be thematically connected. Ensure they share at least one keyword.',\n }];\n }\n return [];\n },\n};\n\n/**\n * All keyword coherence rules\n */\nexport const keywordCoherenceRules: Rule[] = [\n keywordNotInDescription,\n keywordNotInHeadings,\n titleDescriptionNoOverlap,\n];\n","/**\n * Link Validation Rules\n * Validates internal links resolve to existing pages\n *\n * These rules require a LinkExtractor instance (created per-project\n * with the site's internal URL patterns). Use the createLinkRules()\n * factory to build the rule set.\n */\n\nimport type { Rule, ContentItem, RuleContext, LintResult } from '../types.js';\nimport type { LinkExtractor } from '../utils/link-extractor.js';\nimport { getDisplayPath } from '../utils/display-path.js';\nimport { isValidInternalLink } from '../utils/slug-resolver.js';\n\n/**\n * Factory: Create link validation rules that use the provided LinkExtractor\n */\nexport function createLinkRules(linkExtractor: LinkExtractor): Rule[] {\n const { extractLinks, getInternalLinks, isAbsoluteInternalLink } = linkExtractor;\n\n /**\n * Rule: Internal links must resolve to existing pages\n */\n const brokenInternalLink: Rule = {\n name: 'broken-internal-link',\n severity: 'error',\n category: 'technical',\n fixStrategy: 'Fix the link target to point to an existing page',\n run: (item: ContentItem, context: RuleContext): LintResult[] => {\n const results: LintResult[] = [];\n const links = extractLinks(item.body);\n const internalLinks = getInternalLinks(links);\n\n for (const link of internalLinks) {\n if (!isValidInternalLink(link.url, context.validSlugs)) {\n results.push({\n file: getDisplayPath(item),\n field: 'body',\n rule: 'broken-internal-link',\n severity: 'error',\n message: `Broken internal link: ${link.url}`,\n suggestion: `Link \"${link.originalUrl}\" does not resolve to any known page`,\n line: link.line,\n });\n }\n }\n\n return results;\n },\n };\n\n /**\n * Rule: Internal links should use relative paths instead of absolute URLs\n */\n const absoluteInternalLink: Rule = {\n name: 'absolute-internal-link',\n severity: 'warning',\n category: 'technical',\n fixStrategy: 'Use a relative path instead of an absolute URL',\n run: (item: ContentItem): LintResult[] => {\n const results: LintResult[] = [];\n const links = extractLinks(item.body);\n\n for (const link of links) {\n if (link.isInternal && isAbsoluteInternalLink(link.originalUrl)) {\n results.push({\n file: getDisplayPath(item),\n field: 'body',\n rule: 'absolute-internal-link',\n severity: 'warning',\n message: `Absolute internal URL: ${link.originalUrl}`,\n suggestion: `Use relative path \"${link.url}\" instead for better portability`,\n line: link.line,\n });\n }\n }\n\n return results;\n },\n };\n\n /**\n * Rule: Internal links must not point to draft or noindex pages\n * Prevents publishing content that links to hidden pages\n */\n const draftLinkLeak: Rule = {\n name: 'draft-link-leak',\n severity: 'error',\n category: 'technical',\n fixStrategy: 'Remove or update link — the target page is not publicly visible',\n run: (item: ContentItem, context: RuleContext): LintResult[] => {\n // Skip draft/noindex items — they aren't publicly visible\n if (item.draft || item.noindex) {\n return [];\n }\n\n const results: LintResult[] = [];\n const links = extractLinks(item.body);\n const internalLinks = getInternalLinks(links);\n\n // Build set of permalinks for draft/noindex content\n const draftPermalinks = new Set<string>(\n context.allContent\n .filter(c => c.draft === true || c.noindex === true)\n .map(c => c.permalink)\n );\n\n for (const link of internalLinks) {\n if (draftPermalinks.has(link.url)) {\n results.push({\n file: getDisplayPath(item),\n field: 'body',\n rule: 'draft-link-leak',\n severity: 'error',\n message: `Internal link points to draft/noindex page: ${link.url}`,\n suggestion: `Remove or update link \"${link.originalUrl}\" — the target page is not publicly visible`,\n line: link.line,\n });\n }\n }\n\n return results;\n },\n };\n\n /**\n * Rule: Internal links should not have trailing slashes\n * Ensures consistent URL formatting across the site\n */\n const trailingSlashInconsistency: Rule = {\n name: 'trailing-slash-inconsistency',\n severity: 'warning',\n category: 'technical',\n fixStrategy: 'Remove the trailing slash from the internal link',\n run: (item: ContentItem): LintResult[] => {\n const results: LintResult[] = [];\n const links = extractLinks(item.body);\n const internalLinks = getInternalLinks(links);\n\n for (const link of internalLinks) {\n // Check original URL (before normalization) for trailing slash\n // Skip root \"/\" which is fine\n const originalPath = link.originalUrl.split('?')[0].split('#')[0];\n if (originalPath !== '/' && originalPath.endsWith('/')) {\n results.push({\n file: getDisplayPath(item),\n field: 'body',\n rule: 'trailing-slash-inconsistency',\n severity: 'warning',\n message: `Internal link has trailing slash: ${link.originalUrl}`,\n suggestion: `Remove trailing slash: \"${originalPath.slice(0, -1)}\"`,\n line: link.line,\n });\n }\n }\n\n return results;\n },\n };\n\n return [\n brokenInternalLink,\n absoluteInternalLink,\n draftLinkLeak,\n trailingSlashInconsistency,\n ];\n}\n","/**\n * External Link Validation Rules\n * Validates external URLs for correctness, security, and density\n */\n\nimport type { Rule, ContentItem, LintResult } from '../types.js';\nimport type { LinkExtractor } from '../utils/link-extractor.js';\nimport { getDisplayPath } from '../utils/display-path.js';\nimport { countWords } from '../utils/word-counter.js';\n\n/**\n * Outbound link density thresholds.\n * Minimum external links required scales with word count.\n */\nconst OUTBOUND_MIN_BASE = 3;\nconst OUTBOUND_WORDS_PER_EXTRA_LINK = 500;\nconst OUTBOUND_MIN_WORDS_TO_APPLY = 300;\n\n/**\n * Factory: Create external link validation rules that use the provided LinkExtractor\n */\nexport function createExternalLinkRules(linkExtractor: LinkExtractor): Rule[] {\n const { extractLinks } = linkExtractor;\n\n /**\n * Rule: External links must be well-formed URLs\n * Catches typos and malformed URLs before they reach production\n */\n const externalLinkMalformed: Rule = {\n name: 'external-link-malformed',\n severity: 'warning',\n category: 'technical',\n fixStrategy: 'Check the URL for typos or missing protocol (https://)',\n run: (item: ContentItem): LintResult[] => {\n const results: LintResult[] = [];\n const links = extractLinks(item.body);\n\n for (const link of links) {\n if (link.isInternal) {\n continue;\n }\n\n try {\n new URL(link.url);\n } catch {\n results.push({\n file: getDisplayPath(item),\n field: 'body',\n rule: 'external-link-malformed',\n severity: 'warning',\n message: `Malformed external URL: ${link.url}`,\n suggestion: 'Check the URL for typos or missing protocol (https://)',\n line: link.line,\n });\n }\n }\n\n return results;\n },\n };\n\n /**\n * Rule: External links should use HTTPS instead of HTTP\n * HTTP links are insecure and may trigger browser warnings\n */\n const externalLinkHttp: Rule = {\n name: 'external-link-http',\n severity: 'warning',\n category: 'technical',\n fixStrategy: 'Replace http:// with https://',\n run: (item: ContentItem): LintResult[] => {\n const results: LintResult[] = [];\n const links = extractLinks(item.body);\n\n for (const link of links) {\n if (link.isInternal) {\n continue;\n }\n\n // Check for http:// but exclude localhost for development links\n if (link.url.startsWith('http://') && !link.url.startsWith('http://localhost')) {\n results.push({\n file: getDisplayPath(item),\n field: 'body',\n rule: 'external-link-http',\n severity: 'warning',\n message: `Insecure HTTP link: ${link.url}`,\n suggestion: `Use HTTPS instead: \"${link.url.replace('http://', 'https://')}\"`,\n line: link.line,\n });\n }\n }\n\n return results;\n },\n };\n\n /**\n * Rule: Content should include enough outbound links to cite sources.\n * Minimum scales with article length: 3 for short articles,\n * +1 for every additional 500 words.\n */\n const externalLinkMinDensity: Rule = {\n name: 'external-link-low-density',\n severity: 'warning',\n category: 'seo',\n fixStrategy: 'Add outbound links to cite sources, reference tools, or link to studies',\n run: (item: ContentItem): LintResult[] => {\n if (item.contentType !== 'blog') {\n return [];\n }\n\n const wordCount = countWords(item.body);\n if (wordCount < OUTBOUND_MIN_WORDS_TO_APPLY) {\n return [];\n }\n\n const links = extractLinks(item.body);\n const externalLinks = links.filter(link => !link.isInternal);\n const externalCount = externalLinks.length;\n\n const extraLinks = Math.max(\n 0,\n Math.floor((wordCount - OUTBOUND_MIN_WORDS_TO_APPLY) / OUTBOUND_WORDS_PER_EXTRA_LINK)\n );\n const requiredMin = OUTBOUND_MIN_BASE + extraLinks;\n\n if (externalCount < requiredMin) {\n return [{\n file: getDisplayPath(item),\n field: 'body',\n rule: 'external-link-low-density',\n severity: 'warning',\n message: `Only ${externalCount} outbound link(s) for ${wordCount} words (minimum: ${requiredMin})`,\n suggestion: `Add ${requiredMin - externalCount} more outbound link(s) to cite sources, reference tools, or link to studies that support your claims.`,\n }];\n }\n\n return [];\n },\n };\n\n return [\n externalLinkMalformed,\n externalLinkHttp,\n externalLinkMinDensity,\n ];\n}\n","/**\n * Orphan Content Detection Rules\n * Identifies content that is not linked from any other page\n */\n\nimport type { Rule, ContentItem, RuleContext, LintResult } from '../types.js';\nimport type { LinkExtractor } from '../utils/link-extractor.js';\nimport { getDisplayPath } from '../utils/display-path.js';\n\n/**\n * Normalize a permalink for comparison by removing trailing slashes\n */\nfunction normalizePermalink(permalink: string): string {\n if (permalink.length > 1 && permalink.endsWith('/')) {\n return permalink.slice(0, -1);\n }\n return permalink;\n}\n\n/**\n * Factory: Create orphan content detection rules that use the provided LinkExtractor\n */\nexport function createOrphanRules(linkExtractor: LinkExtractor): Rule[] {\n const { extractLinks, getInternalLinks } = linkExtractor;\n\n /**\n * Rule: Blog and project content should be linked from at least one other page\n * Orphan pages are hard to discover and receive less search engine traffic\n */\n const orphanContent: Rule = {\n name: 'orphan-content',\n severity: 'warning',\n category: 'seo',\n fixStrategy: 'Add an internal link to this page from a related page',\n run: (item: ContentItem, context: RuleContext): LintResult[] => {\n // Only check blog posts and projects\n if (item.contentType !== 'blog' && item.contentType !== 'project') {\n return [];\n }\n\n // Skip drafts and noindex content\n if (item.draft || item.noindex) {\n return [];\n }\n\n const itemPermalink = normalizePermalink(item.permalink);\n\n // Check if any other content item links to this item\n for (const other of context.allContent) {\n // Skip self\n if (other.slug === item.slug && other.contentType === item.contentType) {\n continue;\n }\n\n const links = extractLinks(other.body);\n const internalLinks = getInternalLinks(links);\n\n for (const link of internalLinks) {\n const normalizedLink = normalizePermalink(link.url);\n if (normalizedLink === itemPermalink) {\n // Found a link to this item — not orphaned\n return [];\n }\n }\n }\n\n return [{\n file: getDisplayPath(item),\n field: 'body',\n rule: 'orphan-content',\n severity: 'warning',\n message: `No other content links to this ${item.contentType} post`,\n suggestion: `Add an internal link to \"${item.permalink}\" from a related page to improve discoverability`,\n }];\n },\n };\n\n return [orphanContent];\n}\n","/**\n * GEO (Generative Engine Optimization) Analyzer\n * Utilities for analyzing content readiness for LLM visibility\n */\n\nimport { extractHeadings } from './heading-extractor.js';\nimport { countWords } from './word-counter.js';\n\n/**\n * Question words in English and German (case-insensitive matching)\n */\nconst QUESTION_WORDS = [\n 'was', 'wie', 'warum', 'wann', 'wo', 'wer', 'welche',\n 'how', 'what', 'why', 'when', 'where', 'who', 'which',\n 'can', 'do', 'does', 'is', 'are', 'should',\n];\n\n/**\n * Weak lead sentence patterns (case-insensitive)\n * These indicate introductory filler rather than direct answers\n */\nconst WEAK_LEAD_STARTS = [\n 'in this', 'the following', 'diesem',\n \"let's\", 'lass uns',\n 'this section', 'dieser abschnitt',\n];\n\n/**\n * Markdown table separator pattern\n * Matches `|---`, `| ---`, `|:---`, `| :---:` and similar variants\n */\nconst TABLE_SEPARATOR_PATTERN = /\\|\\s*:?-{2,}/;\n\n/**\n * Count headings that are formatted as questions\n * A question heading either ends with '?' or starts with a question word\n */\nexport function countQuestionHeadings(body: string): number {\n const headings = extractHeadings(body);\n let count = 0;\n\n for (const heading of headings) {\n const text = heading.text.trim();\n\n // Check if heading ends with question mark\n if (text.endsWith('?')) {\n count++;\n continue;\n }\n\n // Check if heading starts with a question word\n const firstWord = text.split(/\\s+/)[0]?.toLowerCase() ?? '';\n if (QUESTION_WORDS.includes(firstWord)) {\n count++;\n }\n }\n\n return count;\n}\n\n/**\n * Analyze lead sentences across content sections\n * Splits on H2/H3 headings and checks if the first sentence in each\n * section is a direct answer (not a weak intro)\n */\nexport function analyzeLeadSentences(body: string): {\n totalSections: number;\n sectionsWithDirectAnswers: number;\n} {\n // Split body on H2/H3 heading patterns\n const sectionPattern = /^#{2,3}\\s+.+$/m;\n const sections = body.split(sectionPattern);\n\n // First element is content before the first heading (skip it)\n const contentSections = sections.slice(1);\n\n let totalSections = 0;\n let sectionsWithDirectAnswers = 0;\n\n for (const section of contentSections) {\n const trimmed = section.trim();\n if (!trimmed) continue;\n\n totalSections++;\n\n // Extract first sentence (ends with . ! or ?)\n const sentenceMatch = trimmed.match(/^([^.!?]+[.!?])/);\n if (!sentenceMatch) continue;\n\n const firstSentence = sentenceMatch[1].trim();\n\n // Skip if too short to be meaningful\n if (firstSentence.length <= 20) continue;\n\n // Skip if ends with question mark (not a direct answer)\n if (firstSentence.endsWith('?')) continue;\n\n // Check for weak lead patterns\n const lowerSentence = firstSentence.toLowerCase();\n const isWeakLead = WEAK_LEAD_STARTS.some(\n pattern => lowerSentence.startsWith(pattern)\n );\n\n if (!isWeakLead) {\n sectionsWithDirectAnswers++;\n }\n }\n\n return { totalSections, sectionsWithDirectAnswers };\n}\n\n/**\n * Count statistical data points in the content body\n * Looks for percentages, multipliers, currency values, large numbers, and scales\n */\nexport function countStatistics(body: string): number {\n const patterns: RegExp[] = [\n /\\d+\\s*%/g, // Percentages: 50%, 23 %\n /\\d+(?:\\.\\d+)?\\s*(?:x|mal|times)/gi, // Multipliers: 3x, 2.5 mal, 10 times\n /(?:€|\\$|USD|EUR)\\s*\\d+/g, // Currency: €500, $1000, USD 50\n /\\d{4,}/g, // Large numbers: 10000, 2024 (4+ digits)\n /\\d+(?:\\.\\d+)?\\s*(?:million|billion|mrd|mio)/gi, // Scales: 5 million, 2.3 Mrd\n ];\n\n const matches = new Set<string>();\n\n for (const pattern of patterns) {\n const found = body.matchAll(pattern);\n for (const match of found) {\n // Use position as key to avoid double-counting overlapping matches\n matches.add(`${match.index}:${match[0]}`);\n }\n }\n\n return matches.size;\n}\n\n/**\n * Check if the content body contains a FAQ section\n * Matches common FAQ heading patterns in English and German\n */\nexport function hasFAQSection(body: string): boolean {\n const faqPattern = /#{2,3}\\s*(FAQ|Häufige Fragen|Frequently Asked|Fragen und Antworten)/i;\n return faqPattern.test(body);\n}\n\n/**\n * Check if the content body contains at least one Markdown table\n * Detects the table separator row pattern (e.g., `|---|---`)\n */\nexport function hasMarkdownTable(body: string): boolean {\n return TABLE_SEPARATOR_PATTERN.test(body);\n}\n\n/**\n * Count case-insensitive occurrences of an entity string in the body\n *\n * @example\n * countEntityMentions(\"ACME builds software in Berlin\", \"acme\") // 1\n * countEntityMentions(\"Visit Berlin, the best city. Berlin rocks.\", \"berlin\") // 2\n */\nexport function countEntityMentions(body: string, entity: string): number {\n const escapedEntity = entity.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const pattern = new RegExp(escapedEntity, 'gi');\n const matches = body.match(pattern);\n return matches ? matches.length : 0;\n}\n\n/**\n * Analyze citation blocks after H2 headings\n * For each H2 section, checks whether the first paragraph is substantial\n * (40+ words), which improves AI system extraction and citation likelihood\n *\n * @example\n * analyzeCitationBlocks(\"## Heading\\n\\nA long first paragraph...\") // { totalSections: 1, sectionsWithAdequateBlocks: 1 }\n */\nexport function analyzeCitationBlocks(body: string): {\n totalSections: number;\n sectionsWithAdequateBlocks: number;\n} {\n // Split on H2 headings specifically (not H3+)\n const h2Pattern = /^##\\s+.+$/gm;\n const h2Matches = [...body.matchAll(h2Pattern)];\n\n if (h2Matches.length === 0) {\n return { totalSections: 0, sectionsWithAdequateBlocks: 0 };\n }\n\n let totalSections = 0;\n let sectionsWithAdequateBlocks = 0;\n\n for (let i = 0; i < h2Matches.length; i++) {\n const matchStart = h2Matches[i].index! + h2Matches[i][0].length;\n const nextHeadingStart = i + 1 < h2Matches.length\n ? h2Matches[i + 1].index!\n : body.length;\n\n const sectionContent = body.slice(matchStart, nextHeadingStart).trim();\n if (!sectionContent) continue;\n\n totalSections++;\n\n // Extract the first paragraph: text before the first blank line\n // followed by a heading, or before a sub-heading (### ...)\n const firstParagraph = extractFirstParagraph(sectionContent);\n if (!firstParagraph) continue;\n\n const wordCount = countWords(firstParagraph);\n if (wordCount >= 40) {\n sectionsWithAdequateBlocks++;\n }\n }\n\n return { totalSections, sectionsWithAdequateBlocks };\n}\n\n/**\n * Extract the first paragraph from a section content block\n * A paragraph ends at a blank line or at the next heading\n */\nfunction extractFirstParagraph(sectionContent: string): string {\n const lines = sectionContent.split('\\n');\n const paragraphLines: string[] = [];\n let foundContent = false;\n\n for (const line of lines) {\n const trimmedLine = line.trim();\n\n // Skip leading empty lines\n if (!foundContent && !trimmedLine) continue;\n\n // Stop at a heading line\n if (trimmedLine.startsWith('#')) break;\n\n // Stop at a blank line after content has started\n if (foundContent && !trimmedLine) break;\n\n // Skip import/export statements and JSX component lines\n if (trimmedLine.startsWith('import ') || trimmedLine.startsWith('export ')) continue;\n if (trimmedLine.startsWith('<') && !trimmedLine.startsWith('<a')) continue;\n\n foundContent = true;\n paragraphLines.push(trimmedLine);\n }\n\n return paragraphLines.join(' ');\n}\n","/**\n * GEO (Generative Engine Optimization) Rules\n * Validates content readiness for LLM-based search and AI visibility\n */\n\nimport type { Rule, ContentItem, LintResult } from '../types.js';\nimport { getDisplayPath } from '../utils/display-path.js';\nimport { countWords } from '../utils/word-counter.js';\nimport { extractHeadings } from '../utils/heading-extractor.js';\nimport {\n countQuestionHeadings,\n analyzeLeadSentences,\n countStatistics,\n hasFAQSection,\n hasMarkdownTable,\n countEntityMentions,\n analyzeCitationBlocks,\n} from '../utils/geo-analyzer.js';\n\n/** Minimum word count before GEO rules apply */\nconst GEO_MIN_WORDS = 500;\n\n/** Minimum word count before FAQ section rule applies */\nconst FAQ_MIN_WORDS = 800;\n\n/** Minimum percentage of headings that should be question-formatted */\nconst QUESTION_HEADING_THRESHOLD = 0.2;\n\n/** Minimum percentage of sections with direct answer leads */\nconst DIRECT_ANSWER_THRESHOLD = 0.5;\n\n/** Expected data points per N words */\nconst CITATION_WORDS_PER_STAT = 500;\n\n/** Minimum word count before table rule applies */\nconst TABLE_MIN_WORDS = 1000;\n\n/** Minimum word count before entity density rule applies */\nconst ENTITY_MIN_WORDS = 800;\n\n/** Minimum word count before citation block rule applies */\nconst CITATION_BLOCK_MIN_WORDS = 800;\n\n/** Minimum words for a first paragraph to qualify as a citation block */\nconst CITATION_BLOCK_WORD_THRESHOLD = 40;\n\n/** Minimum ratio of sections that should have adequate citation blocks */\nconst CITATION_BLOCK_SECTION_THRESHOLD = 0.5;\n\n/**\n * Rule: Blog posts should use question-formatted headings for LLM discoverability\n * At least 20% of H2/H3 headings should be questions\n */\nexport const geoNoQuestionHeadings: Rule = {\n name: 'geo-no-question-headings',\n severity: 'warning',\n category: 'geo',\n fixStrategy: 'Rephrase some headings as questions (e.g., \"How does X work?\")',\n run: (item: ContentItem): LintResult[] => {\n if (item.contentType !== 'blog') return [];\n\n const wordCount = countWords(item.body);\n if (wordCount < GEO_MIN_WORDS) return [];\n\n const headings = extractHeadings(item.body);\n const subHeadings = headings.filter(h => h.level === 2 || h.level === 3);\n\n if (subHeadings.length === 0) return [];\n\n const questionCount = countQuestionHeadings(item.body);\n const ratio = questionCount / subHeadings.length;\n\n if (ratio < QUESTION_HEADING_THRESHOLD) {\n return [{\n file: getDisplayPath(item),\n field: 'body',\n rule: 'geo-no-question-headings',\n severity: 'warning',\n message: `Only ${questionCount}/${subHeadings.length} (${Math.round(ratio * 100)}%) H2/H3 headings are question-formatted — aim for at least ${Math.round(QUESTION_HEADING_THRESHOLD * 100)}%`,\n suggestion: 'Rephrase some headings as questions (e.g., \"How does X work?\" / \"Was ist X?\") to improve LLM snippet extraction.',\n }];\n }\n\n return [];\n },\n};\n\n/**\n * Rule: Sections should start with direct answers, not filler introductions\n * At least 50% of sections should lead with substantive first sentences\n */\nexport const geoWeakLeadSentences: Rule = {\n name: 'geo-weak-lead-sentences',\n severity: 'warning',\n category: 'geo',\n fixStrategy: 'Start each section with a concise factual sentence that directly answers the heading',\n run: (item: ContentItem): LintResult[] => {\n if (item.contentType !== 'blog') return [];\n\n const wordCount = countWords(item.body);\n if (wordCount < GEO_MIN_WORDS) return [];\n\n const { totalSections, sectionsWithDirectAnswers } = analyzeLeadSentences(item.body);\n\n if (totalSections === 0) return [];\n\n const ratio = sectionsWithDirectAnswers / totalSections;\n\n if (ratio < DIRECT_ANSWER_THRESHOLD) {\n return [{\n file: getDisplayPath(item),\n field: 'body',\n rule: 'geo-weak-lead-sentences',\n severity: 'warning',\n message: `Only ${sectionsWithDirectAnswers}/${totalSections} (${Math.round(ratio * 100)}%) sections start with direct answers — aim for at least ${Math.round(DIRECT_ANSWER_THRESHOLD * 100)}%`,\n suggestion: 'Start each section with a concise factual sentence that directly answers the heading. Avoid filler like \"In this section...\" or \"Let\\'s look at...\".',\n }];\n }\n\n return [];\n },\n};\n\n/**\n * Rule: Content should include statistical data points for citation potential\n * Expect at least 1 data point per 500 words\n */\nexport const geoLowCitationDensity: Rule = {\n name: 'geo-low-citation-density',\n severity: 'warning',\n category: 'geo',\n fixStrategy: 'Add statistics, percentages, or concrete numbers to increase citation potential',\n run: (item: ContentItem): LintResult[] => {\n if (item.contentType !== 'blog') return [];\n\n const wordCount = countWords(item.body);\n if (wordCount < GEO_MIN_WORDS) return [];\n\n const statCount = countStatistics(item.body);\n const expectedMin = Math.floor(wordCount / CITATION_WORDS_PER_STAT);\n\n if (statCount < expectedMin) {\n return [{\n file: getDisplayPath(item),\n field: 'body',\n rule: 'geo-low-citation-density',\n severity: 'warning',\n message: `Found ${statCount} data points but expected at least ${expectedMin} (1 per ${CITATION_WORDS_PER_STAT} words in ${wordCount}-word post)`,\n suggestion: 'Add statistics, percentages, or concrete numbers to increase citation potential in AI-generated answers.',\n }];\n }\n\n return [];\n },\n};\n\n/**\n * Rule: Longer blog posts should include a FAQ section for LLM extraction\n * Applies to posts with 800+ words\n */\nexport const geoMissingFaqSection: Rule = {\n name: 'geo-missing-faq-section',\n severity: 'warning',\n category: 'geo',\n fixStrategy: 'Add an \"## FAQ\" or \"## Frequently Asked Questions\" section with Q&A pairs',\n run: (item: ContentItem): LintResult[] => {\n if (item.contentType !== 'blog') return [];\n\n const wordCount = countWords(item.body);\n if (wordCount < FAQ_MIN_WORDS) return [];\n\n if (!hasFAQSection(item.body)) {\n return [{\n file: getDisplayPath(item),\n field: 'body',\n rule: 'geo-missing-faq-section',\n severity: 'warning',\n message: 'No FAQ section detected in long-form content',\n suggestion: 'Add an \"## FAQ\" or \"## Häufige Fragen\" section with Q&A pairs. FAQ sections are frequently extracted by LLMs and featured in AI answers.',\n }];\n }\n\n return [];\n },\n};\n\n/**\n * Rule: Long-form blog posts should contain at least one data table\n * Tables are highly extractable by AI systems and increase citation rates\n * Applies to posts with 1000+ words\n */\nexport const geoMissingTable: Rule = {\n name: 'geo-missing-table',\n severity: 'warning',\n category: 'geo',\n fixStrategy: 'Add a comparison table, feature matrix, or data summary table',\n run: (item: ContentItem): LintResult[] => {\n if (item.contentType !== 'blog') return [];\n\n const wordCount = countWords(item.body);\n if (wordCount < TABLE_MIN_WORDS) return [];\n\n if (!hasMarkdownTable(item.body)) {\n return [{\n file: getDisplayPath(item),\n field: 'body',\n rule: 'geo-missing-table',\n severity: 'warning',\n message: 'No data table found in long-form content (tables increase AI citation rates by 2.5x)',\n suggestion: 'Add a comparison table, feature matrix, or data summary table. Tables are highly extractable by AI systems and increase citation likelihood.',\n }];\n }\n\n return [];\n },\n};\n\n/**\n * Rule: H2 sections should start with substantial \"citation blocks\"\n * AI systems extract the first paragraph after headings as citation snippets.\n * At least 50% of sections should have first paragraphs of 40+ words.\n * Applies to posts with 800+ words\n */\nexport const geoShortCitationBlocks: Rule = {\n name: 'geo-short-citation-blocks',\n severity: 'warning',\n category: 'geo',\n fixStrategy: 'Start each section with a 40-60 word paragraph that directly answers the heading',\n run: (item: ContentItem): LintResult[] => {\n if (item.contentType !== 'blog') return [];\n\n const wordCount = countWords(item.body);\n if (wordCount < CITATION_BLOCK_MIN_WORDS) return [];\n\n const { totalSections, sectionsWithAdequateBlocks } = analyzeCitationBlocks(item.body);\n\n if (totalSections === 0) return [];\n\n const ratio = sectionsWithAdequateBlocks / totalSections;\n\n if (ratio < CITATION_BLOCK_SECTION_THRESHOLD) {\n return [{\n file: getDisplayPath(item),\n field: 'body',\n rule: 'geo-short-citation-blocks',\n severity: 'warning',\n message: `Only ${sectionsWithAdequateBlocks}/${totalSections} sections have substantial lead paragraphs (${CITATION_BLOCK_WORD_THRESHOLD}+ words) — AI systems extract these as citation blocks`,\n suggestion: `Start each section with a ${CITATION_BLOCK_WORD_THRESHOLD}-60 word paragraph that directly answers the heading question. These 'citation blocks' are what AI systems extract and cite.`,\n }];\n }\n\n return [];\n },\n};\n\n/**\n * Factory: Create the entity density rule with project-specific brand/city\n * When brandName is empty, the brand check is skipped.\n * When brandCity is empty, the city check is skipped.\n */\nexport function createGeoEntityRule(brandName: string, brandCity: string): Rule {\n return {\n name: 'geo-low-entity-density',\n severity: 'warning',\n category: 'geo',\n fixStrategy: 'Mention brand name and location naturally in the content body',\n run: (item: ContentItem): LintResult[] => {\n if (item.contentType !== 'blog') return [];\n\n const wordCount = countWords(item.body);\n if (wordCount < ENTITY_MIN_WORDS) return [];\n\n const results: LintResult[] = [];\n const displayPath = getDisplayPath(item);\n\n if (brandName && countEntityMentions(item.body, brandName) === 0) {\n results.push({\n file: displayPath,\n field: 'body',\n rule: 'geo-low-entity-density',\n severity: 'warning',\n message: `Brand name '${brandName}' not mentioned in content body — reduces entity recognition by AI systems`,\n suggestion: 'Mention the brand name naturally in the content to strengthen entity signals for AI systems.',\n });\n }\n\n if (brandCity && countEntityMentions(item.body, brandCity) === 0) {\n results.push({\n file: displayPath,\n field: 'body',\n rule: 'geo-low-entity-density',\n severity: 'warning',\n message: `Location '${brandCity}' not mentioned in content body — reduces local entity recognition`,\n suggestion: 'Include a location reference to strengthen local entity recognition for AI search.',\n });\n }\n\n return results;\n },\n };\n}\n\n/** Static GEO rules (no config dependency) */\nexport const geoStaticRules: Rule[] = [\n geoNoQuestionHeadings,\n geoWeakLeadSentences,\n geoLowCitationDensity,\n geoMissingFaqSection,\n geoMissingTable,\n geoShortCitationBlocks,\n];\n\n/**\n * Factory: Build the complete GEO rule set from config\n * Returns all 7 rules: 6 static + 1 entity density from factory\n */\nexport function createGeoRules(geo: { brandName: string; brandCity: string }): Rule[] {\n return [\n ...geoStaticRules,\n createGeoEntityRule(geo.brandName, geo.brandCity),\n ];\n}\n\n/**\n * Alias for backwards compatibility: static rules without entity density\n * Use when no GEO config is available\n */\nexport const geoRules: Rule[] = geoStaticRules;\n","/**\n * Category Validation Rules\n * Validates blog categories against a user-provided canonical list\n */\n\nimport type { Rule, ContentItem, LintResult } from '../types.js';\nimport { getDisplayPath } from '../utils/display-path.js';\n\n/**\n * Find the closest canonical category for a misspelled value\n */\nfunction findSuggestion(value: string, canonicalCategories: Set<string>): string | undefined {\n const lower = value.toLowerCase();\n for (const canonical of canonicalCategories) {\n if (canonical.toLowerCase() === lower) {\n return canonical;\n }\n }\n return undefined;\n}\n\n/**\n * Factory: Create category validation rules from a list of valid categories\n * Returns an empty array when categories is empty (skip both rules).\n */\nexport function createCategoryRules(categories: string[]): Rule[] {\n if (categories.length === 0) {\n return [];\n }\n\n const canonicalCategories = new Set(categories);\n\n const categoryInvalid: Rule = {\n name: 'category-invalid',\n severity: 'error',\n category: 'content',\n fixStrategy: 'Use a valid category from the configured list',\n run: (item: ContentItem): LintResult[] => {\n if (!item.categories || item.categories.length === 0) return [];\n\n const results: LintResult[] = [];\n\n for (const cat of item.categories) {\n if (!canonicalCategories.has(cat)) {\n const suggested = findSuggestion(cat, canonicalCategories);\n results.push({\n file: getDisplayPath(item),\n field: 'categories',\n rule: 'category-invalid',\n severity: 'error',\n message: `Invalid category \"${cat}\"`,\n suggestion: suggested\n ? `Did you mean \"${suggested}\"? Valid categories: ${[...canonicalCategories].join(', ')}`\n : `Valid categories: ${[...canonicalCategories].join(', ')}`,\n });\n }\n }\n\n return results;\n },\n };\n\n const missingCategories: Rule = {\n name: 'missing-categories',\n severity: 'warning',\n category: 'content',\n fixStrategy: 'Add at least one category from the configured list',\n run: (item: ContentItem): LintResult[] => {\n if (item.contentType !== 'blog') return [];\n\n if (!item.categories || item.categories.length === 0) {\n return [{\n file: getDisplayPath(item),\n field: 'categories',\n rule: 'missing-categories',\n severity: 'warning',\n message: 'Blog post has no categories',\n suggestion: `Add at least one category: ${[...canonicalCategories].join(', ')}`,\n }];\n }\n return [];\n },\n };\n\n return [categoryInvalid, missingCategories];\n}\n","/**\n * Canonical URL Validation Rules\n * Validates canonical URL presence and format\n */\n\nimport type { Rule, ContentItem, LintResult } from '../types.js';\nimport { getDisplayPath } from '../utils/display-path.js';\n\n/**\n * Factory: Create canonical URL validation rules for the given site URL\n */\nexport function createCanonicalRules(siteUrl: string): Rule[] {\n /**\n * Rule: Every indexed page should have a canonical URL (permalink)\n * Skips noindexed pages since they don't need canonical tags\n */\n const canonicalMissing: Rule = {\n name: 'canonical-missing',\n severity: 'warning',\n category: 'seo',\n fixStrategy: 'Ensure the content has a valid slug so a permalink can be generated',\n run: (item: ContentItem): LintResult[] => {\n if (item.noindex) return [];\n\n if (!item.permalink || item.permalink.trim().length === 0) {\n return [{\n file: getDisplayPath(item),\n field: 'permalink',\n rule: 'canonical-missing',\n severity: 'warning',\n message: 'Missing canonical URL (permalink)',\n suggestion: 'Ensure the content has a valid slug so a permalink can be generated',\n }];\n }\n return [];\n },\n };\n\n /**\n * Rule: Canonical URL must be a valid relative path or absolute URL on the site domain\n * Catches misconfigured permalinks (wrong domain, http://, fragments, etc.)\n */\n const canonicalMalformed: Rule = {\n name: 'canonical-malformed',\n severity: 'warning',\n category: 'seo',\n fixStrategy: 'Use a relative path (e.g., /blog/my-post) or absolute URL on the site domain',\n run: (item: ContentItem): LintResult[] => {\n if (!item.permalink || item.permalink.trim().length === 0) return [];\n\n const permalink = item.permalink.trim();\n\n // Valid: relative path starting with /\n if (permalink.startsWith('/')) return [];\n\n // Valid: absolute URL on site domain\n if (permalink.startsWith(siteUrl)) return [];\n\n return [{\n file: getDisplayPath(item),\n field: 'permalink',\n rule: 'canonical-malformed',\n severity: 'warning',\n message: `Canonical URL has unexpected format: \"${permalink}\"`,\n suggestion: `Canonical should be a relative path (e.g., /blog/my-post) or absolute URL starting with ${siteUrl}`,\n }];\n },\n };\n\n return [canonicalMissing, canonicalMalformed];\n}\n","/**\n * Rule Registry\n * Builds the complete rule set from configuration and provides runner functions\n */\n\nimport type { Rule, ContentItem, RuleContext, LintResult } from '../types.js';\nimport type { GeoLintConfig } from '../config/types.js';\nimport type { LinkExtractor } from '../utils/link-extractor.js';\n\n// Static rule imports\nimport { titleRules } from './title-rules.js';\nimport { descriptionRules } from './description-rules.js';\nimport { duplicateRules } from './duplicate-rules.js';\nimport { headingRules } from './heading-rules.js';\nimport { imageRules } from './image-rules.js';\nimport { contentRules } from './content-rules.js';\nimport { ogRules } from './og-rules.js';\nimport { performanceRules } from './performance-rules.js';\nimport { robotsRules } from './robots-rules.js';\nimport { slugRules } from './slug-rules.js';\nimport { i18nRules } from './i18n-rules.js';\nimport { dateRules } from './date-rules.js';\nimport { schemaRules } from './schema-rules.js';\nimport { keywordCoherenceRules } from './keyword-coherence-rules.js';\n\n// Factory rule imports\nimport { createLinkRules } from './link-rules.js';\nimport { createExternalLinkRules } from './external-link-rules.js';\nimport { createOrphanRules } from './orphan-rules.js';\nimport { createGeoRules } from './geo-rules.js';\nimport { createCategoryRules } from './category-rules.js';\nimport { createCanonicalRules } from './canonical-rules.js';\n\n/**\n * Build the complete rule set from config and link extractor\n */\nexport function buildRules(config: GeoLintConfig, linkExtractor: LinkExtractor): Rule[] {\n const rules: Rule[] = [\n ...titleRules,\n ...descriptionRules,\n ...duplicateRules,\n ...headingRules,\n ...createLinkRules(linkExtractor),\n ...createExternalLinkRules(linkExtractor),\n ...imageRules,\n ...contentRules,\n ...ogRules,\n ...performanceRules,\n ...createOrphanRules(linkExtractor),\n ...robotsRules,\n ...slugRules,\n ...i18nRules,\n ...dateRules,\n ...(config.categories.length > 0 ? createCategoryRules(config.categories) : []),\n ...schemaRules,\n ...createGeoRules(config.geo),\n ...keywordCoherenceRules,\n ...createCanonicalRules(config.siteUrl),\n ];\n\n // Apply user rule overrides (severity changes + disabling)\n return rules.map(rule => applyRuleOverride(rule, config.rules));\n}\n\n/**\n * Apply a user-configured severity override to a rule.\n * 'off' disables the rule by replacing its run function with a no-op.\n */\nfunction applyRuleOverride(\n rule: Rule,\n overrides: Record<string, 'error' | 'warning' | 'off'>\n): Rule {\n const override = overrides[rule.name];\n if (!override) return rule;\n if (override === 'off') return { ...rule, run: () => [] };\n return { ...rule, severity: override };\n}\n\n/**\n * Run a single rule against a content item (with error isolation)\n */\nexport function runRule(rule: Rule, item: ContentItem, context: RuleContext): LintResult[] {\n try {\n return rule.run(item, context);\n } catch (error) {\n console.error(`Rule ${rule.name} failed on ${item.slug}:`, error);\n return [];\n }\n}\n\n/**\n * Run all provided rules against a single content item\n */\nexport function runRulesOnItem(\n item: ContentItem,\n context: RuleContext,\n rules: Rule[]\n): LintResult[] {\n const results: LintResult[] = [];\n\n for (const rule of rules) {\n const ruleResults = runRule(rule, item, context);\n results.push(...ruleResults);\n }\n\n return results;\n}\n\n/**\n * Run all provided rules against all content items\n */\nexport function runAllRules(\n items: ContentItem[],\n context: RuleContext,\n rules: Rule[]\n): LintResult[] {\n const results: LintResult[] = [];\n\n for (const item of items) {\n const itemResults = runRulesOnItem(item, context, rules);\n results.push(...itemResults);\n }\n\n return results;\n}\n\n// Re-export static rules for selective use\nexport { titleRules } from './title-rules.js';\nexport { descriptionRules } from './description-rules.js';\nexport { duplicateRules } from './duplicate-rules.js';\nexport { headingRules } from './heading-rules.js';\nexport { imageRules } from './image-rules.js';\nexport { contentRules } from './content-rules.js';\nexport { ogRules } from './og-rules.js';\nexport { performanceRules } from './performance-rules.js';\nexport { robotsRules } from './robots-rules.js';\nexport { slugRules } from './slug-rules.js';\nexport { i18nRules } from './i18n-rules.js';\nexport { dateRules } from './date-rules.js';\nexport { schemaRules } from './schema-rules.js';\nexport { keywordCoherenceRules } from './keyword-coherence-rules.js';\nexport { geoStaticRules, geoRules } from './geo-rules.js';\n\n// Re-export factory functions for selective use\nexport { createLinkRules } from './link-rules.js';\nexport { createExternalLinkRules } from './external-link-rules.js';\nexport { createOrphanRules } from './orphan-rules.js';\nexport { createGeoRules, createGeoEntityRule } from './geo-rules.js';\nexport { createCategoryRules } from './category-rules.js';\nexport { createCanonicalRules } from './canonical-rules.js';\n","/**\n * @ijonis/geo-lint Reporter\n * Formats and outputs lint results with color-coding\n */\n\nimport type { LintResult, Severity } from './types.js';\n\n/** ANSI color codes for terminal output */\nconst COLORS = {\n reset: '\\x1b[0m',\n red: '\\x1b[31m',\n green: '\\x1b[32m',\n yellow: '\\x1b[33m',\n cyan: '\\x1b[36m',\n dim: '\\x1b[2m',\n bold: '\\x1b[1m',\n} as const;\n\nexport interface ReportSummary {\n errors: number;\n warnings: number;\n passed: number;\n total: number;\n excluded: number;\n}\n\n/** Group results by file path */\nfunction groupByFile(results: LintResult[]): Map<string, LintResult[]> {\n const grouped = new Map<string, LintResult[]>();\n for (const result of results) {\n const existing = grouped.get(result.file) || [];\n existing.push(result);\n grouped.set(result.file, existing);\n }\n return grouped;\n}\n\n/** Format a single lint result line */\nfunction formatResult(result: LintResult): string {\n const { red, yellow, dim, reset } = COLORS;\n const icon = result.severity === 'error' ? `${red}\\u2717${reset}` : `${yellow}\\u26A0${reset}`;\n const ruleTag = `${dim}[${result.rule}]${reset}`;\n\n let line = ` ${icon} ${ruleTag} ${result.message}`;\n\n if (result.line) {\n line += ` ${dim}(line ${result.line})${reset}`;\n }\n\n if (result.suggestion) {\n line += `\\n ${dim}\\u2192 ${result.suggestion}${reset}`;\n }\n\n return line;\n}\n\n/** Print results grouped by severity */\nfunction printResultsBySection(\n results: LintResult[],\n severity: Severity,\n label: string,\n): void {\n const filtered = results.filter(r => r.severity === severity);\n if (filtered.length === 0) return;\n\n const { red, yellow, bold, reset } = COLORS;\n const color = severity === 'error' ? red : yellow;\n const grouped = groupByFile(filtered);\n\n console.log(`\\n${color}${bold}${label} (${filtered.length}):${reset}\\n`);\n\n for (const [file, fileResults] of grouped) {\n console.log(` ${bold}${file}${reset}`);\n for (const result of fileResults) {\n console.log(formatResult(result));\n }\n console.log('');\n }\n}\n\n/** Print summary statistics */\nfunction printSummary(summary: ReportSummary): void {\n const { red, yellow, green, bold, reset, dim } = COLORS;\n\n console.log(`${dim}${'─'.repeat(50)}${reset}`);\n console.log(`${bold}Summary:${reset}`);\n\n if (summary.errors > 0) {\n console.log(` ${red}\\u2717 ${summary.errors} error(s)${reset} ${dim}(build blocked)${reset}`);\n }\n if (summary.warnings > 0) {\n console.log(` ${yellow}\\u26A0 ${summary.warnings} warning(s)${reset}`);\n }\n if (summary.passed > 0) {\n console.log(` ${green}\\u2713 ${summary.passed} file(s) passed${reset}`);\n }\n if (summary.excluded > 0) {\n console.log(` ${dim}\\u2298 ${summary.excluded} file(s) excluded${reset}`);\n }\n\n console.log(`\\n ${dim}Total: ${summary.total} files checked${reset}\\n`);\n}\n\n/** Format and output all lint results. Returns summary for exit code. */\nexport function formatResults(\n results: LintResult[],\n totalFiles: number,\n excludedFiles: number = 0,\n): ReportSummary {\n const { bold, reset, cyan, green, dim } = COLORS;\n\n const errors = results.filter(r => r.severity === 'error').length;\n const warnings = results.filter(r => r.severity === 'warning').length;\n const filesWithIssues = new Set(results.map(r => r.file)).size;\n const passed = totalFiles - filesWithIssues;\n\n const summary: ReportSummary = { errors, warnings, passed, total: totalFiles, excluded: excludedFiles };\n\n console.log(`\\n${cyan}${bold}GEO Lint Results${reset}`);\n console.log(`${cyan}${'═'.repeat(50)}${reset}`);\n\n if (results.length === 0) {\n console.log(`\\n${green}${bold}\\u2713 GEO Lint: All checks passed!${reset}\\n`);\n console.log(` ${dim}${totalFiles} files checked${reset}\\n`);\n return summary;\n }\n\n printResultsBySection(results, 'error', 'ERRORS');\n printResultsBySection(results, 'warning', 'WARNINGS');\n printSummary(summary);\n\n return summary;\n}\n\n/** Format results as JSON for agent consumption */\nexport function formatResultsJson(results: LintResult[]): string {\n return JSON.stringify(results, null, 2);\n}\n\n/** Print a simple progress message */\nexport function printProgress(message: string): void {\n const { dim, reset } = COLORS;\n console.log(`${dim}${message}${reset}`);\n}\n","/**\n * @ijonis/geo-lint Content Adapter Types\n */\n\nimport type { ContentItem } from '../types.js';\n\n/**\n * Adapter interface for custom content sources.\n * Implement this to integrate geo-lint with any CMS or content pipeline.\n */\nexport interface ContentAdapter {\n /** Load all content items from the source */\n loadItems(projectRoot: string): ContentItem[] | Promise<ContentItem[]>;\n}\n\n/**\n * Create an adapter from a simple function\n */\nexport function createAdapter(\n fn: (projectRoot: string) => ContentItem[] | Promise<ContentItem[]>,\n): ContentAdapter {\n return { loadItems: fn };\n}\n"],"mappings":"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","names":["import_node_path","import_node_fs","import_node_fs","import_node_path","matter","isInCodeBlock","import_node_fs","import_node_path","isInCodeBlock"]}
|