07-campaign-sheetlisted
Install: claude install-skill Zevenue/headless-gtm
# Campaign sheet (07)
The last step in the chain. Everything upstream produced `records.jsonl`; this
turns those records into what an owner actually uses - a ranked "who to contact
first and why" sheet, plus a CSV shaped for a one-click HubSpot import.
It **adds no fields to the chain and nothing reads its output.** The CSV is a
derived, human-facing export (`headless-gtm-shared/CONVENTIONS.md`) - the
canonical record stays `records.jsonl`, and re-running any upstream skill never
depends on this sheet.
## What it reads
A terminal `records.jsonl` from any stage. The script tolerates every shape and
degrades instead of failing:
- **Company name** from `company` (or `name`), **site** from `website` (or
`https://` + `domain`), **phone / city** from wherever the discovery step put
them.
- **Ranking** from 05's `signals[]` - the highest-score signal per record
decides call order; its `signal_type`, `signal_sentence`, and `source_url`
become the "why now" line, and its `approach` (or `fallback_approach`) the
recommended angle. Records with no signals stay in the sheet, ranked last.
- **Contacts** from 06's `person_name` / `matched_title` / `email` /
`verification_status`. When 06 has not run, the contact columns are dropped
entirely rather than shipped empty.
- Rows with no website ride through on their listing fields - the sheet does not
drop them (a registry row with `domain` empty is still a real target).
## What it writes
`runs/<run-id>/` (via `common.runs