seo-fix-apply (writer protocol)
SkillSearchWriter 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.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the seo-fix-apply (writer protocol) skill
What this skill tells your AI
The instructions your AI receives, as published by hainrixz/claude-seo-ai in skills/seo-fix-apply/SKILL.md and read by ahel’s review.
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)
- Git pre-flight.
git status --porcelaininPROJECT_ROOT. Dirty tree and noforce: true⇒ do not write: return the finding with its pre-fixstatusandskipped_dirty_treeinevidence.observed. Neverstash,checkoutorreset— the user's uncommitted work is theirs. - Backup before the first write. Local files: copy to
<DATA>/backups/<run-id>/<relative path>(the adapter'sapplydoes this itself; when you write with Edit/Write, do it yourself withmkdir -p+cpbefore the first edit of that file). Remote resources: the adapter writesbefore/<change_id>.json. Record nothing in the project. - Local diffs — Edit/Write, not the adapter. For
target.kind: filechanges 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 andguard-writeengage. 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 asskipped_unreadyfrom the adapter and is yours to apply by hand — still exactly as previewed. - Remote targets — the adapter CLI, with its ticket. Anything whose
target.kindis notfilegoes throughnode "<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-hoccurl,wp, orshopifycommands — theguard-bashhook blocks them and it is right to. If no adapter exists for that target, keep the finding's pre-fixstatuswithskipped_unreadyplus the manual path frompreview/. - Idempotent. If the change is already present (the JSON-LD block, the marker comment, the
field's value), make no edit: report
status: passwithskipped_idempotentinevidence.observed. Re-running must never duplicate or corrupt content. - Re-verify. Run
verify --run "<RUN_DIR>" --change <id>and the finding'sverification.reproduceverbatim. Record the assertion's pass/fail. A stale CDN or page cache ispending_cacheinevidence.observedwithstatus: warn— never a claimedpass. 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. - Publish — only with
publish: trueand 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,--liveor-ato the Shopify CLI: that path is blocked and publishing has its own op. - 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. - On failure, stop widening. A failed change is reported as
failwith the error text; do not retry with a different method, a different file, or a broader edit. Rollback isrollback --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 insidePROJECT_ROOTor<DATA>. - Never touch
.env*,.git/, lockfiles, key material,wp-config.php, CI configs, or a Shopifyconfig/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 backdatedateModified. - Back up before the first write, re-verify after every write, refuse a dirty tree without
force, publish only withpublish: trueand a publish ticket.
Signals
- GitHub stars
- 59
- Forks
- 5
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
seo-fix-apply- Source
- github.com/hainrixz/claude-seo-ai