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>
78 lines
2.5 KiB
TypeScript
78 lines
2.5 KiB
TypeScript
/**
|
|
* Canonical source = Quinn's Executor workspace (black-independent).
|
|
*
|
|
* With black down, `/www/provider-config` is gone, but the intended per-platform
|
|
* ad copy lives as files in the Executor workspace's `ad-copy/` dir (the "copy of
|
|
* record" kept there while the quinn-my store is down). ad-watch reads those as
|
|
* the intended-copy baseline, and `_RULES.md` as the maintained compliance
|
|
* checklist. Override the location with ADWATCH_ADCOPY_DIR.
|
|
*/
|
|
|
|
import { readFile, readdir } from 'node:fs/promises';
|
|
import { homedir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
|
|
export function adcopyDir(): string {
|
|
return (
|
|
process.env['ADWATCH_ADCOPY_DIR'] ??
|
|
join(homedir(), 'Documents', 'Claude', 'Projects', 'Executor', 'ad-copy')
|
|
);
|
|
}
|
|
|
|
/** Map a registry/platform id to its ad-copy filename stem (most share the id). */
|
|
const FILENAME_BY_ID: Record<string, string> = {
|
|
tryst: 'tryst',
|
|
eros: 'eros',
|
|
ts4rent: 'ts4rent',
|
|
tsescorts: 'tsescorts',
|
|
megapersonals: 'megapersonals',
|
|
adultlook: 'adultlook',
|
|
adultsearch: 'adultsearch',
|
|
skipthegames: 'skipthegames',
|
|
privatedelights: 'privatedelights',
|
|
seeking: 'seeking',
|
|
};
|
|
|
|
export interface IntendedCopy {
|
|
platform: string;
|
|
file: string;
|
|
text: string;
|
|
}
|
|
|
|
/** Load the intended copy file for a platform (tries .txt then .html). Null if absent. */
|
|
export async function loadIntendedCopy(platformId: string): Promise<IntendedCopy | null> {
|
|
const stem = FILENAME_BY_ID[platformId.toLowerCase()] ?? platformId.toLowerCase();
|
|
const dir = adcopyDir();
|
|
for (const ext of ['.txt', '.html']) {
|
|
const file = join(dir, `${stem}${ext}`);
|
|
try {
|
|
const text = await readFile(file, 'utf8');
|
|
return { platform: platformId, file, text };
|
|
} catch {
|
|
// try next extension
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/** List the platform stems that have an ad-copy file on disk. */
|
|
export async function listAdCopyPlatforms(): Promise<string[]> {
|
|
try {
|
|
const entries = await readdir(adcopyDir());
|
|
const stems = entries
|
|
.filter((f) => (f.endsWith('.txt') || f.endsWith('.html')) && !f.startsWith('_') && !f.startsWith('.'))
|
|
.map((f) => f.replace(/\.(txt|html)$/, ''));
|
|
return [...new Set(stems)].sort(); // dedup platforms that have both .txt and .html
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/** Read the raw `_RULES.md` checklist text (the maintained compliance rules). */
|
|
export async function loadRulesDoc(): Promise<string | null> {
|
|
try {
|
|
return await readFile(join(adcopyDir(), '_RULES.md'), 'utf8');
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|