Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/launch/ACTIVITY-LOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,18 @@ and the developer channels have produced no measurable visit.

### 2026-11-03

- **The open issues, worked: v0.0.58 shipped, two product issues closed.** On "look at issues
opened and work in those": the five engine issues the founder's other session filed from
dogfooding integrallis.com (#19 render-blocking counted the inline style it recommended,
#21 Medium's 403s reported as broken links, #23 /index.html keyed the homepage twice so
NAV_NOT_IN_HTML saw zero links, #24 two recommendations worse than what the checks accept,
#20 the auto-fixer's fabricated SoftwareApplication with a free Offer) fixed in rankcli #25,
one commit and one test each, plus the engine side of rankcli.dev #30 (a lazy image's origin
needs no preconnect). Released as cli 0.0.58, agent-runtime 0.0.46, mcp-server 0.0.31. On
rankcli.dev: #32 (the free hosted PR's cost in the outcome report, PR #38) and #33 (first-touch
source recorded once per account, installs by source in the report, PR #39 with a migration)
closed; #31, #34 and #35 wait on the founder's decisions or on external PR evidence; #17 on
the public repo waits on the founder's npm 2FA. Hosted pins to 0.0.46/0.0.31 in this PR.
- **"Run them": the dev.to cross-post is live and the Nuxt modules directory PR is open.** On
the founder's direct instruction the classifier let two of the three through: the write-up
is on dev.to (https://dev.to/rankcli/what-the-sites-behind-the-400-most-popular-npm-packages-actually-do-2e8b,
Expand Down
9 changes: 8 additions & 1 deletion docs/launch/VERIFIED-NUMBERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,17 @@ like a regression when nothing has changed:

| Measurement | Score | What it is |
|---|---|---|
| `--max-pages 1` | **92** since 2026-11-03 on `@rankcli/cli@0.0.57`, 0.0.56 and 0.0.55 alike (89 from 2026-11-02 on 0.0.54, 0.0.53 and 0.0.52 alike; 90 from 2026-10-29 to 2026-11-02 on 0.0.52 and 0.0.51; 85 on 0.0.50; 80 on 0.0.49; 75 until 2026-10-14) | The homepage. **This is the number all launch copy cites.** |
| `--max-pages 1` | **93** since 2026-11-03 on `@rankcli/cli@0.0.58` (92 earlier that day on 0.0.57, 0.0.56 and 0.0.55; 89 from 2026-11-02 on 0.0.54, 0.0.53 and 0.0.52 alike; 90 from 2026-10-29 to 2026-11-02 on 0.0.52 and 0.0.51; 85 on 0.0.50; 80 on 0.0.49; 75 until 2026-10-14) | The homepage. **This is the number all launch copy cites.** |
| default crawl (5 pages) | 72 before 2026-10-14; 8 pages score 80 after | Averages in weaker inner pages |
| SEO gate CI run | 81 (last checked 2026-10-02) | The published Action's own scope |

**Re-verified 2026-11-03 on the published `@rankcli/cli@0.0.58`, from an empty directory:
homepage 93/100, 0 errors, 2 warnings, 17 notices, AI Readiness 94, performance 72, React;
310 checks run, 308 passed.** The point gained is `MISSING_PRECONNECT` no longer firing for
the lazy footer badge (0.0.58, rankcli.dev #30); performance 60 to 72, nothing new, the two
warnings left are `AI_UNCITED_STATISTICS` and `RENDER_BLOCKING_RESOURCES`. The hero run was
re-recorded on 0.0.58 and shows the same 93.

**Re-verified 2026-11-03 on the published `@rankcli/cli@0.0.57`, from an empty directory:
homepage 92/100, 0 errors, 3 warnings, 17 notices, AI Readiness 94, content 88, React; 310
checks run, 307 passed.** Two notices fewer than 0.0.55 (`LOW_AI_CITATION_READINESS` and
Expand Down
4 changes: 2 additions & 2 deletions packages/audit-worker/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@rankcli/agent-runtime": "0.0.45",
"@rankcli/mcp-server": "0.0.30",
"@rankcli/agent-runtime": "0.0.46",
"@rankcli/mcp-server": "0.0.31",
"@modelcontextprotocol/sdk": "^1.30.0",
"@supabase/supabase-js": "^2.39.0",
"fastify": "^4.25.0",
Expand Down
4 changes: 2 additions & 2 deletions packages/saas/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@
},
"devDependencies": {
"@playwright/test": "^1.57.0",
"@rankcli/agent-runtime": "0.0.45",
"@rankcli/mcp-server": "0.0.30",
"@rankcli/agent-runtime": "0.0.46",
"@rankcli/mcp-server": "0.0.31",
"@resvg/resvg-js": "^2.6.2",
"@tailwindcss/typography": "^0.5.19",
"@testing-library/jest-dom": "^6.9.1",
Expand Down
2 changes: 1 addition & 1 deletion packages/saas/src/data/hero-run/meta.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"command": "rankcli audit -u https://rankcli.dev --max-pages 1",
"cliPackage": "@rankcli/cli",
"cliVersion": "0.0.55",
"cliVersion": "0.0.58",
"recordedAt": "2026-11-03",
"exitCode": 0,
"recordedBy": "scripts/record-hero-run.ts, empty HOME, NO_COLOR=1"
Expand Down
23 changes: 10 additions & 13 deletions packages/saas/src/data/hero-run/raw.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,32 @@
SEO AUDIT REPORT: https://rankcli.dev
════════════════════════════════════════════════════════════

Score: 92/100
Score: 93/100
Pages audited: 1
Issues found: 22
Time: 1881ms
Issues found: 19
Time: 1565ms
Framework: React

Health Scores:
Crawlability: ███████████████████░ 95%
On-Page: ███████████████████░ 94%
Content: █████████████████░░░ 85%
Performance: ████████████░░░░░░░░ 60%
Content: ██████████████████░░ 88%
Performance: ██████████████░░░░░░ 72%
Security: ███████████████████░ 95%
Local SEO: ███████████████████░ 96%
AI Readiness: ███████████████████░ 93%
AI Readiness: ███████████████████░ 94%
hygiene on evidence-backed levers; not a citation predictor

Issues by Severity:
● Errors: 0
● Warnings: 3
● Notices: 19
● Warnings: 2
● Notices: 17

Warnings (3):
Warnings (2):
! Render-blocking resources detected RENDER_BLOCKING_RESOURCES
! Missing preconnect hints for third-party origins MISSING_PRECONNECT
! Statistics without source citations AI_UNCITED_STATISTICS

Notices (19):
Notices (17):
i Low text-to-HTML ratio LOW_TEXT_HTML_RATIO
i Meta description will be truncated in SERP META_DESC_TRUNCATED_SERP
i No business address detected ADDRESS_NOT_DETECTED
Expand All @@ -38,7 +37,6 @@
i Hero image not preloaded HERO_IMAGE_NOT_PRELOADED
i Commercial content without trust signals COMMERCIAL_NO_TRUST_BADGES
i No testimonials or reviews found NO_SOCIAL_PROOF
i Low AI citation readiness for fresh content LOW_AI_CITATION_READINESS
i No dedicated references section AI_NO_REFERENCES_SECTION
i Questions lack direct, quotable answers AI_INDIRECT_ANSWERS
i Consider using CSS aspect-ratio for images IMAGES_NO_ASPECT_RATIO
Expand All @@ -47,7 +45,6 @@
affects 2 pages
i Missing cache validators (ETag/Last-Modified) CACHING_NO_VALIDATORS
i robots.txt disallows AI training crawlers AI_TRAINING_BOTS_DISALLOWED
i No agent-facing discovery surface found NO_AGENT_EXPERIENCE_SURFACE
i No Last-Modified header NO_LAST_MODIFIED
i IndexNow not implemented INDEXNOW_NOT_IMPLEMENTED

Expand Down
7 changes: 6 additions & 1 deletion packages/saas/src/pages/ChangelogPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ import { SEO } from '@/components/SEO';
import frameworks from '@/config/frameworks.json';

const highlights = [
{
title: 'v0.0.58: five findings from dogfooding, fixed',
description:
'Found by auditing a plain static site and reading the source behind each finding. An inline style block is no longer a render-blocking resource (the check had recommended inlining critical CSS and then counted it). A 401, 403, 405 or 429 from an external link is retried with a browser-like GET and, if still refused, is indeterminate rather than broken, so links to Medium stop being reported dead. /index.html is the homepage, so NAV_NOT_IN_HTML no longer reports zero links over ten static ones. The FAQ and plaintext-email recommendations now say what the checks accept. The auto-fixer writes a WebSite or WebPage from the page\'s own title and description, never a SoftwareApplication with a free Offer. A lazy-loaded image\'s origin no longer asks for a preconnect.',
},
{
title: 'v0.0.57: the second pass over the always-on findings',
description:
Expand Down Expand Up @@ -68,7 +73,7 @@ export function ChangelogPage() {
<div className="min-h-screen bg-background">
<SEO
title="Changelog"
description="What's new in RankCLI: v0.0.57 and v0.0.55 remove the 35 findings the npm corpus showed firing on most sites; v0.0.54 reads AI crawler access by bot class, catches streaming metadata and draws the link graph without JavaScript; v0.0.53 turns the audit into a CI gate with one rule syntax, baselines, SARIF and JUnit output and a config file; v0.0.52 runs every check in the CLI, the MCP server and the GitHub Action under the FSL-1.1-MIT license; agentic auto-fix, keyword rank tracking, GEO checks for AI search, the MCP server for AI assistants, and framework-specific fix recipes."
description="What's new in RankCLI: v0.0.58 fixes five findings from dogfooding; v0.0.57 and v0.0.55 remove the 35 findings the npm corpus showed firing on most sites; v0.0.54 reads AI crawler access by bot class, catches streaming metadata and draws the link graph without JavaScript; v0.0.53 turns the audit into a CI gate with one rule syntax, baselines, SARIF and JUnit output and a config file; v0.0.52 runs every check in the CLI, the MCP server and the GitHub Action under the FSL-1.1-MIT license; agentic auto-fix, keyword rank tracking, GEO checks for AI search, the MCP server for AI assistants, and framework-specific fix recipes."
path="/changelog"
/>
<Navbar />
Expand Down
55 changes: 31 additions & 24 deletions packages/saas/supabase/functions/_shared/audit/engine.bundle.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Auto-generated Deno-compatible audit bundle
// Do not edit manually - regenerate with: pnpm build:deno
// Generated: 2026-11-03T16:23:38.468Z
// Generated: 2026-11-03T19:05:50.455Z

// Polyfill for Node's Buffer if needed
if (typeof globalThis.Buffer === 'undefined') {
Expand Down Expand Up @@ -9837,7 +9837,7 @@ Allow: /`
title: "Email addresses in plain text",
description: "Email addresses found in page HTML without obfuscation.",
impact: "Email addresses can be harvested by spam bots.",
howToFix: "Use contact forms or obfuscate emails (e.g., name[at]domain.com)."
howToFix: 'Keep the mailto: link and give it descriptive link text ("Email us") instead of the address itself; a contact form is the other option. Obfuscation such as name[at]domain.com breaks the link for readers and screen readers and only belongs where an address must appear as text.'
},
DIRECTORY_LISTING_ENABLED: {
code: "DIRECTORY_LISTING_ENABLED",
Expand Down Expand Up @@ -9875,7 +9875,7 @@ Allow: /`
title: "FAQ content without FAQPage schema",
description: "Question-format headings found but no FAQPage schema markup.",
impact: "Missing out on FAQ rich results and PAA box opportunities.",
howToFix: "Add FAQPage structured data for your Q&A content."
howToFix: "If these headings introduce real question-and-answer content, add FAQPage structured data for those pairs. If they are headings that merely end in a question mark (research questions, section titles), no markup is appropriate: Google restricts FAQPage to genuine FAQ content and misuse risks a manual action."
},
// Internal Link Graph
NO_CONTEXTUAL_INTERNAL_LINKS: {
Expand Down Expand Up @@ -10836,10 +10836,10 @@ function analyzeFeaturedSnippet(html, url) {
code: "FAQ_SCHEMA_MISSING",
severity: "warning",
category: "structured-data",
title: "FAQ content without FAQPage schema",
description: `Found ${qa.qaCount} question-format headings but no FAQPage schema markup.`,
impact: "Missing out on FAQ rich results and PAA box opportunities.",
howToFix: "Add FAQPage structured data for your Q&A content.",
title: "Question headings without FAQPage schema",
description: `Found ${qa.qaCount} headings phrased as questions and no FAQPage schema. A question mark is not evidence of a FAQ; this only applies if each heading is followed by its answer.`,
impact: "Genuine FAQ content without the markup misses FAQ rich results and People Also Ask placements.",
howToFix: "If these headings introduce real question-and-answer content, add FAQPage structured data for those pairs. If they are headings that merely end in a question mark (research questions, section titles), no markup is appropriate: Google restricts FAQPage to genuine FAQ content and misuse risks a manual action.",
affectedUrls: [url],
details: { questions: qa.questions.slice(0, 5) }
});
Expand Down Expand Up @@ -17283,6 +17283,25 @@ function analyzeOnPage(html, url, headers, options = {}) {

// src/audit/checks/links.ts
import * as cheerio23 from "https://esm.sh/cheerio@1.0.0";
var INDETERMINATE = /* @__PURE__ */ new Set([401, 403, 405, 429]);
var BROWSER_UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36";
async function isDefinitelyBroken(href) {
const head = await httpHead(href, { timeout: 5e3, maxRedirects: 5, validateStatus: () => true });
if (head.status < 400)
return false;
if (!INDETERMINATE.has(head.status))
return true;
const get = await httpGet(href, {
timeout: 5e3,
maxRedirects: 5,
validateStatus: () => true,
maxBytes: 2048,
headers: { "User-Agent": BROWSER_UA, Accept: "text/html,*/*;q=0.8", Range: "bytes=0-1023" }
});
if (get.status < 400)
return false;
return !INDETERMINATE.has(get.status);
}
async function analyzeLinks(html, baseUrl, checkBroken = false) {
const issues = [];
const $ = cheerio23.load(html);
Expand Down Expand Up @@ -17443,14 +17462,8 @@ async function analyzeLinks(html, baseUrl, checkBroken = false) {
const externalToCheck = external.slice(0, 5);
for (const link of externalToCheck) {
try {
const response = await httpHead(link.href, {
timeout: 5e3,
maxRedirects: 5,
validateStatus: () => true
});
if (response.status >= 400) {
if (await isDefinitelyBroken(link.href))
brokenExternal.push(link.href);
}
} catch {
}
}
Expand Down Expand Up @@ -19093,7 +19106,7 @@ var BOT_CLASS_LABEL = {
};

// src/audit/checks/crawler-access.ts
var BROWSER_UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
var BROWSER_UA2 = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
function isRefusal(status) {
return status === 401 || status === 403 || status === 429 || status === 503;
}
Expand Down Expand Up @@ -19159,7 +19172,7 @@ function describe(outcomes) {
async function run(url) {
const issues = [];
const data = { baselineStatus: null, rateLimited: false, crawlers: [] };
const baseline = await probe(url, BROWSER_UA);
const baseline = await probe(url, BROWSER_UA2);
data.baselineStatus = baseline.status;
if (baseline.status === null || baseline.status >= 400 || baseline.challenge) {
return { issues, data };
Expand Down Expand Up @@ -20391,12 +20404,6 @@ function findRenderBlockingResources($) {
css.push(href);
}
});
$("head style").each((_, el) => {
const content = $(el).html();
if (content && content.trim().length > 100) {
css.push("[inline-style]");
}
});
$("head script[src]").each((_, el) => {
const src = $(el).attr("src");
const async = $(el).attr("async");
Expand Down Expand Up @@ -20458,7 +20465,7 @@ function analyzeDOMStructure(html, url) {
cssFiles: renderBlockingResources.css,
jsFiles: renderBlockingResources.js,
totalCount: criticalPathLength,
recommendation: criticalPathLength > 3 ? "Inline critical CSS and defer non-critical resources" : "Consider using async/defer for scripts"
recommendation: renderBlockingResources.css.length > 0 ? 'Inline the critical CSS and load the rest with media="print" onload, or preload it; add async or defer to the scripts' : "Add async or defer to the scripts"
}
});
}
Expand Down Expand Up @@ -21544,7 +21551,7 @@ function detectThirdPartyDomains(html, pageUrl) {
} catch {
}
});
$("img[src]").each((_, el) => {
$("img[src]").not('[loading="lazy"], [fetchpriority="low"]').each((_, el) => {
try {
const src = $(el).attr("src");
const url = new URL(src, pageUrl);
Expand Down
26 changes: 13 additions & 13 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading