| name | tam-mapping |
| description | Build TAM databases from scratch using a 7-phase methodology (Source Discovery → Keyword Expansion → Config → Collection → Dedup → Exclusion → Enrichment hand-off). Triggers "tam map", "build tam", "total addressable market", "scrape industry", "map the market", "build a lead database", "venue partnerships tam", "labs tam", "residential tam", "installer tam". Entity-routed — Nites residential (Google Maps ZIP), Supply installer (SAM.gov + Houzz + state license dbs), Labs venue partnerships (Spider.cloud + AI Ark + Discolike + IcyPeas + BlitzAPI + Prospeo + MillionVerifier). Phase 4.5 cross-workspace EB exclusion is MANDATORY (HARD-FAIL on either workspace unreachable). Phase 5 enrichment is pluggable per ADR-008. Distinct from `list-building` (BC-2717 — assumes a TAM already exists via dbt audience views). |
| user-invocable | true |
| allowed-tools | mcp__plugin_marketing_salesforce__*, mcp__plugin_marketing_spider__*, mcp__plugin_marketing_aiark__*, mcp__plugin_marketing_discolike__*, mcp__emailbison-b2b__*, mcp__emailbison-personal__*, WebSearch, WebFetch, Read, Write, Bash |
| metadata | {"version":"0.1.0","upstream":"Revgrowth1/ai-gtm-workflows + Revgrowth1/tam-map@9f5c72e74b","category":"Outbound Lead Gen"} |
TAM Mapping
A BDR, RevOps operator, or marketing lead with a new motion has two failure modes: spend weeks hand-curating a TAM (slow, inconsistent, miss 30–60% of the market) or run enrichment on a list that's 20–40% already-contacted (waste credits + sender reputation). This skill is the third option — a 7-phase pipeline that constructs a deduped, exclusion-filtered, optionally enriched/tiered TAM CSV ready for list-building or launch-campaign.
Reference-build calibration anchors (upstream Revgrowth 02): roofing TAM 20K sendable at $15, coffee shop TAM 11K sendable. Use as Nites/Supply scale anchors. Labs-path reference example in plugins/marketing/references/tam/examples/ (BC-5946).
Three entity routes:
- Nites residential — Google Maps ZIP scraping via WebSearch (absorbs Revgrowth 09 local-enrichment).
- Supply installer — SAM.gov federal contracts + Houzz + state license databases.
- Labs venue partnerships — full 8-provider stack from upstream
Revgrowth1/tam-map: Spider.cloud crawl + AI Ark + Discolike + IcyPeas → BlitzAPI → Prospeo → MillionVerifier → tier delegation to icp-scoring rubric abc.
Distinct from list-building (BC-2717): that skill consumes a TAM (assumes a brite-data-platform dbt audience view exists). This skill constructs one when no audience view exists. If a vertical already has a dbt view, skip this skill and use list-building directly.
Before Starting
Check for product marketing context first. If docs/marketing-context.md exists, read it for entity-specific ICP and voice context. If the file does not exist, fall through to the missing-file fallback below — do not proceed with degraded inferred ICPs. Source taxonomy (Phase 1) and ICP gating (Phase 7) both depend on entity-correct context.
Entity detection
The skill builds a TAM for ONE Brite entity per invocation (Nites / Supply / Labs). Detection logic when --entity is not passed:
| State | Behavior |
|---|
marketing-context.md exists with one entity populated | Use it. Print: Using entity=<X> from marketing-context.md (override with --entity). Proceed. |
marketing-context.md exists with multiple entities populated | Call AskUserQuestion listing the populated entities; user picks one. |
marketing-context.md missing | Call AskUserQuestion with three options: (1) Exit and instruct user to run /marketing:product-marketing-context, then re-invoke tam-mapping after the file lands (recommended — no in-session pause/resume per user feedback). (2) Pick an entity for this run only with inline --entity <X> and --criteria-file <path> (does not save context). (3) Cancel. |
Vertical detection (Labs path)
| State | Behavior |
|---|
--vertical matches a pre-loaded playbook | Lazy-load plugins/marketing/references/vertical-playbooks/{vertical}.md. 6 pre-loaded: zoos, aquariums, casinos, hotels-resorts, ski-resorts, sports-stadiums. |
--vertical does NOT match a pre-loaded playbook | Treat as custom vertical. Require --criteria-file <path> (JSON ICP) OR prompt for inline ICP via AskUserQuestion. NEVER silently default. |
Invocation flags
| Flag | Default | Notes |
|---|
--entity <X> | (auto-detect) | One of brite-nites, brite-supply, brite-labs. |
--vertical <slug> | (required for Labs; optional for Nites/Supply) | Slug for output dir + Labs playbook lookup. |
--criteria-file <path> | (auto-loaded from playbook for Labs pre-loaded verticals) | JSON criteria; required for custom verticals. |
--output-dir <path> | (auto-derived) | Nites/Supply: docs/research/tam/{vertical}-{YYYY-MM-DD}/. Labs: docs/campaigns/labs/tam/{slug}/. Path-traversal segments (..) and absolute paths outside the worktree root are rejected. |
--enrichment-provider <id> | (read from ${user_config.enrichment_provider}) | Override per-run. Enum: `blitz_waterfall |
--max-records N | (unset → cost gate fires) | When set and N >= record count, gate is skipped (caller pre-approved cost). When N < record count, skill stops and reports overflow — does NOT silently truncate. |
--resume | (off) | Force resume from last completed phase based on output-dir file existence. Default behavior (no flag) auto-detects via Operational rule 2 file-existence check. |
Source manifest + TAMConfig location
Both written to the resolved --output-dir:
- Nites/Supply path:
docs/research/tam/{vertical}-{YYYY-MM-DD}/{manifest.json,tam-config.json}.
- Labs path:
docs/campaigns/labs/tam/{slug}/{icp.json,tam-config.json}.
The skill writes; downstream consumers read.
Enrichment-provider selection
Read in priority order (per ADR-008 §Unset resolution order):
--enrichment-provider <id> flag if passed.
${user_config.enrichment_provider} from plugin.json userConfig if explicitly set.
- Auto-detect (when both above are unset):
- Check for brite-enrichment MCP registration in the active session → use
brite_mcp.
- Else check for brite-enrichment CLI at
$BRITE_DATA_PLATFORM/services/enrichment/cli.py → use brite_cli.
- Else fall through to
blitz_waterfall.
skip is never auto-selected; it must be passed explicitly.
The resolved provider is logged at skill invocation so the user sees which path ran (e.g., [tam-mapping] enrichment_provider=blitz_waterfall (auto-detected; brite-enrichment MCP not registered, $BRITE_DATA_PLATFORM unset)). See the canonical 4-row enum table in §3 Phase 5 for per-value implementation and fallback messages.
Resume detection
Per Operational rule 2 (below), if --output-dir already exists with partial output, the skill detects the resume point via file existence in this stable order and continues from the next missing file:
manifest.json (Phase 1)
tam-config.json (Phase 2)
- Per-source
businesses.csv (Nites/Supply) OR companies.jsonl (Labs) (Phase 3)
crawled.jsonl (Labs only — Phase 3 spider sub-step)
all_sources_deduped.csv (Phase 4)
net_new_leads.csv (Nites/Supply) OR both excluded.jsonl AND companies-net-new.jsonl (Labs — both files required for Phase 4.5 to be considered complete) (Phase 4.5)
enriched.jsonl (Phase 5)
verified.jsonl (Labs only — Phase 6)
verified-flat.csv (Labs only — Phase 7 reshape; required by icp-scoring abc contract)
tier-{a,b,c}.csv + catch-all.csv (Labs only — Phase 7)
The skill NEVER restarts from Phase 1 when resume state exists. Stop the file-existence loop at the first missing file; do not check subsequent entries.
Methodology
Adapted from Revgrowth1/ai-gtm-workflows workflow 02 (MIT) for the 7-phase scaffolding, workflow 09 (MIT) for the Nites Phase 3 Google Maps ZIP method, and Revgrowth1/tam-map@9f5c72e74b (MIT) for the Labs-path 8-provider stack. Brite departures annotated inline as # Brite departure: .... See plugins/marketing/references/tam/UPSTREAM.md (BC-5946) for per-file port attribution.
Phase 1 — Source Discovery
Research every data source across 16 categories (literal taxonomy, in order):
- Government / regulatory
- Federal contracts (SAM.gov)
- Industry associations
- Manufacturer / vendor directories
- Company databases (IcyPeas)
- Local / maps
- Review platforms
- Social platforms
- Business directories
- Awards / rankings
- Job boards
- Events / conferences
- Investor / funding
- Open data
- Marketplace / aggregator
- Academic / research
Output: manifest.json listing every researched source with {name, type, url, category, est_record_count, access_method}.
Entity routing in this phase:
- Nites/Supply: Use the Revgrowth 02 taxonomy +
plugins/marketing/references/research-processes/ (BC-5823) for source-discovery query templates.
- Labs: Adds AI Ark + Discolike + IcyPeas to the taxonomy (their MCPs feed directly into Phase 3 collection rather than producing a separate manifest entry).
Open-tracking-OFF reminder. Emit this verbatim string in Phase 1 output (sender-reputation rule):
OPEN-TRACKING DISABLED — sender-reputation rule, see §Brite Implementation → Architectural rules
Phase 1.5 — Keyword Expansion
Discover adjacent keywords sharing the same TAM. Primary keyword alone often misses 30–60% of market.
MANDATORY: Use IcyPeas free-count queries before pulling. Counts are free; pulls cost credits. Query each candidate keyword for total result count; rank by count and adjacency to the primary; commit only the top 5–10 to Phase 3 collection.
Cross-keyword dedup happens later in Phase 4 (Tier 1 domain match catches duplicate companies surfaced by multiple keywords). This phase only expands the keyword set.
Phase 2 — Config Generation
Build TAMConfig JSON from manifest.json. Schema:
{
"vertical_name": "<string>",
"entity": "brite-nites | brite-supply | brite-labs",
"keywords": ["string", "..."],
"naics_codes": ["6-digit-string", "..."],
"sources": [
{ "name": "<string>", "type": "<string>", "config": { /* per-source */ } }
],
"output_dir": "<string>",
"enrichment_provider": "blitz_waterfall | brite_cli | brite_mcp | skip"
}
enrichment_provider reads from ${user_config.enrichment_provider} (ADR-008 §swap-path mechanics). The skill never hardcodes the value. Override via --enrichment-provider <id> flag.
Phase 3 — Collection (entity-routed)
Three sub-routes — pick the one matching entity:
3a. Nites residential — Google Maps ZIP scraping
(absorbs Revgrowth workflow 09)
- Tool:
WebSearch (NOT Serper — cost reason; WebSearch is in allowed-tools).
- Query template:
<keyword> "<city or ZIP>" per ZIP per priority.
- Initial implementation: 100–500 priority ZIPs per campaign (acceptable trade-off vs full 40K-ZIP traversal; split out if needed for scale).
- Output:
{output_dir}/{source}/businesses.csv per source.
3b. Supply installer
- Tool:
WebSearch + WebFetch for SAM.gov federal contracts, Houzz directory, state license databases.
- Query templates per source documented in
plugins/marketing/references/research-processes/ (BC-5823).
- Output: same unified
businesses.csv schema.
3c. Labs venue partnerships — 4-provider parallel discovery
Run in parallel; merge + dedup by domain at completion.
| Provider | Tool | Script reference | Pagination |
|---|
| Spider.cloud | mcp__plugin_marketing_spider__* | plugins/marketing/scripts/tam-map/spider_crawl.py | crawls homepage + /about + /contact |
| AI Ark | mcp__plugin_marketing_aiark__* | plugins/marketing/scripts/tam-map/aiark_client.py + aiark-mcp.js | paginated, no stated rate limit |
| Discolike | mcp__plugin_marketing_discolike__* | plugins/marketing/scripts/tam-map/discolike_client.py + discolike-mcp.js | offset-based, X-Total-Count header |
| IcyPeas | Bash → python plugins/marketing/scripts/tam-map/icypeas_client.py --icp ./output/{slug}/icp.json | (no MCP — script-only per BC-5946) | max 100/page |
Output:
companies.jsonl — merged + deduped firmographic records.
crawled.jsonl — Spider crawl results overlay (tech signals, summarized homepage content).
Unified businesses.csv schema (Nites/Supply)
| Column | Type | Required | Notes |
|---|
domain | string | yes | Normalized lowercase, www. stripped |
company_name | string | yes | Free-text |
state | string | yes | 2-char US state code |
address | string | no | Free-text |
city | string | no | |
zip | string | no | 5-digit |
phone | string | no | Will be normalized in Phase 4 |
email | string | no | Optional pre-enrichment |
industry | string | no | |
employees | integer | no | |
source | string | yes | Which scraper produced this row |
source_url | string | no | |
Labs companies.jsonl schema
{ "domain": "...", "company_name": "...", "linkedin_url": "...", "geo": "...", "tech_signals": [...], "source": "spider|aiark|discolike|icypeas", ... }. Per-provider columns vary; the merger normalizes shared keys (domain, company_name, geo, source); provider-specific keys preserved as namespaced (spider_*, aiark_*, etc.).
Phase 4 — Dedup
3-tier algorithm (in order):
- Tier 1 — domain match. Exact match on normalized domain (lowercase, strip
www., strip trailing /).
- Tier 2 — company name + state. Token-set Jaccard ≥ 0.85 on company name + exact state match. Catches DBA aliases.
- Tier 3 — phone match. Normalized phone (strip non-digits, last 10 digits). Catches franchise locations sharing a phone tree.
Output:
all_sources_deduped.csv — deduped TAM.
dedup_stats.json — per-tier reduction counts:
{
"input_rows": 12300,
"tier_1_domain_dedup_removed": 1200,
"tier_2_name_state_dedup_removed": 450,
"tier_3_phone_dedup_removed": 80,
"output_rows": 10570
}
Phase 4.5 — Exclusion (MANDATORY — never skipped)
Procedure mirrored by list-building § Workflow 2 (BC-2717) for its Sources 2 + 3 (dbt audience CSV, manual CSV) paths. Keep in sync — when one changes, audit the other.
HARD-FAIL rule. If either Email Bison workspace is unreachable (auth failure, network timeout after 3 retries, missing token), the skill HALTS and reports which workspace failed. Does NOT silent-skip and DOES NOT proceed to Phase 5. Reason: Phase 5 enrichment costs real money (BlitzAPI + Prospeo + MillionVerifier per-record); running it on already-contacted leads wastes credits.
Steps:
- Availability checks (issue all 3 in parallel as a single tool-batch — ALL THREE MUST PASS — HARD-FAIL):
mcp__emailbison-b2b__get_active_workspace_info.
mcp__emailbison-personal__get_active_workspace_info.
mcp__plugin_marketing_salesforce__run_soql_query with SELECT Id FROM User LIMIT 1 (per plugins/marketing/tools/integrations/salesforce.md — get_username is NOT a valid liveness check).
- Bulk pagination via
mcp__emailbison-b2b__list_leads AND mcp__emailbison-personal__list_leads. Both workspaces.
- Salesforce union query:
SELECT Id, Email, Domain__c FROM Contact WHERE Domain__c IN (...)
/* + */
SELECT Id, Email, Domain__c FROM Lead WHERE Domain__c IN (...)
- Merge into a domain-level exclusion set.
- Filter
all_sources_deduped.csv against exclusion set.
Output:
- Nites/Supply:
net_new_leads.csv.
- Labs:
excluded.jsonl (the rows that DID match exclusion are recorded for transparency; the surviving rows continue forward as companies.jsonl minus matches, written to a fresh companies-net-new.jsonl).
- Plus
exclusion_stats.json:
{
"input_rows": 10570,
"eb_b2b_excluded": 1900,
"eb_personal_excluded": 320,
"sf_contact_excluded": 850,
"sf_lead_excluded": 280,
"total_excluded": 3350,
"output_rows": 7220,
"exclusion_rate_pct": 31.7
}
Typical exclusion rate: 20–40% (scoping doc cites 31.7% average).
Phase 5 — Enrichment Hand-off (pluggable)
Pluggable per ADR-008. Provider selection follows the resolution order in §Before Starting → Enrichment-provider selection.
enrichment_provider | Implementation | Status |
|---|
blitz_waterfall | Shells to python plugins/marketing/scripts/tam-map/enrich_waterfall.py --in <input> --out enriched.jsonl (BlitzAPI 5 req/s serialized, Prospeo fallback max 20 workers) | Default. Production-ready. |
brite_cli | Shells to services/enrichment/cli.py in brite-data-platform | Pending repo wiring; falls through to blitz_waterfall if repo missing locally |
brite_mcp | Calls mcp__plugin_marketing_enrichment__* (brite-enrichment MCP) | Pending BC-5537/5538 GA. Currently NOT in allowed-tools; falls through to blitz_waterfall with pending BC-5537/5538 GA message |
skip | No enrichment — pass through unenriched | Opt-in for testing or for handing off to BC-2717 list-building (which has its own enrichment pre-flight) |
Pre-flight (all providers):
- Email-verification credit check. Read MillionVerifier balance.
- Volume-vs-budget cost estimate (Labs path uses BlitzAPI + Prospeo + MillionVerifier per-record costs):
estimated enrichment cost: $X.XX for N records (BlitzAPI: $A, Prospeo: $B, MillionVerifier: $C)
This verbatim string MUST appear in output before any enrichment call (grep test in evals).
- Cost gate. Use
AskUserQuestion to confirm BEFORE invocation if cost > $20 (configurable threshold per --max-records interaction; see §Before Starting).
- API availability — read-only ping to each provider's auth endpoint.
Adaptive email waterfall (within blitz_waterfall):
- Sample 100–200 records. Run BlitzAPI → Prospeo waterfall on the sample. Measure hit rate.
- If sample hit rate < 30%, skill warns and asks user to confirm before committing the full waterfall.
- If sample hit rate ≥ 30%, proceed with the full list.
Post-enrichment verification:
- Keep records with verification status
valid. Drop unknown, error, disposable, invalid.
- Output:
enriched.jsonl.
Labs path note. The catch_all boolean flag is produced downstream in Phase 6 (SMTP verify), not here — Phase 5 has no source of catch-all signal. Nites/Supply paths terminate after Phase 5 without the catch-all distinction; their downstream consumers (BC-2717 / BC-5826) handle SMTP verify on their own schedule.
Pattern-based recovery anti-rule. Do NOT try info@<domain>, contact@<domain>, hello@<domain> for single-location businesses. Upstream tested 10 patterns × 15,934 domains = 0 hits. Single-location TAMs (Nites Google Maps, many Supply local installers, many Labs venues) almost never use generic mailboxes.
Entity routing exit:
- Nites/Supply: Phase 5 is the final phase. Hand off to
list-building (BC-2717) when further enrichment needed, or directly to launch-campaign (BC-5826).
- Labs: Continue to Phase 6.
Phase 6 — SMTP Verify (Labs only)
Nites/Supply paths skip this phase. Downstream consumers (BC-2717 or BC-5826) handle SMTP verification.
- Tool:
Bash → python plugins/marketing/scripts/tam-map/verify_smtp.py --in enriched.jsonl --out verified.jsonl.
- Throughput: 160 req/sec (MillionVerifier rate limit).
- Filter result codes:
- Keep code
1 (valid).
- Keep code
2 (catch_all) — but flag with catch_all=true so Phase 7 can route them to a separate output file.
- Drop codes
3–6 (unknown, error, disposable, invalid).
- Output:
verified.jsonl with explicit catch_all boolean column.
Catch-all isolation rule (non-negotiable). catch_all=true rows are kept in verified.jsonl so Phase 7 can route them to a separate output file. They are NEVER mixed into the tier-A/B/C CSVs. This is a user-explicit requirement for the tam-map port.
Phase 7 — Tier + Segment delegation (Labs only)
Delegates to icp-scoring (BC-5831) with --rubric abc. tam-mapping does NOT run a classifier itself.
Pre-tier filter — Operational rule 1 (No free-email providers in B2B output). BEFORE delegation, filter verified.jsonl rows whose email domain is one of gmail.com / yahoo.com / hotmail.com / outlook.com / icloud.com. Route them to personal-contacts.csv for manual outreach. NEVER include them in tier-A/B/C CSVs.
Reshape — JSONL → flat CSV with top-level catch_all (REQUIRED before delegation). Per icp-scoring's abc delegation contract (BC-5831 SKILL.md § "Tam-mapping delegation contract"), the caller (this skill) owns the JSONL→CSV reshape. verified.jsonl from Phase 6 nests the catch-all flag under record.smtp.catch_all; icp-scoring abc requires a flat CSV with a top-level catch_all column and stops with missing required column 'catch_all' for --rubric abc if absent. Before invoking the delegation call below, write verified-flat.csv with these columns: domain, company_name (if present), industry (if present), employees (if present), geography (if present), catch_all (boolean, flattened from smtp.catch_all). Free-email rows already routed to personal-contacts.csv in the prior step are excluded from verified-flat.csv.
Note on the two icp-scoring upstream feeders. verified-flat.csv (6 cols, this file) and list-building's enriched_leads.csv (16 cols, BC-2717) are deliberately different shapes. tam-mapping emits the tier-classification-only feeder (just what icp-scoring abc needs to score); list-building emits the fully-enriched feeder (also includes contact-level columns). Both satisfy icp-scoring's required column set; downstream consumers needing contact-level fields (e.g., launch-campaign's post-icp-scoring step) MUST consume from list-building's enriched_leads.csv, not from this verified-flat.csv.
Delegation call:
icp-scoring \
--rubric abc \
--max-records <N> \
--output-dir docs/campaigns/labs/tam/{slug}/ \
--criteria-file docs/campaigns/labs/tam/{slug}/icp.json
icp-scoring reads verified-flat.csv from --output-dir as input. Per icp-scoring's abc mode contract, the prompt template is read verbatim from plugins/marketing/references/tam/fit-scoring.md — not inlined here. When upstream tam-map updates the prompt, we re-port to fit-scoring.md and both skills inherit.
Returns:
tier-a.csv — strong-fit prospects.
tier-b.csv — secondary-fit prospects.
tier-c.csv — weak/exploratory-fit prospects.
catch-all.csv — catch_all=true rows scored separately (per icp-scoring abc mode contract).
report.md — counts, drop reasons, top firmographic patterns.
The 4-CSV split is the contract launch-campaign (BC-5826) and list-building (BC-2717) consume.
Brite Implementation
Tools this skill calls
Organized by phase + reason:
| What the skill needs to do | MCP server / tool | Repo or system | Reason (ADR / source) |
|---|
| Phase 3a Nites — scrape Google Maps ZIP | WebSearch | Public web | Revgrowth 09 + cost (no Serper) |
| Phase 3b Supply — federal contracts + dirs | WebSearch + WebFetch | SAM.gov, Houzz, state license dbs | Revgrowth 02 |
| Phase 3c Labs — web crawl | mcp__plugin_marketing_spider__* | Spider.cloud | tam-map upstream; registered BC-5947 |
| Phase 3c Labs — firmographic discovery | mcp__plugin_marketing_aiark__* | AI Ark | tam-map upstream; registered BC-5947 |
| Phase 3c Labs — lookalike expansion | mcp__plugin_marketing_discolike__* | Discolike | tam-map upstream; registered BC-5947 |
| Phase 3c Labs — keyword search | Bash → icypeas_client.py | IcyPeas | BC-5946 (script-only — no MCP wrapper) |
| Phase 4.5 — workspace 1 exclusion | mcp__emailbison-b2b__list_leads | Email Bison b2b workspace | ADR 2a (sole sequencer) |
| Phase 4.5 — workspace 2 exclusion | mcp__emailbison-personal__list_leads | Email Bison personal workspace | ADR 2a — two-workspace requirement (per BC-5832 scope) |
| Phase 4.5 — SF exclusion | mcp__plugin_marketing_salesforce__run_soql_query | brite-salesforce (production org) | ADR 2a (CRM SoR) |
| Phase 5 — enrichment (default) | Bash → enrich_waterfall.py | BlitzAPI + Prospeo | ADR-008 default blitz_waterfall |
| Phase 5 — enrichment (future swap) | mcp__plugin_marketing_enrichment__* | brite-enrichment | ADR-008 + BC-5537/5538 (NOT in allowed-tools until GA) |
| Phase 6 — SMTP verify (Labs) | Bash → verify_smtp.py |
Architectural rules that apply
- MCP-cap exception ratified. Marketing plugin runs 4 plugin-level MCPs today (
salesforce, spider, aiark, discolike) — within the ~5–6 advisory cap. Per BC-5945 §1, measurement methodology applies on each addition. Do not remove MCPs to "fix" perceived capacity issues; verify startup-latency < 2s and context-budget < 500 tokens deltas vs clean baseline before adding more.
- Open tracking OFF. Per upstream tam-map self-check #2: trackers trash sender reputation. Reminder emitted in Phase 1 output (verbatim string above).
- Phase 4.5 exclusion HARD-FAILS if either workspace unreachable. See §3 Phase 4.5 — never silent-skip; never single-workspace fallback.
- Catch-all isolation non-negotiable (Labs path).
catch-all.csv is ALWAYS separate from tier-{a,b,c}.csv. User-explicit requirement.
- No free-email providers in B2B output (Operational rule 1; Labs path). Filter
gmail / yahoo / hotmail / outlook / icloud before Phase 7 tier delegation.
- Incremental saves + resume from last completed phase (Operational rule 2). Each phase writes its output before advancing. Resume detects via file-existence in stable order.
Cross-skill boundaries
- Owns: TAM database construction. Source discovery. Keyword expansion. 3-tier dedup. Cross-workspace EB exclusion. Phase 5 hand-off orchestration. Entity-aware routing (Nites/Supply/Labs).
- Receives from: User invocation with
--entity + --vertical. Optional gtm-strategy output for segment/ICP inputs.
- Hands off to (entity-specific):
- Nites/Supply →
list-building (BC-2717) when further enrichment needed, or directly to launch-campaign (BC-5826).
- Labs →
icp-scoring (BC-5831, Phase 7 delegation, --rubric abc) → launch-campaign (BC-5826) or list-building (BC-2717).
- Does not own:
- Audience-view design in dbt →
brite-data-platform.
- Per-prospect enrichment beyond waterfall coverage → BC-2727.
- List-to-campaign config →
launch-campaign.
- ICP scoring itself → delegated to
icp-scoring.
MCP Tool Reference
Workflows grouped by phase, not by server. See plugins/marketing/tools/integrations/ for the integration guides per provider.
Workflow 1 — Phase 3 Labs collection (parallel discovery)
- Availability checks (issue all 4 in parallel as a single tool-batch):
mcp__plugin_marketing_spider__spider_get_credits — confirms auth + balance.
- AI Ark MCP liveness (lightest tool — see
plugins/marketing/tools/integrations/ai-ark.md for the canonical liveness call).
- Discolike MCP liveness (see
plugins/marketing/tools/integrations/discolike.md).
- IcyPeas: a free
count query against a stub keyword (the script icypeas_client.py does not currently expose a dedicated healthcheck flag — see plugins/marketing/tools/integrations/icypeas.md for the canonical free-count probe shape; if missing, file a follow-up to add --healthcheck to the script).
- On any failure, stop and report which provider failed.
- Run all 4 providers in parallel against the keyword set from Phase 1.5.
- Merge JSONL outputs; dedup by domain inline (Tier 1 only — full 3-tier dedup happens in Phase 4).
Workflow 2 — Phase 4.5 cross-workspace + SF exclusion
See §3 Phase 4.5 for the full step list (5 steps: 3-probe availability check, dual-workspace list_leads pagination, SF Contact + Lead union SOQL, merge + filter, write net_new_leads.csv or excluded.jsonl + exclusion_stats.json). This Workflow entry exists to confirm tool routing — the canonical procedure is in §3 Phase 4.5.
Workflow 3 — Phase 5 enrichment (provider-routed via Bash)
- Resolve
enrichment_provider per §Before Starting (priority: --enrichment-provider flag → ${user_config.enrichment_provider} → blitz_waterfall).
- Switch on enum per §3 Phase 5 enum table. The table is the canonical source for each provider's invocation, fallback message, and status.
- Cost gate before invocation (see §3 Phase 5 — verbatim string +
AskUserQuestion if > $20).
Workflow 4 — Phase 6 SMTP verify (Labs)
Bash → python plugins/marketing/scripts/tam-map/verify_smtp.py --in enriched.jsonl --out verified.jsonl.
- Filter result codes 1 + 2 (with explicit
catch_all flag); drop 3–6.
Workflow 5 — Phase 7 tier delegation (Labs)
- Pre-filter
verified.jsonl for free-email domains → personal-contacts.csv (Operational rule 1).
- Reshape remaining
verified.jsonl rows to verified-flat.csv with a top-level catch_all column (flattened from smtp.catch_all). icp-scoring abc requires flat CSV with catch_all as a top-level column per its delegation contract (BC-5831).
- Invoke
icp-scoring skill with --rubric abc --max-records <N> --output-dir <slug-dir> --criteria-file <icp.json>.
- icp-scoring reads
verified-flat.csv and the prompt template at plugins/marketing/references/tam/fit-scoring.md (single source of truth).
- Returns 4 CSVs +
report.md.
MCP confirmation gates (out-of-scope reminders):
- Email Bison
import_leads_to_campaign, resume_campaign, unsubscribe_lead, blacklist_lead, archive_campaign, enable_warmup, remove_email_from_blocklist, remove_domain_from_blocklist — these all have MCP-level confirmation gates per the Email Bison integration guide. tam-mapping does NOT call these (handed off to launch-campaign).
- IcyPeas paid
find-companies is preceded by free count-companies per anti-slop rule 2.
Operational Runbook
Task 1 — Brite Nites residential TAM build
Preconditions:
docs/marketing-context.md exists and identifies entity brite-nites.
--vertical <slug> chosen (e.g., austin-municipalities).
- Output dir auto-derived:
docs/research/tam/{vertical}-{YYYY-MM-DD}/.
Steps (Phases 1 → 5):
- Phase 1 source discovery →
manifest.json.
- Phase 1.5 IcyPeas free-count keyword expansion.
- Phase 2 TAMConfig generation (writes
tam-config.json with enrichment_provider from userConfig).
- Phase 3a Google Maps ZIP scraping via
WebSearch → per-source businesses.csv.
- Phase 4 dedup →
all_sources_deduped.csv + dedup_stats.json.
- Phase 4.5 exclusion (MANDATORY — both EB workspaces + SF) →
net_new_leads.csv + exclusion_stats.json.
- Phase 5 enrichment via resolved provider →
enriched.jsonl (or --enrichment-provider skip to pass through).
- Hand off to
list-building (BC-2717) or directly to launch-campaign (BC-5826).
Expected output dir contents:
docs/research/tam/{vertical}-{YYYY-MM-DD}/
├── manifest.json
├── tam-config.json
├── {source}/businesses.csv # one per Phase 3 scraper
├── all_sources_deduped.csv
├── dedup_stats.json
├── net_new_leads.csv
├── exclusion_stats.json
└── enriched.jsonl # if Phase 5 ran
Error handling:
- Any MCP availability failure halts.
- EB workspace unreachable → HARD-FAIL with which workspace failed.
- Phase 5 cost > $20 without
--max-records pre-approval → AskUserQuestion confirmation gate.
Task 2 — Brite Supply installer TAM build
Preconditions:
marketing-context.md exists with brite-supply entity populated.
--vertical <slug> chosen (e.g., landscape-installers-tx).
Steps: mirror Task 1 but Phase 3b uses SAM.gov + Houzz + state license databases (WebSearch + WebFetch) instead of Google Maps. Phases 4, 4.5, 5 unchanged.
Hand off to BC-2717 (typical for Supply because installer enrichment often needs further per-prospect work).
Task 3 — Brite Labs venue partnerships TAM build (full tam-map path, all 7 phases)
Preconditions:
marketing-context.md exists with brite-labs entity populated.
--vertical matches one of the 6 pre-loaded playbooks (zoos, aquariums, casinos, hotels-resorts, ski-resorts, sports-stadiums) OR user provides --criteria-file.
- Output dir auto-derived:
docs/campaigns/labs/tam/{slug}/.
- BOTH EB workspaces must be reachable (HARD-FAIL otherwise).
Steps (Phases 1 → 7):
- Phase 1 source discovery — Labs taxonomy adds AI Ark + Discolike + IcyPeas.
- Phase 1.5 keyword expansion (IcyPeas free-count).
- Phase 2 TAMConfig generation; lazy-load
plugins/marketing/references/vertical-playbooks/{vertical}.md → write icp.json.
- Phase 3c parallel discovery (Spider + AI Ark + Discolike + IcyPeas) →
companies.jsonl + crawled.jsonl.
- Phase 4 dedup →
all_sources_deduped.csv (also kept as .jsonl for Labs continuity).
- Phase 4.5 exclusion (MANDATORY) →
excluded.jsonl + companies-net-new.jsonl.
- Cost gate + Phase 5 enrichment →
enriched.jsonl.
- Phase 6 SMTP verify →
verified.jsonl.
- Free-email filter (Operational rule 1) →
personal-contacts.csv separated.
- JSONL→CSV reshape →
verified-flat.csv with top-level catch_all column (per icp-scoring abc contract).
- Phase 7 tier delegation to
icp-scoring --rubric abc → tier-a.csv, tier-b.csv, tier-c.csv, catch-all.csv, report.md.
- Hand off to
launch-campaign (BC-5826) or list-building (BC-2717).
Expected output dir contents:
docs/campaigns/labs/tam/{slug}/
├── icp.json
├── tam-config.json
├── companies.jsonl
├── crawled.jsonl
├── all_sources_deduped.csv (or .jsonl)
├── excluded.jsonl
├── companies-net-new.jsonl
├── enriched.jsonl
├── verified.jsonl
├── personal-contacts.csv
├── verified-flat.csv
├── tier-a.csv
├── tier-b.csv
├── tier-c.csv
├── catch-all.csv
└── report.md
Task 4 — Resume an interrupted run
Preconditions:
- A previous run's output directory exists with partial output.
Steps:
- Skill reads
--output-dir and runs the file-existence check in stable order (see §Before Starting → Resume detection).
- Resume from the first missing file's phase.
- NEVER restart from Phase 1.
- If
--resume flag is passed, force resume even when state could be ambiguous (e.g., partial JSONL writes — the skill validates the last record line and resumes from the next).
Expected output: pipeline continues; no duplicate work; final output dir contents match Task 1/2/3 expectations for the active entity.
Health Scoring Rubric
| Score | Criteria |
|---|
| 10 | Runs the entity-correct phase route end-to-end. Cites all 16 source categories in Phase 1 manifest. Phase 4.5 exclusion runs against BOTH EB workspaces + SF, HARD-FAILS on missing token. Cost gate fires before Phase 5. Open-tracking-OFF reminder emitted verbatim. Catch-all isolation enforced (Labs). Free-email filter applied (Labs). Resume detection works without restarting from Phase 1. References ADR-008 for enrichment pluggability. Cross-links to BC-2717 / BC-5826 / BC-5831 are present. |
| 7-9 | Same as 10 but skips one verification — e.g. forgets the open-tracking reminder, OR skips the cost gate, OR runs Phase 4.5 against only one EB workspace. Output is functional but missing one architectural rule. |
| 4-6 | Runs the phases but skips Phase 4.5 entirely OR mixes catch-all into tier CSVs OR uses pattern-based email recovery on single-location businesses OR restarts from Phase 1 on resume. Functional but violates a core rule. |
| 1-3 | Hallucinates source taxonomy. Calls unregistered MCP servers (e.g., mcp__plugin_marketing_enrichment__* before BC-5538 GA). Skips IcyPeas free-count and pulls credits blind. Outputs gmail/yahoo addresses in tier CSVs. Hard-fails silently. |
Anti-Slop Guardrails
- Do not generate generic marketing jargon ("synergy", "leverage", "best-in-class").
- Do not fabricate statistics, case studies, or testimonials — always attribute to a source.
- Do not produce output that ignores
docs/marketing-context.md.
- Do not recommend tools the plugin does not have access to (no hallucinated MCP servers, no assumed local clones).
- Always run Phase 4.5 exclusion against BOTH EB workspaces + SF. Never skip — costs real money to enrich already-contacted leads.
- Always run IcyPeas
count queries before paid find-companies. Counts are free; pulls cost credits.
- Always emit unified
businesses.csv schema from every Phase 3 Nites/Supply scraper (and the JSONL equivalent for Labs). Schema variance breaks Phase 4 dedup.
- Always dedup before enrichment. Never enrich a pre-dedup TAM.
- Never use pattern-based email recovery (
info@, contact@, hello@) on single-location businesses. Upstream tested 10 patterns × 15,934 domains = 0 hits.
- Never mix catch-all rows into tier-A/B/C.csv (Labs path).
catch-all.csv is ALWAYS a separate file. User-explicit requirement.
- Never include free-email-provider rows (
gmail.com, yahoo.com, hotmail.com, outlook.com, icloud.com) in tier-A/B/C.csv (Labs path). Sender-reputation rule. Route to personal-contacts.csv instead.
Behavioral Tests
Tier 1 — Free assertions (5)
- Given user invokes "build a TAM for Nites residential in the Austin metro", output must walk through Phases 1, 1.5, 2, 3, 4, 4.5, 5 and explicitly skip Phases 6 + 7 (Labs-only).
- Given user invokes "build a Labs zoos TAM", output must walk all 7 phases and explicitly cite the lazy-load step
plugins/marketing/references/vertical-playbooks/zoos.md.
- Output sample tier-A/B/C rows must NOT contain free-email domains (
gmail.com, yahoo.com, hotmail.com, outlook.com, icloud.com).
- Given a vertical with no playbook (e.g., "build a Labs TAM for breweries"), output must accept
--criteria-file or interactive ICP entry as fallback — NOT silently fail.
- Given a resume scenario ("the last run died at Phase 5"), output must detect the resume point from file-existence and NOT restart from Phase 1.
Tier 2 — Tool-assisted (7)
- If
docs/marketing-context.md exists, output must reference Brite entity from that file in the Phase 1 manifest.
- If
mcp__emailbison-b2b__get_active_workspace_info returns auth failure, skill HARD-FAILS at Phase 4.5 — does NOT proceed to Phase 5.
- If
mcp__emailbison-personal__get_active_workspace_info returns auth failure (with b2b OK), skill STILL HARD-FAILS at Phase 4.5 — both must succeed.
- If
${user_config.enrichment_provider} is brite_mcp and the MCP is unavailable, output emits "pending BC-5537/5538 GA" message and falls through to blitz_waterfall.
- If
${user_config.enrichment_provider} is skip, Phase 5 short-circuits; downstream Phases 6+7 still run for Labs.
- Open-tracking-OFF reminder appears as verbatim string
OPEN-TRACKING DISABLED in Phase 1 output (grep test in evals).
- Cost-estimate string
estimated enrichment cost: appears in output before any Phase 5 enrichment call (grep test in evals).