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
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Changelog

## 3.0.0 (2026-06-08)

### Breaking
- **Letter grades removed — the audit is now a pure 0–100 score.** The `grade`-family fields are gone from the JSON: `AuditReport.overallGrade`, `SitemapAuditReport.aggregateGrade`, `SitemapPageResult.overallGrade`, `ScoredFactor.grade`, `CrossCuttingIssue.avgGrade`, and `AgentSummary.grade` (so `--format agent` now emits `{ schemaVersion, tool, mode, url, score, pass, criticalDefectCount, issues }`). `PrioritizedFix.avgGrade` (a letter) is replaced by **`PrioritizedFix.avgScore`** (a 0–100 number, cross-cutting fixes only). The `scoreToGrade()` export is removed from `@ainyc/aeo-audit/scoring`. Migrate by reading the 0–100 `overallScore` / `aggregateScore` / per-factor `score` / `avgScore` and thresholding to your own bands.
- **Per-factor `status` band removed.** `ScoredFactor.status` (`'pass' | 'partial' | 'fail'`) and the `scoreToStatus()` export are gone. `ScoredFactor` is now structurally identical to `RawFactorResult` (`id, name, weight, score, findings, recommendations`). Derive any banding from `score` directly. (`SitemapPageResult.status` — `'success' | 'error'` — is unrelated and unchanged, as is the `AgentSummary.pass` ≥ 70 gate and all CLI exit codes.)
- **`schemaVersion` bumped to `2.0`** to mark the removed fields. Parsers pinned to `1.x` should expect `grade`/`status` to be absent.

### Changed
- The single-page report `summary` now reads `Overall score <N>/100. …` instead of `Overall grade <letter>. …`. Text and markdown reports show the numeric score (and a score-derived color/icon) wherever a letter grade previously appeared; the markdown factor table drops its `Grade` and `Status` columns, and the per-page table drops `Grade` (keeping the `success`/`error` `Status`).

## 2.1.0 (2026-06-03)

### Added
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pnpm lint # Run linter
```
src/
index.ts # Main entry: runAeoAudit(url, options)
scoring.ts # Factor definitions, weights, grade calculation
scoring.ts # Factor definitions, weights, score calculation
fetch-page.ts # URL fetching with SSRF protection
errors.ts # AeoAuditError class
cli.ts # CLI argument parsing
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

**The most comprehensive open-source technical AEO (Answer Engine Optimization) audit tool.** Scores any website across 16 ranking factors that decide whether AI answer engines (ChatGPT, Perplexity, Gemini, Claude) will cite your content.

- Grade any URL across **16 AEO factors**: structured data, `llms.txt`, E-E-A-T, extractability, snippet eligibility, and more. [Scoring](docs/scoring.md)
- Score any URL across **16 AEO factors**: structured data, `llms.txt`, E-E-A-T, extractability, snippet eligibility, and more. [Scoring](docs/scoring.md)
- Audit a **whole site** from its sitemap; per-page findings roll up into ranked fixes. [Sitemap mode](docs/cli.md#sitemap-mode)
- Audit **built HTML offline** in CI: a `next export` / `dist` / `out` directory, no network. [Static output](docs/cli.md#static-output-mode)
- Detect the **platform / CMS / framework**: WordPress, Webflow, Shopify, Next.js, Vercel. [Platform detection](docs/cli.md#platform-detection)
Expand All @@ -21,7 +21,7 @@ Website: [canonry.ai](https://canonry.ai)
npx @ainyc/aeo-audit https://example.com
```

Prints a graded report. Common variations:
Prints a scored report. Common variations:

```bash
# Every page in the sitemap, site-wide issues only
Expand Down Expand Up @@ -51,7 +51,7 @@ Modes: audit, fix, schema, `llms.txt`, monitor. See the [skill guide](docs/skill
| Doc | What's in it |
|---|---|
| [CLI reference](docs/cli.md) | Every flag, mode, and exit code |
| [Scoring](docs/scoring.md) | The 16 factors, weights, grading scale |
| [Scoring](docs/scoring.md) | The 16 factors, weights, score bands |
| [Programmatic API](docs/api.md) | `runAeoAudit`, `runSitemapAudit`, `runStaticAudit` |
| [Skill](docs/skill.md) | `/aeo` modes and install |
| [Changelog](CHANGELOG.md) | Release history |
Expand Down
11 changes: 5 additions & 6 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ const report = await runAeoAudit('https://example.com/specific-page', {
// Scoped to that exact host; redirects/sitemap entries to other private hosts stay blocked.
})

console.log(report.overallGrade) // 'A+'
console.log(report.overallScore) // 98
console.log(report.factors) // Array of factor results with scores, findings, recommendations
```
Expand All @@ -33,8 +32,8 @@ const report = await runSitemapAudit('https://example.com', {
factors: ['schema-validity', 'structured-data'], // Optional subset
})

