diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..ac3f36a
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,20 @@
+
+
+## What does this PR change?
+
+
+
+## Which guideline(s) are affected?
+
+
+
+## Checklist
+
+- [ ] H1 matches `title` in frontmatter
+- [ ] All required body sections present and in order (`What & why`, `Scoring`, `Steps`, `References`, optionally `How Forter helps`)
+- [ ] `forterApplies` and `How Forter helps` are in sync
+- [ ] Every `#guideline-M-N` cross-reference points to an existing guideline
+- [ ] References include at least one canonical source per claim
diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml
new file mode 100644
index 0000000..3d09355
--- /dev/null
+++ b/.github/workflows/validate.yml
@@ -0,0 +1,24 @@
+name: validate
+
+on:
+ pull_request:
+ paths:
+ - "content/**"
+ - "audit/**"
+ - "scripts/**"
+ - "package.json"
+ - ".github/workflows/validate.yml"
+ push:
+ branches: [main]
+
+jobs:
+ validate:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version: "20"
+ cache: "npm"
+ - run: npm ci
+ - run: npm test
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..578e229
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,11 @@
+# OS / editor
+.DS_Store
+.vscode/
+.idea/
+*.swp
+
+# Just in case anyone runs tooling locally
+node_modules/
+
+# Audit reports — generated locally, never committed
+report/
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..12d2718
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,74 @@
+# Contributing
+
+Thanks for opening a PR. This repo is content-only - markdown files in `content/`, no build tooling. The rendered PDF and webinar live in a separate internal pipeline.
+
+## What to change
+
+- **Fix a fact, a link, or wording** in any existing guideline (`content/m*-*.md`).
+- **Improve the steps or references** in a guideline.
+- **Propose a new guideline** by opening an issue first - module numbering and scoping benefit from discussion before drafting.
+
+## Frontmatter schema
+
+Every guideline file starts with YAML frontmatter:
+
+```yaml
+---
+id: m4-1-openapi-spec
+module: actionable # discoverable | comprehensible | trustworthy | actionable | experiential
+moduleNumber: 4 # 1-5, must match module
+guidelineNumber: 1 # unique within module
+title: Ship OpenAPI specification
+complexity: 4 # 1 (trivial) to 5 (major engineering project); rendered as "Effort" in the guide body
+impact: 5 # 1 (nice to have) to 5 (table stakes)
+visualChange: low # optional: none | low | medium | high
+forterApplies: partial # no | partial | yes | flagship
+---
+```
+
+Chapter and module-overview files have a lighter frontmatter:
+
+```yaml
+---
+id: module-discoverable
+title: Module 1 - Be Discoverable
+kind: module-overview # front-matter | chapter | module-overview | appendix
+moduleNumber: 1 # required for kind=module-overview
+---
+```
+
+## Required body sections
+
+Each guideline must have, in order:
+
+1. `#
` - H1 must match the frontmatter `title` exactly.
+2. `## What & why` - what the guideline is and why it matters.
+3. `## Scoring` - concrete, observable criteria for pass / partial / fail. This is what auditors (human or agent) will use.
+4. `## Steps` - numbered, concrete actions to implement the guideline.
+5. `## References` - links to specs, RFCs, blog posts, code examples.
+6. `## How Forter helps` - **only** when `forterApplies` is `partial`, `yes`, or `flagship`. Skip this section when `forterApplies: no`.
+
+The internal validator (run in CI) enforces this structure and will fail the PR if a section is missing, mis-ordered, or if `forterApplies` doesn't match the presence of "How Forter helps".
+
+## Cross-references
+
+Link between guidelines with relative file paths, e.g. `[3.1](./m3-1-oauth-discovery.md)`. Audit rubrics link the same way (`[m3-2](./m3-2.md)`) and link back to content with `../content/...`. The validator (`npm test`) resolves every `(./mX-Y-*.md)` / `(../content|audit/...)` reference and fails the PR on a dangling link.
+
+## Checklist before opening a PR
+
+- [ ] H1 matches `title` in frontmatter.
+- [ ] All required sections present and in order.
+- [ ] `forterApplies` and `How Forter helps` are in sync (both present or both absent).
+- [ ] Every `[x.y](./mX-Y-*.md)` cross-reference points to an existing file.
+- [ ] Tags are lowercase, kebab-case.
+- [ ] References include at least one canonical source per claim.
+
+## Tone
+
+- Concrete over abstract. "Add `Accept: application/ld+json`" beats "consider content negotiation".
+- Cite RFCs and specs by number. Link to the canonical source, not a third-party tutorial.
+- Don't sell Forter outside the "How Forter helps" section. The body of the guideline should be useful regardless of vendor choice.
+
+## License
+
+By submitting a PR you agree your contribution is licensed [CC BY 4.0](./LICENSE), the same as the rest of the repo.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..da6ab6c
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,396 @@
+Attribution 4.0 International
+
+=======================================================================
+
+Creative Commons Corporation ("Creative Commons") is not a law firm and
+does not provide legal services or legal advice. Distribution of
+Creative Commons public licenses does not create a lawyer-client or
+other relationship. Creative Commons makes its licenses and related
+information available on an "as-is" basis. Creative Commons gives no
+warranties regarding its licenses, any material licensed under their
+terms and conditions, or any related information. Creative Commons
+disclaims all liability for damages resulting from their use to the
+fullest extent possible.
+
+Using Creative Commons Public Licenses
+
+Creative Commons public licenses provide a standard set of terms and
+conditions that creators and other rights holders may use to share
+original works of authorship and other material subject to copyright
+and certain other rights specified in the public license below. The
+following considerations are for informational purposes only, are not
+exhaustive, and do not form part of our licenses.
+
+ Considerations for licensors: Our public licenses are
+ intended for use by those authorized to give the public
+ permission to use material in ways otherwise restricted by
+ copyright and certain other rights. Our licenses are
+ irrevocable. Licensors should read and understand the terms
+ and conditions of the license they choose before applying it.
+ Licensors should also secure all rights necessary before
+ applying our licenses so that the public can reuse the
+ material as expected. Licensors should clearly mark any
+ material not subject to the license. This includes other CC-
+ licensed material, or material used under an exception or
+ limitation to copyright. More considerations for licensors:
+ wiki.creativecommons.org/Considerations_for_licensors
+
+ Considerations for the public: By using one of our public
+ licenses, a licensor grants the public permission to use the
+ licensed material under specified terms and conditions. If
+ the licensor's permission is not necessary for any reason--for
+ example, because of any applicable exception or limitation to
+ copyright--then that use is not regulated by the license. Our
+ licenses grant only permissions under copyright and certain
+ other rights that a licensor has authority to grant. Use of
+ the licensed material may still be restricted for other
+ reasons, including because others have copyright or other
+ rights in the material. A licensor may make special requests,
+ such as asking that all changes be marked or described.
+ Although not required by our licenses, you are encouraged to
+ respect those requests where reasonable. More considerations
+ for the public:
+ wiki.creativecommons.org/Considerations_for_licensees
+
+=======================================================================
+
+Creative Commons Attribution 4.0 International Public License
+
+By exercising the Licensed Rights (defined below), You accept and agree
+to be bound by the terms and conditions of this Creative Commons
+Attribution 4.0 International Public License ("Public License"). To the
+extent this Public License may be interpreted as a contract, You are
+granted the Licensed Rights in consideration of Your acceptance of
+these terms and conditions, and the Licensor grants You such rights in
+consideration of benefits the Licensor receives from making the
+Licensed Material available under these terms and conditions.
+
+
+Section 1 -- Definitions.
+
+ a. Adapted Material means material subject to Copyright and Similar
+ Rights that is derived from or based upon the Licensed Material
+ and in which the Licensed Material is translated, altered,
+ arranged, transformed, or otherwise modified in a manner requiring
+ permission under the Copyright and Similar Rights held by the
+ Licensor. For purposes of this Public License, where the Licensed
+ Material is a musical work, performance, or sound recording,
+ Adapted Material is always produced where the Licensed Material is
+ synched in timed relation with a moving image.
+
+ b. Adapter's License means the license You apply to Your Copyright
+ and Similar Rights in Your contributions to Adapted Material in
+ accordance with the terms and conditions of this Public License.
+
+ c. Copyright and Similar Rights means copyright and/or similar rights
+ closely related to copyright including, without limitation,
+ performance, broadcast, sound recording, and Sui Generis Database
+ Rights, without regard to how the rights are labeled or
+ categorized. For purposes of this Public License, the rights
+ specified in Section 2(b)(1)-(2) are not Copyright and Similar
+ Rights.
+
+ d. Effective Technological Measures means those measures that, in the
+ absence of proper authority, may not be circumvented under laws
+ fulfilling obligations under Article 11 of the WIPO Copyright
+ Treaty adopted on December 20, 1996, and/or similar international
+ agreements.
+
+ e. Exceptions and Limitations means fair use, fair dealing, and/or
+ any other exception or limitation to Copyright and Similar Rights
+ that applies to Your use of the Licensed Material.
+
+ f. Licensed Material means the artistic or literary work, database,
+ or other material to which the Licensor applied this Public
+ License.
+
+ g. Licensed Rights means the rights granted to You subject to the
+ terms and conditions of this Public License, which are limited to
+ all Copyright and Similar Rights that apply to Your use of the
+ Licensed Material and that the Licensor has authority to license.
+
+ h. Licensor means the individual(s) or entity(ies) granting rights
+ under this Public License.
+
+ i. Share means to provide material to the public by any means or
+ process that requires permission under the Licensed Rights, such
+ as reproduction, public display, public performance, distribution,
+ dissemination, communication, or importation, and to make material
+ available to the public including in ways that members of the
+ public may access the material from a place and at a time
+ individually chosen by them.
+
+ j. Sui Generis Database Rights means rights other than copyright
+ resulting from Directive 96/9/EC of the European Parliament and of
+ the Council of 11 March 1996 on the legal protection of databases,
+ as amended and/or succeeded, as well as other essentially
+ equivalent rights anywhere in the world.
+
+ k. You means the individual or entity exercising the Licensed Rights
+ under this Public License. Your has a corresponding meaning.
+
+
+Section 2 -- Scope.
+
+ a. License grant.
+
+ 1. Subject to the terms and conditions of this Public License,
+ the Licensor hereby grants You a worldwide, royalty-free,
+ non-sublicensable, non-exclusive, irrevocable license to
+ exercise the Licensed Rights in the Licensed Material to:
+
+ a. reproduce and Share the Licensed Material, in whole or
+ in part; and
+
+ b. produce, reproduce, and Share Adapted Material.
+
+ 2. Exceptions and Limitations. For the avoidance of doubt, where
+ Exceptions and Limitations apply to Your use, this Public
+ License does not apply, and You do not need to comply with
+ its terms and conditions.
+
+ 3. Term. The term of this Public License is specified in Section
+ 6(a).
+
+ 4. Media and formats; technical modifications allowed. The
+ Licensor authorizes You to exercise the Licensed Rights in
+ all media and formats whether now known or hereafter created,
+ and to make technical modifications necessary to do so. The
+ Licensor waives and/or agrees not to assert any right or
+ authority to forbid You from making technical modifications
+ necessary to exercise the Licensed Rights, including
+ technical modifications necessary to circumvent Effective
+ Technological Measures. For purposes of this Public License,
+ simply making modifications authorized by this Section 2(a)
+ (4) never produces Adapted Material.
+
+ 5. Downstream recipients.
+
+ a. Offer from the Licensor -- Licensed Material. Every
+ recipient of the Licensed Material automatically
+ receives an offer from the Licensor to exercise the
+ Licensed Rights under the terms and conditions of this
+ Public License.
+
+ b. No downstream restrictions. You may not offer or impose
+ any additional or different terms or conditions on, or
+ apply any Effective Technological Measures to, the
+ Licensed Material if doing so restricts exercise of the
+ Licensed Rights by any recipient of the Licensed
+ Material.
+
+ 6. No endorsement. Nothing in this Public License constitutes or
+ may be construed as permission to assert or imply that You
+ are, or that Your use of the Licensed Material is, connected
+ with, or sponsored, endorsed, or granted official status by,
+ the Licensor or others designated to receive attribution as
+ provided in Section 3(a)(1)(A)(i).
+
+ b. Other rights.
+
+ 1. Moral rights, such as the right of integrity, are not
+ licensed under this Public License, nor are publicity,
+ privacy, and/or other similar personality rights; however, to
+ the extent possible, the Licensor waives and/or agrees not to
+ assert any such rights held by the Licensor to the limited
+ extent necessary to allow You to exercise the Licensed
+ Rights, but not otherwise.
+
+ 2. Patent and trademark rights are not licensed under this
+ Public License.
+
+ 3. To the extent possible, the Licensor waives any right to
+ collect royalties from You for the exercise of the Licensed
+ Rights, whether directly or through a collecting society
+ under any voluntary or waivable statutory or compulsory
+ licensing scheme. In all other cases the Licensor expressly
+ reserves any right to collect such royalties.
+
+
+Section 3 -- License Conditions.
+
+Your exercise of the Licensed Rights is expressly made subject to the
+following conditions.
+
+ a. Attribution.
+
+ 1. If You Share the Licensed Material (including in modified
+ form), You must:
+
+ a. retain the following if it is supplied by the Licensor
+ with the Licensed Material:
+
+ i. identification of the creator(s) of the Licensed
+ Material and any others designated to receive
+ attribution, in any reasonable manner requested by
+ the Licensor (including by pseudonym if
+ designated);
+
+ ii. a copyright notice;
+
+ iii. a notice that refers to this Public License;
+
+ iv. a notice that refers to the disclaimer of
+ warranties;
+
+ v. a URI or hyperlink to the Licensed Material to the
+ extent reasonably practicable;
+
+ b. indicate if You modified the Licensed Material and
+ retain an indication of any previous modifications; and
+
+ c. indicate the Licensed Material is licensed under this
+ Public License, and include the text of, or the URI or
+ hyperlink to, this Public License.
+
+ 2. You may satisfy the conditions in Section 3(a)(1) in any
+ reasonable manner based on the medium, means, and context in
+ which You Share the Licensed Material. For example, it may be
+ reasonable to satisfy the conditions by providing a URI or
+ hyperlink to a resource that includes the required
+ information.
+
+ 3. If requested by the Licensor, You must remove any of the
+ information required by Section 3(a)(1)(A) to the extent
+ reasonably practicable.
+
+ 4. If You Share Adapted Material You produce, the Adapter's
+ License You apply must not prevent recipients of the Adapted
+ Material from complying with this Public License.
+
+
+Section 4 -- Sui Generis Database Rights.
+
+Where the Licensed Rights include Sui Generis Database Rights that
+apply to Your use of the Licensed Material:
+
+ a. for the avoidance of doubt, Section 2(a)(1) grants You the right
+ to extract, reuse, reproduce, and Share all or a substantial
+ portion of the contents of the database;
+
+ b. if You include all or a substantial portion of the database
+ contents in a database in which You have Sui Generis Database
+ Rights, then the database in which You have Sui Generis Database
+ Rights (but not its individual contents) is Adapted Material; and
+
+ c. You must comply with the conditions in Section 3(a) if You Share
+ all or a substantial portion of the contents of the database.
+
+For the avoidance of doubt, this Section 4 supplements and does not
+replace Your obligations under this Public License where the Licensed
+Rights include other Copyright and Similar Rights.
+
+
+Section 5 -- Disclaimer of Warranties and Limitation of Liability.
+
+ a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
+ EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
+ AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
+ ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
+ IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
+ WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
+ ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
+ KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
+ ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
+
+ b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
+ TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
+ NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
+ INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
+ COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
+ USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
+ ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
+ DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
+ IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
+
+ c. The disclaimer of warranties and limitation of liability provided
+ above shall be interpreted in a manner that, to the extent
+ possible, most closely approximates an absolute disclaimer and
+ waiver of all liability.
+
+
+Section 6 -- Term and Termination.
+
+ a. This Public License applies for the term of the Copyright and
+ Similar Rights licensed here. However, if You fail to comply with
+ this Public License, then Your rights under this Public License
+ terminate automatically.
+
+ b. Where Your right to use the Licensed Material has terminated under
+ Section 6(a), it reinstates:
+
+ 1. automatically as of the date the violation is cured, provided
+ it is cured within 30 days of Your discovery of the
+ violation; or
+
+ 2. upon express reinstatement by the Licensor.
+
+ For the avoidance of doubt, this Section 6(b) does not affect any
+ right the Licensor may have to seek remedies for Your violations
+ of this Public License.
+
+ c. For the avoidance of doubt, the Licensor may also offer the
+ Licensed Material under separate terms or conditions or stop
+ distributing the Licensed Material at any time; however, doing so
+ will not terminate this Public License.
+
+ d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
+ License.
+
+
+Section 7 -- Other Terms and Conditions.
+
+ a. The Licensor shall not be bound by any additional or different
+ terms or conditions communicated by You unless expressly agreed.
+
+ b. Any arrangements, understandings, or agreements regarding the
+ Licensed Material not stated herein are separate from and
+ independent of the terms and conditions of this Public License.
+
+
+Section 8 -- Interpretation.
+
+ a. For the avoidance of doubt, this Public License does not, and
+ shall not be interpreted to, reduce, limit, restrict, or impose
+ conditions on any use of the Licensed Material that could lawfully
+ be made without permission under this Public License.
+
+ b. To the extent possible, if any provision of this Public License is
+ deemed unenforceable, it shall be automatically reformed to the
+ minimum extent necessary to make it enforceable. If the provision
+ cannot be reformed, it shall be severed from this Public License
+ without affecting the enforceability of the remaining terms and
+ conditions.
+
+ c. No term or condition of this Public License will be waived and no
+ failure to comply consented to unless expressly agreed to by the
+ Licensor.
+
+ d. Nothing in this Public License constitutes or may be interpreted
+ as a limitation upon, or waiver of, any privileges and immunities
+ that apply to the Licensor or You, including from the legal
+ processes of any jurisdiction or authority.
+
+
+=======================================================================
+
+Creative Commons is not a party to its public
+licenses. Notwithstanding, Creative Commons may elect to apply one of
+its public licenses to material it publishes and in those instances
+will be considered the “Licensor.” The text of the Creative Commons
+public licenses is dedicated to the public domain under the CC0 Public
+Domain Dedication. Except for the limited purpose of indicating that
+material is shared under a Creative Commons public license or as
+otherwise permitted by the Creative Commons policies published at
+creativecommons.org/policies, Creative Commons does not authorize the
+use of the trademark "Creative Commons" or any other trademark or logo
+of Creative Commons without its prior written consent including,
+without limitation, in connection with any unauthorized modifications
+to any of its public licenses or any other arrangements,
+understandings, or agreements concerning use of licensed material. For
+the avoidance of doubt, this paragraph does not form part of the
+public licenses.
+
+Creative Commons may be contacted at creativecommons.org.
+
diff --git a/README.md b/README.md
index ce1c3bb..47de541 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,137 @@
-# agentic-readiness-guide
-Agentic Readiness Guide
+# Forter Agentic Readiness Guide
+
+A practical, opinionated guide to making any website **agent-ready** - discoverable, comprehensible, trustworthy, actionable, and experiential - so that LLM-driven agents (and the humans behind them) can find, understand, trust, and act on your product.
+
+Five modules, 25 guidelines, each one a single markdown file in [`content/`](./content). Every guideline has a "What & why", a 1-5 complexity/impact score, concrete steps, and references. Where Forter ships infrastructure that satisfies a guideline, the file ends with a "How Forter helps" callout.
+
+## Download it offline
+
+https://github.com/user-attachments/assets/9817a638-6ffc-42b3-94b4-a0124f280cea
+
+**[Download the full guide (PDF)](https://l.forter.com/hubfs/Forter-agentic-readiness-guide.pdf)**
+
+## Read it online
+
+Browse [`content/`](./content) directly on GitHub. Files are organized by module: `m1-*` Discoverable, `m2-*` Comprehensible, `m3-*` Trustworthy, `m4-*` Actionable, `m5-*` Experiential.
+
+## Audit your own site
+
+This repo ships a Claude Code skill in [`SKILL.md`](./SKILL.md), backed by 25 machine-testable rubrics in [`audit/`](./audit), that turns the guide into an automated auditor. Point it at a site (and optionally its source) and it will:
+
+1. Run the probe in each `audit/m{M}-{N}.md` rubric against your site.
+2. Score each guideline **Pass / Partial / Fail / N/A**, citing the literal probe response as evidence.
+3. Rank Fails and Partials by **impact × (6 - complexity)** so the highest-leverage fixes float to the top.
+4. Optionally apply fixes as commits when you give it your repo path.
+
+### Install the skill
+
+[Claude Code](https://docs.claude.com/claude-code) auto-discovers skills under `~/.claude/skills/`. Clone once and **symlink** the repo in - no copying, so `git pull` keeps the skill current and your skills folder stays clean:
+
+```bash
+git clone https://github.com/forter/agentic-readiness-guide.git
+mkdir -p ~/.claude/skills
+ln -s "$(pwd)/agentic-readiness-guide" ~/.claude/skills/forter-agentic-readiness-audit
+```
+
+(The symlink's name matches the skill's `name`. The repo's root `SKILL.md` and `audit/` resolve straight through the symlink. To uninstall, `rm ~/.claude/skills/forter-agentic-readiness-audit` - that removes only the link, not your clone.)
+
+Then in any Claude Code session:
+
+```bash
+claude "Audit https://your-site.example.com against the Agentic Readiness Guide"
+```
+
+Claude picks the skill up from its frontmatter `description` and runs it. Add `--add-dir /path/to/your/site` to include your source repo - fixes get applied as commits there. Type `/skills` inside Claude Code to confirm the skill is loaded.
+
+**Prereqs.** The probes shell out to `curl`, `jq`, and `python3` (for HTML parsing). All three are standard on macOS and most Linux distros; on a bare container, install via `apt-get install curl jq python3` or `brew install jq` (curl and python3 usually ship).
+
+### What you get
+
+A single `report/AUDIT.md` with, in order:
+
+- **One-line scoreboard** - `Score N/W (P%) · X Pass · Y Partial · Z Fail · K N/A`.
+- **Headline** - two sentences a non-technical reader can grasp: where the site sits and the rough shape of the gap.
+- **Action plan** - ordered fixes (smallest concrete change → guideline it unblocks → effort → point gain), with a projected post-fix score.
+- **Cross-cutting blockers** - when one bug gates ≥ 3 guidelines, it gets a dedicated reproduce-and-fix subsection instead of being repeated per row.
+- **Findings table** - 25 rows, one per guideline, with the literal probe response as inline evidence.
+
+Raw probe outputs land alongside in `report/*.out`; opt into `report/score.json` (machine-readable, schema below) and a PR-ready issue list by asking for them. A typical first audit on a real e-commerce site closes the discoverability tier in a day and surfaces 15-20 deeper items the team can sequence over a sprint.
+
+### `report/score.json` (machine-readable, opt-in)
+
+Ask for it explicitly ("also write `score.json`") and the skill emits a single JSON file next to `AUDIT.md` - the same scores as the report, structured for CI/CD gates, dashboards, or trend tracking. The findings table is for humans; `score.json` is for machines.
+
+```jsonc
+{
+ "host": "example.com", // bare host audited (no scheme)
+ "tested_at": "2026-06-02T14:30:00Z", // ISO-8601 UTC timestamp of the run
+ "scope": ["m1-1", "m1-2", "..."], // guideline ids actually scored this run (default: all 25)
+ "scoreboard": {
+ "pass": 4, "partial": 8, "fail": 11, "na": 2, // guideline counts by status
+ "blocked": 0, // guidelines gated by a cross-cutting blocker (↺)
+ "weighted": { "points": 31, "total": 142, "pct": 22 } // summed sub-check weights; pct = points/total
+ },
+ "blockers": [ // cross-cutting issues gating ≥3 guidelines (may be empty)
+ { "id": "A", "title": "WAF challenges agent fetchers", "gates": ["m1-1", "m1-3", "m2-2"] }
+ ],
+ "guidelines": [
+ {
+ "id": "m1-1", // matches content/m1-1-*.md and audit/m1-1.md
+ "title": "Discovery files", // the audit rubric's short title
+ "status": "partial", // "pass" | "partial" | "fail" | "na" | "blocked"
+ "points": 2, // sub-check weights earned
+ "weight_total": 10, // sum of the rubric's sub-check weights (matches audit frontmatter)
+ "complexity": 1, // 1-5, from the rubric/content frontmatter
+ "impact": 4, // 1-5, from the rubric/content frontmatter
+ "priority": 20, // impact × (6 - complexity); higher = fix first
+ "visual_change": "none", // "none" | "low" | "medium" | "high"
+ "sub_checks": [ // one entry per rubric row, in order
+ { "name": "sitemap.xml exists", "pass": true, "weight": 1, "evidence": "200 application/xml" },
+ { "name": "Content-Signal present", "pass": false, "weight": 1, "evidence": "no Content-Signal: line" }
+ // weight: 0 rows are manual/bonus checks - surfaced but never drag the automated score
+ ],
+ "fix_summary": "Add Content-Signal directive; create llms.txt and index.md; emit Link: headers."
+ }
+ // ... one object per guideline in scope
+ ]
+}
+```
+
+**Field notes.** `weighted.total` counts only guidelines that were scored (N/A and blocked guidelines are excluded), so `pct` reflects what was actually testable. `status` maps from `points / weight_total` per the rubric's thresholds (default **Pass ≥ 85%**, **Partial ≥ 30%**, **Fail < 30%** - some rubrics override this; see [`audit/README.md`](./audit/README.md)). A sub-check with `"weight": 0` is a `(manual)` or bonus row: it appears for visibility but can never lower the automated score. `priority` is the same `impact × (6 - complexity)` ranking the action plan uses.
+
+## Cite it
+
+Licensed CC BY 4.0 - use, adapt, and quote freely with attribution to Forter.
+
+## Contribute
+
+Spotted an outdated reference? Missing protocol? Better wording for a guideline? PRs welcome.
+
+1. Edit the relevant markdown file in `content/`.
+2. Keep the frontmatter and H1 in sync (see [`CONTRIBUTING.md`](./CONTRIBUTING.md) for the schema).
+3. Open a PR against `main`.
+
+The rendered PDF and webinar are produced from a separate build pipeline. CI (`npm test`) validates frontmatter, audit↔content alignment, and required section structure on every PR - so you'll find out before merge if something is off.
+
+## Structure
+
+```
+content/ Human-facing guide (markdown, the editorial source of truth)
+ 00-toc.md Table of contents
+ 01-introduction.md Lifecycle frame: Discover → Comprehend → Trust → Act → Experience
+ m{1-5}-0-module-*.md Five module overviews
+ m{1-5}-{1-N}-*.md Twenty-five guideline files
+
+audit/ Machine-testable rubrics - one per guideline, used by SKILL.md
+ m{1-5}-{1-N}.md Probe + weighted sub-checks + codebase hints
+ README.md Rubric file format & strict scoring rules
+
+SKILL.md Claude Code skill - orchestrates probes and writes report/AUDIT.md
+CONTRIBUTING.md Frontmatter schema, body-section checklist, PR template
+scripts/validate.mjs Self-contained frontmatter + alignment validator (npm test)
+LICENSE CC BY 4.0
+```
+
+## License
+
+Content is licensed [CC BY 4.0](./LICENSE). Built and maintained by [Forter](https://www.forter.com).
diff --git a/SKILL.md b/SKILL.md
new file mode 100644
index 0000000..d35ea63
--- /dev/null
+++ b/SKILL.md
@@ -0,0 +1,244 @@
+---
+name: forter-agentic-readiness-audit
+description: Audit a website against the Forter Agentic Readiness Guide. Loads the 25 weighted rubrics in `audit/`, probes the target site (and optional source code), scores each guideline Pass/Partial/Fail/N/A with sub-check granularity, and produces a prioritized fix report. Use when a user asks "score my site against the agentic readiness guide", "audit https://… for agent readiness", or "what do I need to fix to be agent-ready".
+---
+
+# Forter Agentic Readiness Audit
+
+You score a website against the 25 guidelines in this repo. Each guideline has a machine-testable rubric in `audit/m{M}-{N}.md` (probe + weighted sub-checks + codebase hints). Your job: run the probes, score, prioritize, report.
+
+## Prerequisites
+
+The probes shell out to `curl`, `jq`, and `python3`. If any is missing, surface the error to the user with the install command for their platform (`brew install jq`, `apt-get install jq python3`, etc.) and stop - don't continue with degraded probes.
+
+## Inputs
+
+Ask the user for these if not provided:
+
+- **URL** - `https://example.com`. Required.
+- **Local source path** (optional but strongly recommended) - enables framework detection and per-file fix hints.
+- **Scope** (optional) - `all`, `top N`, or a comma-separated list of guideline IDs (`m1-1,m4-1,m4-4`). Default: all 25.
+- **Output format(s)** - markdown report (always), plus opt-in `score.json` and PR-ready issue list.
+
+If only a URL is given, run with codebase hints set to generic-only.
+
+## How the skill is wired
+
+- **`content/`** is the human-facing guide. Don't read it during scoring - it's prose. Surface its URLs in references and fixes only.
+- **`audit/m{M}-{N}.md`** is the source of truth for probes and scoring. Each file has frontmatter (`complexity`, `impact`, `weight_total`) and three sections: `## Probe`, `## Rubric`, `## Codebase hints`.
+- **`audit/README.md`** documents the rubric format. Read it once if you're unfamiliar.
+
+## Process
+
+### 1. Set up
+
+Resolve and export shell variables once:
+
+```bash
+URL=''
+HOST=$(printf '%s' "$URL" | sed -E 's|^https?://([^/]+).*|\1|')
+export HOST ORIGIN="https://$HOST"
+mkdir -p ./report && cd ./report
+```
+
+If a source path was given, detect the framework once and cache the result:
+
+```bash
+REPO=''
+# Detect: presence of files → framework label
+# package.json + "next" → next.js (app router if app/ exists, else pages router)
+# package.json + "express"|"fastify"|"@nestjs" → node-server
+# Gemfile → rails
+# requirements.txt|pyproject.toml + django|flask|fastapi → python-
+# composer.json → php-
+# *.php in webroot, no composer.json → php-classic
+# astro.config.* → astro · hugo.toml → hugo · config.yml + _posts → jekyll
+echo "$FRAMEWORK" > ./report/framework
+```
+
+### 1.5 Pre-flight - can an agent even reach the site?
+
+Before scoring anything, run one cheap reachability probe. If the origin blocks or challenges agent fetchers, _every_ downstream guideline is moot - an agent bounces before it reads a byte. This mirrors how a real agent (ChatGPT-User, Claude-User, PerplexityBot) experiences the site.
+
+```bash
+BASE=$(curl -fsS -A 'Mozilla/5.0' -o /dev/null -w '%{http_code}' "$ORIGIN/")
+echo "baseline(browser) $BASE"
+for UA in 'ChatGPT-User/1.0' 'Claude-User/1.0' 'PerplexityBot/1.0' 'OAI-SearchBot/1.0'; do
+ read code size < <(curl -fsS -A "$UA" -o /tmp/pf.html -w '%{http_code} %{size_download}' "$ORIGIN/" 2>/dev/null || echo "000 0")
+ grep -iqE 'just a moment|cf-browser-verification|captcha|enable javascript to continue' /tmp/pf.html && chal=" CHALLENGE" || chal=""
+ printf ' %-20s %s bytes=%s%s\n' "$UA" "$code" "$size" "$chal"
+done
+```
+
+**If agent fetchers are blocked or challenged** (403/429/503, a Cloudflare/captcha interstitial, or a byte size wildly below baseline), treat it as **cross-cutting Blocker A** in the report. It gates m1-1, m1-3, m2-*, and every API/MCP/commerce guideline that needs the agent to fetch a real response. Score those as `↺` (blocked), and make "allowlist agent fetchers in your WAF / Cloudflare AI Crawl Control" action 1 in the plan. Don't let a high score on file-presence checks mask the fact that no agent can get through. Distinguish this from a site that *intentionally* blocks *training\* crawlers (GPTBot/CCBot) while staying open to fetchers - that's fine (see m1-1 sub-check 4).
+
+### 2. Run probes in parallel
+
+For each guideline in scope, copy the `## Probe` block from `audit/m{M}-{N}.md`, substitute `$ORIGIN`/`$HOST`, and execute. Probes are independent - run them in parallel using background shells or multiple Bash tool calls in a single message.
+
+Keep raw probe output in `./report/m{M}-{N}.out` so you can re-score without re-fetching.
+
+**Emit progress text.** The user cannot read tool output in real time the same way you can - emit a one-sentence text message before each probe batch so they see what's happening. Suggested cadence: one line on start ("Resolving target…"), one line per probe batch ("Probing m1-_ discovery files…", "Probing m4-_ actionable / commerce…"), one line on scoring ("Scoring 25 guidelines…"), one line on write ("Writing report/AUDIT.md…"). Don't narrate every curl - group by module or by batch. Keep each line under 80 chars.
+
+Probes that POST to live endpoints are safe - they're discovery calls with no side effects: m4-4 `initialize` + `tools/list`, m4-9 `OPTIONS` (and a `{}` POST that only reads back a structured validation error), m4-10 `/ask`, and m5-4's DCR probe (POSTs an intentionally-incomplete body so the server rejects it with a validation error rather than registering a real client). Don't run probes that explicitly require manual confirmation (m5-1, m5-4 sub-check 5); flag them as `(manual)` and surface in the report.
+
+### 3. Score each guideline
+
+For each guideline:
+
+1. Apply the `## Rubric` table from its `audit/` file. Walk row-by-row, derive each sub-check's pass condition from the probe output, sum weights.
+2. Map `points / weight_total` to status:
+ - **Pass** ≥ 85%
+ - **Partial** 30-84%
+ - **Fail** < 30%
+ - **N/A** if the rubric's `N/A condition` matches the site (e.g., commerce protocols on a static blog).
+
+ Each `audit/` file may override these thresholds - check the "Status:" line under the rubric table.
+
+3. Record the **exact probe** run and **exact response** observed - that's your evidence.
+
+**Evidence rules (non-negotiable)**
+
+These are the rules that separate a useful audit from a flattering one. Violating them produces over-claims that the user has to refute.
+
+- **Live-web sub-checks score from HTTP responses only.** If the rubric asks whether `/llms.txt` exists, the answer comes from `curl https://site/llms.txt`. It does not come from `find $REPO -name llms.txt`. A file existing in a repo does not mean a URL serves it; an endpoint file being present in `public/` does not mean nginx routes to it; a PHP doc-comment describing a webhook shape does not mean the live endpoint accepts that shape.
+- **Repo inspection is for _fix hints only_.** When you've decided a sub-check Fails based on live evidence, you may consult the repo to suggest which file to edit. You may NOT consult the repo to upgrade a Fail to a Pass.
+- **When a probe can't complete, the sub-check Fails.** This includes: auth-gated endpoints you don't have credentials for, manual checks (platform listings, Wikipedia presence) that require a human, JS-rendered content you'd need a headless browser to read, third-party services that timed out. Call out the specific reason. The user can re-run with credentials or confirm manually.
+- **Ambiguous responses get the conservative reading.** A 400 with a domain-specific error message (e.g., `{"error":"Expected event.type = order.created"}`) is evidence the endpoint understands a specific protocol - that's a Pass on "endpoint exists and validates schema." It is NOT evidence of "the protocol is fully implemented" - that requires sending a valid payload and getting a domain-correct response. Score each sub-check at the resolution it asks for.
+- **Cite the probe and the response verbatim, not your paraphrase.** "Got 200" is not evidence. "Got 200 with `content-type: application/json` and `.endpoints.checkout` field present" is.
+
+### 4. Prioritize fixes
+
+Rank Fail + Partial guidelines by:
+
+```
+priority = impact × (6 - complexity)
+```
+
+Both values come from the rubric frontmatter. Higher = fix first. Tie-break by `visualChange` (`none` and `low` before `medium`/`high`) - these ship faster.
+
+Show the cumulative impact too: "After top 5 fixes, projected score: X / Y (was A / Y)."
+
+### 4.5 Turn every gap into a concrete, environment-aware fix
+
+A score is half the value; the **fix** is the other half. Every Fail and Partial must ship with a fix the user can act on _in their stack_ - not generic advice. For each one:
+
+1. Start from the rubric's `## Codebase hints` and `## Auto-fix template`.
+2. **Specialize to the detected framework** (from `./report/framework`). Surface only the matching hint row, with the real file path - `app/robots.ts` for Next.js app-router, `config/routes.rb` for Rails, webroot drop for PHP-classic, etc.
+3. **If a repo path was given**, ground it in the actual tree: name the exact file to create/edit (`grep`/`ls` to confirm where headers/middleware/routes already live), and reference any half-built feature you found (e.g. "`agentic-oauth.php` exists in the repo but nothing routes `/.well-known/oauth-authorization-server` to it"). Repo inspection is for _fix hints only_ - it never upgrades a live Fail to a Pass.
+4. Make the change the **smallest** one that moves the sub-check from Fail to Pass, and quote the literal snippet (robots line, JSON-LD block, header, well-known file) inlined from the rubric's auto-fix template with `$ORIGIN`/brand substituted.
+
+**Detect & propose only.** Put these fixes in the report (Action plan rows, Blocker `Fix` blocks, and the opt-in PR-issue list). **Do not write any files** - applying changes happens only in step 6, only when the user explicitly says "apply", and only against the repo path.
+
+### 5. Report
+
+Write a single markdown file at `./report/AUDIT.md`. The format is DRY - every fact appears in exactly one place. The user reads top-down and stops as deep as they need to go: header → headline → action plan → blockers → findings table.
+
+```
+# Agentic Readiness Audit -
+
+**Score N / W (P%)** · X Pass · Y Partial · Z Fail · K N/A · · YYYY-MM-DD
+
+> **Headline.** <2-3 sentences, executive-summary style. State where the site sits overall ("foundational stage", "production-ready on discovery but gapped on actionability", etc.), the shape of the gap, and the rough cost/upside of closing it. Do NOT name specific endpoints, file paths, server bugs, or RFC numbers in the headline - those live in the Blockers and Findings sections. A non-technical reader (PM, founder, exec) should be able to grasp it in one read.>
+
+## Action plan - do in order
+
+| # | Action | Unblocks | Effort | +pts |
+|---|--------|----------|--------|------|
+| 1 | | | | +N |
+| ... |
+
+**Projected: → (P%) after rows 1-N.**
+
+## Cross-cutting blockers
+
+For each blocker (typically 1-3 per audit) that gates ≥ 3 guidelines, write a dedicated subsection titled `### Blocker A - `. Inside: a `Reproduce` block with the literal probe + response, then a `Fix` block with the concrete change. Then in the Findings table use the `↺` status and reference "Blocker A" in the Unlocks column. Mention each specific bug exactly once, here - never repeat it across the guideline rows.
+
+## Findings - 25 guidelines
+
+Legend: ✅ Pass · ⚠️ Partial · ❌ Fail · ➖ N/A · ↺ blocked by a cross-cutting blocker.
+
+| ID | Guideline | Score | Live evidence (verbatim probe results) | Unlocks via |
+|----|-----------|------:|-----------------------------------------|-------------|
+| m1-1 | Discovery files | ⚠️ 2/6 | `sitemap.xml 404`; `robots.txt 200, has Content-Signal:`, no `Sitemap:` line; `llms.txt 404`; `index.md 404`; `Link: (none)` | action 3, 4 |
+| m1-2 | Well-known agent files | ↺ 0/5 | All `/.well-known/*.json → 301 /well_known/ → 404` | Blocker A + action 2 |
+| ... |
+
+## Quick wins outside the action plan
+
+3-5 bullets: each < 1 hour, gains a point, doesn't gate anything.
+
+## Methodology
+
+One paragraph: probes defined in audit/, evidence-only-from-HTTP, status thresholds. Link to `report/score.json` for the machine-readable breakdown.
+```
+
+**Rules for each section:**
+
+- **Headline** is the most-skipped-by-engineers, most-read-by-execs section. Lead with the _story_ (where does this site stand), not the _findings_ (what did probes return). Two sentences, max three. Don't name endpoints or files.
+- **Action plan** is ordered by "what depends on what," not just by priority score. If action 2 needs action 1's fix, action 1 comes first even if it has lower priority.
+- **Blockers** absorb the cross-cutting story (one nginx bug, one missing template, one missing auth setup). If you find yourself writing "same bug as m1-2" in another row, that's the cue to lift it into a Blocker.
+- **Findings table** is one row per guideline. Evidence column carries the literal probe results inline - backtick-quote the response strings. No nested tables, no per-guideline detail sections. If a finding needs more than 200 characters to explain, it belongs in a Blocker, not the table.
+- **Probe/Response/Verdict triplets** that you produced during scoring are kept in the raw `./report/*.out` files and `score.json` - they don't appear in the report markdown unless the reader explicitly asks for them. The Findings table's evidence column is the user-facing distillation.
+- **Don't repeat the totals.** The score appears once at the top. Don't add a per-module breakdown - it's redundant with the Findings table.
+- **Don't write "what changed since last run."** That's commit-message territory.
+
+**Score.json (opt-in)** - written to `report/score.json`. Schema documented in [`README.md`](./README.md#reportscorejson-machine-readable-opt-in); keep the two in sync. `weight_total` per guideline MUST match that guideline's `audit/` frontmatter. A `weight: 0` sub-check is a `(manual)`/bonus row that never lowers the automated score.
+
+```json
+{
+ "host": "example.com",
+ "tested_at": "YYYY-MM-DDTHH:MM:SSZ",
+ "scope": ["m1-1", "m1-2", "..."],
+ "scoreboard": { "pass": 4, "partial": 8, "fail": 11, "na": 2, "blocked": 0, "weighted": { "points": 31, "total": 142, "pct": 22 } },
+ "blockers": [
+ { "id": "A", "title": "WAF challenges agent fetchers", "gates": ["m1-1", "m1-3", "m2-2"] }
+ ],
+ "guidelines": [
+ {
+ "id": "m1-1", "title": "Discovery files",
+ "status": "partial", "points": 2, "weight_total": 10,
+ "complexity": 1, "impact": 4, "priority": 20, "visual_change": "none",
+ "sub_checks": [
+ { "name": "sitemap.xml exists", "pass": true, "weight": 1, "evidence": "200 application/xml" },
+ { "name": "robots.txt exists", "pass": true, "weight": 1, "evidence": "200, Sitemap: line found" },
+ { "name": "Content-Signal in robots", "pass": false, "weight": 1, "evidence": "no Content-Signal: line" },
+ ...
+ ],
+ "fix_summary": "Add Content-Signal directive; create llms.txt and index.md; emit Link: headers."
+ }
+ ]
+}
+```
+
+**PR-ready issue list (opt-in)**:
+
+One markdown block per Fail/Partial guideline, ready to paste as a GitHub issue body. Includes title (`agentic-readiness: (m{M}-{N})`), evidence, fix steps, file paths.
+
+### 6. Apply fixes (only when asked)
+
+If the user says "apply the top N" and gave you the repo path:
+
+- One commit per guideline. Message: `agentic-readiness: (m{M}-{N})`.
+- Make the smallest change that pushes the guideline to Pass. Don't expand scope.
+- After each commit, re-run only that guideline's probe to confirm.
+- Stop at the user's quota.
+
+Never push, never open PRs without explicit consent.
+
+## Conventions
+
+- **Never fabricate scores.** If a probe doesn't complete (network error, JSON parse failure, JS-only render, auth required), the sub-check **Fails**. The Probe/Response/Verdict triplet captures the reason - `Verdict: FAIL - auth required, no token supplied`. The user can grant credentials and re-run.
+- **Each guideline is independent.** Score from its own rubric, not your overall impression of the site.
+- **Ignore `How Forter helps` sections** in `content/`. They're vendor commentary, not requirements. The audit must give the same score whether or not Forter is in use.
+- **Don't recommend Forter** unless the user explicitly asks "should we use Forter for this?".
+- **Keep responses verbatim.** Copy the response bytes (status line + relevant headers + first ~200 chars of body). Don't summarize "got an ACP-shaped error" - show `{"error":"Expected event.type = order.created"}`.
+- **Repo grep is for fix hints, not evidence.** If a sub-check failed live but the repo shows the feature is half-built, note it under **Fix** (e.g., "agentic-oauth.php exists in repo but no nginx route") - don't promote the Fail to a Pass.
+- **Keep the report tight.** The Probe/Response/Verdict triplet is the only ceremony. No essays.
+
+## When the user says "go"
+
+1. Ask for URL and (recommended) repo path if not given.
+2. Ask which output formats - markdown only, or also `score.json` and/or PR issue list.
+3. Run probes in parallel, score, prioritize, report.
+4. If they then say "apply the top three", apply them as commits, then re-probe those three to confirm.
diff --git a/audit/README.md b/audit/README.md
new file mode 100644
index 0000000..ef64534
--- /dev/null
+++ b/audit/README.md
@@ -0,0 +1,111 @@
+# Audit rubrics
+
+Machine-testable companions to `content/`. One file per guideline (`m1-1.md` … `m5-4.md`). The skill in `SKILL.md` loads these to probe a target site and assign weighted scores.
+
+`content/` is the human-facing guide. `audit/` is the tester's rubric. Don't merge them - content evolves on its own cadence, rubrics evolve as probes improve.
+
+## Consistency contract
+
+Three layers must agree on module + guideline numbering: `content/m{M}-{N}-*.md` ↔ `audit/m{M}-{N}.md` ↔ the report's findings table (`m{M}-{N}` row id). The CI validator (`npm test`) enforces:
+
+- `audit/m{M}-{N}.md` exists for every content guideline (warning otherwise).
+- `audit.id` matches `content.id`'s M-N prefix.
+- `audit.complexity` and `audit.impact` equal the values in `content/`.
+- `audit.visualChange` matches `content.visualChange` when both are set.
+- `audit.weight_total` equals the sum of the rubric table's row weights (the final integer cell of each data row, across every table in the `## Rubric` section). Add a sub-check → bump `weight_total` to match, or CI fails.
+
+Titles intentionally diverge: `content/` uses imperative verbs ("Implement OAuth", "Verify bots cryptographically") because the guide reads top-to-bottom; `audit/` uses short noun-phrase tags ("OAuth discovery", "Web Bot Auth") that fit a scoreboard column. The report uses the audit title.
+
+## File format
+
+Every `audit/m{M}-{N}.md` must follow this structure:
+
+```markdown
+---
+id: m{M}-{N} # MUST match the content guideline's M-N
+title: # may differ from content's imperative title
+complexity: <1-5, MUST match content frontmatter>
+impact: <1-5, MUST match content frontmatter>
+visualChange:
+weight_total:
+---
+
+# m{M}-{N} -
+
+## Probe
+
+Exact shell commands that gather evidence. Use `$HOST` as the bare host (no scheme), `$ORIGIN` as `https://$HOST`. Probes should be:
+- Idempotent (safe to re-run).
+- Fast (<5s each; offload heavy work to optional sub-checks).
+- Non-destructive (no POST to live endpoints unless explicitly a sandbox).
+- Self-contained (no shared state between probes).
+
+```bash
+curl -fsSI $ORIGIN/sitemap.xml
+curl -fsS $ORIGIN/robots.txt | grep -iE '^(sitemap|content-signal):'
+# ... etc
+```
+
+## Rubric
+
+A table of weighted sub-checks. Each row: a name, the pass condition (observable, derivable from probe output), and a point weight.
+
+| # | Sub-check | Pass when | Weight |
+|---|-----------|-----------|--------|
+| 1 | sitemap.xml | HTTP 200 and `content-type` includes `xml` | 1 |
+| 2 | robots.txt | HTTP 200 and references at least one `Sitemap:` line | 1 |
+| ... |
+
+**Status mapping** (default; override only with explicit reason):
+- **Pass** - score ≥ 85% of `weight_total`.
+- **Partial** - score ≥ 30% of `weight_total`.
+- **Fail** - score < 30% of `weight_total`.
+- **N/A** - the guideline genuinely does not apply (e.g., commerce protocols on a static marketing site).
+
+**Strict scoring rules** (binding on every rubric):
+- Every sub-check's `Pass when` clause MUST be derivable from HTTP response bytes alone. "File present in repo at path X" is never a valid Pass condition; only "URL X returns Y" is.
+- A sub-check that can't be probed (auth required, manual platform check, JS-only render) **Fails**. Don't introduce `Unknown` - the orchestrator surfaces such cases with the exact probe so a human can re-run.
+- Ambiguous responses get the conservative reading. A 400 error message that names a protocol's event type is *only* evidence the endpoint validates schema, not evidence the protocol is fully implemented.
+- Repo inspection is reserved for `## Codebase hints` - it MAY suggest which file to edit when a sub-check Fails. It MAY NOT promote a Fail to a Pass.
+
+## Codebase hints
+
+A short bulleted list mapping common frameworks → file paths to edit. The orchestrator detects framework from the repo (`package.json`, `Gemfile`, `composer.json`, `requirements.txt`, `next.config.js`, `astro.config.mjs`, etc.) and surfaces only the relevant row.
+
+- **Next.js (app router)**: `app/robots.ts`, `app/sitemap.ts`
+- **Next.js (pages router)**: `pages/api/robots.ts`, `public/sitemap.xml`
+- **Rails**: `config/routes.rb` + `app/views/robots.text.erb`
+- **Django**: `django.contrib.sitemaps`, `urls.py`
+- **Express / Node**: middleware route, set headers via `res.set()`
+- **PHP / classic**: drop file at webroot (`public/`, `htdocs/`, `www/`)
+- **Cloudflare Pages / Workers**: `_headers`, `public/`
+- **Static (Hugo/Jekyll/Astro/11ty)**: `static/` or `public/` directory
+- **Other / unknown**: drop file at webroot
+
+If a guideline is framework-agnostic (just a JSON file at a well-known path), say so and skip the per-framework table.
+
+## Auto-fix template (optional)
+
+A copy-pasteable starter for the most common stack. Keep minimal and correct over comprehensive. Include only when there's an obvious one-file change that covers ≥80% of sites.
+
+```text
+# /robots.txt
+Sitemap: $ORIGIN/sitemap.xml
+
+User-agent: *
+Content-Signal: search=yes, ai-input=yes, ai-train=no
+```
+
+## References
+
+One line: link back to the source-of-truth guideline in `content/`. Specs/RFCs already live there - don't duplicate.
+
+`See: [content/m{M}-{N}-*.md](../content/m{M}-{N}-*.md)`
+
+## Notes for contributors
+
+- Sub-checks should be **independently testable** - a reader should be able to see each row's pass/fail from the probe output alone.
+- Weights should be roughly proportional to user-visible impact within the guideline (don't weight a `Link:` header equal to publishing `llms.txt` itself).
+- Composite guidelines (m1-1, m4-9, m2-2) should split into sub-checks per discrete artifact (file, protocol, endpoint).
+- When a sub-check requires a paid/destructive action (e.g., actually completing an OAuth flow), mark it `weight: 0` and tag it `(manual)` - the orchestrator surfaces it as a checklist item rather than a probe failure.
+- Keep each file under ~100 lines. If you're writing more, the rubric is too coarse - split into sub-checks.
diff --git a/audit/m1-1.md b/audit/m1-1.md
new file mode 100644
index 0000000..bc85243
--- /dev/null
+++ b/audit/m1-1.md
@@ -0,0 +1,171 @@
+---
+id: m1-1
+title: Discovery files
+complexity: 1
+impact: 4
+visualChange: none
+weight_total: 10
+---
+
+# m1-1 - Discovery files
+
+## Probe
+
+```bash
+# 1. sitemap
+curl -fsSI $ORIGIN/sitemap.xml -o /dev/null -w '%{http_code} %{content_type}\n'
+
+# 2. robots.txt + content
+curl -fsS $ORIGIN/robots.txt | tee /tmp/robots.txt
+grep -iE '^sitemap:' /tmp/robots.txt
+grep -iE '^content-signal:' /tmp/robots.txt
+
+# 2b. robots AI-policy quality: are agent *fetchers* (user-triggered) blocked, and
+# is the Content-Signal grammar valid? Distinguish them from training crawlers.
+python3 - <<'PY'
+import re
+try:
+ body=open("/tmp/robots.txt").read()
+except FileNotFoundError:
+ print("robots_parse no-file"); raise SystemExit
+# Group rules by user-agent
+groups={}; cur=[]
+for raw in body.splitlines():
+ line=raw.split('#',1)[0].strip()
+ if not line: continue
+ k,_,v=line.partition(':'); k=k.strip().lower(); v=v.strip()
+ if k=='user-agent':
+ cur=groups.setdefault(v.lower(), [])
+ elif k=='disallow' and cur is not None:
+ cur.append(v)
+def blocked(ua):
+ rules=groups.get(ua.lower())
+ return rules is not None and any(r=='/' for r in rules)
+# user-triggered agent fetchers - these MUST stay reachable for agent-readiness
+FETCHERS=["ChatGPT-User","OAI-SearchBot","Claude-User","PerplexityBot","Perplexity-User"]
+# training/bulk crawlers - blocking these is a legitimate, separate choice
+TRAINERS=["GPTBot","CCBot","Google-Extended","ClaudeBot","anthropic-ai","Bytespider"]
+star_blocked=blocked('*')
+blocked_fetchers=[u for u in FETCHERS if blocked(u) or (star_blocked and u.lower() not in groups)]
+print("agent_fetchers_blocked", blocked_fetchers)
+print("training_crawlers_blocked", [u for u in TRAINERS if blocked(u)])
+# Content-Signal grammar: tokens must be =, keys in {search,ai-input,ai-train}
+sig=[l for l in body.splitlines() if l.lower().strip().startswith('content-signal:')]
+valid=False
+if sig:
+ toks=re.findall(r'([a-z-]+)\s*=\s*(yes|no)', sig[0].split(':',1)[1].lower())
+ keys={k for k,_ in toks}
+ valid = bool(toks) and keys <= {"search","ai-input","ai-train"}
+print("content_signal_present", bool(sig), "content_signal_valid", valid)
+PY
+
+# 3. llms.txt (+ soft-404 guard: a 200 that is actually an HTML error page must fail)
+curl -fsSI $ORIGIN/llms.txt -o /dev/null -w '%{http_code} %{content_type} %{size_download}\n'
+curl -fsS $ORIGIN/llms.txt -o /tmp/llms.txt; wc -c < /tmp/llms.txt
+head -c 200 /tmp/llms.txt | grep -iqE ' (the signal that tells an agent a page changed)
+curl -fsS $ORIGIN/sitemap.xml -o /tmp/sitemap.xml 2>/dev/null
+grep -iqE '[^<]+' /tmp/sitemap.xml && echo "sitemap_lastmod present" || echo "sitemap_lastmod absent"
+
+# 7. modular (per-area) llms.txt variants - an agent on a specific task pulls just its slice
+for p in docs/llms.txt api/llms.txt developers/llms.txt; do
+ code=$(curl -fsS -o /tmp/mod.txt -w '%{http_code}' "$ORIGIN/$p" 2>/dev/null)
+ if [ "$code" = "200" ] && ! head -c 200 /tmp/mod.txt | grep -iqE '=` (`content_signal_valid` true) | 1 |
+| 4 | Agent fetchers not blocked | `agent_fetchers_blocked` is empty - no `Disallow: /` (directly or via `*`) hits `ChatGPT-User`, `Claude-User`, `PerplexityBot`, etc. Blocking *training* crawlers (GPTBot/CCBot) does **not** fail this. | 1 |
+| 5 | llms.txt exists & non-stub | HTTP 200, `text/plain`/`text/markdown`, ≥ 200 bytes, **and not an HTML soft-404** | 1 |
+| 6 | /index.md fallback | HTTP 200 and `content-type` includes `text/markdown` | 1 |
+| 7 | Link headers on homepage | At least 1 `Link:` rel of `sitemap`, `describedby`, `service-desc`, or `api-catalog` | 1 |
+| 8 | Link header targets resolve | Each `Link:` target URL returns HTTP < 400 (dead pointers don't count) | 1 |
+| 9 | Sitemap carries `` | `/sitemap.xml` body contains ≥ 1 `…` element (`sitemap_lastmod present`) - the freshness signal that tells an agent a page changed | 1 |
+| 10 | Modular llms.txt | At least one per-area variant (`/docs/llms.txt`, `/api/llms.txt`, `/developers/llms.txt`) returns HTTP 200 and is not an HTML soft-404 | 1 |
+
+Status: **Pass** ≥ 9/10 · **Partial** 3-8/10 · **Fail** 0-2/10.
+
+> Sub-check 4 encodes the most-missed nuance in agent-readiness: an agent acting *on behalf of a user* (e.g. `ChatGPT-User`, `Claude-User`) is not a training crawler. A site may legitimately block `GPTBot`/`CCBot` (training) while staying fully open to fetchers - that is a Pass. A blanket `User-agent: * / Disallow: /`, or an explicit fetcher block, fails it.
+
+## Codebase hints
+
+- **PHP (classic)**: drop static `sitemap.xml`, `robots.txt`, `llms.txt`, `index.md` in webroot; emit `Link:` via `header()` calls in a shared bootstrap (`inc/headers.php`).
+- **Next.js (app router)**: `app/robots.ts`, `app/sitemap.ts`, `public/llms.txt`, `public/index.md`, `middleware.ts` for `Link:` headers.
+- **Next.js (pages router)**: `pages/api/robots.ts`, `pages/sitemap.xml.ts`, `public/llms.txt`, `public/index.md`, custom server or middleware for `Link:`.
+- **Rails**: `config/routes.rb` + `app/views/robots.text.erb`, `sitemap_generator` gem, `public/llms.txt`, `public/index.md`, `before_action` to set headers.
+- **Django**: `django.contrib.sitemaps`, `urls.py` route for `robots.txt`, static `llms.txt`, middleware for `Link:`.
+- **Express / Node**: routes for `/robots.txt`, `/sitemap.xml`, `/llms.txt`, `/index.md`; `res.setHeader('Link', …)` in shared middleware.
+- **Cloudflare Pages / Workers**: `_headers` for `Link:`, `public/` for the four files.
+- **Static (Hugo/Jekyll/Astro/11ty)**: `static/` or `public/` directory.
+
+## Auto-fix template
+
+```text
+# /robots.txt
+Sitemap: $ORIGIN/sitemap.xml
+
+# Default policy: declare AI preferences, allow everyone to crawl.
+User-agent: *
+Content-Signal: search=yes, ai-input=yes, ai-train=no
+Disallow:
+
+# OPTIONAL - block *training* crawlers while staying open to agents.
+# Do NOT add ChatGPT-User / Claude-User / PerplexityBot here: those are
+# user-triggered fetchers, and blocking them fails agent-readiness (sub-check 4).
+User-agent: GPTBot
+Disallow: /
+
+User-agent: CCBot
+Disallow: /
+
+User-agent: Google-Extended
+Disallow: /
+```
+
+```text
+# /llms.txt
+#
+
+> One sentence: what you do and who you serve.
+
+## Use cases
+- ...
+- ...
+
+## API & docs
+- Docs: $ORIGIN/docs
+- OpenAPI: $ORIGIN/openapi.json
+```
+
+```text
+# /index.md
+#
+
+
+```
+
+```text
+# Link: headers (set on every HTML response)
+Link: ; rel="sitemap"
+Link: ; rel="describedby"
+Link: ; rel="service-desc"
+Link: ; rel="api-catalog"
+```
+
+## References
+
+See: [content/m1-1-discovery-files.md](../content/m1-1-discovery-files.md)
diff --git a/audit/m1-2.md b/audit/m1-2.md
new file mode 100644
index 0000000..5a668d3
--- /dev/null
+++ b/audit/m1-2.md
@@ -0,0 +1,124 @@
+---
+id: m1-2
+title: Well-known agent files
+complexity: 1
+impact: 3
+visualChange: none
+weight_total: 7
+---
+
+# m1-2 - Well-known agent files
+
+## Probe
+
+```bash
+# All well-known JSONs an agent looks for. Guard against HTML soft-404s:
+# a 200 that returns an HTML error page must NOT count as a valid file.
+for p in ai-plugin.json agent.json agent-card.json mcp.json mcp/server-card.json; do
+ printf '%s → ' "/.well-known/$p"
+ code=$(curl -fsS -o /tmp/wk.json -w '%{http_code}' "$ORIGIN/.well-known/$p" 2>/dev/null)
+ printf '%s ' "$code"
+ if jq -e 'type=="object" or type=="array"' /tmp/wk.json >/dev/null 2>&1; then
+ echo "valid JSON"
+ else
+ head -c 80 /tmp/wk.json | grep -iqE '= 0)
+' /tmp/a2a.json >/dev/null 2>&1 && echo "a2a-card valid" || echo "a2a-card missing/invalid"
+
+# DNS-AID (draft-mozleywilliams-dnsop-dnsaid) - pure DNS-over-HTTPS, no resolver install.
+# Org agent index lives at _index._agents. as an SVCB record (protocol carried in the
+# `alpn` SvcParam, not in _mcp/_a2a labels). A _agents-challenge. TXT proves domain
+# control. Records SHOULD be DNSSEC-signed - the DoH `AD` flag reports authenticated data.
+BARE=$(echo "$HOST" | sed -E 's/^www\.//')
+dohq() { curl -fsS -H 'accept: application/dns-json' \
+ "https://cloudflare-dns.com/dns-query?name=$1&type=$2" 2>/dev/null; }
+dohq "_index._agents.$BARE" SVCB \
+ | jq -r '"dnsaid_index answers=" + ((.Answer // []) | length | tostring) + " AD=" + ((.AD // false)|tostring)' \
+ 2>/dev/null || echo "dnsaid_index answers=0 AD=false"
+dohq "_agents-challenge.$BARE" TXT \
+ | jq -r '"dnsaid_challenge answers=" + ((.Answer // []) | length | tostring)' \
+ 2>/dev/null || echo "dnsaid_challenge answers=0"
+```
+
+## Rubric
+
+| # | Sub-check | Pass when | Weight |
+|---|-----------|-----------|--------|
+| 1 | `/.well-known/ai-plugin.json` | HTTP 200, valid JSON (not HTML soft-404) with `name_for_model` + `api.url` | 1 |
+| 2 | `/.well-known/agent.json` OR `agent-card.json` | HTTP 200, valid JSON describing agent identity | 1 |
+| 3 | `/.well-known/mcp.json` OR `mcp/server-card.json` | HTTP 200, valid JSON pointing at an MCP server endpoint (`url`/`serverUrl`/`endpoint`, incl. inside `mcpServers[]`) and/or declaring a `transport` (e.g. `streamable-http`) | 1 |
+| 4 | Each file declared has `name` + `description` ≥ 40 chars | Names+descriptions present (not placeholders) | 1 |
+| 5 | Each file references a reachable endpoint | URLs inside resolve to HTTP < 500 | 1 |
+| 6 | A2A agent-card conforms to schema | `agent-card.json` has identity (`name`/`id`) **+** endpoint (`url`/`endpoint`) **+** `version` (the `a2a-card valid` line) | 1 |
+| 7 | DNS-AID record published | `_index._agents.` returns ≥ 1 SVCB answer (the org agent index), or a `_agents-challenge.` TXT is present. DNSSEC-authenticated (`AD=true`) is a bonus, never required | 1 |
+
+Status: **Pass** ≥ 6/7 · **Partial** 3-5/7 · **Fail** 0-2/7.
+
+> Sub-checks 6-7 are additive: a site passes comfortably on the classic well-known files alone (5/7 → Partial→Pass boundary), while a forward-looking setup also publishes a schema-valid A2A card and DNS-AID records so agents can discover it without first fetching HTML. DNS-AID (`draft-mozleywilliams-dnsop-dnsaid`) is an early IETF draft - treat its presence as a forward-looking signal, never penalise its absence.
+
+## Codebase hints
+
+Framework-agnostic - these are static JSON files at fixed paths.
+
+- **Any framework**: serve from webroot under `.well-known/`. On nginx ensure `location ~ /\. { allow all; }` doesn't block dotfile directories.
+- **PHP**: `.well-known/*.json` as plain files in webroot.
+- **Next.js**: `public/.well-known/*.json` (App Router serves them automatically).
+- **Cloudflare Pages**: `public/.well-known/*.json` works as-is.
+- **GitHub Pages / Jamstack**: include `.well-known/` in build output, set `include: [.well-known]` in `_config.yml` for Jekyll.
+- **DNS-AID** (sub-check 7): add records at your DNS provider - no app change. Publish an `SVCB` record at `_index._agents.` pointing at your agent index (per-agent `SVCB` records at `agent-name.` carry the protocol in the `alpn` SvcParam, e.g. `alpn="mcp"`/`"a2a"`). Sign the zone with DNSSEC so consumers can trust the records (Cloudflare, Route 53, NS1 all support SVCB + DNSSEC).
+- **A2A card** (sub-check 6): the `url`/`endpoint` should point at your A2A server; if you don't run one, you can still publish identity + `skills: []` so agents resolve who you are.
+
+## Auto-fix template
+
+```json
+// /.well-known/ai-plugin.json
+{
+ "schema_version": "v1",
+ "name_for_human": "",
+ "name_for_model": "",
+ "description_for_human": "",
+ "description_for_model": "Use this tool to . Auth: OAuth.",
+ "auth": { "type": "oauth", "authorization_url": "https://example.com/.well-known/oauth-authorization-server" },
+ "api": { "type": "openapi", "url": "https://example.com/openapi.json" },
+ "logo_url": "https://example.com/logo.png",
+ "contact_email": "support@example.com",
+ "legal_info_url": "https://example.com/legal"
+}
+```
+
+```json
+// /.well-known/mcp.json (or a 307 from /.well-known/mcp). Discovery-file field names
+// track the MCP discovery SEPs (SEP-1649 / SEP-1960), still stabilizing; the firm part
+// is transport: "streamable-http". Same shape as the content guideline's example.
+{
+ "mcpServers": [
+ {
+ "name": "",
+ "description": "",
+ "version": "1.0.0",
+ "url": "https://mcp.example.com",
+ "transport": "streamable-http",
+ "authorization": { "type": "oauth2", "metadata": "https://example.com/.well-known/oauth-authorization-server" }
+ }
+ ]
+}
+```
+
+```text
+# DNS-AID - DNS records (draft-mozleywilliams-dnsop-dnsaid; set at your DNS provider).
+# Org index → SVCB pointing at an agent-index host; per-agent SVCB carries the protocol in alpn.
+_index._agents.example.com. 3600 IN SVCB 1 agent-index.example.com. ( alpn="a2a,mcp" )
+mcp._agents.example.com. 3600 IN SVCB 1 mcp.example.com. ( alpn="mcp" port=443 )
+# Sign the zone with DNSSEC so consumers can authenticate these records (RFC 9364).
+```
+
+## References
+
+See: [content/m1-2-well-known-agent-files.md](../content/m1-2-well-known-agent-files.md)
diff --git a/audit/m1-3.md b/audit/m1-3.md
new file mode 100644
index 0000000..7c90d9e
--- /dev/null
+++ b/audit/m1-3.md
@@ -0,0 +1,91 @@
+---
+id: m1-3
+title: Render content without JavaScript
+complexity: 3
+impact: 4
+visualChange: low
+weight_total: 9
+---
+
+# m1-3 - Render content without JavaScript
+
+## Probe
+
+```bash
+# Fetch raw HTML (no JS execution) of homepage and a representative deep page
+curl -fsSL -A 'Mozilla/5.0 (compatible; AgentAudit/1.0)' $ORIGIN/ -o /tmp/home.html
+wc -c /tmp/home.html
+
+# Strip script/style/comments, count visible text + content-efficiency ratio
+python3 -c '
+import re,sys
+raw=open("/tmp/home.html").read()
+h=re.sub(r"(?is)<(script|style|noscript)\b.*?\1>","",raw)
+h=re.sub(r"(?is)","",h)
+txt=re.sub(r"(?is)<[^>]+>"," ",h)
+txt=re.sub(r"\s+"," ",txt).strip()
+nchars=len(txt); nbytes=len(raw)
+print("text_chars",nchars)
+print("html_bytes",nbytes)
+print("content_efficiency", round(nchars/nbytes,3) if nbytes else 0) # visible-text / total-bytes
+print("est_tokens", nchars//4) # rough token cost to read the page
+print("h1_count", len(re.findall(r"(?is)