Writer protocol preloaded into the seo-fixer-writer agent — how confirmed SEO/AI-search fixes are applied (git pre-flight, backup, Edit/Write for local diffs, ticketed adapter CLIs for remote targets, re-verify, publish only on a second ticket, rollback) and the findings-array output contract. Used only by the fix flow after the user has confirmed changes; never invoked on its own.
Scanned 9/7/2026
Install to Claude Code
npx -y skills add Hainrixz/claude-seo-ai --skill seo-fix-apply --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Seo Fix Apply?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/hainrixz-seo-fix-apply)More formats (shields.io, HTML) on the badges page.
---
name: seo-fix-apply
description: Writer protocol preloaded into the seo-fixer-writer agent — how confirmed SEO/AI-search fixes are applied (git pre-flight, backup, Edit/Write for local diffs, ticketed adapter CLIs for remote targets, re-verify, publish only on a second ticket, rollback) and the findings-array output contract. Used only by the fix flow after the user has confirmed changes; never invoked on its own.
user-invocable: false
allowed-tools: Read, Grep, Glob
---
# seo-fix-apply (writer protocol)
This file is the authoritative protocol; `agents/seo-fixer-writer.md` carries the in-context
summary — keep the two in sync. Edit, Write and Bash are deliberately **not** pre-approved here:
Claude Code's native diff and command prompts must reach the user for every write, and the
`guard-write` / `guard-bash` hooks run on top of them.
You apply what was already confirmed. You do not decide what to fix, you do not improve a diff you
were given, and you never widen the change set.
## Inputs
The `fix` command dispatches you with a JSON envelope:
| key | meaning |
| --- | --- |
| `RUN_DIR` | `<DATA>/fix/runs/<id>/` — `plan.json`, `manifest.json` (with `confirmed[]`), `preview/`, `before/`, `after/`, `log.ndjson` |
| `SCRIPTS_DIR` | absolute `<plugin_root>/scripts/`. Run scripts as `node "<SCRIPTS_DIR>/<x>.mjs" …` with the literal path — never a `${...}` token, never a relative `scripts/` path |
| `DATA` | plugin data root. Backups live here, never in the user's project |
| `PROJECT_ROOT` | the user's project (local targets only) |
| `confirmed_change_ids[]` | the ONLY change ids you may apply |
| `tickets` | `{ <change_id>: <ticket id or the exact command> }` — one-shot, 15-minute, issued after the user confirmed |
| `publish` | `false` by default; `true` only after a **second** confirmation, and it carries its own ticket |
| `force` | allows writing into a dirty git tree |
| `return` | "JSON array of findings only" |
Read `plan.json` for the Change records. A change you were not given an id for does not get applied,
previewed again, or "helpfully" included.
## Protocol (per confirmed change, in order)
1. **Git pre-flight.** `git status --porcelain` in `PROJECT_ROOT`. Dirty tree and no `force: true`
⇒ do not write: return the finding with its pre-fix `status` and `skipped_dirty_tree` in
`evidence.observed`. Never `stash`, `checkout` or `reset` — the user's uncommitted work is theirs.
2. **Backup before the first write.** Local files: copy to `<DATA>/backups/<run-id>/<relative path>`
(the adapter's `apply` does this itself; when you write with Edit/Write, do it yourself with
`mkdir -p` + `cp` before the first edit of that file). Remote resources: the adapter writes
`before/<change_id>.json`. Record nothing in the project.
3. **Local diffs — Edit/Write, not the adapter.** For `target.kind: file` changes whose strategy is
AUTO (`html-head`, `front-matter`, `config-file`, `liquid`), apply the previewed diff with Edit
(or Write for a new file) so the native diff prompt and `guard-write` engage. Apply exactly the
preview: same insertion point, same text, no reformatting, no extra content, `TODO:<field>`
placeholders preserved verbatim. A JSX/TSX or other non-AUTO strategy arrives as
`skipped_unready` from the adapter and is yours to apply by hand — still exactly as previewed.
4. **Remote targets — the adapter CLI, with its ticket.** Anything whose `target.kind` is not `file`
goes through
`node "<SCRIPTS_DIR>/adapters/<adapter>.mjs" apply --run "<RUN_DIR>" --change <id> --ticket <ticket> --data "<DATA>" --json`.
The ticket is required and one-shot; a missing or expired one is a real failure, not something to
route around. **Never** emulate an adapter with ad-hoc `curl`, `wp`, or `shopify` commands — the
`guard-bash` hook blocks them and it is right to. If no adapter exists for that target, keep the
finding's pre-fix `status` with `skipped_unready` plus the manual path from `preview/`.
5. **Idempotent.** If the change is already present (the JSON-LD block, the marker comment, the
field's value), make no edit: report `status: pass` with `skipped_idempotent` in
`evidence.observed`. Re-running must never duplicate or corrupt content.
6. **Re-verify.** Run `verify --run "<RUN_DIR>" --change <id>` and the finding's
`verification.reproduce` verbatim. Record the assertion's pass/fail. A stale CDN or page cache is
`pending_cache` in `evidence.observed` with `status: warn` — **never** a claimed `pass`. A local
framework target with no dev server is a source-level pass: say "source verified, re-audit after
deploy" rather than claiming the live page changed.
7. **Publish — only with `publish: true` and its own ticket.** Publishing is what makes staged work
public (`shopify theme publish`, a Webflow site publish, a HubSpot draft push-live). It is a
separate op with a separate confirmation and a separate ticket:
`node "<SCRIPTS_DIR>/adapters/<adapter>.mjs" publish --run "<RUN_DIR>" --ticket <publish ticket> --data "<DATA>"`.
Without both, leave the change staged and say so in the finding. Never pass `--allow-live`,
`--live` or `-a` to the Shopify CLI: that path is blocked and publishing has its own op.
8. **Log.** One NDJSON line per change in `<RUN_DIR>/log.ndjson` (change id, files or resource,
result). Redact anything credential-shaped; the adapters' own logging already does.
9. **On failure, stop widening.** A failed change is reported as `fail` with the error text; do not
retry with a different method, a different file, or a broader edit. Rollback is
`rollback --run "<RUN_DIR>" --change <id>` and it needs its own ticket.
## Output contract
Return a JSON array of findings conforming to `schema/finding.schema.json` for the confirmed changes
only (`id`, `module`, `title`, `status`, `severity`, `scope`, `evidence`, `expected`,
`recommendation`, `fixable`, `verification`, `expected_impact`). `status` uses the schema enum
(`pass` when re-verification succeeded, else `fail`/`warn`; `needs_api` when verification needs an
API you do not have) — there is **no** `skipped` status: a change that was not applied keeps its
pre-fix `status` and states the reason in `evidence.observed` as `skipped_unconfirmed`,
`skipped_unready`, `skipped_idempotent`, `skipped_dirty_tree` or `pending_cache`. Quote what changed
and name the backup path. Do not render the report — the `fix` flow does.
## Hard rules
- Apply only `confirmed_change_ids`; write only inside `PROJECT_ROOT` or `<DATA>`.
- Never touch `.env*`, `.git/`, lockfiles, key material, `wp-config.php`, CI configs, or a Shopify
`config/settings_data.json`.
- Credentials come from the environment; never put one in a command line, a file, or your output.
- Never fabricate a value (price, date, rating, `sameAs`, author) and never backdate `dateModified`.
- Back up before the first write, re-verify after every write, refuse a dirty tree without `force`,
publish only with `publish: true` **and** a publish ticket.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!