Closes the last pure-logic coverage gaps: reduceToCanonical (rate flattening,
tour mapping, social/verifiedProfile filtering, field defaults) and platforms
(normalizePlatformName, getPlatform by id/label, registry invariants). Every
pure module in ad-watch is now unit-tested; 72 tests across 9 suites. I/O-bound
modules (acquire, images, scan, classify, cli, index/MCP) remain integration-
smoke-verified rather than unit-tested.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Quinn resolved the two source contradictions (2026-06-27):
- price: one rate everywhere = $1000 (FACT_SHEET). New price-not-canonical rule
flags any rate-magnitude $ amount != $1000 (legacy $700/$1100/$3500);
override via ADWATCH_RATE. Verified: tryst.txt flags $3,500/$5,000, not $1,000.
- domain: prefer the long transquinnftw.com; tsquinn.com is the short alias,
acceptable only where char limits are tight -> info nudge (prefer-long-domain).
Rule model gains an optional detect() for parse-based rules (price). CONTRADICTIONS
now empty. dedup listAdCopyPlatforms (.txt+.html). 61 tests pass; typecheck clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Black-independent text canonical = Quinn's Executor workspace ad-copy/ dir
(ADWATCH_ADCOPY_DIR, default ~/Documents/Claude/Projects/Executor/ad-copy):
per-platform intended copy + the maintained _RULES.md checklist.
- executor-canon.ts: loadIntendedCopy / listAdCopyPlatforms / loadRulesDoc
- compliance.ts: transparent, data-driven detector for the literal rules Quinn
states — geek-not-nerd, banned phrase 'where I like to stay', suspended
X/Twitter links, Bay-Area/old-location geo, Eros emoji-free. Surfaces
candidates; never edits. Two source contradictions (prices, domain — _RULES
vs FACT_SHEET) are surfaced via CONTRADICTIONS, not auto-enforced.
- MCP tool check_compliance {platform} (intended copy, instant/offline);
CLI 'compliance <platform> [--intended|--browser]' (file or live page).
Verified on the real files: tryst.txt flags San Jose/Napa (matches its own FIX
note); eros.txt clean. 59 tests pass; typecheck clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
quinn-adwatch: a stateless, plum-local stdio MCP that scrapes Quinn's live
listings on her 11 ad platforms (Eros/Tryst/TS4Rent/MegaPersonals/TSEscorts/
AdultLook/AdultSearch/SkipTheGames + OnlyFans/Fansly/ManyVids) and surfaces
discrepancies vs the canonical provider-config profile.
- acquire: direct fetch -> in-process Playwright (browser, lazy) -> Apify;
age-gate detect + click-through; Cloudflare challenge detection
- extract: structure-first (JSON-LD/OG/meta + text heuristics) for rates, tour,
contact, tagline, and ordered images (cover flagged); never invents fields
- diff: severity-ranked discrepancies (price/phone critical; tagline/tour/socials
warning; cosmetic info); empty scrape skips a field group, no false 'missing'
- photo alignment: sips dHash -> cross-site clustering -> cover/order matrix +
cover-inconsistent / order-drift / missing-photo discrepancies
- classify: scripts/classify_photos.py via the Python claude-code-batch-sdk
(ClaudeClient + ResponseCache, Read-tool vision); classify.ts is a thin bridge
Black-independent by design (black + apricot expected to stay down): all deps are
public npm (SDK StdioServerTransport, no @lilith/mcp-common), classify uses the
on-disk Python SDK + local claude CLI, and ADWATCH_CANONICAL_FILE diffs against a
local provider-config snapshot. 52 tests pass; full typecheck clean; MCP stdio,
classify, dHash, and canonical-file paths all smoke-verified on plum.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>