koala73/worldmonitor · error

needs a summary for the llms.txt Comparisons section

Error message

 needs a summary for the llms.txt Comparisons section

What it means

comparisonDiscoveryEntries builds the llms.txt Comparisons section, listing each comparison page with its summary as the description. A page without a non-blank summary string cannot be advertised in llms.txt, so the build throws '<slug> needs a summary for the llms.txt Comparisons section'.

Solutions

  1. Add a concise one-to-two sentence summary string to the named page seed.
  2. Ensure the field is exactly summary (not description) on the merged page object.
  3. Confirm applyComparisonNarrative's merge isn't overwriting or dropping summary from the seed.

Example fix

// before
{
  slug: 'chokepoint-monitoring-tools',
  // no summary
}
// after
{
  slug: 'chokepoint-monitoring-tools',
  summary: 'Compare the leading chokepoint monitoring tools and how World Monitor tracks maritime chokepoints in real time.',
}
Defensive patterns

Strategy: validation

Validate before calling

COMPARISON_PAGES.forEach((p) => {
  if (typeof p.summary !== 'string' || !p.summary.trim()) throw new Error(`${p.slug} missing summary`);
});

Type guard

const hasSummary = (p) => typeof p?.summary === 'string' && p.summary.trim() !== '';

Prevention

When it happens

Trigger: Mapping COMPARISON_PAGES in comparisonDiscoveryEntries when a page's summary is undefined, not a string, an empty string, or whitespace-only (summary.trim() === '').

Common situations: New comparison page seed missing the summary field; summary defined as an empty placeholder ''; summary moved to a different field name (e.g. description) during a refactor; llms.txt discovery build run against seeds from another branch.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15). Data as JSON: /api/errors/0036b96f53536c14. Report an issue: GitHub.

Appendix: source

Thrown at scripts/build-comparison-pages.mjs:731

    throw new Error(label + ' meta description must be 90-160 chars (got ' + length + ')');
  }
}

/**
 * One `[title](url): description` entry per /compare/ route for llms.txt and
 * llms-full.txt (#7746). Titles are the page h1 — the query the page answers,
 * which is the string an engine matches — and descriptions are each page's
 * hand-written `summary`, so the discovery index cannot drift from the pages.
 */
export function comparisonDiscoveryEntries(baseUrl) {
  const hub = {
    title: COMPARE_HUB_TITLE,
    url: new URL(COMPARE_HUB_PATH, baseUrl).href,
    description: COMPARE_HUB_DESCRIPTION,
  };
  const pages = COMPARISON_PAGES.map((page) => {
    if (typeof page.summary !== 'string' || page.summary.trim() === '') {
      throw new Error(page.slug + ' needs a summary for the llms.txt Comparisons section');
    }
    return { title: page.h1, url: new URL(page.path, baseUrl).href, description: page.summary };
  });
  return [hub, ...pages];
}

function renderCompareHub({ tpl, baseUrl, lastmod, snapshotDate }) {
  const { escapeHtml, breadcrumbLd, pageDocument } = tpl;
  const path = COMPARE_HUB_PATH;
  const description = COMPARE_HUB_DESCRIPTION;
  assertMetaDescription(description, 'compare hub');
  const cards = COMPARISON_PAGES
    .map((page) => '        <a class="card" href="' + escapeHtml(page.path) + '"><strong>' + escapeHtml(page.h1) + '</strong><br><span>' + escapeHtml(page.claim) + '</span></a>')
    .join('\n');
  const body = [
    '      <p class="eyebrow">Compare</p>',
    '      <h1>Compare World Monitor</h1>',
    '      <p class="lede">' + escapeHtml(COMPARE_HUB_NARRATIVE.lede) + '</p>',

View on GitHub (pinned to 7d06c8633d)