console.log(report.schemaVersion) // '1.1', JSON shape version (see "Machine-readable output")
console.log(report.aggregateGrade) // 'B+'
console.log(report.schemaVersion) // '2.0', JSON shape version (see "Machine-readable output")
console.log(report.aggregateScore) // 84
console.log(report.pagesAudited) // 22
console.log(report.criticalDefects) // Binary per-page defects (multiple/missing H1, missing title/meta), grouped by defect
console.log(report.crossCuttingIssues) // Per-factor rollup with affectedUrls for every recommendation
Expand All @@ -43,7 +42,7 @@ console.log(report.prioritizedFixes) // Ranked PrioritizedFix[]: critical defe

Each entry in `crossCuttingIssues[].topIssues` carries a `recommendation` plus the exact `affectedUrls` so you can attribute each problem to specific pages, e.g. "FAQPage duplicate" pointing at every blog post that has it.

`criticalDefects` surfaces **binary structural defects by impact, not prevalence**. The cross-cutting rollup ranks by how many pages a factor affects, so an unambiguous one-line-fix defect on a single important page (a homepage split across four `<h1>`s, or a `/contact-us` page with none) would otherwise be averaged into a passing factor grade and excluded from `prioritizedFixes`. Each group names the offending pages (homepage and high sitemap-`priority` pages first), and the critical-severity ones lead `prioritizedFixes`.
`criticalDefects` surfaces **binary structural defects by impact, not prevalence**. The cross-cutting rollup ranks by how many pages a factor affects, so an unambiguous one-line-fix defect on a single important page (a homepage split across four `<h1>`s, or a `/contact-us` page with none) would otherwise be averaged into a passing factor score and excluded from `prioritizedFixes`. Each group names the offending pages (homepage and high sitemap-`priority` pages first), and the critical-severity ones lead `prioritizedFixes`.

### Machine-readable output (for AI agents)

Expand All @@ -64,9 +63,9 @@ const result = await runStaticAudit('./out', {
})

if (result.kind === 'single') {
console.log(result.report.overallGrade) // single .html file → AuditReport
console.log(result.report.overallScore) // single .html file → AuditReport
} else {
console.log(result.report.aggregateGrade) // directory → SitemapAuditReport shape
console.log(result.report.aggregateScore) // directory → SitemapAuditReport shape
console.log(result.report.criticalDefects)
console.log(result.report.crossCuttingIssues)
}
Expand Down
4 changes: 2 additions & 2 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ npx @ainyc/aeo-audit https://example.com --sitemap --format agent

