seo-fix-apply (writer protocol)

SkillSearch

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.

Available today. Use it from your connected AI after setup.

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:

keymeaning
RUN_DIR<DATA>/fix/runs/<id>/plan.json, manifest.json (with confirmed[]), preview/, before/, after/, log.ndjson
SCRIPTS_DIRabsolute <plugin_root>/scripts/. Run scripts as node "<SCRIPTS_DIR>/<x>.mjs" … with the literal path — never a ${...} token, never a relative scripts/ path
DATAplugin data root. Backups live here, never in the user's project
PROJECT_ROOTthe 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
publishfalse by default; true only after a second confirmation, and it carries its own ticket
forceallows 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: warnnever 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.

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