Candidate schema
A collection of AI agent skills for optimizing political campaign websites for AI search (GEO). Built for candidates, campaign managers, and advocacy staff who want to help their site get accurately read and cited.
npx -y skills add meturley/trail-marker-geo --skill candidate-schemaAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- 19 days oldThe repository was created 19 days ago. New is not bad, but a brand new repository carrying a familiar-sounding name is the shape a typosquat arrives in, and there has been no time for anyone else to find a problem with it.
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Generate structured data (JSON-LD Person schema) for a political candidate's own website so AI search engines and answer engines correctly identify and disambiguate them from people with similar names. Use this skill whenever a candidate or their campaign staff wants to add schema markup, structured data, JSON-LD, a "knowledge panel" signal, Wikidata/sameAs links, or "help AI find me / tell me apart from the other [name]" to their site. Trigger it for phrases like "candidate schema," "structured data for my campaign site," "AI can't tell me apart from," "add JSON-LD," "disambiguate me," or when someone pastes their campaign facts and asks how to make search/AI recognize them. Also produces platform-specific paste-in instructions for Wix, Squarespace, WordPress, and RUN! (designedtorun.com).
SKILL.md
10.0 KB, as published. Nobody here has run it
Candidate Schema
Generate a complete, syntactically valid <script type="application/ld+json"> block describing a political candidate as a schema.org Person, plus plain-English instructions for pasting it into their website. The goal is disambiguation: helping AI search engines and answer engines correctly identify the candidate and distinguish them from other people who share their name.
This skill is part of a suite that shares a fact-sheet.md file (see "The fact-sheet.md convention" below). Read from it first; write new verified facts back to it.
Core workflow
- Look for
fact-sheet.mdin the project/working directory. If it exists, read every field from it instead of re-asking. Only ask the user for fields that are missing or blank. - Interview the user for any missing verified facts (see "Fields to collect").
- Write any newly gathered facts back to
fact-sheet.md(create it if absent), so sibling skills can reuse them. - Generate the JSON-LD block.
- Validate that the emitted JSON is syntactically correct (see "Validation").
- Output the block plus platform-specific paste-in instructions and the FEC disclaimer note.
Hard rules — never violate these
These protect the user legally and protect the integrity of the output. State them plainly if the user pushes against them.
- Only describe the candidate running this skill, on properties they own. Generate schema for one real person — the user (or the candidate the user represents) — intended for that person's own website. Never generate schema describing an opponent, a third party, or for placement on a site the user doesn't control. If asked to, decline and explain that structured data must live on the subject's own owned property to be trusted, and that misrepresenting authorship of another person's identity markup is deceptive.
- Never fabricate a Wikidata ID, social URL, Ballotpedia URL, or any other identifier. If the user doesn't have one, omit that
sameAsentry entirely. A wrong or invented ID actively harms disambiguation — it's worse than omitting it. Never guess a Q-ID from the name. Only include URLs the user explicitly provides or confirms. - Only include facts the user has verified. Don't infer a district, party, or job title. If a field is missing, ask; if the user can't confirm, leave it out rather than guessing.
- Always validate the JSON before presenting it (see "Validation"). Malformed JSON-LD is silently ignored by crawlers, so an unvalidated block is worthless.
- Always include the FEC disclaimer note (see "FEC disclaimer note") in the output.
Fields to collect
Ask for these in a single friendly batch (skip any already in fact-sheet.md). Explain that everything must be a verified fact, since it's identity markup.
- Full name — the candidate's full legal/campaign name as it should appear.
- Office sought — e.g. "U.S. House of Representatives," "Ohio State Senate," "Mayor of Columbus."
- Current / most recent office — if any; otherwise note they're not a current officeholder.
- District or jurisdiction — e.g. "Ohio's 9th congressional district," "Ward 3," "statewide."
- Party — e.g. Democratic, Republican, Independent, Green.
- Key policy areas — the topics they campaign on (becomes
knowsAbout), e.g. healthcare, veterans' affairs, infrastructure. - Official website — the campaign site URL (the site this markup will live on).
- Social profile URLs — every profile they want linked: X/Twitter, Facebook, Instagram, YouTube, TikTok, LinkedIn, Bluesky, Threads, etc. Collect the full URLs.
- Ballotpedia URL — if they have a Ballotpedia page.
- Wikidata Q-ID — if they have one (format
Qfollowed by digits, e.g.Q12345). If they're not sure, they can search wikidata.org for their name; if none exists, omit it. - Key accomplishments — optional, each with a date and a source, used only for the
fact-sheet.mdand any optional FAQ answers.
When you have what you need (or the user says they don't have some items), proceed. Missing optional items are fine — just omit them from the output.
Building the JSON-LD
Read references/schema-spec.md for the exact property mapping, the disambiguatingDescription formula, the optional nested ProfilePage and FAQPage structures, and worked examples. Follow that spec precisely.
Summary of what the Person object must contain:
name— full name.sameAs— an array of every provided social URL plus the Wikidata entity URL (https://www.wikidata.org/wiki/Q…) and Ballotpedia URL, if provided. Omit any the user lacks. Never invent entries.disambiguatingDescription— a single one-line identity statement (see the formula in the spec), e.g. "U.S. Representative for Ohio's 9th congressional district, elected 2024."knowsAbout— array of the policy areas.jobTitle— current or sought office title.- Optionally a nested
mainEntityOfPage→ProfilePage, and/or aFAQPageblock, where the candidate has content that warrants them (see spec).
Validation
Before showing the block to the user, validate it. Extract the JSON from inside the <script> tags and parse it:
python3 scripts/validate_jsonld.py <path-to-file-containing-the-block>
The script strips the <script type="application/ld+json"> wrapper, runs json.loads, and checks that required Person keys are present and that sameAs is an array with no obviously malformed URLs. If it reports an error, fix the block and re-run until it passes. Only present validated output. Do not claim the JSON is valid unless the validator confirmed it.
Output format
Present the result in three clearly separated parts:
- The block — the full
<script type="application/ld+json">…</script>, in a copy-pasteable code block, with a one-line note that it's been validated. - Where to paste it — the platform-specific instructions. Read
references/platform-instructions.mdand include the sections for the platform(s) the user uses (or all four if unknown): Wix, Squarespace, WordPress, and RUN! (designedtorun.com). Each names the exact settings field the block goes into. - FEC disclaimer note — the templated reminder (see below).
FEC disclaimer note
Always append this note (fill the bracket with the committee name if known, otherwise leave the template placeholder). The regulator reference depends on the office level from "Office sought" — the FEC only has jurisdiction over federal races (U.S. House, U.S. Senate, President); state and local offices (state legislature, mayor, city council, county positions, etc.) are governed by their own state or local election/campaign-finance authority, not the FEC.
For federal offices:
Reminder — "Paid for by" disclaimer: Federal campaign sites generally must carry a disclaimer identifying who paid for the communication. This JSON-LD is invisible identity markup and does not satisfy that requirement. Make sure your site displays a visible line such as "Paid for by [Committee Name]" (e.g., in the footer). Requirements vary by jurisdiction — confirm the exact wording with your compliance counsel or the FEC. This note is a reminder, not legal advice.
For state or local offices:
Reminder — "Paid for by" disclaimer: Campaign sites generally must carry a disclaimer identifying who paid for the communication. This JSON-LD is invisible identity markup and does not satisfy that requirement. Make sure your site displays a visible line such as "Paid for by [Committee Name]" (e.g., in the footer). Requirements vary by state and locality — confirm the exact wording with your compliance counsel or your state/local election authority (the FEC has no jurisdiction over state or local races). This note is a reminder, not legal advice.
If the office level is ambiguous or unclear from what the user provided, ask before appending the disclaimer, rather than guessing which regulator to name.
The fact-sheet.md convention
All skills in this suite read from and write to a shared fact-sheet.md in the project directory. It's a plain markdown file with labeled fields. When this skill gathers new verified facts, it appends or updates them here so sibling skills don't re-ask. Use assets/fact-sheet-template.md as the canonical structure. The fields are:
- Name
- Office Sought
- Current Office
- District
- Party
- Policy Areas
- Website
- Socials (one labeled URL per line)
- Wikidata ID
- Ballotpedia
- Key Accomplishments (each a bullet with a date and a source URL)
When writing back: preserve existing values, fill blanks, and never overwrite a user-verified value with a guess. If a value changes, confirm with the user first.
Runtime & scope
- Posture: Generative with a validation gate. It produces JSON-LD identity markup for the candidate's own site and validates the block before presenting it.
- Neutrality: Only describes the candidate running the skill, on properties they own; never fabricates an identifier (Wikidata Q-ID, social or Ballotpedia URL) and never marks up an opponent or third party.
- Runtime: Python 3.6+, standard library only (the
validate_jsonld.pyscript). No third-party dependencies.