`--format json` is the contract for programmatic and agent consumers: every report carries a `schemaVersion` (so a parser can detect breaking shape drift) and sitemap reports expose a `criticalDefects` rollup plus a ranked `prioritizedFixes` array of structured objects. See [api.md](api.md#machine-readable-output-for-ai-agents) for the field shapes.

`--format agent` returns just the decision, not the report: `{ schemaVersion, tool, mode, url, score, grade, pass, criticalDefectCount, issues }`, where `issues` is the ranked `PrioritizedFix[]` (critical defects first, then cross-cutting by prevalence). It omits the per-factor and per-page detail so an agent can act without averaging and re-ranking scores itself. Works for single-URL, sitemap, and static-output audits; in `--detect-platform` mode it falls back to the structured JSON.
`--format agent` returns just the decision, not the report: `{ schemaVersion, tool, mode, url, score, pass, criticalDefectCount, issues }`, where `issues` is the ranked `PrioritizedFix[]` (critical defects first, then cross-cutting by prevalence). It omits the per-factor and per-page detail so an agent can act without averaging and re-ranking scores itself. Works for single-URL, sitemap, and static-output audits; in `--detect-platform` mode it falls back to the structured JSON.

## Running a subset of factors

Expand Down Expand Up @@ -99,7 +99,7 @@ Auto-discovery checks `/sitemap.xml` → `/sitemap-index.xml` → `Sitemap:` dir

When the sitemap has more URLs than `--limit`, the run audits the highest-priority pages and prints a notice to stderr listing how many were skipped and how to audit them all.

A **Critical Defects** section lists binary, one-line-fix structural defects (an `<h1>` count other than one, a missing `<title>`, a missing meta description) surfaced **regardless of how few pages they affect**, with the offending pages named (homepage and high sitemap-`priority` pages first). These would otherwise be averaged into a passing factor grade and excluded from the prevalence-ranked fixes; the critical-severity ones also lead the prioritized fix list. The section is shown even with `--top-issues`. See the machine-readable shapes in [api.md](api.md#machine-readable-output-for-ai-agents).
A **Critical Defects** section lists binary, one-line-fix structural defects (an `<h1>` count other than one, a missing `<title>`, a missing meta description) surfaced **regardless of how few pages they affect**, with the offending pages named (homepage and high sitemap-`priority` pages first). These would otherwise be averaged into a passing factor score and excluded from the prevalence-ranked fixes; the critical-severity ones also lead the prioritized fix list. The section is shown even with `--top-issues`. See the machine-readable shapes in [api.md](api.md#machine-readable-output-for-ai-agents).

The optional in-process factors are honored per page: pass `--include-geo` and/or `--include-agent-skills` to add them to every audited page. `--lighthouse` is the exception: it cannot be combined with `--sitemap` because each PageSpeed Insights call takes 15-30s.

Expand Down
23 changes: 11 additions & 12 deletions docs/scoring.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,14 @@ These are excluded by default; when included, the weights renormalize.

> **Note on Google's guidance.** Google's [AI features and your website][google-aeo] guide says `llms.txt` and heavy structured data aren't required for AI Overviews or AI Mode. We still score them: Google is one engine; ChatGPT, Perplexity, and Claude do rely on them. Snippet eligibility is the one hard gate Google enforces: a page must be indexable and snippet-eligible to appear in AI features.

## Grading scale

| Grade | Score | Meaning |
|-------|-------|---------|
| A+ | 97-100 | Exceptional AEO readiness |
| A / A- | 90-96 | Strong foundation |
| B+/B/B- | 80-89 | Good with clear gaps |
| C+/C/C- | 70-79 | Moderate, needs work |
| D+/D/D- | 60-69 | Weak |
| F | <60 | Critical |

The CLI exits `0` for a score ≥ 70 and `1` below; see [Exit codes](cli.md#exit-codes).
## Score bands

The audit emits a **0–100 score** for every factor and a weighted **overall score** (0–100) — there are no letter grades or pass/partial/fail labels. Threshold the raw score to whatever bands suit your use. For reference, the CLI's own conventions:

| Score | Meaning |
|-------|---------|
| 70–100 | Strong — meets the CLI's pass gate |
| 40–69 | Moderate — clear gaps remain |
| 0–39 | Weak — major work needed |

The CLI exits `0` for an overall score ≥ 70 and `1` below; see [Exit codes](cli.md#exit-codes).
2 changes: 1 addition & 1 deletion docs/skill.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ ClawHub package: [arberx/aeo](https://clawhub.ai/arberx/aeo)

## Modes

- `audit`: grading and diagnosis
- `audit`: scoring and diagnosis
- `fix`: code changes after an audit
- `schema`: JSON-LD validation
- `llms`: generate `llms.txt` and `llms-full.txt`
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@ainyc/aeo-audit",
"version": "2.1.0",
"version": "3.0.0",
"description": "The most comprehensive open-source Answer Engine Optimization (AEO) audit tool. Scores websites across 16 ranking factors that determine AI citation.",
"type": "module",
"main": "./dist/index.js",
Expand Down
14 changes: 7 additions & 7 deletions skills/aeo/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ npx @ainyc/aeo-audit@1 "<url>" [flags] --format json

## Modes

- `audit`: grade and diagnose a site
- `audit`: score and diagnose a site
- `fix`: apply code changes after an audit
- `schema`: validate JSON-LD and entity consistency
- `llms`: create or improve `llms.txt` and `llms-full.txt`
Expand Down Expand Up @@ -84,7 +84,7 @@ Use for broad requests such as "audit this site" or "why am I not being cited?"
npx @ainyc/aeo-audit@1 "<url>" [flags] --format json
```
2. Return:
- Overall grade and score
- Overall score
- Short summary
- Factor breakdown
- Top strengths
Expand Down Expand Up @@ -117,15 +117,15 @@ Flags:
Pages are audited with bounded concurrency (5 in flight) to avoid hammering the target origin.

Returns:
- Per-page scores and grades
- **Critical defects** — binary, one-line-fix structural defects (an `<h1>` count other than one, a missing `<title>`, a missing meta description) surfaced **regardless of how few pages they affect**, with the offending pages named (homepage and high sitemap-`priority` pages first). These would otherwise be averaged into a passing factor grade; the JSON field is `criticalDefects` and critical-severity ones are also promoted to the top of `prioritizedFixes`. Shown even with `--top-issues`.
- Per-page scores
- **Critical defects** — binary, one-line-fix structural defects (an `<h1>` count other than one, a missing `<title>`, a missing meta description) surfaced **regardless of how few pages they affect**, with the offending pages named (homepage and high sitemap-`priority` pages first). These would otherwise be averaged into a passing factor score; the JSON field is `criticalDefects` and critical-severity ones are also promoted to the top of `prioritizedFixes`. Shown even with `--top-issues`.
- Cross-cutting issues (factors failing across multiple pages)
- Aggregate score and grade
- Aggregate score
- Prioritized fixes (critical defects first, then ranked by site-wide impact)

#### Machine-readable output (for agents)

Use `--format json` for the full report, or **`--format agent`** for just the decision: `{ schemaVersion, tool, mode, url, score, grade, pass, criticalDefectCount, issues }`, where `issues` is the ranked `prioritizedFixes` and the per-factor/per-page detail is omitted. Prefer `--format agent` when you only need to decide and act. Key fields for acting on the result without parsing prose:
Use `--format json` for the full report, or **`--format agent`** for just the decision: `{ schemaVersion, tool, mode, url, score, pass, criticalDefectCount, issues }`, where `issues` is the ranked `prioritizedFixes` and the per-factor/per-page detail is omitted. Prefer `--format agent` when you only need to decide and act. Key fields for acting on the result without parsing prose:
- `schemaVersion` (on every audit report) versions the JSON shape independently of the package version — pin to it and treat a major bump as breaking; absence means a pre-2.0 report.
- `prioritizedFixes` is a ranked array of objects, each with a stable `id`, `kind`, optional `severity`, the complete `affectedPages` list (never truncated), `affectsHomepage`, `prevalencePct`, and a human `summary`. It's the pre-computed to-do list — no need to re-rank factor scores yourself.
- Stable identifiers everywhere — `criticalDefects[].id`, `prioritizedFixes[].id`, and every factor finding's `code` (e.g. `technical-seo.h1.multiple`) — let integrations key on codes rather than message strings.
Expand Down Expand Up @@ -220,7 +220,7 @@ Use when the user wants code changes applied after the audit.
```bash
npx @ainyc/aeo-audit@1 "<url>" [flags] --format json
```
2. Find factors with status `partial` or `fail`.
2. Find factors scoring below 70 (lowest first).
3. Apply targeted fixes in the current codebase.
4. Prioritize:
- Structured data and schema completeness
Expand Down
2 changes: 0 additions & 2 deletions src/agent-summary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ export function agentSummaryFromAudit(report: AuditReport): AgentSummary {
mode: 'single',
url: report.finalUrl,
score: report.overallScore,
grade: report.overallGrade,
pass: report.overallScore >= PASS_THRESHOLD,
criticalDefectCount: criticalDefects.filter((g) => g.severity === 'critical').length,
issues,
Expand All @@ -40,7 +39,6 @@ export function agentSummaryFromSitemap(report: SitemapAuditReport): AgentSummar
mode: 'sitemap',
url: report.sitemapUrl,
score: report.aggregateScore,
grade: report.aggregateGrade,
pass: report.aggregateScore >= PASS_THRESHOLD,
criticalDefectCount: report.criticalDefects.filter((g) => g.severity === 'critical').length,
issues: report.prioritizedFixes,
Expand Down
Loading
Loading