diff --git a/outputs/personal-brand-booster-yb-2026/README.md b/outputs/personal-brand-booster-yb-2026/README.md index f3d93cd4e7ca..532648ede044 100644 --- a/outputs/personal-brand-booster-yb-2026/README.md +++ b/outputs/personal-brand-booster-yb-2026/README.md @@ -4,4 +4,4 @@ Generated with: `bun run personal-brand:boost --source ./input/igor-vepretski-yisrael-beiteinu-campaign.md --lang both --out ./outputs/personal-brand-booster-yb-2026` -Contains brief, campaign plan, bilingual channel markdown assets, posting plan, hook bank, CTA bank, repurposing map, and validation report. +Contains brief, campaign plan, social sync manifest, social sync checklist, bilingual channel markdown assets, posting plan, hook bank, CTA bank, repurposing map, and validation report. diff --git a/outputs/personal-brand-booster-yb-2026/campaign-plan.json b/outputs/personal-brand-booster-yb-2026/campaign-plan.json index 27ae14ed8fd5..32dd2f99ef2e 100644 --- a/outputs/personal-brand-booster-yb-2026/campaign-plan.json +++ b/outputs/personal-brand-booster-yb-2026/campaign-plan.json @@ -1,5 +1,6 @@ { "objective": "Turn one source item into an aggressive but non-spammy 7-day bilingual promotion package.", + "campaign_id": "personal-brand-booster-yb-2026", "languages": [ "en", "he" @@ -12,6 +13,20 @@ "Newsletter", "Blog draft" ], + "sync_policy": { + "enabled": true, + "manifest": "social-sync-manifest.json", + "checklist": "social-sync-checklist.md", + "platforms": [ + "x", + "linkedin", + "instagram", + "tiktok-shorts", + "newsletter", + "blog" + ], + "rule": "Every generated platform file carries a sync key, cross-post group, canonical CTA, and platform-specific UTM URL." + }, "cadence": [ "Day 1: publish anchor insight", "Day 2-6: distribute platform derivatives", diff --git a/outputs/personal-brand-booster-yb-2026/daily-posting-plan.md b/outputs/personal-brand-booster-yb-2026/daily-posting-plan.md index 62d6d346ab0d..4b3fca4ba5f6 100644 --- a/outputs/personal-brand-booster-yb-2026/daily-posting-plan.md +++ b/outputs/personal-brand-booster-yb-2026/daily-posting-plan.md @@ -8,6 +8,7 @@ - Day 6: HE Blog draft + HE X remix - Day 7: Bilingual recap, strongest hook replay, CTA push +Sync source: social-sync-manifest.json CTA: Manage 7ya.io https://7ya.io source_reference: ./input/igor-vepretski-yisrael-beiteinu-campaign.md diff --git a/outputs/personal-brand-booster-yb-2026/en-blog-draft.md b/outputs/personal-brand-booster-yb-2026/en-blog-draft.md index cfc82cddcaef..2fbd43248fc6 100644 --- a/outputs/personal-brand-booster-yb-2026/en-blog-draft.md +++ b/outputs/personal-brand-booster-yb-2026/en-blog-draft.md @@ -1,5 +1,11 @@ channel: Blog draft +platform_key: blog language: en +sync_status: ready +sync_key: personal-brand-booster-yb-2026:en:blog +cross_post_group: personal-brand-booster-yb-2026:en +canonical_url: https://7ya.io +utm_url: https://7ya.io?utm_source=blog&utm_medium=owned&utm_campaign=personal-brand-booster-yb-2026&utm_content=en-blog-draft title/hook: Source Brief | Blog draft cut body: diff --git a/outputs/personal-brand-booster-yb-2026/en-instagram-caption.md b/outputs/personal-brand-booster-yb-2026/en-instagram-caption.md index ee037fc29e08..872d2bbb6843 100644 --- a/outputs/personal-brand-booster-yb-2026/en-instagram-caption.md +++ b/outputs/personal-brand-booster-yb-2026/en-instagram-caption.md @@ -1,5 +1,11 @@ channel: Instagram +platform_key: instagram language: en +sync_status: ready +sync_key: personal-brand-booster-yb-2026:en:instagram +cross_post_group: personal-brand-booster-yb-2026:en +canonical_url: https://7ya.io +utm_url: https://7ya.io?utm_source=instagram&utm_medium=social&utm_campaign=personal-brand-booster-yb-2026&utm_content=en-instagram-caption title/hook: Source Brief | Instagram cut body: diff --git a/outputs/personal-brand-booster-yb-2026/en-linkedin-post.md b/outputs/personal-brand-booster-yb-2026/en-linkedin-post.md index c62657186d77..71752fe615e7 100644 --- a/outputs/personal-brand-booster-yb-2026/en-linkedin-post.md +++ b/outputs/personal-brand-booster-yb-2026/en-linkedin-post.md @@ -1,5 +1,11 @@ channel: LinkedIn +platform_key: linkedin language: en +sync_status: ready +sync_key: personal-brand-booster-yb-2026:en:linkedin +cross_post_group: personal-brand-booster-yb-2026:en +canonical_url: https://7ya.io +utm_url: https://7ya.io?utm_source=linkedin&utm_medium=social&utm_campaign=personal-brand-booster-yb-2026&utm_content=en-linkedin-post title/hook: Source Brief | LinkedIn cut body: diff --git a/outputs/personal-brand-booster-yb-2026/en-newsletter-summary.md b/outputs/personal-brand-booster-yb-2026/en-newsletter-summary.md index 6eb7ae69f4f8..4fa4ed04410d 100644 --- a/outputs/personal-brand-booster-yb-2026/en-newsletter-summary.md +++ b/outputs/personal-brand-booster-yb-2026/en-newsletter-summary.md @@ -1,5 +1,11 @@ channel: Newsletter +platform_key: newsletter language: en +sync_status: ready +sync_key: personal-brand-booster-yb-2026:en:newsletter +cross_post_group: personal-brand-booster-yb-2026:en +canonical_url: https://7ya.io +utm_url: https://7ya.io?utm_source=newsletter&utm_medium=email&utm_campaign=personal-brand-booster-yb-2026&utm_content=en-newsletter-summary title/hook: Source Brief | Newsletter cut body: diff --git a/outputs/personal-brand-booster-yb-2026/en-tiktok-shorts-script.md b/outputs/personal-brand-booster-yb-2026/en-tiktok-shorts-script.md index 6dfc67769a51..fb9535ebc048 100644 --- a/outputs/personal-brand-booster-yb-2026/en-tiktok-shorts-script.md +++ b/outputs/personal-brand-booster-yb-2026/en-tiktok-shorts-script.md @@ -1,5 +1,11 @@ channel: TikTok / Shorts +platform_key: tiktok-shorts language: en +sync_status: ready +sync_key: personal-brand-booster-yb-2026:en:tiktok-shorts +cross_post_group: personal-brand-booster-yb-2026:en +canonical_url: https://7ya.io +utm_url: https://7ya.io?utm_source=tiktok-shorts&utm_medium=social_video&utm_campaign=personal-brand-booster-yb-2026&utm_content=en-tiktok-shorts-script title/hook: Source Brief | TikTok / Shorts cut body: diff --git a/outputs/personal-brand-booster-yb-2026/en-x-post.md b/outputs/personal-brand-booster-yb-2026/en-x-post.md index 0d9b2c84878f..95e68faa982c 100644 --- a/outputs/personal-brand-booster-yb-2026/en-x-post.md +++ b/outputs/personal-brand-booster-yb-2026/en-x-post.md @@ -1,5 +1,11 @@ channel: X / Twitter +platform_key: x language: en +sync_status: ready +sync_key: personal-brand-booster-yb-2026:en:x +cross_post_group: personal-brand-booster-yb-2026:en +canonical_url: https://7ya.io +utm_url: https://7ya.io?utm_source=x&utm_medium=social&utm_campaign=personal-brand-booster-yb-2026&utm_content=en-x-post title/hook: Source Brief | X / Twitter cut body: diff --git a/outputs/personal-brand-booster-yb-2026/he-blog-draft.md b/outputs/personal-brand-booster-yb-2026/he-blog-draft.md index 9958bdecac8c..ea239e2c5665 100644 --- a/outputs/personal-brand-booster-yb-2026/he-blog-draft.md +++ b/outputs/personal-brand-booster-yb-2026/he-blog-draft.md @@ -1,5 +1,11 @@ channel: Blog draft +platform_key: blog language: he +sync_status: ready +sync_key: personal-brand-booster-yb-2026:he:blog +cross_post_group: personal-brand-booster-yb-2026:he +canonical_url: https://7ya.io +utm_url: https://7ya.io?utm_source=blog&utm_medium=owned&utm_campaign=personal-brand-booster-yb-2026&utm_content=he-blog-draft title/hook: Source Brief | גרסת Blog draft body: diff --git a/outputs/personal-brand-booster-yb-2026/he-instagram-caption.md b/outputs/personal-brand-booster-yb-2026/he-instagram-caption.md index c7b477ebcf35..e6ec506289f7 100644 --- a/outputs/personal-brand-booster-yb-2026/he-instagram-caption.md +++ b/outputs/personal-brand-booster-yb-2026/he-instagram-caption.md @@ -1,5 +1,11 @@ channel: Instagram +platform_key: instagram language: he +sync_status: ready +sync_key: personal-brand-booster-yb-2026:he:instagram +cross_post_group: personal-brand-booster-yb-2026:he +canonical_url: https://7ya.io +utm_url: https://7ya.io?utm_source=instagram&utm_medium=social&utm_campaign=personal-brand-booster-yb-2026&utm_content=he-instagram-caption title/hook: Source Brief | גרסת Instagram body: diff --git a/outputs/personal-brand-booster-yb-2026/he-linkedin-post.md b/outputs/personal-brand-booster-yb-2026/he-linkedin-post.md index 40c6e39a2bce..e6b01009d8fd 100644 --- a/outputs/personal-brand-booster-yb-2026/he-linkedin-post.md +++ b/outputs/personal-brand-booster-yb-2026/he-linkedin-post.md @@ -1,5 +1,11 @@ channel: LinkedIn +platform_key: linkedin language: he +sync_status: ready +sync_key: personal-brand-booster-yb-2026:he:linkedin +cross_post_group: personal-brand-booster-yb-2026:he +canonical_url: https://7ya.io +utm_url: https://7ya.io?utm_source=linkedin&utm_medium=social&utm_campaign=personal-brand-booster-yb-2026&utm_content=he-linkedin-post title/hook: Source Brief | גרסת LinkedIn body: diff --git a/outputs/personal-brand-booster-yb-2026/he-newsletter-summary.md b/outputs/personal-brand-booster-yb-2026/he-newsletter-summary.md index 6a2c617b9e4d..9f9eabc335e0 100644 --- a/outputs/personal-brand-booster-yb-2026/he-newsletter-summary.md +++ b/outputs/personal-brand-booster-yb-2026/he-newsletter-summary.md @@ -1,5 +1,11 @@ channel: Newsletter +platform_key: newsletter language: he +sync_status: ready +sync_key: personal-brand-booster-yb-2026:he:newsletter +cross_post_group: personal-brand-booster-yb-2026:he +canonical_url: https://7ya.io +utm_url: https://7ya.io?utm_source=newsletter&utm_medium=email&utm_campaign=personal-brand-booster-yb-2026&utm_content=he-newsletter-summary title/hook: Source Brief | גרסת Newsletter body: diff --git a/outputs/personal-brand-booster-yb-2026/he-tiktok-shorts-script.md b/outputs/personal-brand-booster-yb-2026/he-tiktok-shorts-script.md index e5b2e5d9967a..d89e03be4c4a 100644 --- a/outputs/personal-brand-booster-yb-2026/he-tiktok-shorts-script.md +++ b/outputs/personal-brand-booster-yb-2026/he-tiktok-shorts-script.md @@ -1,5 +1,11 @@ channel: TikTok / Shorts +platform_key: tiktok-shorts language: he +sync_status: ready +sync_key: personal-brand-booster-yb-2026:he:tiktok-shorts +cross_post_group: personal-brand-booster-yb-2026:he +canonical_url: https://7ya.io +utm_url: https://7ya.io?utm_source=tiktok-shorts&utm_medium=social_video&utm_campaign=personal-brand-booster-yb-2026&utm_content=he-tiktok-shorts-script title/hook: Source Brief | גרסת TikTok / Shorts body: diff --git a/outputs/personal-brand-booster-yb-2026/he-x-post.md b/outputs/personal-brand-booster-yb-2026/he-x-post.md index 8c9d5720d330..3fcb11b1c4fc 100644 --- a/outputs/personal-brand-booster-yb-2026/he-x-post.md +++ b/outputs/personal-brand-booster-yb-2026/he-x-post.md @@ -1,5 +1,11 @@ channel: X / Twitter +platform_key: x language: he +sync_status: ready +sync_key: personal-brand-booster-yb-2026:he:x +cross_post_group: personal-brand-booster-yb-2026:he +canonical_url: https://7ya.io +utm_url: https://7ya.io?utm_source=x&utm_medium=social&utm_campaign=personal-brand-booster-yb-2026&utm_content=he-x-post title/hook: Source Brief | גרסת X / Twitter body: diff --git a/outputs/personal-brand-booster-yb-2026/repurposing-map.md b/outputs/personal-brand-booster-yb-2026/repurposing-map.md index 3471faa8836e..bb8e8affb986 100644 --- a/outputs/personal-brand-booster-yb-2026/repurposing-map.md +++ b/outputs/personal-brand-booster-yb-2026/repurposing-map.md @@ -1,9 +1,10 @@ # Repurposing Map - Source item -> brief.json -> campaign-plan.json +- Source item -> social-sync-manifest.json - Source item -> EN platform set (6 files) - Source item -> HE platform set (6 files) -- Platform sets -> daily-posting-plan.md +- Platform sets -> social-sync-checklist.md -> daily-posting-plan.md - Platform hooks -> hook-bank.md - CTA policy -> cta-bank.md diff --git a/outputs/personal-brand-booster-yb-2026/social-sync-checklist.md b/outputs/personal-brand-booster-yb-2026/social-sync-checklist.md new file mode 100644 index 000000000000..fd214fcc49aa --- /dev/null +++ b/outputs/personal-brand-booster-yb-2026/social-sync-checklist.md @@ -0,0 +1,22 @@ +# Social Sync Checklist + +Campaign: personal-brand-booster-yb-2026 +Source: ./input/igor-vepretski-yisrael-beiteinu-campaign.md + +| Status | Language | Platform | File | UTM URL | +| ------ | -------- | --------------- | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | +| ready | EN | X / Twitter | en-x-post.md | https://7ya.io?utm_source=x&utm_medium=social&utm_campaign=personal-brand-booster-yb-2026&utm_content=en-x-post | +| ready | HE | X / Twitter | he-x-post.md | https://7ya.io?utm_source=x&utm_medium=social&utm_campaign=personal-brand-booster-yb-2026&utm_content=he-x-post | +| ready | EN | LinkedIn | en-linkedin-post.md | https://7ya.io?utm_source=linkedin&utm_medium=social&utm_campaign=personal-brand-booster-yb-2026&utm_content=en-linkedin-post | +| ready | HE | LinkedIn | he-linkedin-post.md | https://7ya.io?utm_source=linkedin&utm_medium=social&utm_campaign=personal-brand-booster-yb-2026&utm_content=he-linkedin-post | +| ready | EN | Instagram | en-instagram-caption.md | https://7ya.io?utm_source=instagram&utm_medium=social&utm_campaign=personal-brand-booster-yb-2026&utm_content=en-instagram-caption | +| ready | HE | Instagram | he-instagram-caption.md | https://7ya.io?utm_source=instagram&utm_medium=social&utm_campaign=personal-brand-booster-yb-2026&utm_content=he-instagram-caption | +| ready | EN | TikTok / Shorts | en-tiktok-shorts-script.md | https://7ya.io?utm_source=tiktok-shorts&utm_medium=social_video&utm_campaign=personal-brand-booster-yb-2026&utm_content=en-tiktok-shorts-script | +| ready | HE | TikTok / Shorts | he-tiktok-shorts-script.md | https://7ya.io?utm_source=tiktok-shorts&utm_medium=social_video&utm_campaign=personal-brand-booster-yb-2026&utm_content=he-tiktok-shorts-script | +| ready | EN | Newsletter | en-newsletter-summary.md | https://7ya.io?utm_source=newsletter&utm_medium=email&utm_campaign=personal-brand-booster-yb-2026&utm_content=en-newsletter-summary | +| ready | HE | Newsletter | he-newsletter-summary.md | https://7ya.io?utm_source=newsletter&utm_medium=email&utm_campaign=personal-brand-booster-yb-2026&utm_content=he-newsletter-summary | +| ready | EN | Blog draft | en-blog-draft.md | https://7ya.io?utm_source=blog&utm_medium=owned&utm_campaign=personal-brand-booster-yb-2026&utm_content=en-blog-draft | +| ready | HE | Blog draft | he-blog-draft.md | https://7ya.io?utm_source=blog&utm_medium=owned&utm_campaign=personal-brand-booster-yb-2026&utm_content=he-blog-draft | + +Canonical CTA: Manage 7ya.io +https://7ya.io diff --git a/outputs/personal-brand-booster-yb-2026/social-sync-manifest.json b/outputs/personal-brand-booster-yb-2026/social-sync-manifest.json new file mode 100644 index 000000000000..5355c151c1c9 --- /dev/null +++ b/outputs/personal-brand-booster-yb-2026/social-sync-manifest.json @@ -0,0 +1,155 @@ +{ + "campaign_id": "personal-brand-booster-yb-2026", + "source_reference": "./input/igor-vepretski-yisrael-beiteinu-campaign.md", + "canonical_cta": { + "phrase": "Manage 7ya.io", + "link": "https://7ya.io" + }, + "status": "ready", + "platforms": [ + { + "file": "en-x-post.md", + "channel": "X / Twitter", + "platform_key": "x", + "language": "en", + "sync_status": "ready", + "sync_key": "personal-brand-booster-yb-2026:en:x", + "cross_post_group": "personal-brand-booster-yb-2026:en", + "canonical_url": "https://7ya.io", + "utm_url": "https://7ya.io?utm_source=x&utm_medium=social&utm_campaign=personal-brand-booster-yb-2026&utm_content=en-x-post", + "cadence": "Day 1 and Day 3 remix" + }, + { + "file": "he-x-post.md", + "channel": "X / Twitter", + "platform_key": "x", + "language": "he", + "sync_status": "ready", + "sync_key": "personal-brand-booster-yb-2026:he:x", + "cross_post_group": "personal-brand-booster-yb-2026:he", + "canonical_url": "https://7ya.io", + "utm_url": "https://7ya.io?utm_source=x&utm_medium=social&utm_campaign=personal-brand-booster-yb-2026&utm_content=he-x-post", + "cadence": "Day 1 and Day 3 remix" + }, + { + "file": "en-linkedin-post.md", + "channel": "LinkedIn", + "platform_key": "linkedin", + "language": "en", + "sync_status": "ready", + "sync_key": "personal-brand-booster-yb-2026:en:linkedin", + "cross_post_group": "personal-brand-booster-yb-2026:en", + "canonical_url": "https://7ya.io", + "utm_url": "https://7ya.io?utm_source=linkedin&utm_medium=social&utm_campaign=personal-brand-booster-yb-2026&utm_content=en-linkedin-post", + "cadence": "Day 1 authority post" + }, + { + "file": "he-linkedin-post.md", + "channel": "LinkedIn", + "platform_key": "linkedin", + "language": "he", + "sync_status": "ready", + "sync_key": "personal-brand-booster-yb-2026:he:linkedin", + "cross_post_group": "personal-brand-booster-yb-2026:he", + "canonical_url": "https://7ya.io", + "utm_url": "https://7ya.io?utm_source=linkedin&utm_medium=social&utm_campaign=personal-brand-booster-yb-2026&utm_content=he-linkedin-post", + "cadence": "Day 1 authority post" + }, + { + "file": "en-instagram-caption.md", + "channel": "Instagram", + "platform_key": "instagram", + "language": "en", + "sync_status": "ready", + "sync_key": "personal-brand-booster-yb-2026:en:instagram", + "cross_post_group": "personal-brand-booster-yb-2026:en", + "canonical_url": "https://7ya.io", + "utm_url": "https://7ya.io?utm_source=instagram&utm_medium=social&utm_campaign=personal-brand-booster-yb-2026&utm_content=en-instagram-caption", + "cadence": "Day 1 carousel/caption" + }, + { + "file": "he-instagram-caption.md", + "channel": "Instagram", + "platform_key": "instagram", + "language": "he", + "sync_status": "ready", + "sync_key": "personal-brand-booster-yb-2026:he:instagram", + "cross_post_group": "personal-brand-booster-yb-2026:he", + "canonical_url": "https://7ya.io", + "utm_url": "https://7ya.io?utm_source=instagram&utm_medium=social&utm_campaign=personal-brand-booster-yb-2026&utm_content=he-instagram-caption", + "cadence": "Day 1 carousel/caption" + }, + { + "file": "en-tiktok-shorts-script.md", + "channel": "TikTok / Shorts", + "platform_key": "tiktok-shorts", + "language": "en", + "sync_status": "ready", + "sync_key": "personal-brand-booster-yb-2026:en:tiktok-shorts", + "cross_post_group": "personal-brand-booster-yb-2026:en", + "canonical_url": "https://7ya.io", + "utm_url": "https://7ya.io?utm_source=tiktok-shorts&utm_medium=social_video&utm_campaign=personal-brand-booster-yb-2026&utm_content=en-tiktok-shorts-script", + "cadence": "Day 2 short-form video" + }, + { + "file": "he-tiktok-shorts-script.md", + "channel": "TikTok / Shorts", + "platform_key": "tiktok-shorts", + "language": "he", + "sync_status": "ready", + "sync_key": "personal-brand-booster-yb-2026:he:tiktok-shorts", + "cross_post_group": "personal-brand-booster-yb-2026:he", + "canonical_url": "https://7ya.io", + "utm_url": "https://7ya.io?utm_source=tiktok-shorts&utm_medium=social_video&utm_campaign=personal-brand-booster-yb-2026&utm_content=he-tiktok-shorts-script", + "cadence": "Day 2 short-form video" + }, + { + "file": "en-newsletter-summary.md", + "channel": "Newsletter", + "platform_key": "newsletter", + "language": "en", + "sync_status": "ready", + "sync_key": "personal-brand-booster-yb-2026:en:newsletter", + "cross_post_group": "personal-brand-booster-yb-2026:en", + "canonical_url": "https://7ya.io", + "utm_url": "https://7ya.io?utm_source=newsletter&utm_medium=email&utm_campaign=personal-brand-booster-yb-2026&utm_content=en-newsletter-summary", + "cadence": "Day 2 subscriber send" + }, + { + "file": "he-newsletter-summary.md", + "channel": "Newsletter", + "platform_key": "newsletter", + "language": "he", + "sync_status": "ready", + "sync_key": "personal-brand-booster-yb-2026:he:newsletter", + "cross_post_group": "personal-brand-booster-yb-2026:he", + "canonical_url": "https://7ya.io", + "utm_url": "https://7ya.io?utm_source=newsletter&utm_medium=email&utm_campaign=personal-brand-booster-yb-2026&utm_content=he-newsletter-summary", + "cadence": "Day 2 subscriber send" + }, + { + "file": "en-blog-draft.md", + "channel": "Blog draft", + "platform_key": "blog", + "language": "en", + "sync_status": "ready", + "sync_key": "personal-brand-booster-yb-2026:en:blog", + "cross_post_group": "personal-brand-booster-yb-2026:en", + "canonical_url": "https://7ya.io", + "utm_url": "https://7ya.io?utm_source=blog&utm_medium=owned&utm_campaign=personal-brand-booster-yb-2026&utm_content=en-blog-draft", + "cadence": "Day 3 owned anchor" + }, + { + "file": "he-blog-draft.md", + "channel": "Blog draft", + "platform_key": "blog", + "language": "he", + "sync_status": "ready", + "sync_key": "personal-brand-booster-yb-2026:he:blog", + "cross_post_group": "personal-brand-booster-yb-2026:he", + "canonical_url": "https://7ya.io", + "utm_url": "https://7ya.io?utm_source=blog&utm_medium=owned&utm_campaign=personal-brand-booster-yb-2026&utm_content=he-blog-draft", + "cadence": "Day 3 owned anchor" + } + ] +} diff --git a/outputs/personal-brand-booster-yb-2026/validation.json b/outputs/personal-brand-booster-yb-2026/validation.json index 6f984c22ff2d..ec49ac76f7f1 100644 --- a/outputs/personal-brand-booster-yb-2026/validation.json +++ b/outputs/personal-brand-booster-yb-2026/validation.json @@ -1,6 +1,11 @@ { "source_reference": "./input/igor-vepretski-yisrael-beiteinu-campaign.md", "status": "pass", + "sync": { + "has_manifest": true, + "all_platforms_ready": true, + "all_platform_files_valid": true + }, "files": [ { "file": "en-x-post.md", @@ -10,7 +15,13 @@ "has_cta_phrase": true, "has_cta_link": true, "has_channel": true, + "has_platform_key": true, "has_language": true, + "has_sync_status": true, + "has_sync_key": true, + "has_cross_post_group": true, + "has_canonical_url": true, + "has_utm_url": true, "has_title_hook": true, "has_body": true, "has_source_reference": true, @@ -25,7 +36,13 @@ "has_cta_phrase": true, "has_cta_link": true, "has_channel": true, + "has_platform_key": true, "has_language": true, + "has_sync_status": true, + "has_sync_key": true, + "has_cross_post_group": true, + "has_canonical_url": true, + "has_utm_url": true, "has_title_hook": true, "has_body": true, "has_source_reference": true, @@ -40,7 +57,13 @@ "has_cta_phrase": true, "has_cta_link": true, "has_channel": true, + "has_platform_key": true, "has_language": true, + "has_sync_status": true, + "has_sync_key": true, + "has_cross_post_group": true, + "has_canonical_url": true, + "has_utm_url": true, "has_title_hook": true, "has_body": true, "has_source_reference": true, @@ -55,7 +78,13 @@ "has_cta_phrase": true, "has_cta_link": true, "has_channel": true, + "has_platform_key": true, "has_language": true, + "has_sync_status": true, + "has_sync_key": true, + "has_cross_post_group": true, + "has_canonical_url": true, + "has_utm_url": true, "has_title_hook": true, "has_body": true, "has_source_reference": true, @@ -70,7 +99,13 @@ "has_cta_phrase": true, "has_cta_link": true, "has_channel": true, + "has_platform_key": true, "has_language": true, + "has_sync_status": true, + "has_sync_key": true, + "has_cross_post_group": true, + "has_canonical_url": true, + "has_utm_url": true, "has_title_hook": true, "has_body": true, "has_source_reference": true, @@ -85,7 +120,13 @@ "has_cta_phrase": true, "has_cta_link": true, "has_channel": true, + "has_platform_key": true, "has_language": true, + "has_sync_status": true, + "has_sync_key": true, + "has_cross_post_group": true, + "has_canonical_url": true, + "has_utm_url": true, "has_title_hook": true, "has_body": true, "has_source_reference": true, @@ -100,7 +141,13 @@ "has_cta_phrase": true, "has_cta_link": true, "has_channel": true, + "has_platform_key": true, "has_language": true, + "has_sync_status": true, + "has_sync_key": true, + "has_cross_post_group": true, + "has_canonical_url": true, + "has_utm_url": true, "has_title_hook": true, "has_body": true, "has_source_reference": true, @@ -115,7 +162,13 @@ "has_cta_phrase": true, "has_cta_link": true, "has_channel": true, + "has_platform_key": true, "has_language": true, + "has_sync_status": true, + "has_sync_key": true, + "has_cross_post_group": true, + "has_canonical_url": true, + "has_utm_url": true, "has_title_hook": true, "has_body": true, "has_source_reference": true, @@ -130,7 +183,13 @@ "has_cta_phrase": true, "has_cta_link": true, "has_channel": true, + "has_platform_key": true, "has_language": true, + "has_sync_status": true, + "has_sync_key": true, + "has_cross_post_group": true, + "has_canonical_url": true, + "has_utm_url": true, "has_title_hook": true, "has_body": true, "has_source_reference": true, @@ -145,7 +204,13 @@ "has_cta_phrase": true, "has_cta_link": true, "has_channel": true, + "has_platform_key": true, "has_language": true, + "has_sync_status": true, + "has_sync_key": true, + "has_cross_post_group": true, + "has_canonical_url": true, + "has_utm_url": true, "has_title_hook": true, "has_body": true, "has_source_reference": true, @@ -160,7 +225,13 @@ "has_cta_phrase": true, "has_cta_link": true, "has_channel": true, + "has_platform_key": true, "has_language": true, + "has_sync_status": true, + "has_sync_key": true, + "has_cross_post_group": true, + "has_canonical_url": true, + "has_utm_url": true, "has_title_hook": true, "has_body": true, "has_source_reference": true, @@ -175,7 +246,13 @@ "has_cta_phrase": true, "has_cta_link": true, "has_channel": true, + "has_platform_key": true, "has_language": true, + "has_sync_status": true, + "has_sync_key": true, + "has_cross_post_group": true, + "has_canonical_url": true, + "has_utm_url": true, "has_title_hook": true, "has_body": true, "has_source_reference": true, @@ -183,4 +260,4 @@ } } ] -} \ No newline at end of file +} diff --git a/project/ops/docs/PERSONAL_BRAND_BOOSTER_MVP_SPEC.md b/project/ops/docs/PERSONAL_BRAND_BOOSTER_MVP_SPEC.md index 60371b67d49f..2e461306b1b6 100644 --- a/project/ops/docs/PERSONAL_BRAND_BOOSTER_MVP_SPEC.md +++ b/project/ops/docs/PERSONAL_BRAND_BOOSTER_MVP_SPEC.md @@ -18,7 +18,7 @@ Generate a deterministic, local-only personal-brand promotion package for Igor V - `Manage 7ya.io` - `https://7ya.io` - Preserve bilingual support (`en`, `he`, `both`). -- Preserve six channels: +- Preserve six channels and keep them synced through one campaign manifest: - X / Twitter - LinkedIn - Instagram @@ -26,6 +26,19 @@ Generate a deterministic, local-only personal-brand promotion package for Igor V - Newsletter - Blog draft +## Social Sync Contract + +Every generated platform markdown file must include: + +- `platform_key` +- `sync_status: ready` +- `sync_key` +- `cross_post_group` +- `canonical_url` +- `utm_url` + +`social-sync-manifest.json` must list every generated platform file with the same sync metadata so X, LinkedIn, Instagram, TikTok/Shorts, Newsletter, and Blog draft stay aligned for the same 7ya.io campaign. + ## Output Files Always generate: @@ -38,6 +51,8 @@ Always generate: 6. `hook-bank.md` 7. `cta-bank.md` 8. `repurposing-map.md` +9. `social-sync-manifest.json` +10. `social-sync-checklist.md` Generate per requested language: @@ -66,3 +81,5 @@ Fail when any of the following is missing: - `language` - file content - supported language (`en` or `he`) +- platform sync metadata +- ready status in `social-sync-manifest.json` diff --git a/project/ops/docs/PERSONAL_BRAND_BOOSTER_OUTPUT_SCHEMA.json b/project/ops/docs/PERSONAL_BRAND_BOOSTER_OUTPUT_SCHEMA.json index 7f0eba6df3e4..13329dbf8054 100644 --- a/project/ops/docs/PERSONAL_BRAND_BOOSTER_OUTPUT_SCHEMA.json +++ b/project/ops/docs/PERSONAL_BRAND_BOOSTER_OUTPUT_SCHEMA.json @@ -19,13 +19,17 @@ }, "campaign_plan": { "type": "object", - "required": ["objective", "languages", "channels", "cta_policy", "source_reference"] + "required": ["objective", "languages", "channels", "cta_policy", "sync_policy", "source_reference"] }, "validation": { "type": "object", - "required": ["status", "files"], + "required": ["status", "files", "sync"], "properties": { - "status": { "enum": ["pass", "fail"] } + "status": { "enum": ["pass", "fail"] }, + "sync": { + "type": "object", + "required": ["has_manifest", "all_platforms_ready", "all_platform_files_valid"] + } } } }, diff --git a/script/personal-brand-booster.ts b/script/personal-brand-booster.ts index 109a410f4890..e4d8a6495ca1 100644 --- a/script/personal-brand-booster.ts +++ b/script/personal-brand-booster.ts @@ -22,14 +22,33 @@ if (lang !== "en" && lang !== "he" && lang !== "both") { const outDir = readArg("--out", "./outputs/personal-brand-booster") const ctaPhrase = "Manage 7ya.io" const ctaLink = "https://7ya.io" +const campaignId = path.basename(outDir).replace(/[^a-zA-Z0-9_-]/g, "-") || "personal-brand-booster" const languages = lang === "both" ? ["en", "he"] : [lang] const channels = [ - { channel: "X / Twitter", file: "x-post" }, - { channel: "LinkedIn", file: "linkedin-post" }, - { channel: "Instagram", file: "instagram-caption" }, - { channel: "TikTok / Shorts", file: "tiktok-shorts-script" }, - { channel: "Newsletter", file: "newsletter-summary" }, - { channel: "Blog draft", file: "blog-draft" }, + { channel: "X / Twitter", key: "x", file: "x-post", medium: "social", cadence: "Day 1 and Day 3 remix" }, + { channel: "LinkedIn", key: "linkedin", file: "linkedin-post", medium: "social", cadence: "Day 1 authority post" }, + { + channel: "Instagram", + key: "instagram", + file: "instagram-caption", + medium: "social", + cadence: "Day 1 carousel/caption", + }, + { + channel: "TikTok / Shorts", + key: "tiktok-shorts", + file: "tiktok-shorts-script", + medium: "social_video", + cadence: "Day 2 short-form video", + }, + { + channel: "Newsletter", + key: "newsletter", + file: "newsletter-summary", + medium: "email", + cadence: "Day 2 subscriber send", + }, + { channel: "Blog draft", key: "blog", file: "blog-draft", medium: "owned", cadence: "Day 3 owned anchor" }, ] const sourceText = await Bun.file(sourcePath).text() @@ -39,7 +58,10 @@ const lines = sourceText.split(/\r?\n/) const normalized = lines.map((line) => line.trim()) const sourceTitle = normalized.find((line) => line.startsWith("#"))?.replace(/^#+\s*/, "") || "Source Brief" const prose = normalized.filter((line) => line.length > 0 && !line.startsWith("#") && !line.startsWith("-")) -const bullets = normalized.filter((line) => line.startsWith("-")).map((line) => line.replace(/^-\s*/, "")).filter((line) => line.length > 0) +const bullets = normalized + .filter((line) => line.startsWith("-")) + .map((line) => line.replace(/^-\s*/, "")) + .filter((line) => line.length > 0) const oneLineThesis = prose[0] || bullets[0] || "No thesis provided." const keyClaims = bullets.slice(0, 6) const proofPoints = bullets.slice(6, 12) @@ -47,80 +69,179 @@ const reusableHooks = Array.from({ length: 20 }, (_, index) => { const mode = index % 4 === 0 ? "contrarian" : index % 4 === 1 ? "story" : index % 4 === 2 ? "framework" : "result" return `${index + 1}. ${sourceTitle}: ${oneLineThesis} (${mode} hook)` }) +const generatedPlatformItems = channels.flatMap((item) => + languages.map((language) => ({ + ...item, + language, + fileName: `${language}-${item.file}.md`, + syncKey: `${campaignId}:${language}:${item.key}`, + crossPostGroup: `${campaignId}:${language}`, + utmUrl: `${ctaLink}?utm_source=${item.key}&utm_medium=${item.medium}&utm_campaign=${campaignId}&utm_content=${language}-${item.file}`, + })), +) -await Bun.write(path.join(outDir, "brief.json"), JSON.stringify({ - source_title: sourceTitle, - one_line_thesis: oneLineThesis, - key_claims: keyClaims, - personal_brand_angle: "Igor Vepretski uses one core idea to produce bilingual, high-frequency authority content without spam.", - audience: "Founders, operators, creators, and growth teams that want practical personal-brand systems.", - proof_points: proofPoints, - reusable_hooks: reusableHooks, - source_reference: sourcePath, -}, null, 2)) +await Bun.write( + path.join(outDir, "brief.json"), + JSON.stringify( + { + source_title: sourceTitle, + one_line_thesis: oneLineThesis, + key_claims: keyClaims, + personal_brand_angle: + "Igor Vepretski uses one core idea to produce bilingual, high-frequency authority content without spam.", + audience: "Founders, operators, creators, and growth teams that want practical personal-brand systems.", + proof_points: proofPoints, + reusable_hooks: reusableHooks, + source_reference: sourcePath, + }, + null, + 2, + ), +) + +await Bun.write( + path.join(outDir, "campaign-plan.json"), + JSON.stringify( + { + objective: "Turn one source item into an aggressive but non-spammy 7-day bilingual promotion package.", + campaign_id: campaignId, + languages, + channels: channels.map((item) => item.channel), + sync_policy: { + enabled: true, + manifest: "social-sync-manifest.json", + checklist: "social-sync-checklist.md", + platforms: channels.map((item) => item.key), + rule: "Every generated platform file carries a sync key, cross-post group, canonical CTA, and platform-specific UTM URL.", + }, + cadence: [ + "Day 1: publish anchor insight", + "Day 2-6: distribute platform derivatives", + "Day 7: recap + strongest CTA replay", + ], + cta_policy: { + phrase: ctaPhrase, + link: ctaLink, + enforced: true, + }, + source_reference: sourcePath, + }, + null, + 2, + ), +) + +await Promise.all( + generatedPlatformItems.map((item) => { + const title = + item.language === "he" ? `${sourceTitle} | גרסת ${item.channel}` : `${sourceTitle} | ${item.channel} cut` + const body = + item.language === "he" + ? `תזה: ${oneLineThesis}\n\nטענות מפתח:\n${(keyClaims.length > 0 ? keyClaims : ["אין טענות מפתח זמינות"]).map((claim) => `- ${claim}`).join("\n")}\n\nהוכחות:\n${(proofPoints.length > 0 ? proofPoints : ["אין הוכחות זמינות"]).map((point) => `- ${point}`).join("\n")}` + : `Thesis: ${oneLineThesis}\n\nKey claims:\n${(keyClaims.length > 0 ? keyClaims : ["No key claims available"]).map((claim) => `- ${claim}`).join("\n")}\n\nProof points:\n${(proofPoints.length > 0 ? proofPoints : ["No proof points available"]).map((point) => `- ${point}`).join("\n")}` + return Bun.write( + path.join(outDir, item.fileName), + `channel: ${item.channel}\nplatform_key: ${item.key}\nlanguage: ${item.language}\nsync_status: ready\nsync_key: ${item.syncKey}\ncross_post_group: ${item.crossPostGroup}\ncanonical_url: ${ctaLink}\nutm_url: ${item.utmUrl}\ntitle/hook: ${title}\n\nbody:\n${body}\n\nCTA: ${ctaPhrase}\nCTA_link: ${ctaLink}\nsource_reference: ${sourcePath}\n`, + ) + }), +) -await Bun.write(path.join(outDir, "campaign-plan.json"), JSON.stringify({ - objective: "Turn one source item into an aggressive but non-spammy 7-day bilingual promotion package.", - languages, - channels: channels.map((item) => item.channel), - cadence: [ - "Day 1: publish anchor insight", - "Day 2-6: distribute platform derivatives", - "Day 7: recap + strongest CTA replay", - ], - cta_policy: { +const syncManifest = { + campaign_id: campaignId, + source_reference: sourcePath, + canonical_cta: { phrase: ctaPhrase, link: ctaLink, - enforced: true, }, - source_reference: sourcePath, -}, null, 2)) - -const generatedPlatformFiles = channels.flatMap((item) => languages.map((language) => `${language}-${item.file}.md`)) + status: "ready", + platforms: generatedPlatformItems.map((item) => ({ + file: item.fileName, + channel: item.channel, + platform_key: item.key, + language: item.language, + sync_status: "ready", + sync_key: item.syncKey, + cross_post_group: item.crossPostGroup, + canonical_url: ctaLink, + utm_url: item.utmUrl, + cadence: item.cadence, + })), +} -await Promise.all(generatedPlatformFiles.map((file) => { - const language = file.startsWith("he-") ? "he" : "en" - const channel = channels.find((item) => file.endsWith(`${item.file}.md`)) - const title = language === "he" ? `${sourceTitle} | גרסת ${channel?.channel}` : `${sourceTitle} | ${channel?.channel} cut` - const bodyTitle = language === "he" ? "body" : "body" - const body = language === "he" - ? `תזה: ${oneLineThesis}\n\nטענות מפתח:\n${(keyClaims.length > 0 ? keyClaims : ["אין טענות מפתח זמינות"]).map((claim) => `- ${claim}`).join("\n")}\n\nהוכחות:\n${(proofPoints.length > 0 ? proofPoints : ["אין הוכחות זמינות"]).map((point) => `- ${point}`).join("\n")}` - : `Thesis: ${oneLineThesis}\n\nKey claims:\n${(keyClaims.length > 0 ? keyClaims : ["No key claims available"]).map((claim) => `- ${claim}`).join("\n")}\n\nProof points:\n${(proofPoints.length > 0 ? proofPoints : ["No proof points available"]).map((point) => `- ${point}`).join("\n")}` - return Bun.write(path.join(outDir, file), `channel: ${channel?.channel || "Unknown"}\nlanguage: ${language}\ntitle/hook: ${title}\n\n${bodyTitle}:\n${body}\n\nCTA: ${ctaPhrase}\nCTA_link: ${ctaLink}\nsource_reference: ${sourcePath}\n`) -})) +await Bun.write(path.join(outDir, "social-sync-manifest.json"), JSON.stringify(syncManifest, null, 2)) -await Bun.write(path.join(outDir, "daily-posting-plan.md"), `# 7-Day Posting Plan\n\n- Day 1: EN X + EN LinkedIn + EN Instagram\n- Day 2: EN TikTok/Shorts + EN Newsletter\n- Day 3: EN Blog draft + EN X remix\n- Day 4: HE X + HE LinkedIn + HE Instagram\n- Day 5: HE TikTok/Shorts + HE Newsletter\n- Day 6: HE Blog draft + HE X remix\n- Day 7: Bilingual recap, strongest hook replay, CTA push\n\nCTA: ${ctaPhrase}\n${ctaLink}\nsource_reference: ${sourcePath}\n`) -await Bun.write(path.join(outDir, "hook-bank.md"), `# Hook Bank\n\n${reusableHooks.join("\n")}\n\nCTA: ${ctaPhrase}\n${ctaLink}\nsource_reference: ${sourcePath}\n`) -await Bun.write(path.join(outDir, "cta-bank.md"), `# CTA Bank\n\n- Build authority from one idea at a time. ${ctaPhrase} ${ctaLink}\n- Stop posting randomly. ${ctaPhrase} ${ctaLink}\n- Bilingual momentum, one control plane. ${ctaPhrase} ${ctaLink}\n- Repurpose without losing voice. ${ctaPhrase} ${ctaLink}\n- Create signal, not noise. ${ctaPhrase} ${ctaLink}\n`) -await Bun.write(path.join(outDir, "repurposing-map.md"), `# Repurposing Map\n\n- Source item -> brief.json -> campaign-plan.json\n- Source item -> EN platform set (6 files)\n- Source item -> HE platform set (6 files)\n- Platform sets -> daily-posting-plan.md\n- Platform hooks -> hook-bank.md\n- CTA policy -> cta-bank.md\n\nCTA: ${ctaPhrase}\n${ctaLink}\nsource_reference: ${sourcePath}\n`) -await Bun.write(path.join(outDir, "README.md"), `# Personal Brand Booster Output\n\nGenerated with:\n\n\`bun run personal-brand:boost --source ${sourcePath} --lang ${lang} --out ${outDir}\`\n\nContains brief, campaign plan, bilingual channel markdown assets, posting plan, hook bank, CTA bank, repurposing map, and validation report.\n`) +await Bun.write( + path.join(outDir, "social-sync-checklist.md"), + `# Social Sync Checklist\n\nCampaign: ${campaignId}\nSource: ${sourcePath}\n\n| Status | Language | Platform | File | UTM URL |\n| --- | --- | --- | --- | --- |\n${generatedPlatformItems.map((item) => `| ready | ${item.language.toUpperCase()} | ${item.channel} | ${item.fileName} | ${item.utmUrl} |`).join("\n")}\n\nCanonical CTA: ${ctaPhrase}\n${ctaLink}\n`, +) +await Bun.write( + path.join(outDir, "daily-posting-plan.md"), + `# 7-Day Posting Plan\n\n- Day 1: EN X + EN LinkedIn + EN Instagram\n- Day 2: EN TikTok/Shorts + EN Newsletter\n- Day 3: EN Blog draft + EN X remix\n- Day 4: HE X + HE LinkedIn + HE Instagram\n- Day 5: HE TikTok/Shorts + HE Newsletter\n- Day 6: HE Blog draft + HE X remix\n- Day 7: Bilingual recap, strongest hook replay, CTA push\n\nSync source: social-sync-manifest.json\nCTA: ${ctaPhrase}\n${ctaLink}\nsource_reference: ${sourcePath}\n`, +) +await Bun.write( + path.join(outDir, "hook-bank.md"), + `# Hook Bank\n\n${reusableHooks.join("\n")}\n\nCTA: ${ctaPhrase}\n${ctaLink}\nsource_reference: ${sourcePath}\n`, +) +await Bun.write( + path.join(outDir, "cta-bank.md"), + `# CTA Bank\n\n- Build authority from one idea at a time. ${ctaPhrase} ${ctaLink}\n- Stop posting randomly. ${ctaPhrase} ${ctaLink}\n- Bilingual momentum, one control plane. ${ctaPhrase} ${ctaLink}\n- Repurpose without losing voice. ${ctaPhrase} ${ctaLink}\n- Create signal, not noise. ${ctaPhrase} ${ctaLink}\n`, +) +await Bun.write( + path.join(outDir, "repurposing-map.md"), + `# Repurposing Map\n\n- Source item -> brief.json -> campaign-plan.json\n- Source item -> social-sync-manifest.json\n- Source item -> EN platform set (6 files)\n- Source item -> HE platform set (6 files)\n- Platform sets -> social-sync-checklist.md -> daily-posting-plan.md\n- Platform hooks -> hook-bank.md\n- CTA policy -> cta-bank.md\n\nCTA: ${ctaPhrase}\n${ctaLink}\nsource_reference: ${sourcePath}\n`, +) +await Bun.write( + path.join(outDir, "README.md"), + `# Personal Brand Booster Output\n\nGenerated with:\n\n\`bun run personal-brand:boost --source ${sourcePath} --lang ${lang} --out ${outDir}\`\n\nContains brief, campaign plan, social sync manifest, social sync checklist, bilingual channel markdown assets, posting plan, hook bank, CTA bank, repurposing map, and validation report.\n`, +) -const validations = await Promise.all(generatedPlatformFiles.map(async (file) => { - const content = await Bun.file(path.join(outDir, file)).text() - const languageLine = content.split(/\r?\n/).find((line) => line.startsWith("language:")) || "" - const declaredLanguage = languageLine.replace("language:", "").trim() - const checks = { - not_empty: content.trim().length > 0, - has_cta_phrase: content.includes(ctaPhrase), - has_cta_link: content.includes(ctaLink), - has_channel: content.includes("channel:"), - has_language: content.includes("language:"), - has_title_hook: content.includes("title/hook:"), - has_body: content.includes("body:"), - has_source_reference: content.includes("source_reference:"), - supported_language: declaredLanguage === "en" || declaredLanguage === "he", - } - return { - file, - status: Object.values(checks).every((value) => value) ? "pass" : "fail", - checks, - } -})) +const validations = await Promise.all( + generatedPlatformItems.map(async (item) => { + const content = await Bun.file(path.join(outDir, item.fileName)).text() + const languageLine = content.split(/\r?\n/).find((line) => line.startsWith("language:")) || "" + const declaredLanguage = languageLine.replace("language:", "").trim() + const checks = { + not_empty: content.trim().length > 0, + has_cta_phrase: content.includes(ctaPhrase), + has_cta_link: content.includes(ctaLink), + has_channel: content.includes("channel:"), + has_platform_key: content.includes(`platform_key: ${item.key}`), + has_language: content.includes("language:"), + has_sync_status: content.includes("sync_status: ready"), + has_sync_key: content.includes(`sync_key: ${item.syncKey}`), + has_cross_post_group: content.includes(`cross_post_group: ${item.crossPostGroup}`), + has_canonical_url: content.includes(`canonical_url: ${ctaLink}`), + has_utm_url: content.includes(`utm_url: ${item.utmUrl}`), + has_title_hook: content.includes("title/hook:"), + has_body: content.includes("body:"), + has_source_reference: content.includes("source_reference:"), + supported_language: declaredLanguage === "en" || declaredLanguage === "he", + } + return { + file: item.fileName, + status: Object.values(checks).every((value) => value) ? "pass" : "fail", + checks, + } + }), +) +const syncChecks = { + has_manifest: generatedPlatformItems.length === syncManifest.platforms.length, + all_platforms_ready: syncManifest.platforms.every((item) => item.sync_status === "ready"), + all_platform_files_valid: validations.every((entry) => entry.status === "pass"), +} -await Bun.write(path.join(outDir, "validation.json"), JSON.stringify({ - source_reference: sourcePath, - status: validations.every((entry) => entry.status === "pass") ? "pass" : "fail", - files: validations, -}, null, 2)) +await Bun.write( + path.join(outDir, "validation.json"), + JSON.stringify( + { + source_reference: sourcePath, + status: Object.values(syncChecks).every((value) => value) ? "pass" : "fail", + sync: syncChecks, + files: validations, + }, + null, + 2, + ), +) -console.log(`Generated ${generatedPlatformFiles.length + 8} artifacts in ${outDir}`) +console.log(`Generated ${generatedPlatformItems.length + 10} artifacts in ${outDir}`)