Vibecode AIOSEO
track this build4 phases, 12 steps, beginner friendly0%AIOSEO's solo core is compact: build a focused WordPress SEO plugin audit for user-owned sites that crawls pages, records technical signals, and exports a prioritized CSV and HTML report. A competent builder can reach a useful personal version in one sitting, while the paid product mainly wins on proprietary data, index, crawl scale.
You are building a lean indie version of AIOSEO. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # AIOSEO · indie build A technical SEO layer whose value is correctness rather than volume: sitemaps that match what is actually indexable, canonical and redirect handling that does not contradict itself, and structured data emitted with an honest record of whether it still does anything. Estimated effort: **multi-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Crawling | A local crawler over your own site | you cannot report on indexability without seeing what a crawler sees | | Rules | Indexability derived from directives, canonicals and redirects together | these three contradict each other constantly and the contradiction is the bug | | Sitemaps | Generated from what is actually indexable, not from what exists | a sitemap listing noindexed pages is a signal you are not paying attention | | Structured data | Emitted with a dated support table per type | search engines retire rich result types and a tool should know which | | Runtime | TypeScript with SQLite | the work is crawling, comparison and reporting | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the crawler and the report generator. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **A real site of at least five hundred pages** · free - Why: Contradicting signals, redirect chains and sitemap divergence only appear on a site with history, and a fresh site has none of them. - Get it: Your own site, or one you have permission to crawl, ideally one that has been through a migration. - Verify: Five hundred pages crawl and at least one redirect chain longer than one hop exists - [ ] **Current structured data support information** · free to read - Why: The build keeps a dated table per type, and it has to be populated from the search engine's own current documentation rather than from memory. - Get it: Read the current rich results documentation and record, per type you intend to emit, whether it still produces a result and the date you checked. - Verify: A dated support record exists per emitted type with a source link - [ ] **Access to the site's search console** (optional) · free - Why: It is the only source for what the search engine actually did with your pages, as opposed to what your crawler thinks it should have. - Get it: Verify ownership of the site in the search engine's own console. - Verify: Index coverage data is visible for the site - [ ] **Which issues you will report** · free - Why: A report padded with low-value warnings is a report nobody reads, so the inclusion rule belongs before the checks are written. - Get it: Decide the rule: an issue is reported only if you can state its mechanism in one sentence. Write the list. - Verify: Every planned check has a one-sentence mechanism written down ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Keyword research and ranking data. - Content quality assessment. - Backlink analysis. - Any promise about rankings. - fresh proprietary keyword, backlink, or ranking data - proprietary web and keyword indexes - fresh rank and clickstream data - large-scale crawling infrastructure - historical competitor datasets and support If one of those is essential to you, that is the reason to keep paying for AIOSEO, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · AIOSEO The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me technical SEO for a site, reporting only what actually still does something to replace All in One SEO. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Most of this category's output is theatre. A plugin that emits structured data and reports it as an achievement is measuring its own activity rather than any outcome, and search engines retire features constantly. A concrete example that is current: Google deprecated FAQ rich results on 7 May 2026, with Search Console reporting and Rich Results Test support ending in June 2026 and API support in August. HowTo went the same way earlier. The markup is still valid schema and harmless to keep, but any tool telling you that adding it improved anything is describing a feature that no longer exists. Product, Article, LocalBusiness, Event and Review still produce rich results. So this build keeps a dated support table per structured data type, refuses to present an emitted type as a win when it no longer produces a result, and reports the things that genuinely matter: whether pages are indexable, whether canonicals are consistent, whether redirects chain, and whether the sitemap tells the truth. Those do not go out of date. ### Stack (fixed, do not substitute) - Crawling: A local crawler over your own site · you cannot report on indexability without seeing what a crawler sees - Rules: Indexability derived from directives, canonicals and redirects together · these three contradict each other constantly and the contradiction is the bug - Sitemaps: Generated from what is actually indexable, not from what exists · a sitemap listing noindexed pages is a signal you are not paying attention - Structured data: Emitted with a dated support table per type · search engines retire rich result types and a tool should know which - Runtime: TypeScript with SQLite · the work is crawling, comparison and reporting ### Data model (create this before Phase 1) - pages(id, url, status, title, meta_description, canonical, robots_directives, indexable, reason) - links(from_page_id, to_url, rel, is_internal) - redirects(from_url, to_url, status, chain_length, resolved_target, at) - sitemap_entries(url, lastmod, included, exclusion_reason) · exclusions recorded so the sitemap is explicable - schema_types(name, produces_rich_result, deprecated_on, checked_on, source_url) · dated, because this changes - schema_emissions(page_id, type_name, valid, warnings_json) - audits(id, run_at, pages_crawled, issues_by_kind_json) - A page's indexability is derived and stores its reason, because the interesting cases are where three signals disagree. ### Phase 1 · Seeing the site as a crawler does Build: Crawl the site, recording status, directives, canonical and links. Derive indexability from all of them together and record the reason. Surface pages where the signals contradict each other. Trap: Treating a page as indexable because it returns a successful status ignores a noindex directive, a canonical pointing elsewhere or a robots rule blocking the crawl, and those three disagree with each other on real sites constantly. Derive indexability from all the signals and report the contradiction rather than picking one. Done when: Indexability is derived from status, directives, canonical and crawl rules together, with the reason recorded. A page whose canonical points elsewhere while declaring itself indexable is surfaced as a contradiction. Pages blocked from crawling are distinguished from pages excluded from indexing. A crawl of a thousand pages completes and is repeatable. Do not build yet: No sitemaps, redirects or structured data. ### Phase 2 · Redirects that do not chain Build: Follow every redirect to its final target, recording the chain. Report chains longer than one hop and any loop. Detect a redirect pointing at a page that is itself not indexable. Trap: A redirect chain accumulates over years as pages move, and each hop loses a little of whatever the original link was worth, but nothing ever reports it because each individual redirect works fine. Resolve the whole chain and report its length, since the problem is only visible end to end. Done when: Every redirect's full chain is resolved and its length reported. A loop is detected rather than followed indefinitely. A redirect ending at a noindexed page is reported as a wasted redirect. Suggested collapses point each redirect directly at its final target. Do not build yet: No sitemaps or structured data yet. ### Phase 3 · A sitemap that tells the truth Build: Generate sitemaps from derived indexability rather than from the page list. Record why each excluded page was excluded. Split large sitemaps correctly and keep last-modified dates honest. Trap: A last-modified date regenerated on every build tells search engines that every page changed every day, which trains them to ignore the field entirely. Derive it from actual content change, and leave it out rather than inventing one. Done when: The sitemap contains only pages derived as indexable, verified against the crawl. Every exclusion has a recorded reason. Last-modified reflects real content change, tested by rebuilding without changing anything and confirming dates do not move. A large site splits into correctly indexed sitemap files. Do not build yet: No structured data yet. ### Phase 4 · Structured data, with an honest support table Build: Emit structured data with a dated table recording, per type, whether it still produces a rich result and when that was checked. Validate emitted markup. Never report an emitted type as a win when the table says it produces nothing. Trap: Reporting structured data emission as an improvement is measuring your own activity, and it stays wrong for years because nobody revisits it after a search engine retires a feature. Keep the support state as dated data with a source, and surface entries that have not been rechecked recently. Done when: Each schema type has a dated support record with a source. A type marked as no longer producing rich results is emitted if you want but never reported as an improvement, verified in the report output. Emitted markup is validated for correctness. Support records older than a stated period are surfaced for rechecking. Do not build yet: No reporting or content checks yet. ### Phase 5 · A report about the site, not about the tool Build: Report issues ranked by whether they affect indexability, ordered so the first item is the most consequential. Exclude anything you cannot state a mechanism for. Track issues across audits so you can see what got fixed. Trap: Padding a report with dozens of low-value warnings makes it feel thorough and guarantees nobody reads past the first screen, so the one genuine problem is buried among forty about meta description length. Report what affects indexability first, and leave out anything whose mechanism you cannot state in a sentence. Done when: Issues are ranked by effect on indexability with the most consequential first. Every issue type has a stated mechanism in one sentence, verified by reading the list. Issues are tracked across audits so fixes are visible. A clean site produces a short report rather than a padded one. Do not build yet: Nothing after this. ### Out of scope (and why) - Keyword research and ranking data, which requires data sources you would have to buy and which change constantly. - Content quality assessment, which is where most of this category's marketing lives and which no tool measures well. - Backlink analysis, which needs a crawl of the web rather than of your site. - Any promise about rankings, which nobody controls and which no technical change guarantees. ### README must contain - That structured data support is dated data with a source, because search engines retire rich result types - That emitting a deprecated type is never reported as an improvement - That indexability is derived from every signal together and the reason is recorded - That the sitemap is generated from derived indexability rather than from the page list - That last-modified reflects real content change rather than build time ===== AGENTS.md ===== # Agent instructions · AIOSEO indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: A local crawler over your own site, Indexability derived from directives, canonicals and redirects together, Generated from what is actually indexable, not from what exists, Emitted with a dated support table per type, TypeScript with SQLite. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Treating a page as indexable because it returns a successful status ignores a noindex directive, a canonical pointing elsewhere or a robots rule blocking the crawl, and those three disagree with each other on real sites constantly. Derive indexability from all the signals and report the contradiction rather than picking one. - A redirect chain accumulates over years as pages move, and each hop loses a little of whatever the original link was worth, but nothing ever reports it because each individual redirect works fine. Resolve the whole chain and report its length, since the problem is only visible end to end. - A last-modified date regenerated on every build tells search engines that every page changed every day, which trains them to ignore the field entirely. Derive it from actual content change, and leave it out rather than inventing one. - Reporting structured data emission as an improvement is measuring your own activity, and it stays wrong for years because nobody revisits it after a search engine retires a feature. Keep the support state as dated data with a source, and surface entries that have not been rechecked recently. ===== BUILD_PLAN.md ===== # Build plan · AIOSEO A technical SEO layer whose value is correctness rather than volume: sitemaps that match what is actually indexable, canonical and redirect handling that does not contradict itself, and structured data emitted with an honest record of whether it still does anything. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Seeing the site as a crawler does You know which pages are actually indexable and why. ### Steps 1. Record every signal separately Status, directives, canonical, crawl rules. Then derive. Files: `src/crawl.ts` 2. Derive indexability with a reason The reason is the useful output, not the boolean. Files: `src/indexable.ts`, `src/indexable.test.ts` 3. Surface contradictions Where signals disagree is where the real problems are. Files: `src/contradictions.ts` ### Done when - [ ] Indexability derives from all signals with a reason - [ ] Contradicting signals are surfaced - [ ] Blocked and excluded pages are distinguished - [ ] A thousand page crawl is repeatable ### Watch out - Treating a page as indexable because it returns a successful status ignores a noindex directive, a canonical pointing elsewhere or a robots rule blocking the crawl, and those three disagree with each other on real sites constantly. Derive indexability from all the signals and report the contradiction rather than picking one. ## Phase 2 · Redirects that do not chain Every redirect resolves in one hop and no loop exists. ### Steps 1. Resolve the whole chain Each hop works fine. The chain is the problem and only shows end to end. Files: `src/redirects.ts` 2. Detect loops without hanging A cap and a report, not an infinite follow. Files: `src/redirects.ts` 3. Report redirects to nowhere useful A chain ending at a noindexed page is entirely wasted. Files: `src/redirects.ts` ### Done when - [ ] Chains are resolved and their length reported - [ ] Loops are detected rather than followed - [ ] Redirects to noindexed pages are reported - [ ] Suggested collapses target the final URL ### Watch out - A redirect chain accumulates over years as pages move, and each hop loses a little of whatever the original link was worth, but nothing ever reports it because each individual redirect works fine. Resolve the whole chain and report its length, since the problem is only visible end to end. ## Phase 3 · A sitemap that tells the truth The sitemap contains exactly what is indexable and nothing else. ### Steps 1. Generate from derived indexability Not from the page list. A sitemap listing noindexed pages is a contradiction. Files: `src/sitemap.ts` 2. Record exclusion reasons So a missing page is explicable rather than mysterious. Files: `src/sitemap.ts` 3. Keep last-modified honest Regenerating it every build teaches crawlers to ignore it. Files: `src/lastmod.ts`, `tests/lastmod.test.ts` ### Done when - [ ] The sitemap contains only indexable pages - [ ] Every exclusion has a recorded reason - [ ] Rebuilding without changes does not move dates - [ ] A large site splits and indexes correctly ### Watch out - A last-modified date regenerated on every build tells search engines that every page changed every day, which trains them to ignore the field entirely. Derive it from actual content change, and leave it out rather than inventing one. ## Phase 4 · Structured data, with an honest support table You know which types still produce a result and which are decoration. ### Steps 1. Keep support state as dated data With a source and a checked date. This changes and your tool should know. Files: `config/schema-support.json` 2. Validate emitted markup Emitting invalid markup is worse than emitting none. Files: `src/schema.ts` 3. Never report a dead type as a win Emitting is activity. Producing a result is an outcome. Files: `src/report.ts`, `tests/no-fake-wins.test.ts` ### Done when - [ ] Each type has a dated support record with a source - [ ] A deprecated type is never reported as an improvement - [ ] Emitted markup validates - [ ] Stale support records are surfaced ### Watch out - Reporting structured data emission as an improvement is measuring your own activity, and it stays wrong for years because nobody revisits it after a search engine retires a feature. Keep the support state as dated data with a source, and surface entries that have not been rechecked recently. ## Not in this build - Keyword research and ranking data. - Content quality assessment. - Backlink analysis. - Any promise about rankings. ## After v1, if you want it - Comparing your sitemap against what the search console says was indexed, which is where the real gap shows - Internal link analysis, since orphaned pages are a genuine and fixable problem - Change alerts when a deployment breaks indexability, which is when it usually happens ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Crawl results and audit history. DB_PATH=./seo.db # Required. The site to crawl. Yours, or one you have permission for. SITE_URL=https://example.com # Required. Even your own site should not be hammered by your own tool. CRAWL_DELAY_MS=500 # Required. Dated support per type. The file that stops the tool reporting dead features as wins. SCHEMA_SUPPORT_PATH=./config/schema-support.json # Required. After this a support record is surfaced as stale. Rich result types get retired. SUPPORT_RECHECK_DAYS=90 # Required. Present as a constant. An issue you cannot explain in a sentence is padding. REPORT_ONLY_WITH_MECHANISM=always
You are building a lean indie version of AIOSEO. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # AIOSEO · indie build A technical SEO layer whose value is correctness rather than volume: sitemaps that match what is actually indexable, canonical and redirect handling that does not contradict itself, and structured data emitted with an honest record of whether it still does anything. Estimated effort: **multi-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Crawling | A local crawler over your own site | you cannot report on indexability without seeing what a crawler sees | | Rules | Indexability derived from directives, canonicals and redirects together | these three contradict each other constantly and the contradiction is the bug | | Sitemaps | Generated from what is actually indexable, not from what exists | a sitemap listing noindexed pages is a signal you are not paying attention | | Structured data | Emitted with a dated support table per type | search engines retire rich result types and a tool should know which | | Runtime | TypeScript with SQLite | the work is crawling, comparison and reporting | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the crawler and the report generator. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **A real site of at least five hundred pages** · free - Why: Contradicting signals, redirect chains and sitemap divergence only appear on a site with history, and a fresh site has none of them. - Get it: Your own site, or one you have permission to crawl, ideally one that has been through a migration. - Verify: Five hundred pages crawl and at least one redirect chain longer than one hop exists - [ ] **Current structured data support information** · free to read - Why: The build keeps a dated table per type, and it has to be populated from the search engine's own current documentation rather than from memory. - Get it: Read the current rich results documentation and record, per type you intend to emit, whether it still produces a result and the date you checked. - Verify: A dated support record exists per emitted type with a source link - [ ] **Access to the site's search console** (optional) · free - Why: It is the only source for what the search engine actually did with your pages, as opposed to what your crawler thinks it should have. - Get it: Verify ownership of the site in the search engine's own console. - Verify: Index coverage data is visible for the site - [ ] **Which issues you will report** · free - Why: A report padded with low-value warnings is a report nobody reads, so the inclusion rule belongs before the checks are written. - Get it: Decide the rule: an issue is reported only if you can state its mechanism in one sentence. Write the list. - Verify: Every planned check has a one-sentence mechanism written down ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Keyword research and ranking data. - Content quality assessment. - Backlink analysis. - Any promise about rankings. - fresh proprietary keyword, backlink, or ranking data - proprietary web and keyword indexes - fresh rank and clickstream data - large-scale crawling infrastructure - historical competitor datasets and support If one of those is essential to you, that is the reason to keep paying for AIOSEO, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · AIOSEO The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me technical SEO for a site, reporting only what actually still does something to replace All in One SEO. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Most of this category's output is theatre. A plugin that emits structured data and reports it as an achievement is measuring its own activity rather than any outcome, and search engines retire features constantly. A concrete example that is current: Google deprecated FAQ rich results on 7 May 2026, with Search Console reporting and Rich Results Test support ending in June 2026 and API support in August. HowTo went the same way earlier. The markup is still valid schema and harmless to keep, but any tool telling you that adding it improved anything is describing a feature that no longer exists. Product, Article, LocalBusiness, Event and Review still produce rich results. So this build keeps a dated support table per structured data type, refuses to present an emitted type as a win when it no longer produces a result, and reports the things that genuinely matter: whether pages are indexable, whether canonicals are consistent, whether redirects chain, and whether the sitemap tells the truth. Those do not go out of date. ### Stack (fixed, do not substitute) - Crawling: A local crawler over your own site · you cannot report on indexability without seeing what a crawler sees - Rules: Indexability derived from directives, canonicals and redirects together · these three contradict each other constantly and the contradiction is the bug - Sitemaps: Generated from what is actually indexable, not from what exists · a sitemap listing noindexed pages is a signal you are not paying attention - Structured data: Emitted with a dated support table per type · search engines retire rich result types and a tool should know which - Runtime: TypeScript with SQLite · the work is crawling, comparison and reporting ### Data model (create this before Phase 1) - pages(id, url, status, title, meta_description, canonical, robots_directives, indexable, reason) - links(from_page_id, to_url, rel, is_internal) - redirects(from_url, to_url, status, chain_length, resolved_target, at) - sitemap_entries(url, lastmod, included, exclusion_reason) · exclusions recorded so the sitemap is explicable - schema_types(name, produces_rich_result, deprecated_on, checked_on, source_url) · dated, because this changes - schema_emissions(page_id, type_name, valid, warnings_json) - audits(id, run_at, pages_crawled, issues_by_kind_json) - A page's indexability is derived and stores its reason, because the interesting cases are where three signals disagree. ### Phase 1 · Seeing the site as a crawler does Build: Crawl the site, recording status, directives, canonical and links. Derive indexability from all of them together and record the reason. Surface pages where the signals contradict each other. Trap: Treating a page as indexable because it returns a successful status ignores a noindex directive, a canonical pointing elsewhere or a robots rule blocking the crawl, and those three disagree with each other on real sites constantly. Derive indexability from all the signals and report the contradiction rather than picking one. Done when: Indexability is derived from status, directives, canonical and crawl rules together, with the reason recorded. A page whose canonical points elsewhere while declaring itself indexable is surfaced as a contradiction. Pages blocked from crawling are distinguished from pages excluded from indexing. A crawl of a thousand pages completes and is repeatable. Do not build yet: No sitemaps, redirects or structured data. ### Phase 2 · Redirects that do not chain Build: Follow every redirect to its final target, recording the chain. Report chains longer than one hop and any loop. Detect a redirect pointing at a page that is itself not indexable. Trap: A redirect chain accumulates over years as pages move, and each hop loses a little of whatever the original link was worth, but nothing ever reports it because each individual redirect works fine. Resolve the whole chain and report its length, since the problem is only visible end to end. Done when: Every redirect's full chain is resolved and its length reported. A loop is detected rather than followed indefinitely. A redirect ending at a noindexed page is reported as a wasted redirect. Suggested collapses point each redirect directly at its final target. Do not build yet: No sitemaps or structured data yet. ### Phase 3 · A sitemap that tells the truth Build: Generate sitemaps from derived indexability rather than from the page list. Record why each excluded page was excluded. Split large sitemaps correctly and keep last-modified dates honest. Trap: A last-modified date regenerated on every build tells search engines that every page changed every day, which trains them to ignore the field entirely. Derive it from actual content change, and leave it out rather than inventing one. Done when: The sitemap contains only pages derived as indexable, verified against the crawl. Every exclusion has a recorded reason. Last-modified reflects real content change, tested by rebuilding without changing anything and confirming dates do not move. A large site splits into correctly indexed sitemap files. Do not build yet: No structured data yet. ### Phase 4 · Structured data, with an honest support table Build: Emit structured data with a dated table recording, per type, whether it still produces a rich result and when that was checked. Validate emitted markup. Never report an emitted type as a win when the table says it produces nothing. Trap: Reporting structured data emission as an improvement is measuring your own activity, and it stays wrong for years because nobody revisits it after a search engine retires a feature. Keep the support state as dated data with a source, and surface entries that have not been rechecked recently. Done when: Each schema type has a dated support record with a source. A type marked as no longer producing rich results is emitted if you want but never reported as an improvement, verified in the report output. Emitted markup is validated for correctness. Support records older than a stated period are surfaced for rechecking. Do not build yet: No reporting or content checks yet. ### Phase 5 · A report about the site, not about the tool Build: Report issues ranked by whether they affect indexability, ordered so the first item is the most consequential. Exclude anything you cannot state a mechanism for. Track issues across audits so you can see what got fixed. Trap: Padding a report with dozens of low-value warnings makes it feel thorough and guarantees nobody reads past the first screen, so the one genuine problem is buried among forty about meta description length. Report what affects indexability first, and leave out anything whose mechanism you cannot state in a sentence. Done when: Issues are ranked by effect on indexability with the most consequential first. Every issue type has a stated mechanism in one sentence, verified by reading the list. Issues are tracked across audits so fixes are visible. A clean site produces a short report rather than a padded one. Do not build yet: Nothing after this. ### Out of scope (and why) - Keyword research and ranking data, which requires data sources you would have to buy and which change constantly. - Content quality assessment, which is where most of this category's marketing lives and which no tool measures well. - Backlink analysis, which needs a crawl of the web rather than of your site. - Any promise about rankings, which nobody controls and which no technical change guarantees. ### README must contain - That structured data support is dated data with a source, because search engines retire rich result types - That emitting a deprecated type is never reported as an improvement - That indexability is derived from every signal together and the reason is recorded - That the sitemap is generated from derived indexability rather than from the page list - That last-modified reflects real content change rather than build time ===== AGENTS.md ===== # Agent instructions · AIOSEO indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: A local crawler over your own site, Indexability derived from directives, canonicals and redirects together, Generated from what is actually indexable, not from what exists, Emitted with a dated support table per type, TypeScript with SQLite. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Treating a page as indexable because it returns a successful status ignores a noindex directive, a canonical pointing elsewhere or a robots rule blocking the crawl, and those three disagree with each other on real sites constantly. Derive indexability from all the signals and report the contradiction rather than picking one. - A redirect chain accumulates over years as pages move, and each hop loses a little of whatever the original link was worth, but nothing ever reports it because each individual redirect works fine. Resolve the whole chain and report its length, since the problem is only visible end to end. - A last-modified date regenerated on every build tells search engines that every page changed every day, which trains them to ignore the field entirely. Derive it from actual content change, and leave it out rather than inventing one. - Reporting structured data emission as an improvement is measuring your own activity, and it stays wrong for years because nobody revisits it after a search engine retires a feature. Keep the support state as dated data with a source, and surface entries that have not been rechecked recently. ===== BUILD_PLAN.md ===== # Build plan · AIOSEO A technical SEO layer whose value is correctness rather than volume: sitemaps that match what is actually indexable, canonical and redirect handling that does not contradict itself, and structured data emitted with an honest record of whether it still does anything. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Seeing the site as a crawler does You know which pages are actually indexable and why. ### Steps 1. Record every signal separately Status, directives, canonical, crawl rules. Then derive. Files: `src/crawl.ts` 2. Derive indexability with a reason The reason is the useful output, not the boolean. Files: `src/indexable.ts`, `src/indexable.test.ts` 3. Surface contradictions Where signals disagree is where the real problems are. Files: `src/contradictions.ts` ### Done when - [ ] Indexability derives from all signals with a reason - [ ] Contradicting signals are surfaced - [ ] Blocked and excluded pages are distinguished - [ ] A thousand page crawl is repeatable ### Watch out - Treating a page as indexable because it returns a successful status ignores a noindex directive, a canonical pointing elsewhere or a robots rule blocking the crawl, and those three disagree with each other on real sites constantly. Derive indexability from all the signals and report the contradiction rather than picking one. ## Phase 2 · Redirects that do not chain Every redirect resolves in one hop and no loop exists. ### Steps 1. Resolve the whole chain Each hop works fine. The chain is the problem and only shows end to end. Files: `src/redirects.ts` 2. Detect loops without hanging A cap and a report, not an infinite follow. Files: `src/redirects.ts` 3. Report redirects to nowhere useful A chain ending at a noindexed page is entirely wasted. Files: `src/redirects.ts` ### Done when - [ ] Chains are resolved and their length reported - [ ] Loops are detected rather than followed - [ ] Redirects to noindexed pages are reported - [ ] Suggested collapses target the final URL ### Watch out - A redirect chain accumulates over years as pages move, and each hop loses a little of whatever the original link was worth, but nothing ever reports it because each individual redirect works fine. Resolve the whole chain and report its length, since the problem is only visible end to end. ## Phase 3 · A sitemap that tells the truth The sitemap contains exactly what is indexable and nothing else. ### Steps 1. Generate from derived indexability Not from the page list. A sitemap listing noindexed pages is a contradiction. Files: `src/sitemap.ts` 2. Record exclusion reasons So a missing page is explicable rather than mysterious. Files: `src/sitemap.ts` 3. Keep last-modified honest Regenerating it every build teaches crawlers to ignore it. Files: `src/lastmod.ts`, `tests/lastmod.test.ts` ### Done when - [ ] The sitemap contains only indexable pages - [ ] Every exclusion has a recorded reason - [ ] Rebuilding without changes does not move dates - [ ] A large site splits and indexes correctly ### Watch out - A last-modified date regenerated on every build tells search engines that every page changed every day, which trains them to ignore the field entirely. Derive it from actual content change, and leave it out rather than inventing one. ## Phase 4 · Structured data, with an honest support table You know which types still produce a result and which are decoration. ### Steps 1. Keep support state as dated data With a source and a checked date. This changes and your tool should know. Files: `config/schema-support.json` 2. Validate emitted markup Emitting invalid markup is worse than emitting none. Files: `src/schema.ts` 3. Never report a dead type as a win Emitting is activity. Producing a result is an outcome. Files: `src/report.ts`, `tests/no-fake-wins.test.ts` ### Done when - [ ] Each type has a dated support record with a source - [ ] A deprecated type is never reported as an improvement - [ ] Emitted markup validates - [ ] Stale support records are surfaced ### Watch out - Reporting structured data emission as an improvement is measuring your own activity, and it stays wrong for years because nobody revisits it after a search engine retires a feature. Keep the support state as dated data with a source, and surface entries that have not been rechecked recently. ## Not in this build - Keyword research and ranking data. - Content quality assessment. - Backlink analysis. - Any promise about rankings. ## After v1, if you want it - Comparing your sitemap against what the search console says was indexed, which is where the real gap shows - Internal link analysis, since orphaned pages are a genuine and fixable problem - Change alerts when a deployment breaks indexability, which is when it usually happens ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Crawl results and audit history. DB_PATH=./seo.db # Required. The site to crawl. Yours, or one you have permission for. SITE_URL=https://example.com # Required. Even your own site should not be hammered by your own tool. CRAWL_DELAY_MS=500 # Required. Dated support per type. The file that stops the tool reporting dead features as wins. SCHEMA_SUPPORT_PATH=./config/schema-support.json # Required. After this a support record is surfaced as stale. Rich result types get retired. SUPPORT_RECHECK_DAYS=90 # Required. Present as a constant. An issue you cannot explain in a sentence is padding. REPORT_ONLY_WITH_MECHANISM=always
You are building a production product version of AIOSEO. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== PRODUCT.md ===== # AIOSEO · product brief ## Problem AIOSEO's solo core is compact: build a focused WordPress SEO plugin audit for user-owned sites that crawls pages, records technical signals, and exports a prioritized CSV and HTML report. A competent builder can reach a useful personal version in one sitting, while the paid product mainly wins on proprietary data, index, crawl scale. ## Product outcome A technical SEO tool that reports the handful of things actually preventing pages from being indexed, and that knows which structured data types still do anything. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - Python 3.12 - Playwright browsers - permission to crawl the target site ## Explicit non-goals for v1 - Keyword research and ranking data. - Content quality assessment. - Backlink analysis. - Any promise about rankings. - fresh proprietary keyword, backlink, or ranking data - proprietary web and keyword indexes - fresh rank and clickstream data - large-scale crawling infrastructure - historical competitor datasets and support ## Success criteria - Indexability is confirmed derived from every signal with a reason - Redirect chains are confirmed resolved and reported - The sitemap is confirmed to contain only indexable pages - A deprecated schema type is confirmed never reported as a win - Every issue type is confirmed to state its mechanism ===== BRIEF.md ===== # Build brief · AIOSEO The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me technical SEO for a site, reporting only what actually still does something to replace All in One SEO. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Most of this category's output is theatre. A plugin that emits structured data and reports it as an achievement is measuring its own activity rather than any outcome, and search engines retire features constantly. A concrete example that is current: Google deprecated FAQ rich results on 7 May 2026, with Search Console reporting and Rich Results Test support ending in June 2026 and API support in August. HowTo went the same way earlier. The markup is still valid schema and harmless to keep, but any tool telling you that adding it improved anything is describing a feature that no longer exists. Product, Article, LocalBusiness, Event and Review still produce rich results. So this build keeps a dated support table per structured data type, refuses to present an emitted type as a win when it no longer produces a result, and reports the things that genuinely matter: whether pages are indexable, whether canonicals are consistent, whether redirects chain, and whether the sitemap tells the truth. Those do not go out of date. ### Stack (fixed, do not substitute) - Crawling: A local crawler over your own site · you cannot report on indexability without seeing what a crawler sees - Rules: Indexability derived from directives, canonicals and redirects together · these three contradict each other constantly and the contradiction is the bug - Sitemaps: Generated from what is actually indexable, not from what exists · a sitemap listing noindexed pages is a signal you are not paying attention - Structured data: Emitted with a dated support table per type · search engines retire rich result types and a tool should know which - Runtime: TypeScript with SQLite · the work is crawling, comparison and reporting ### Data model (create this before Phase 1) - pages(id, url, status, title, meta_description, canonical, robots_directives, indexable, reason) - links(from_page_id, to_url, rel, is_internal) - redirects(from_url, to_url, status, chain_length, resolved_target, at) - sitemap_entries(url, lastmod, included, exclusion_reason) · exclusions recorded so the sitemap is explicable - schema_types(name, produces_rich_result, deprecated_on, checked_on, source_url) · dated, because this changes - schema_emissions(page_id, type_name, valid, warnings_json) - audits(id, run_at, pages_crawled, issues_by_kind_json) - A page's indexability is derived and stores its reason, because the interesting cases are where three signals disagree. ### Phase 1 · Seeing the site as a crawler does Build: Crawl the site, recording status, directives, canonical and links. Derive indexability from all of them together and record the reason. Surface pages where the signals contradict each other. Trap: Treating a page as indexable because it returns a successful status ignores a noindex directive, a canonical pointing elsewhere or a robots rule blocking the crawl, and those three disagree with each other on real sites constantly. Derive indexability from all the signals and report the contradiction rather than picking one. Done when: Indexability is derived from status, directives, canonical and crawl rules together, with the reason recorded. A page whose canonical points elsewhere while declaring itself indexable is surfaced as a contradiction. Pages blocked from crawling are distinguished from pages excluded from indexing. A crawl of a thousand pages completes and is repeatable. Do not build yet: No sitemaps, redirects or structured data. ### Phase 2 · Redirects that do not chain Build: Follow every redirect to its final target, recording the chain. Report chains longer than one hop and any loop. Detect a redirect pointing at a page that is itself not indexable. Trap: A redirect chain accumulates over years as pages move, and each hop loses a little of whatever the original link was worth, but nothing ever reports it because each individual redirect works fine. Resolve the whole chain and report its length, since the problem is only visible end to end. Done when: Every redirect's full chain is resolved and its length reported. A loop is detected rather than followed indefinitely. A redirect ending at a noindexed page is reported as a wasted redirect. Suggested collapses point each redirect directly at its final target. Do not build yet: No sitemaps or structured data yet. ### Phase 3 · A sitemap that tells the truth Build: Generate sitemaps from derived indexability rather than from the page list. Record why each excluded page was excluded. Split large sitemaps correctly and keep last-modified dates honest. Trap: A last-modified date regenerated on every build tells search engines that every page changed every day, which trains them to ignore the field entirely. Derive it from actual content change, and leave it out rather than inventing one. Done when: The sitemap contains only pages derived as indexable, verified against the crawl. Every exclusion has a recorded reason. Last-modified reflects real content change, tested by rebuilding without changing anything and confirming dates do not move. A large site splits into correctly indexed sitemap files. Do not build yet: No structured data yet. ### Phase 4 · Structured data, with an honest support table Build: Emit structured data with a dated table recording, per type, whether it still produces a rich result and when that was checked. Validate emitted markup. Never report an emitted type as a win when the table says it produces nothing. Trap: Reporting structured data emission as an improvement is measuring your own activity, and it stays wrong for years because nobody revisits it after a search engine retires a feature. Keep the support state as dated data with a source, and surface entries that have not been rechecked recently. Done when: Each schema type has a dated support record with a source. A type marked as no longer producing rich results is emitted if you want but never reported as an improvement, verified in the report output. Emitted markup is validated for correctness. Support records older than a stated period are surfaced for rechecking. Do not build yet: No reporting or content checks yet. ### Phase 5 · A report about the site, not about the tool Build: Report issues ranked by whether they affect indexability, ordered so the first item is the most consequential. Exclude anything you cannot state a mechanism for. Track issues across audits so you can see what got fixed. Trap: Padding a report with dozens of low-value warnings makes it feel thorough and guarantees nobody reads past the first screen, so the one genuine problem is buried among forty about meta description length. Report what affects indexability first, and leave out anything whose mechanism you cannot state in a sentence. Done when: Issues are ranked by effect on indexability with the most consequential first. Every issue type has a stated mechanism in one sentence, verified by reading the list. Issues are tracked across audits so fixes are visible. A clean site produces a short report rather than a padded one. Do not build yet: Nothing after this. ### Out of scope (and why) - Keyword research and ranking data, which requires data sources you would have to buy and which change constantly. - Content quality assessment, which is where most of this category's marketing lives and which no tool measures well. - Backlink analysis, which needs a crawl of the web rather than of your site. - Any promise about rankings, which nobody controls and which no technical change guarantees. ### README must contain - That structured data support is dated data with a source, because search engines retire rich result types - That emitting a deprecated type is never reported as an improvement - That indexability is derived from every signal together and the reason is recorded - That the sitemap is generated from derived indexability rather than from the page list - That last-modified reflects real content change rather than build time ===== ARCHITECTURE.md ===== # Architecture · AIOSEO ## Stack | Part | Choice | Why | | --- | --- | --- | | Crawling | A local crawler over your own site | you cannot report on indexability without seeing what a crawler sees | | Rules | Indexability derived from directives, canonicals and redirects together | these three contradict each other constantly and the contradiction is the bug | | Sitemaps | Generated from what is actually indexable, not from what exists | a sitemap listing noindexed pages is a signal you are not paying attention | | Structured data | Emitted with a dated support table per type | search engines retire rich result types and a tool should know which | | Runtime | TypeScript with SQLite | the work is crawling, comparison and reporting | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Crawl | Seeing the site as a crawler does. | Any crawler, provided every signal is recorded separately before deriving. | | Indexability | Derivation and contradiction detection. | Any implementation, provided the reason is recorded rather than just the verdict. | | Sitemap | Generation from derived indexability. | Any generator, provided it is not driven by the page list. | | Schema | Emission with a dated support table. | Any emitter, provided the support state is data with a date and a source. | | Report | Ranked issues with stated mechanisms. | Any presentation, provided padding is excluded by rule. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DB_PATH` · required · Crawl results and audit history. - `SITE_URL` · required · The site to crawl. Yours, or one you have permission for. - `CRAWL_DELAY_MS` · required · Even your own site should not be hammered by your own tool. - `SCHEMA_SUPPORT_PATH` · required · Dated support per type. The file that stops the tool reporting dead features as wins. - `SUPPORT_RECHECK_DAYS` · required · After this a support record is surfaced as stale. Rich result types get retired. - `REPORT_ONLY_WITH_MECHANISM` · required · Present as a constant. An issue you cannot explain in a sentence is padding. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it. ===== AGENTS.md ===== # Agent instructions · AIOSEO product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: A local crawler over your own site, Indexability derived from directives, canonicals and redirects together, Generated from what is actually indexable, not from what exists, Emitted with a dated support table per type, TypeScript with SQLite. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - Treating a page as indexable because it returns a successful status ignores a noindex directive, a canonical pointing elsewhere or a robots rule blocking the crawl, and those three disagree with each other on real sites constantly. Derive indexability from all the signals and report the contradiction rather than picking one. - A redirect chain accumulates over years as pages move, and each hop loses a little of whatever the original link was worth, but nothing ever reports it because each individual redirect works fine. Resolve the whole chain and report its length, since the problem is only visible end to end. - A last-modified date regenerated on every build tells search engines that every page changed every day, which trains them to ignore the field entirely. Derive it from actual content change, and leave it out rather than inventing one. - Reporting structured data emission as an improvement is measuring your own activity, and it stays wrong for years because nobody revisits it after a search engine retires a feature. Keep the support state as dated data with a source, and surface entries that have not been rechecked recently. - Padding a report with dozens of low-value warnings makes it feel thorough and guarantees nobody reads past the first screen, so the one genuine problem is buried among forty about meta description length. Report what affects indexability first, and leave out anything whose mechanism you cannot state in a sentence. ===== MILESTONES.md ===== # Delivery milestones · AIOSEO Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Seeing the site as a crawler does You know which pages are actually indexable and why. ### Steps 1. Record every signal separately Status, directives, canonical, crawl rules. Then derive. Files: `src/crawl.ts` 2. Derive indexability with a reason The reason is the useful output, not the boolean. Files: `src/indexable.ts`, `src/indexable.test.ts` 3. Surface contradictions Where signals disagree is where the real problems are. Files: `src/contradictions.ts` ### Done when - [ ] Indexability derives from all signals with a reason - [ ] Contradicting signals are surfaced - [ ] Blocked and excluded pages are distinguished - [ ] A thousand page crawl is repeatable ### Watch out - Treating a page as indexable because it returns a successful status ignores a noindex directive, a canonical pointing elsewhere or a robots rule blocking the crawl, and those three disagree with each other on real sites constantly. Derive indexability from all the signals and report the contradiction rather than picking one. ## M2 · Redirects that do not chain Every redirect resolves in one hop and no loop exists. ### Steps 1. Resolve the whole chain Each hop works fine. The chain is the problem and only shows end to end. Files: `src/redirects.ts` 2. Detect loops without hanging A cap and a report, not an infinite follow. Files: `src/redirects.ts` 3. Report redirects to nowhere useful A chain ending at a noindexed page is entirely wasted. Files: `src/redirects.ts` ### Done when - [ ] Chains are resolved and their length reported - [ ] Loops are detected rather than followed - [ ] Redirects to noindexed pages are reported - [ ] Suggested collapses target the final URL ### Watch out - A redirect chain accumulates over years as pages move, and each hop loses a little of whatever the original link was worth, but nothing ever reports it because each individual redirect works fine. Resolve the whole chain and report its length, since the problem is only visible end to end. ## M3 · A sitemap that tells the truth The sitemap contains exactly what is indexable and nothing else. ### Steps 1. Generate from derived indexability Not from the page list. A sitemap listing noindexed pages is a contradiction. Files: `src/sitemap.ts` 2. Record exclusion reasons So a missing page is explicable rather than mysterious. Files: `src/sitemap.ts` 3. Keep last-modified honest Regenerating it every build teaches crawlers to ignore it. Files: `src/lastmod.ts`, `tests/lastmod.test.ts` ### Done when - [ ] The sitemap contains only indexable pages - [ ] Every exclusion has a recorded reason - [ ] Rebuilding without changes does not move dates - [ ] A large site splits and indexes correctly ### Watch out - A last-modified date regenerated on every build tells search engines that every page changed every day, which trains them to ignore the field entirely. Derive it from actual content change, and leave it out rather than inventing one. ## M4 · Structured data, with an honest support table You know which types still produce a result and which are decoration. ### Steps 1. Keep support state as dated data With a source and a checked date. This changes and your tool should know. Files: `config/schema-support.json` 2. Validate emitted markup Emitting invalid markup is worse than emitting none. Files: `src/schema.ts` 3. Never report a dead type as a win Emitting is activity. Producing a result is an outcome. Files: `src/report.ts`, `tests/no-fake-wins.test.ts` ### Done when - [ ] Each type has a dated support record with a source - [ ] A deprecated type is never reported as an improvement - [ ] Emitted markup validates - [ ] Stale support records are surfaced ### Watch out - Reporting structured data emission as an improvement is measuring your own activity, and it stays wrong for years because nobody revisits it after a search engine retires a feature. Keep the support state as dated data with a source, and surface entries that have not been rechecked recently. ## M5 · A report about the site, not about the tool (production only) Every issue reported is one a person could act on and would want to. ### Steps 1. Rank by effect on indexability The first screen should hold the things that actually matter. Files: `src/report.ts` 2. Require a stated mechanism per issue If you cannot say why it matters in a sentence, leave it out. Files: `config/issues.json` 3. Track issues across audits So a fix is visible and a regression is caught. Files: `src/audits.ts` ### Done when - [ ] Issues rank by effect on indexability - [ ] Every issue type states its mechanism - [ ] Fixes are visible across audits - [ ] A clean site produces a short report ### Watch out - Padding a report with dozens of low-value warnings makes it feel thorough and guarantees nobody reads past the first screen, so the one genuine problem is buried among forty about meta description length. Report what affects indexability first, and leave out anything whose mechanism you cannot state in a sentence. ===== OPERATIONS.md ===== # Operations · AIOSEO ## Backup Audit history is what makes a regression visible, and it is small. Back it up; the crawl data itself regenerates. ## Restore Restore and confirm audit history is intact, since without it you cannot tell whether an issue is new or has been there for a year. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Staleness of the structured data support records is the maintenance burden that actually matters, because it is the part of the tool that silently becomes wrong without any code changing. ## Incident checklist Reporting an improvement that is not one is the failure this build is organised against, and it happens when a support record goes stale rather than when code breaks. The recheck window is the control. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Indexability is confirmed derived from every signal with a reason - [ ] Redirect chains are confirmed resolved and reported - [ ] The sitemap is confirmed to contain only indexable pages - [ ] A deprecated schema type is confirmed never reported as a win - [ ] Every issue type is confirmed to state its mechanism ## Launch constraint Do not market omitted AIOSEO capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered. ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. Crawl results and audit history. DB_PATH=./seo.db # Required. The site to crawl. Yours, or one you have permission for. SITE_URL=https://example.com # Required. Even your own site should not be hammered by your own tool. CRAWL_DELAY_MS=500 # Required. Dated support per type. The file that stops the tool reporting dead features as wins. SCHEMA_SUPPORT_PATH=./config/schema-support.json # Required. After this a support record is surfaced as stale. Rich result types get retired. SUPPORT_RECHECK_DAYS=90 # Required. Present as a constant. An issue you cannot explain in a sentence is padding. REPORT_ONLY_WITH_MECHANISM=always
# AIOSEO · indie build A technical SEO layer whose value is correctness rather than volume: sitemaps that match what is actually indexable, canonical and redirect handling that does not contradict itself, and structured data emitted with an honest record of whether it still does anything. Estimated effort: **multi-day**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Crawling | A local crawler over your own site | you cannot report on indexability without seeing what a crawler sees | | Rules | Indexability derived from directives, canonicals and redirects together | these three contradict each other constantly and the contradiction is the bug | | Sitemaps | Generated from what is actually indexable, not from what exists | a sitemap listing noindexed pages is a signal you are not paying attention | | Structured data | Emitted with a dated support table per type | search engines retire rich result types and a tool should know which | | Runtime | TypeScript with SQLite | the work is crawling, comparison and reporting | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Runs the crawler and the report generator. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **A real site of at least five hundred pages** · free - Why: Contradicting signals, redirect chains and sitemap divergence only appear on a site with history, and a fresh site has none of them. - Get it: Your own site, or one you have permission to crawl, ideally one that has been through a migration. - Verify: Five hundred pages crawl and at least one redirect chain longer than one hop exists - [ ] **Current structured data support information** · free to read - Why: The build keeps a dated table per type, and it has to be populated from the search engine's own current documentation rather than from memory. - Get it: Read the current rich results documentation and record, per type you intend to emit, whether it still produces a result and the date you checked. - Verify: A dated support record exists per emitted type with a source link - [ ] **Access to the site's search console** (optional) · free - Why: It is the only source for what the search engine actually did with your pages, as opposed to what your crawler thinks it should have. - Get it: Verify ownership of the site in the search engine's own console. - Verify: Index coverage data is visible for the site - [ ] **Which issues you will report** · free - Why: A report padded with low-value warnings is a report nobody reads, so the inclusion rule belongs before the checks are written. - Get it: Decide the rule: an issue is reported only if you can state its mechanism in one sentence. Write the list. - Verify: Every planned check has a one-sentence mechanism written down ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Keyword research and ranking data. - Content quality assessment. - Backlink analysis. - Any promise about rankings. - fresh proprietary keyword, backlink, or ranking data - proprietary web and keyword indexes - fresh rank and clickstream data - large-scale crawling infrastructure - historical competitor datasets and support If one of those is essential to you, that is the reason to keep paying for AIOSEO, and the README should say so rather than pretend.
# Build brief · AIOSEO The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me technical SEO for a site, reporting only what actually still does something to replace All in One SEO. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Most of this category's output is theatre. A plugin that emits structured data and reports it as an achievement is measuring its own activity rather than any outcome, and search engines retire features constantly. A concrete example that is current: Google deprecated FAQ rich results on 7 May 2026, with Search Console reporting and Rich Results Test support ending in June 2026 and API support in August. HowTo went the same way earlier. The markup is still valid schema and harmless to keep, but any tool telling you that adding it improved anything is describing a feature that no longer exists. Product, Article, LocalBusiness, Event and Review still produce rich results. So this build keeps a dated support table per structured data type, refuses to present an emitted type as a win when it no longer produces a result, and reports the things that genuinely matter: whether pages are indexable, whether canonicals are consistent, whether redirects chain, and whether the sitemap tells the truth. Those do not go out of date. ### Stack (fixed, do not substitute) - Crawling: A local crawler over your own site · you cannot report on indexability without seeing what a crawler sees - Rules: Indexability derived from directives, canonicals and redirects together · these three contradict each other constantly and the contradiction is the bug - Sitemaps: Generated from what is actually indexable, not from what exists · a sitemap listing noindexed pages is a signal you are not paying attention - Structured data: Emitted with a dated support table per type · search engines retire rich result types and a tool should know which - Runtime: TypeScript with SQLite · the work is crawling, comparison and reporting ### Data model (create this before Phase 1) - pages(id, url, status, title, meta_description, canonical, robots_directives, indexable, reason) - links(from_page_id, to_url, rel, is_internal) - redirects(from_url, to_url, status, chain_length, resolved_target, at) - sitemap_entries(url, lastmod, included, exclusion_reason) · exclusions recorded so the sitemap is explicable - schema_types(name, produces_rich_result, deprecated_on, checked_on, source_url) · dated, because this changes - schema_emissions(page_id, type_name, valid, warnings_json) - audits(id, run_at, pages_crawled, issues_by_kind_json) - A page's indexability is derived and stores its reason, because the interesting cases are where three signals disagree. ### Phase 1 · Seeing the site as a crawler does Build: Crawl the site, recording status, directives, canonical and links. Derive indexability from all of them together and record the reason. Surface pages where the signals contradict each other. Trap: Treating a page as indexable because it returns a successful status ignores a noindex directive, a canonical pointing elsewhere or a robots rule blocking the crawl, and those three disagree with each other on real sites constantly. Derive indexability from all the signals and report the contradiction rather than picking one. Done when: Indexability is derived from status, directives, canonical and crawl rules together, with the reason recorded. A page whose canonical points elsewhere while declaring itself indexable is surfaced as a contradiction. Pages blocked from crawling are distinguished from pages excluded from indexing. A crawl of a thousand pages completes and is repeatable. Do not build yet: No sitemaps, redirects or structured data. ### Phase 2 · Redirects that do not chain Build: Follow every redirect to its final target, recording the chain. Report chains longer than one hop and any loop. Detect a redirect pointing at a page that is itself not indexable. Trap: A redirect chain accumulates over years as pages move, and each hop loses a little of whatever the original link was worth, but nothing ever reports it because each individual redirect works fine. Resolve the whole chain and report its length, since the problem is only visible end to end. Done when: Every redirect's full chain is resolved and its length reported. A loop is detected rather than followed indefinitely. A redirect ending at a noindexed page is reported as a wasted redirect. Suggested collapses point each redirect directly at its final target. Do not build yet: No sitemaps or structured data yet. ### Phase 3 · A sitemap that tells the truth Build: Generate sitemaps from derived indexability rather than from the page list. Record why each excluded page was excluded. Split large sitemaps correctly and keep last-modified dates honest. Trap: A last-modified date regenerated on every build tells search engines that every page changed every day, which trains them to ignore the field entirely. Derive it from actual content change, and leave it out rather than inventing one. Done when: The sitemap contains only pages derived as indexable, verified against the crawl. Every exclusion has a recorded reason. Last-modified reflects real content change, tested by rebuilding without changing anything and confirming dates do not move. A large site splits into correctly indexed sitemap files. Do not build yet: No structured data yet. ### Phase 4 · Structured data, with an honest support table Build: Emit structured data with a dated table recording, per type, whether it still produces a rich result and when that was checked. Validate emitted markup. Never report an emitted type as a win when the table says it produces nothing. Trap: Reporting structured data emission as an improvement is measuring your own activity, and it stays wrong for years because nobody revisits it after a search engine retires a feature. Keep the support state as dated data with a source, and surface entries that have not been rechecked recently. Done when: Each schema type has a dated support record with a source. A type marked as no longer producing rich results is emitted if you want but never reported as an improvement, verified in the report output. Emitted markup is validated for correctness. Support records older than a stated period are surfaced for rechecking. Do not build yet: No reporting or content checks yet. ### Phase 5 · A report about the site, not about the tool Build: Report issues ranked by whether they affect indexability, ordered so the first item is the most consequential. Exclude anything you cannot state a mechanism for. Track issues across audits so you can see what got fixed. Trap: Padding a report with dozens of low-value warnings makes it feel thorough and guarantees nobody reads past the first screen, so the one genuine problem is buried among forty about meta description length. Report what affects indexability first, and leave out anything whose mechanism you cannot state in a sentence. Done when: Issues are ranked by effect on indexability with the most consequential first. Every issue type has a stated mechanism in one sentence, verified by reading the list. Issues are tracked across audits so fixes are visible. A clean site produces a short report rather than a padded one. Do not build yet: Nothing after this. ### Out of scope (and why) - Keyword research and ranking data, which requires data sources you would have to buy and which change constantly. - Content quality assessment, which is where most of this category's marketing lives and which no tool measures well. - Backlink analysis, which needs a crawl of the web rather than of your site. - Any promise about rankings, which nobody controls and which no technical change guarantees. ### README must contain - That structured data support is dated data with a source, because search engines retire rich result types - That emitting a deprecated type is never reported as an improvement - That indexability is derived from every signal together and the reason is recorded - That the sitemap is generated from derived indexability rather than from the page list - That last-modified reflects real content change rather than build time
# Agent instructions · AIOSEO indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: A local crawler over your own site, Indexability derived from directives, canonicals and redirects together, Generated from what is actually indexable, not from what exists, Emitted with a dated support table per type, TypeScript with SQLite. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Treating a page as indexable because it returns a successful status ignores a noindex directive, a canonical pointing elsewhere or a robots rule blocking the crawl, and those three disagree with each other on real sites constantly. Derive indexability from all the signals and report the contradiction rather than picking one. - A redirect chain accumulates over years as pages move, and each hop loses a little of whatever the original link was worth, but nothing ever reports it because each individual redirect works fine. Resolve the whole chain and report its length, since the problem is only visible end to end. - A last-modified date regenerated on every build tells search engines that every page changed every day, which trains them to ignore the field entirely. Derive it from actual content change, and leave it out rather than inventing one. - Reporting structured data emission as an improvement is measuring your own activity, and it stays wrong for years because nobody revisits it after a search engine retires a feature. Keep the support state as dated data with a source, and surface entries that have not been rechecked recently.
# Build plan · AIOSEO A technical SEO layer whose value is correctness rather than volume: sitemaps that match what is actually indexable, canonical and redirect handling that does not contradict itself, and structured data emitted with an honest record of whether it still does anything. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · Seeing the site as a crawler does You know which pages are actually indexable and why. ### Steps 1. Record every signal separately Status, directives, canonical, crawl rules. Then derive. Files: `src/crawl.ts` 2. Derive indexability with a reason The reason is the useful output, not the boolean. Files: `src/indexable.ts`, `src/indexable.test.ts` 3. Surface contradictions Where signals disagree is where the real problems are. Files: `src/contradictions.ts` ### Done when - [ ] Indexability derives from all signals with a reason - [ ] Contradicting signals are surfaced - [ ] Blocked and excluded pages are distinguished - [ ] A thousand page crawl is repeatable ### Watch out - Treating a page as indexable because it returns a successful status ignores a noindex directive, a canonical pointing elsewhere or a robots rule blocking the crawl, and those three disagree with each other on real sites constantly. Derive indexability from all the signals and report the contradiction rather than picking one. ## Phase 2 · Redirects that do not chain Every redirect resolves in one hop and no loop exists. ### Steps 1. Resolve the whole chain Each hop works fine. The chain is the problem and only shows end to end. Files: `src/redirects.ts` 2. Detect loops without hanging A cap and a report, not an infinite follow. Files: `src/redirects.ts` 3. Report redirects to nowhere useful A chain ending at a noindexed page is entirely wasted. Files: `src/redirects.ts` ### Done when - [ ] Chains are resolved and their length reported - [ ] Loops are detected rather than followed - [ ] Redirects to noindexed pages are reported - [ ] Suggested collapses target the final URL ### Watch out - A redirect chain accumulates over years as pages move, and each hop loses a little of whatever the original link was worth, but nothing ever reports it because each individual redirect works fine. Resolve the whole chain and report its length, since the problem is only visible end to end. ## Phase 3 · A sitemap that tells the truth The sitemap contains exactly what is indexable and nothing else. ### Steps 1. Generate from derived indexability Not from the page list. A sitemap listing noindexed pages is a contradiction. Files: `src/sitemap.ts` 2. Record exclusion reasons So a missing page is explicable rather than mysterious. Files: `src/sitemap.ts` 3. Keep last-modified honest Regenerating it every build teaches crawlers to ignore it. Files: `src/lastmod.ts`, `tests/lastmod.test.ts` ### Done when - [ ] The sitemap contains only indexable pages - [ ] Every exclusion has a recorded reason - [ ] Rebuilding without changes does not move dates - [ ] A large site splits and indexes correctly ### Watch out - A last-modified date regenerated on every build tells search engines that every page changed every day, which trains them to ignore the field entirely. Derive it from actual content change, and leave it out rather than inventing one. ## Phase 4 · Structured data, with an honest support table You know which types still produce a result and which are decoration. ### Steps 1. Keep support state as dated data With a source and a checked date. This changes and your tool should know. Files: `config/schema-support.json` 2. Validate emitted markup Emitting invalid markup is worse than emitting none. Files: `src/schema.ts` 3. Never report a dead type as a win Emitting is activity. Producing a result is an outcome. Files: `src/report.ts`, `tests/no-fake-wins.test.ts` ### Done when - [ ] Each type has a dated support record with a source - [ ] A deprecated type is never reported as an improvement - [ ] Emitted markup validates - [ ] Stale support records are surfaced ### Watch out - Reporting structured data emission as an improvement is measuring your own activity, and it stays wrong for years because nobody revisits it after a search engine retires a feature. Keep the support state as dated data with a source, and surface entries that have not been rechecked recently. ## Not in this build - Keyword research and ranking data. - Content quality assessment. - Backlink analysis. - Any promise about rankings. ## After v1, if you want it - Comparing your sitemap against what the search console says was indexed, which is where the real gap shows - Internal link analysis, since orphaned pages are a genuine and fixable problem - Change alerts when a deployment breaks indexability, which is when it usually happens
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Crawl results and audit history. DB_PATH=./seo.db # Required. The site to crawl. Yours, or one you have permission for. SITE_URL=https://example.com # Required. Even your own site should not be hammered by your own tool. CRAWL_DELAY_MS=500 # Required. Dated support per type. The file that stops the tool reporting dead features as wins. SCHEMA_SUPPORT_PATH=./config/schema-support.json # Required. After this a support record is surfaced as stale. Rich result types get retired. SUPPORT_RECHECK_DAYS=90 # Required. Present as a constant. An issue you cannot explain in a sentence is padding. REPORT_ONLY_WITH_MECHANISM=always
# AIOSEO · product brief ## Problem AIOSEO's solo core is compact: build a focused WordPress SEO plugin audit for user-owned sites that crawls pages, records technical signals, and exports a prioritized CSV and HTML report. A competent builder can reach a useful personal version in one sitting, while the paid product mainly wins on proprietary data, index, crawl scale. ## Product outcome A technical SEO tool that reports the handful of things actually preventing pages from being indexed, and that knows which structured data types still do anything. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - Python 3.12 - Playwright browsers - permission to crawl the target site ## Explicit non-goals for v1 - Keyword research and ranking data. - Content quality assessment. - Backlink analysis. - Any promise about rankings. - fresh proprietary keyword, backlink, or ranking data - proprietary web and keyword indexes - fresh rank and clickstream data - large-scale crawling infrastructure - historical competitor datasets and support ## Success criteria - Indexability is confirmed derived from every signal with a reason - Redirect chains are confirmed resolved and reported - The sitemap is confirmed to contain only indexable pages - A deprecated schema type is confirmed never reported as a win - Every issue type is confirmed to state its mechanism
# Build brief · AIOSEO The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me technical SEO for a site, reporting only what actually still does something to replace All in One SEO. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Rule zero Most of this category's output is theatre. A plugin that emits structured data and reports it as an achievement is measuring its own activity rather than any outcome, and search engines retire features constantly. A concrete example that is current: Google deprecated FAQ rich results on 7 May 2026, with Search Console reporting and Rich Results Test support ending in June 2026 and API support in August. HowTo went the same way earlier. The markup is still valid schema and harmless to keep, but any tool telling you that adding it improved anything is describing a feature that no longer exists. Product, Article, LocalBusiness, Event and Review still produce rich results. So this build keeps a dated support table per structured data type, refuses to present an emitted type as a win when it no longer produces a result, and reports the things that genuinely matter: whether pages are indexable, whether canonicals are consistent, whether redirects chain, and whether the sitemap tells the truth. Those do not go out of date. ### Stack (fixed, do not substitute) - Crawling: A local crawler over your own site · you cannot report on indexability without seeing what a crawler sees - Rules: Indexability derived from directives, canonicals and redirects together · these three contradict each other constantly and the contradiction is the bug - Sitemaps: Generated from what is actually indexable, not from what exists · a sitemap listing noindexed pages is a signal you are not paying attention - Structured data: Emitted with a dated support table per type · search engines retire rich result types and a tool should know which - Runtime: TypeScript with SQLite · the work is crawling, comparison and reporting ### Data model (create this before Phase 1) - pages(id, url, status, title, meta_description, canonical, robots_directives, indexable, reason) - links(from_page_id, to_url, rel, is_internal) - redirects(from_url, to_url, status, chain_length, resolved_target, at) - sitemap_entries(url, lastmod, included, exclusion_reason) · exclusions recorded so the sitemap is explicable - schema_types(name, produces_rich_result, deprecated_on, checked_on, source_url) · dated, because this changes - schema_emissions(page_id, type_name, valid, warnings_json) - audits(id, run_at, pages_crawled, issues_by_kind_json) - A page's indexability is derived and stores its reason, because the interesting cases are where three signals disagree. ### Phase 1 · Seeing the site as a crawler does Build: Crawl the site, recording status, directives, canonical and links. Derive indexability from all of them together and record the reason. Surface pages where the signals contradict each other. Trap: Treating a page as indexable because it returns a successful status ignores a noindex directive, a canonical pointing elsewhere or a robots rule blocking the crawl, and those three disagree with each other on real sites constantly. Derive indexability from all the signals and report the contradiction rather than picking one. Done when: Indexability is derived from status, directives, canonical and crawl rules together, with the reason recorded. A page whose canonical points elsewhere while declaring itself indexable is surfaced as a contradiction. Pages blocked from crawling are distinguished from pages excluded from indexing. A crawl of a thousand pages completes and is repeatable. Do not build yet: No sitemaps, redirects or structured data. ### Phase 2 · Redirects that do not chain Build: Follow every redirect to its final target, recording the chain. Report chains longer than one hop and any loop. Detect a redirect pointing at a page that is itself not indexable. Trap: A redirect chain accumulates over years as pages move, and each hop loses a little of whatever the original link was worth, but nothing ever reports it because each individual redirect works fine. Resolve the whole chain and report its length, since the problem is only visible end to end. Done when: Every redirect's full chain is resolved and its length reported. A loop is detected rather than followed indefinitely. A redirect ending at a noindexed page is reported as a wasted redirect. Suggested collapses point each redirect directly at its final target. Do not build yet: No sitemaps or structured data yet. ### Phase 3 · A sitemap that tells the truth Build: Generate sitemaps from derived indexability rather than from the page list. Record why each excluded page was excluded. Split large sitemaps correctly and keep last-modified dates honest. Trap: A last-modified date regenerated on every build tells search engines that every page changed every day, which trains them to ignore the field entirely. Derive it from actual content change, and leave it out rather than inventing one. Done when: The sitemap contains only pages derived as indexable, verified against the crawl. Every exclusion has a recorded reason. Last-modified reflects real content change, tested by rebuilding without changing anything and confirming dates do not move. A large site splits into correctly indexed sitemap files. Do not build yet: No structured data yet. ### Phase 4 · Structured data, with an honest support table Build: Emit structured data with a dated table recording, per type, whether it still produces a rich result and when that was checked. Validate emitted markup. Never report an emitted type as a win when the table says it produces nothing. Trap: Reporting structured data emission as an improvement is measuring your own activity, and it stays wrong for years because nobody revisits it after a search engine retires a feature. Keep the support state as dated data with a source, and surface entries that have not been rechecked recently. Done when: Each schema type has a dated support record with a source. A type marked as no longer producing rich results is emitted if you want but never reported as an improvement, verified in the report output. Emitted markup is validated for correctness. Support records older than a stated period are surfaced for rechecking. Do not build yet: No reporting or content checks yet. ### Phase 5 · A report about the site, not about the tool Build: Report issues ranked by whether they affect indexability, ordered so the first item is the most consequential. Exclude anything you cannot state a mechanism for. Track issues across audits so you can see what got fixed. Trap: Padding a report with dozens of low-value warnings makes it feel thorough and guarantees nobody reads past the first screen, so the one genuine problem is buried among forty about meta description length. Report what affects indexability first, and leave out anything whose mechanism you cannot state in a sentence. Done when: Issues are ranked by effect on indexability with the most consequential first. Every issue type has a stated mechanism in one sentence, verified by reading the list. Issues are tracked across audits so fixes are visible. A clean site produces a short report rather than a padded one. Do not build yet: Nothing after this. ### Out of scope (and why) - Keyword research and ranking data, which requires data sources you would have to buy and which change constantly. - Content quality assessment, which is where most of this category's marketing lives and which no tool measures well. - Backlink analysis, which needs a crawl of the web rather than of your site. - Any promise about rankings, which nobody controls and which no technical change guarantees. ### README must contain - That structured data support is dated data with a source, because search engines retire rich result types - That emitting a deprecated type is never reported as an improvement - That indexability is derived from every signal together and the reason is recorded - That the sitemap is generated from derived indexability rather than from the page list - That last-modified reflects real content change rather than build time
# Architecture · AIOSEO ## Stack | Part | Choice | Why | | --- | --- | --- | | Crawling | A local crawler over your own site | you cannot report on indexability without seeing what a crawler sees | | Rules | Indexability derived from directives, canonicals and redirects together | these three contradict each other constantly and the contradiction is the bug | | Sitemaps | Generated from what is actually indexable, not from what exists | a sitemap listing noindexed pages is a signal you are not paying attention | | Structured data | Emitted with a dated support table per type | search engines retire rich result types and a tool should know which | | Runtime | TypeScript with SQLite | the work is crawling, comparison and reporting | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Crawl | Seeing the site as a crawler does. | Any crawler, provided every signal is recorded separately before deriving. | | Indexability | Derivation and contradiction detection. | Any implementation, provided the reason is recorded rather than just the verdict. | | Sitemap | Generation from derived indexability. | Any generator, provided it is not driven by the page list. | | Schema | Emission with a dated support table. | Any emitter, provided the support state is data with a date and a source. | | Report | Ranked issues with stated mechanisms. | Any presentation, provided padding is excluded by rule. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `DB_PATH` · required · Crawl results and audit history. - `SITE_URL` · required · The site to crawl. Yours, or one you have permission for. - `CRAWL_DELAY_MS` · required · Even your own site should not be hammered by your own tool. - `SCHEMA_SUPPORT_PATH` · required · Dated support per type. The file that stops the tool reporting dead features as wins. - `SUPPORT_RECHECK_DAYS` · required · After this a support record is surfaced as stale. Rich result types get retired. - `REPORT_ONLY_WITH_MECHANISM` · required · Present as a constant. An issue you cannot explain in a sentence is padding. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it.
# Agent instructions · AIOSEO product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: A local crawler over your own site, Indexability derived from directives, canonicals and redirects together, Generated from what is actually indexable, not from what exists, Emitted with a dated support table per type, TypeScript with SQLite. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - Treating a page as indexable because it returns a successful status ignores a noindex directive, a canonical pointing elsewhere or a robots rule blocking the crawl, and those three disagree with each other on real sites constantly. Derive indexability from all the signals and report the contradiction rather than picking one. - A redirect chain accumulates over years as pages move, and each hop loses a little of whatever the original link was worth, but nothing ever reports it because each individual redirect works fine. Resolve the whole chain and report its length, since the problem is only visible end to end. - A last-modified date regenerated on every build tells search engines that every page changed every day, which trains them to ignore the field entirely. Derive it from actual content change, and leave it out rather than inventing one. - Reporting structured data emission as an improvement is measuring your own activity, and it stays wrong for years because nobody revisits it after a search engine retires a feature. Keep the support state as dated data with a source, and surface entries that have not been rechecked recently. - Padding a report with dozens of low-value warnings makes it feel thorough and guarantees nobody reads past the first screen, so the one genuine problem is buried among forty about meta description length. Report what affects indexability first, and leave out anything whose mechanism you cannot state in a sentence.
# Delivery milestones · AIOSEO Estimated effort: **multi-day** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · Seeing the site as a crawler does You know which pages are actually indexable and why. ### Steps 1. Record every signal separately Status, directives, canonical, crawl rules. Then derive. Files: `src/crawl.ts` 2. Derive indexability with a reason The reason is the useful output, not the boolean. Files: `src/indexable.ts`, `src/indexable.test.ts` 3. Surface contradictions Where signals disagree is where the real problems are. Files: `src/contradictions.ts` ### Done when - [ ] Indexability derives from all signals with a reason - [ ] Contradicting signals are surfaced - [ ] Blocked and excluded pages are distinguished - [ ] A thousand page crawl is repeatable ### Watch out - Treating a page as indexable because it returns a successful status ignores a noindex directive, a canonical pointing elsewhere or a robots rule blocking the crawl, and those three disagree with each other on real sites constantly. Derive indexability from all the signals and report the contradiction rather than picking one. ## M2 · Redirects that do not chain Every redirect resolves in one hop and no loop exists. ### Steps 1. Resolve the whole chain Each hop works fine. The chain is the problem and only shows end to end. Files: `src/redirects.ts` 2. Detect loops without hanging A cap and a report, not an infinite follow. Files: `src/redirects.ts` 3. Report redirects to nowhere useful A chain ending at a noindexed page is entirely wasted. Files: `src/redirects.ts` ### Done when - [ ] Chains are resolved and their length reported - [ ] Loops are detected rather than followed - [ ] Redirects to noindexed pages are reported - [ ] Suggested collapses target the final URL ### Watch out - A redirect chain accumulates over years as pages move, and each hop loses a little of whatever the original link was worth, but nothing ever reports it because each individual redirect works fine. Resolve the whole chain and report its length, since the problem is only visible end to end. ## M3 · A sitemap that tells the truth The sitemap contains exactly what is indexable and nothing else. ### Steps 1. Generate from derived indexability Not from the page list. A sitemap listing noindexed pages is a contradiction. Files: `src/sitemap.ts` 2. Record exclusion reasons So a missing page is explicable rather than mysterious. Files: `src/sitemap.ts` 3. Keep last-modified honest Regenerating it every build teaches crawlers to ignore it. Files: `src/lastmod.ts`, `tests/lastmod.test.ts` ### Done when - [ ] The sitemap contains only indexable pages - [ ] Every exclusion has a recorded reason - [ ] Rebuilding without changes does not move dates - [ ] A large site splits and indexes correctly ### Watch out - A last-modified date regenerated on every build tells search engines that every page changed every day, which trains them to ignore the field entirely. Derive it from actual content change, and leave it out rather than inventing one. ## M4 · Structured data, with an honest support table You know which types still produce a result and which are decoration. ### Steps 1. Keep support state as dated data With a source and a checked date. This changes and your tool should know. Files: `config/schema-support.json` 2. Validate emitted markup Emitting invalid markup is worse than emitting none. Files: `src/schema.ts` 3. Never report a dead type as a win Emitting is activity. Producing a result is an outcome. Files: `src/report.ts`, `tests/no-fake-wins.test.ts` ### Done when - [ ] Each type has a dated support record with a source - [ ] A deprecated type is never reported as an improvement - [ ] Emitted markup validates - [ ] Stale support records are surfaced ### Watch out - Reporting structured data emission as an improvement is measuring your own activity, and it stays wrong for years because nobody revisits it after a search engine retires a feature. Keep the support state as dated data with a source, and surface entries that have not been rechecked recently. ## M5 · A report about the site, not about the tool (production only) Every issue reported is one a person could act on and would want to. ### Steps 1. Rank by effect on indexability The first screen should hold the things that actually matter. Files: `src/report.ts` 2. Require a stated mechanism per issue If you cannot say why it matters in a sentence, leave it out. Files: `config/issues.json` 3. Track issues across audits So a fix is visible and a regression is caught. Files: `src/audits.ts` ### Done when - [ ] Issues rank by effect on indexability - [ ] Every issue type states its mechanism - [ ] Fixes are visible across audits - [ ] A clean site produces a short report ### Watch out - Padding a report with dozens of low-value warnings makes it feel thorough and guarantees nobody reads past the first screen, so the one genuine problem is buried among forty about meta description length. Report what affects indexability first, and leave out anything whose mechanism you cannot state in a sentence.
# Operations · AIOSEO ## Backup Audit history is what makes a regression visible, and it is small. Back it up; the crawl data itself regenerates. ## Restore Restore and confirm audit history is intact, since without it you cannot tell whether an issue is new or has been there for a year. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring Staleness of the structured data support records is the maintenance burden that actually matters, because it is the part of the tool that silently becomes wrong without any code changing. ## Incident checklist Reporting an improvement that is not one is the failure this build is organised against, and it happens when a support record goes stale rather than when code breaks. The recheck window is the control. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] Indexability is confirmed derived from every signal with a reason - [ ] Redirect chains are confirmed resolved and reported - [ ] The sitemap is confirmed to contain only indexable pages - [ ] A deprecated schema type is confirmed never reported as a win - [ ] Every issue type is confirmed to state its mechanism ## Launch constraint Do not market omitted AIOSEO capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered.
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. Crawl results and audit history. DB_PATH=./seo.db # Required. The site to crawl. Yours, or one you have permission for. SITE_URL=https://example.com # Required. Even your own site should not be hammered by your own tool. CRAWL_DELAY_MS=500 # Required. Dated support per type. The file that stops the tool reporting dead features as wins. SCHEMA_SUPPORT_PATH=./config/schema-support.json # Required. After this a support record is surfaced as stale. Rich result types get retired. SUPPORT_RECHECK_DAYS=90 # Required. Present as a constant. An issue you cannot explain in a sentence is padding. REPORT_ONLY_WITH_MECHANISM=always
$ choose a build depth, inspect the files, then open the complete pack in your agent
AIOSEO: The dashboard is reproducible; the expensive part is continuously collecting, cleaning, storing, and defending a useful index of the web.
xfresh proprietary keyword, backlink, or ranking data
xproprietary web and keyword indexes
xfresh rank and clickstream data
xlarge-scale crawling infrastructure
xhistorical competitor datasets and support
Don't feel like building it? These folks already made it free.
no votes, no pay-to-list · just what's real
AIOSEO pricing
| plan | monthly | annual (per mo) | what you get |
|---|---|---|---|
| aioseo lite | $0/workspace | $0/workspace | No time limit; numeric site and AI-credit caps are not published for the free plugin. |
| basic | — | $4.13/workspace | 1 site; 10,000 AI credits; 10 tracked keywords; unlimited SEO audits. |
| plus | — | $8.29/workspace | 3 sites; 25,000 AI credits; 10 tracked keywords; 15 SEO revisions. |
| pro | — | $16.63/workspace | 10 sites; 50,000 AI credits; 10 tracked keywords; 30 SEO revisions. |
| elite | — | $24.96/workspace | 100 sites; 200,000 AI credits; 10 tracked keywords; unlimited SEO revisions. |
free tierpermanent free plugin with no time limit; numeric site and AI-credit caps are not published
billingannual only for paid licences; displayed prices are first-year promotional totals and auto-renew at the higher regular annual price
hidden costsrenewal roughly doubles each displayed first-year price; AI-credit top-ups and installation/configuration services cost extra; AI-credit refunds require fewer than 500 credits used
verified 2026-08-13 · source ↗
Vibecode AIOSEO
Yes. A competent AI coding agent (Claude Code, Codex, Cursor) can build a usable personal AIOSEO replacement in one session with the prompt on this page. It runs on your own machine or server with no subscription.
How much does AIOSEO cost?
AIOSEO's pricing is usage-based or varies by plan · The official pricing URL is recorded, but a stable current amount was not safely recoverable in this pass. Do not publish a number until rechecked..
What do I lose by replacing AIOSEO?
Honestly: fresh proprietary keyword, backlink, or ranking data; proprietary web and keyword indexes; fresh rank and clickstream data; large-scale crawling infrastructure; historical competitor datasets and support. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to AIOSEO?
Yes: Rank Math SEO (Most of the paid checklist for zero dollars; analytics refresh weekly and the upsells never quite leave the room.) The prompt is for when you want it exactly your way.