@misar/seo-guards (0.1.0)

Published 2026-07-21 09:08:01 +00:00 by misaradmin

Installation

@misar:registry=
npm install @misar/seo-guards@0.1.0
"@misar/seo-guards": "0.1.0"

About this package

@misar/seo-guards

Advisory-first SEO / AEO / GEO organic-readiness validators for the Misar AI monorepo. Zero runtime dependencies (plain Node.js ESM, Node built-ins only). Same config schema, guard shape, CLI contract and Forgejo-annotation output as @misar/guards — this is the organic-growth sibling of the convention linter.

It validates the substrate that lets search engines and AI answer engines (Google AI Overview / Gemini, Perplexity, ChatGPT search, Bing) index and cite Misar pages. It does not write content, earn authority, or enforce per-request bot behavior — see docs/DEVOPS/organic-readiness.md for the honest 3-layer model and what CI does / does not guarantee.

Install / run

Runs via plain node — no install step required in CI:

# from a repo root that has a seo-guards.config.json
pnpm dlx @misar/seo-guards --advisory
# or, in the monorepo
node packages/seo-guards/bin/misar-seo-guards.mjs --advisory

CLI

misar-seo-guards [options]

  --advisory        Emit ::warning annotations and ALWAYS exit 0 (default posture).
  --blocking        Force blocking mode even if the config is advisory.
  --only=<ids>      Run only these guard ids (comma-separated).
  --json            Machine-readable JSON ({ ran, counts, advisory, findings }).
  --config=<path>   Path to a config file (default: ./seo-guards.config.json).
  --list            List all guard ids and exit.
  -h, --help        Show help.

Exit: 0 no findings (or advisory) · 1 blocking findings · 2 usage/config error.

Advisory-first: unlike @misar/guards, advisory defaults to true here (absent config → advisory). Findings default to warning severity. A repo only starts failing CI on SEO regressions after it deliberately sets "advisory": false (or passes --blocking). This is the advisory → blocking ramp.

Config — seo-guards.config.json

Identical schema to @misar/guards:

{
  "advisory": true,
  "ignore": ["packages/seo-guards", "apps/template"],
  "guards": {
    "json-ld-presence": true,
    "json-ld-validity": true,
    "metadata-completeness": { "multiLocale": true },
    "hreflang-correctness": true,
    "answer-block-extractability": { "answerMaxWords": 80, "wallWords": 400 },
    "robots-sitemap-llms-drift": {
      "appSitemap": "src/app/sitemap.ts",
      "appRobots": "src/app/robots.ts",
      "landingRobots": "src/landing/src/app/robots.txt/route.ts"
    }
  }
}
  • A guard listed true/{opts} is enabled; false disables it. Not listed → on iff default-on (all six SEO guards are default-on).
  • ignore entries are repo-relative path prefixes (also substring-matched).
  • See seo-guards.config.example.json.

Guards

id what it flags (all advisory)
json-ld-presence App root emits no WebSite/Organization JSON-LD anywhere; a content route (blog/article/post/docs/faq/[slug]) renders a page but references no structured data. Import of a shared <StructuredData/> counts as presence.
json-ld-validity Statically-parseable <script type="application/ld+json"> blocks that fail JSON.parse (error severity), declare a @type missing its schema.org required props, or use a deprecated @type. JSON.stringify(var) blocks are skipped (not statically knowable).
metadata-completeness A declared Next.js metadata / generateMetadata scope with title but no description; openGraph/twitter present without the other (card parity); page-level metadata with no alternates.canonical; multi-locale apps missing alternates.languages. Only fires on files that already declare metadata (inheritance-safe).
hreflang-correctness alternates.languages maps with invalid BCP-47 codes, no x-default, or (when the app's locale set is statically known and the map is literal) omitted declared locales. Dynamically-generated maps are validated only for their literal keys.
answer-block-extractability AEO/GEO heuristic on published content (.mdx; .md under /content/ or /articles/): a question-form heading not followed by an answer-first paragraph, a hedged opener, or a wall-of-text with zero question headings.
robots-sitemap-llms-drift Ports MisarReach's sitemap/robots drift check and extends it: llms.txt/llms-full.txt presence, robots ↔ sitemap ↔ llms cross-reference, and host agreement across the three. Each sub-check is a no-op if its inputs are absent.

The spec behind the AEO/GEO guards is captured in the geo-aio-citation-surfaces and paa-people-also-ask notes: the citation unit is a passage (question heading + concise answer-first block), WebSite.name drives the AI-Overview site-name chip, and Organization anchors the brand entity.

Design guarantees

  • Low false positives. Guards trigger on structure (headings, declared metadata scopes, parseable JSON-LD), never on prose quality, and each check no-ops when its inputs are absent.
  • Zero dependencies. Runs under any Node ≥18 with no install.
  • Additive. Never modifies app behavior; it only reads and reports.

Tests

node tests/test.mjs   # fixtures written to a temp dir at runtime, never committed

Keywords

misar ci seo aeo geo json-ld hreflang guards
Details
npm
2026-07-21 09:08:01 +00:00
3
latest
18 KiB
Assets (1)
Versions (1) View all
0.1.0 2026-07-21