fix · diff
git:20260906.fa9c08a to git:20260906.ceea309
137 added, 20 removed. Audit A to A.
---
name: fix
description: Opt-in fixer (the /claude-seo-ai:fix command). Applies the safe, deterministic SEO/AI-search fixes from a persisted audit to the user's site — meta viewport/charset/lang, JSON-LD, robots.txt AI directives, hreflang, sitemaps, OG/Twitter cards, image dimensions, canonical, AI discovery files — through the adapter that matches the platform (local files, Shopify theme/Admin, WordPress REST/WP-CLI, page APIs, or step-by-step instructions). Dry-run preview by default; writes only after explicit per-change confirmation. Runs only when the user invokes it — never auto-triggered.
disable-model-invocation: true
argument-hint: "<url|path> [--target auto|local|shopify-theme|shopify-admin|wordpress-rest|wordpress-wpcli|page-api|instructions] [--category schema|meta|robots|sitemap|hreflang|alt|canonical|social|ai-discovery] [--include-proposed] [--store <shop>] [--theme <id>] [--site <id>] [--wp-url <u>] [--ssh user@host:/path] [--project <dir>] [--dev-url <u>] [--run <run_id|latest>] [--report <path>] [--lang en|es] [--dry-run] [--publish] [--rollback <run_id>] [--force]"
allowed-tools: Read, Grep, Glob, Bash, Agent
---
# fix (opt-in writer)
- `disable-model-invocation: true` means the model can **never** trigger this on its own — only the user running `/claude-seo-ai:fix`. Writes happen only through the **seo-fixer-writer** subagent (the one agent with Write/Edit) and only after explicit confirmation. `--category` takes a pipe list (`schema|meta|robots|sitemap|hreflang|alt|canonical|social|ai-discovery`; `llms` is an alias of `ai-discovery`). <!-- reconcile-flags-v0.2 -->
+ `disable-model-invocation: true` means the model can **never** trigger this on its own — only the
+ user running `/claude-seo-ai:fix`. Writes happen only through the **seo-fixer-writer** subagent (the
+ one agent with Write/Edit) and only after explicit confirmation. `--category` takes a pipe list
+ (`schema|meta|robots|sitemap|hreflang|alt|canonical|social|ai-discovery`; `llms` is an alias of
+ `ai-discovery`).
+ Two absolutes, before anything else:
+ - **Never fix from memory.** Every change comes from a persisted `report.json` on disk. If there is
+ no report for this target, run the audit first — do not reconstruct findings from the transcript.
+ - **You never write.** This thread runs read-only ops (`capabilities`, `plan`, `preview`, `verify`).
+ `apply`, `publish` and `rollback` belong to the writer subagent, and each needs a ticket.
+
## Fixability classes (from each finding's `fixable` field — see `schema/finding.schema.json`)
- - **AUTO** — deterministic, additive, machine-verifiable, low-semantic-risk. May be written (with diff + confirmation):
- meta `viewport`/`charset`/`<html lang>`; Tier-1 JSON-LD blocks; `sameAs`/`@id`/`dateModified` (from confirmed inputs only); robots.txt AI-crawler presets + `Sitemap:` line; self-referential canonical; hreflang link sets; OG/Twitter cards; image `width`/`height`; XML sitemap entries; `llms.txt` (disclosure-gated, scored 0).
- - **PROPOSED** — changes prose/meaning or is editorial; generate a draft diff and require per-item accept: generated `<title>` and meta description, answer-block/TL;DR rewrites, internal-link insertions, heading restructuring, **generated image alt text** (titles, descriptions, and image alt are editorial messaging, not deterministic).
- - **ADVISORY** — never written: content/E-E-A-T rewrites, adding stats/citations/original data, Core Web Vitals/performance, rendering strategy, redirects/status codes, link-building, Merchant Center/GBP backend data.
+ - **AUTO** — deterministic, additive, machine-verifiable, low-semantic-risk. May be written (with
+ diff + confirmation): meta `viewport`/`charset`/`<html lang>`; Tier-1 JSON-LD blocks; `sameAs`/
+ `@id`/`dateModified` (from confirmed inputs only); robots.txt AI-crawler presets + `Sitemap:`
+ line; self-referential canonical; hreflang link sets; OG/Twitter cards; image `width`/`height`;
+ XML sitemap entries; `llms.txt` (disclosure-gated, scored 0).
+ - **PROPOSED** — changes prose or meaning; draft the diff and require a per-item accept: generated
+ `<title>` and meta description, answer-block/TL;DR rewrites, internal-link insertions, heading
+ restructuring, **generated image alt text**.
+ - **ADVISORY** — never written: content/E-E-A-T rewrites, added stats or citations, Core Web Vitals,
+ rendering strategy, redirects/status codes, link-building, Merchant Center/GBP backend data.
- ## Workflow
- 1. **Load the latest report from `${CLAUDE_PLUGIN_DATA}/runs/<host>/latest` (or `--run` / `--report`) — never fix from memory.** If it is missing or older than 24 h, offer to re-run `/claude-seo-ai:audit` first. Read `report.json` for findings and `profile.json` for the platform profile; confirm the profile with the user when confidence is below high or `--target auto` is ambiguous.
- 2. Filter to `fixable: auto` (+ `proposed` with `--include-proposed`). Honor `--category`. Resolve any required real-world inputs (e.g. `sameAs` URLs, locale map, publish dates) by **asking the user** — never invent them.
- 3. Choose the write path from the profile and `--target`: a local path → file diffs (plus platform adapters for per-resource fields); Shopify → theme + Admin; WordPress → REST when credentials and the SEO plugin allow it, else WP-CLI over `--ssh`, else instructions; other hosted builders → page API when credentials exist, else instructions. List missing credential **key names** only. <!-- phase3-adapters: TODO — detailed adapter flow (capabilities, fix-plan.mjs, preview, tickets, publish, verify, rollback) lands in the Phase 3 pass. -->
- 4. For each change, build a **unified diff** (or the adapter payload) with the exact insertion point and a `live_impact` badge.
- 5. **Dry-run (default)**: print every diff grouped by target. Write nothing. Summarize what `fix` (without `--dry-run`) would change.
- 6. On explicit confirmation (and only then): delegate to **seo-fixer-writer** with the run dir, plugin root, and the confirmed change ids. Per-change or batch confirmation is the user's choice. `--publish` (Shopify theme publish, live REST writes) requires a **second** confirmation.
- 7. Re-verify and report: re-snapshot the preview/live/`--dev-url` target and re-run each finding's `verification.reproduce`; print a summary table, cache caveats, and the `--rollback <run_id>` command.
+ ## Setup for every step
+ ```
+ DATA="${CLAUDE_SEO_AI_HOME:-${CLAUDE_PLUGIN_DATA:-$HOME/.claude-seo-ai}}"
+ SCRIPTS="${CLAUDE_PLUGIN_ROOT}/scripts"
+ ```
+ Every command below is `node "${CLAUDE_PLUGIN_ROOT}/scripts/<x>.mjs" …` with `--data "$DATA"`.
+ Credentials are **never** passed as flags: adapters read `CLAUDE_PLUGIN_OPTION_<KEY>` then `<KEY>`
+ from the environment. If a key is missing, name the key — never ask the user to paste a value here.
+ ## Flow
+
+ **0. Resolve and short-circuit.** Resolve `DATA` and `SCRIPTS`. If `--rollback <run_id>` was given,
+ skip everything else: read `<DATA>/fix/runs/<run_id>/manifest.json`, show what would be restored
+ (files from `<DATA>/backups/<run_id>/`, `before/*.json` payloads, `previous_live_id` for a published
+ theme), confirm, then have the **writer** run each adapter's `rollback --all`. Report and stop.
+
+ **1. Load the report — never from memory.** Take `--report <path>`, else `--run <id>`, else the
+ latest run for this target under `<DATA>/runs/<host>/latest`. Read `report.json` (findings, scores,
+ target) and the `profile.json` beside it. If the report is missing, or `generated_at` is more than
+ 24 h old, say so and offer to re-run `/claude-seo-ai:audit` first. Do not proceed on a stale report
+ without the user saying to.
+
+ **2. Confirm the platform profile.** Read `profile.platform`, `profile.framework`,
+ `profile.capabilities`, `profile.write_targets`. When platform confidence is below `high`, or
+ `--target auto` leaves more than one plausible write path, show what was detected (with the signals)
+ and ask the user to confirm or override with `--target`. A wrong profile writes to the wrong surface.
+
+ **3. Choose adapters and check readiness.** Selection rules, in this order:
+ - a local path target (or `--project <dir>`) ⇒ `local-files`, plus a platform adapter for
+ per-resource fields the source tree does not own;
+ - shopify ⇒ `shopify-theme` + `shopify-admin`;
+ - wordpress ⇒ `wordpress-rest` when its credentials exist and the SEO plugin exposes writes, else
+ `wordpress-wpcli` when `--ssh`/`WP_SSH` is set, else `instructions`;
+ - webflow / wix / ghost / hubspot / bigcommerce ⇒ `page-api` when the key exists, else `instructions`;
+ - squarespace / framer / unknown ⇒ `instructions`.
+
+ Run `node "${CLAUDE_PLUGIN_ROOT}/scripts/adapters/<adapter>.mjs" capabilities --data "$DATA"` for
+ each. List missing keys **by name only**, with the hint: run `/plugin` for claude-seo-ai to enter
+ them, or export them in the shell that starts Claude Code. A missing key downgrades that target to
+ `instructions`; it never stops the run.
+
+ **4. Plan.** One call builds the whole plan:
+ ```
+ node "${CLAUDE_PLUGIN_ROOT}/scripts/fix-plan.mjs" --report <report.json> --profile <profile.json> \
+ --targets <a,b> --run <run_id> [--category …] [--include-proposed] [--answers <json>] \
+ [--project <dir>] [--dev-url <u>] [--lang en|es] --data "$DATA"
+ ```
+ It writes `plan.json` + `manifest.json` into `<DATA>/fix/runs/<run_id>/` and returns a grouped
+ summary. Unresolved real-world inputs — a locale map, `sameAs` profile URLs, the robots preset, a
+ redirect's destination, a publication date — are **asked in chat and passed back via `--answers`**.
+ Never invent them, and never let a `TODO:<field>` placeholder reach a write.
+
+ **5. Preview.** For each change, `preview --run <dir> --change <id>`. Group the output by target and
+ badge every change with its `live_impact`: `[none]` (a file or a staged theme), `[staged]` (written
+ to a draft/unpublished surface, invisible to visitors), `[LIVE]` (visible immediately). Show the
+ unified diff for files, the request + variables + `before` values for API payloads, and the exact
+ command for CLI ops. Shopify theme changes also carry their `theme check` result — an error there
+ blocks apply.
+
+ **6. Stop on `--dry-run`.** Print the summary, say plainly that nothing was written, and end.
+ Dry-run is the default posture: if the user did not clearly ask to apply, treat it as a dry run.
+
+ **7. Confirm, then issue tickets.** Ask per change, or in one batch the user can accept as a whole —
+ their choice. Confirmation must name the `live_impact` of what they are accepting. For each
+ confirmed change, in the same turn as the confirmation:
+ ```
+ node "${CLAUDE_PLUGIN_ROOT}/scripts/fix-ticket.mjs" issue --command "<the exact apply command>" \
+ --run <run_id> --change <chg_id> --data "$DATA"
+ ```
+ A ticket is one-shot and expires in 15 minutes. Record the confirmed ids in
+ `manifest.json.confirmed`. Never issue a ticket for a change the user has not just accepted, and
+ never for a command you have not shown them.
+
+ **8. Dispatch the writer — once.** One `Agent(seo-fixer-writer)` call with a JSON envelope:
+ `RUN_DIR`, `SCRIPTS_DIR` (the absolute `${CLAUDE_PLUGIN_ROOT}/scripts`), `DATA`, `PROJECT_ROOT`
+ (local targets), `confirmed_change_ids[]`, `tickets` (id per change), `publish` (default `false`),
+ `force`, `lang`, and `return: "JSON array of findings only"`. `--publish` requires a **second**
+ confirmation and its **own** ticket for the publish command — publishing is what makes staged work
+ visible, so it is never bundled with the apply confirmation.
+
+ **9. Verify.** For each applied change: `verify --run <dir> --change <id> [--dev-url <u>]`, plus the
+ finding's own `verification.reproduce`. Read the result honestly — a stale CDN or page cache is
+ `pending_cache`, never a pass. A local framework target with no dev server verifies at source level
+ only: say "source verified; re-audit after deploy" rather than claiming the live page is fixed.
+
+ **10. Report.** A table of change · target · live_impact · status · verification, then: what is
+ staged and not yet public, cache caveats, the rollback command
+ (`/claude-seo-ai:fix --rollback <run_id>`), and the next steps (publish, re-audit, remaining
+ `instructions` items the user has to click through).
+
+ ## Target shapes
+ - **URL only** (no `--project`): never selects `local-files` — there is no source tree to edit. A
+ framework site gets its per-resource fixes through the platform adapter and everything else
+ through `instructions`, plus an offer to re-run with `--project <dir>` for source edits.
+ - **Local framework project**: the audit may have run against `http://localhost:3000` or built
+ output, while the fix edits **source** through `route-map` (the route → file mapping), and
+ `--dev-url` re-fetches the running dev server to verify. Say which of the three you verified.
+ - **Hosted builder with no API key**: `instructions` renders the exact click path in `--lang en|es`,
+ with the values already filled in. That is a real outcome, not a failure.
+
## Safety (hard rules)
- - **Dry-run is the default**; writing requires the user to drop `--dry-run` and confirm.
- - **Git-aware**: refuse to write to a dirty working tree unless `--force`; prefer creating/using a branch. Detect via `git status --porcelain`.
- - **Backup** every file before first modification to `${CLAUDE_PLUGIN_DATA}/backups/<timestamp>/<path>`; remote writes store a `before.json` per resource for `--rollback`.
- - **Idempotent**: detect existing tags/blocks; update in place, never duplicate (re-running `fix` produces no new diffs once applied).
- - **Re-verify**: after writing, re-run the finding's `verification.assertion` (e.g. `node "${CLAUDE_PLUGIN_ROOT}/scripts/validate-jsonld.mjs" --snapshot <pages/<slug>.json>`) and report pass/fail per change. <!-- reconcile-flags-v0.2 -->
- - **Never touch** `.git/`, `.env`/secrets, lockfiles, or files outside the project root. Never put a credential in argv — env indirection only.
- - **No fabrication**: never write invented statistics, citations, dates (no backdating `dateModified`), credentials, or identity links.
+ - **Dry-run is the default.** Writing requires the user to ask for it and confirm the change.
+ - **Git-aware**: refuse to write into a dirty working tree unless `--force`; check with
+ `git status --porcelain` and prefer a `seo-fix/<date>` branch.
+ - **Backups first**: local files to `<DATA>/backups/<run_id>/<relpath>`, remote resources to
+ `<RUN_DIR>/before/<change_id>.json`, Shopify to a full theme copy. `--rollback <run_id>` restores
+ from exactly these.
+ - **Idempotent**: every change carries a marker or an existence check; re-running produces
+ `skipped_idempotent`, never a duplicate block.
+ - **Never live by default**: Shopify pushes to an unpublished theme, Webflow/HubSpot stay staged,
+ WordPress never touches `status` or post content, Wix sets `publish:false` unless the endpoint
+ requires otherwise. Anything `[LIVE]` says so before you ask.
+ - **Never touch** `.git/`, `.env*`, lockfiles, key material, `wp-config.php`, or a Shopify
+ `config/settings_data.json`. The `guard-write` and `guard-bash` hooks enforce this independently.
+ - **Credentials**: environment only, never in argv, never echoed, never written to a file in the
+ repo. Commands in the log are redacted.
+ - **No fabrication**: no invented statistics, citations, prices, ratings or identity links, and no
+ backdated `dateModified`.