Gợi ý ý tưởng, chiến lược và chiến thuật marketing, tăng trưởng cho sản phẩm SaaS hoặc phần mềm.
---
name: marketing-ideas
description: "When the user needs marketing ideas, inspiration, or strategies for their SaaS or software product. Also use when the user asks for 'marketing ideas,' 'growth ideas,' 'how to market,' 'marketing strategies,' 'marketing tactics,' 'ways to promote,' 'ideas to grow,' 'what else can I try,' 'I don't know how to market this,' 'brainstorm marketing,' or 'what marketing should I do.' Use this as a starting point whenever someone is stuck or looking for inspiration on how to grow. For specific channel execution, see the relevant skill (ads, social, emails, etc.)."
metadata:
version: 2.0.1
---
# Marketing Ideas for SaaS
You are a marketing strategist with a library of 139 proven marketing ideas. Your goal is to help users find the right marketing strategies for their specific situation, stage, and resources.
## How to Use This Skill
**Check for product marketing context first:**
If `.agents/product-marketing.md` exists (or `.claude/product-marketing.md`, or the legacy `product-marketing-context.md` filename, in older setups), read it before asking questions. Use that context and only ask for information not already covered or specific to this task.
When asked for marketing ideas:
1. Ask about their product, audience, and current stage if not clear
2. Suggest 3-5 most relevant ideas based on their context
3. Provide details on implementation for chosen ideas
4. Consider their resources (time, budget, team size)
---
## Ideas by Category (Quick Reference)
| Category | Ideas | Examples |
|----------|-------|----------|
| Content & SEO | 1-10 | Programmatic SEO, Glossary marketing, Content repurposing |
| Competitor | 11-13 | Comparison pages, Marketing jiu-jitsu |
| Free Tools | 14-22 | Calculators, Generators, Chrome extensions |
| Paid Ads | 23-34 | LinkedIn, Google, Retargeting, Podcast ads |
| Social & Community | 35-44 | LinkedIn audience, Reddit marketing, Short-form video |
| Email | 45-53 | Founder emails, Onboarding sequences, Win-back |
| Partnerships | 54-64 | Affiliate programs, Integration marketing, Newsletter swaps |
| Events | 65-72 | Webinars, Conference speaking, Virtual summits |
| PR & Media | 73-76 | Press coverage, Documentaries |
| Launches | 77-86 | Product Hunt, Lifetime deals, Giveaways |
| Product-Led | 87-96 | Viral loops, Powered-by marketing, Free migrations |
| Content Formats | 97-109 | Podcasts, Courses, Annual reports, Year wraps |
| Unconventional | 110-122 | Awards, Challenges, [Guerrilla marketing](references/guerrilla-marketing.md) |
| Platforms | 123-130 | App marketplaces, Review sites, YouTube |
| International | 131-132 | Expansion, Price localization |
| Developer | 133-136 | DevRel, Certifications |
| Audience-Specific | 137-139 | Referrals, Podcast tours, Customer language |
**For the complete list with descriptions**: See [references/ideas-by-category.md](references/ideas-by-category.md)
**Deep dives** (full framework + case library for a single idea):
- **Guerrilla marketing (#121)**: [references/guerrilla-marketing.md](references/guerrilla-marketing.md) — the direct-mail 3-rule framework (relevance / relationship-building / precision targeting), ROI discipline, "think in stories, not campaigns," "test small before going big," and a named case library (WePay, Xero, Red Bull, Antimetal, Arrows, ProfitWell, Buzzsprout, Wistia).
---
## Implementation Tips
### By Stage
**Pre-launch:**
- Waitlist referrals (#79)
- Early access pricing (#81)
- Product Hunt prep (#78)
**Early stage:**
- Content & SEO (#1-10)
- Community (#35)
- Founder-led sales (#47)
**Growth stage:**
- Paid acquisition (#23-34)
- Partnerships (#54-64)
- Events (#65-72)
**Scale:**
- Brand campaigns
- International (#131-132)
- Media acquisitions (#73)
### By Budget
**Free:**
- Content & SEO
- Community building
- Social media
- Comment marketing
**Low budget:**
- Targeted ads
- Sponsorships
- Free tools
**Medium budget:**
- Events
- Partnerships
- PR
**High budget:**
- Acquisitions
- Conferences
- Brand campaigns
### By Timeline
**Quick wins:**
- Ads, email, social posts
**Medium-term:**
- Content, SEO, community
**Long-term:**
- Brand, thought leadership, platform effects
---
## Top Ideas by Use Case
### Need Leads Fast
- Google Ads (#31) - High-intent search
- LinkedIn Ads (#28) - B2B targeting
- Engineering as Marketing (#15) - Free tool lead gen
### Building Authority
- Conference Speaking (#70)
- Book Marketing (#104)
- Podcasts (#107)
### Low Budget Growth
- Easy Keyword Ranking (#1)
- Reddit Marketing (#38)
- Comment Marketing (#44)
### Product-Led Growth
- Viral Loops (#93)
- Powered By Marketing (#87)
- In-App Upsells (#91)
### Enterprise Sales
- Investor Marketing (#133)
- Expert Networks (#57)
- Conference Sponsorship (#72)
---
## Output Format
When recommending ideas, provide for each:
- **Idea name**: One-line description
- **Why it fits**: Connection to their situation
- **How to start**: First 2-3 implementation steps
- **Expected outcome**: What success looks like
- **Resources needed**: Time, budget, skills required
---
## Task-Specific Questions
1. What's your current stage and main growth goal?
2. What's your marketing budget and team size?
3. What have you already tried that worked or didn't?
4. What competitor tactics do you admire?
---
## Related Skills
- **marketing-plan**: When the user wants a comprehensive plan instead of standalone ideas. Section 12 of the plan cross-references all 139 ideas here against AARRR stages and client-specific status.
- **programmatic-seo**: For scaling SEO content (#4)
- **competitors**: For comparison pages (#11)
- **emails**: For email marketing tactics
- **free-tools**: For engineering as marketing (#15)
- **referrals**: For viral growth (#93)
FILE:evals/evals.json
{
"skill_name": "marketing-ideas",
"evals": [
{
"id": 1,
"prompt": "I need marketing ideas for my SaaS product. We're a bootstrapped team of 3, sell a $49/month analytics tool for e-commerce, and have about 200 customers. Budget is tight — maybe $500/month for marketing.",
"expected_output": "Should check for product-marketing.md first. Should filter ideas by low budget and early-stage constraints. Should pull relevant ideas from the 139 marketing ideas organized by category. Should provide ideas appropriate for bootstrapped SaaS: content marketing, community building, SEO, partnerships, referral programs, social media, Product Hunt, and others that don't require large budgets. Output should follow the format: idea name, why it fits, how to start, expected outcome, resources needed. Should prioritize by likely impact given their stage.",
"assertions": [
"Checks for product-marketing.md",
"Filters ideas by low budget constraint",
"Provides ideas from the 139 marketing ideas catalog",
"Ideas are appropriate for bootstrapped SaaS stage",
"Output follows structured format per idea",
"Includes why it fits, how to start, expected outcome",
"Prioritizes by likely impact",
"Includes resources needed per idea"
],
"files": []
},
{
"id": 2,
"prompt": "What's the fastest way to get more leads? We sell enterprise security software and have a $10k/month marketing budget.",
"expected_output": "Should apply the 'top ideas by use case' — specifically 'leads fast' recommendations. Should recommend paid channels (Google Ads, LinkedIn Ads for enterprise), outbound (cold email, LinkedIn outreach), and content-based lead magnets. Should filter for enterprise-appropriate tactics. Should provide the structured output with why each idea fits, how to start, expected timeline, and resources needed. Should note that 'fast leads' typically means paid or outbound channels.",
"assertions": [
"Applies 'leads fast' use case filter",
"Recommends paid channels appropriate for enterprise",
"Recommends outbound tactics",
"Filters for enterprise-appropriate tactics",
"Provides structured output per idea",
"Notes that fast leads means paid or outbound",
"Includes timeline expectations"
],
"files": []
},
{
"id": 3,
"prompt": "how do we grow without spending money on ads? we're a PLG (product-led growth) company with a freemium model",
"expected_output": "Should trigger on casual phrasing. Should apply the 'PLG' use case filter from top ideas. Should recommend PLG-specific tactics: product virality features, referral programs, community building, content marketing, SEO, free tools/calculators, open-source contributions, social proof loops. Should avoid ad-dependent ideas given the constraint. Should provide structured output with implementation guidance.",
"assertions": [
"Triggers on casual phrasing",
"Applies PLG use case filter",
"Recommends PLG-specific tactics",
"Avoids ad-dependent ideas",
"Includes virality and referral tactics",
"Provides structured output with implementation guidance"
],
"files": []
},
{
"id": 4,
"prompt": "We want to build authority and thought leadership in the HR tech space. We're a newer company and nobody knows who we are yet.",
"expected_output": "Should apply the 'authority building' use case filter. Should recommend thought leadership tactics: original research/surveys, guest posting, podcast appearances, speaking engagements, LinkedIn content, industry report publishing, expert roundups. Should note that authority building is a longer-term play. Should provide structured output with how to start each idea, expected outcomes, and timeline.",
"assertions": [
"Applies 'authority building' use case filter",
"Recommends thought leadership tactics",
"Includes original research and content",
"Includes community and media appearances",
"Notes authority building is longer-term",
"Provides structured output with timelines"
],
"files": []
},
{
"id": 5,
"prompt": "Give me 20 marketing ideas. We sell project management software.",
"expected_output": "Should provide a curated list of ~20 ideas from the catalog. Should organize them by category or by effort/impact. Should provide brief implementation context for each. Should vary the ideas across categories (content, community, partnerships, product, paid, etc.) for a well-rounded set. Output should follow the structured format with at least idea name and brief description for each.",
"assertions": [
"Provides approximately 20 ideas",
"Ideas span multiple categories",
"Organizes by category or effort/impact",
"Provides brief implementation context per idea",
"Output follows structured format",
"Ideas are relevant to project management software"
],
"files": []
},
{
"id": 6,
"prompt": "We want to set up a referral program. How should we structure it?",
"expected_output": "Should recognize this is specifically a referral program design request. Should defer to or cross-reference the referrals skill, which provides detailed guidance on referral loop design, incentive structures, implementation, and optimization. May briefly mention referral programs as a marketing idea but should make clear that referrals is the right skill for detailed program design.",
"assertions": [
"Recognizes this as a referral program design request",
"References or defers to referrals skill",
"Does not attempt detailed referral program design",
"May briefly mention as a marketing idea"
],
"files": []
},
{
"id": 7,
"prompt": "We sell a $30k/year data platform and can't get marketing VPs at target accounts to reply to email or take calls. We have budget for something creative. What guerrilla tactics could break through?",
"expected_output": "Should route to the guerrilla-marketing deep-dive reference. Should surface the direct-mail 3-rule framework (relevance, relationship-building, precision targeting) and the ROI discipline that a $50-200 package to an unqualified lead is malpractice — qualify the named accounts first, test small before scaling. Should note the deal size ($30k/year) justifies high-cost mailers. Should draw on the case library for inspiration (e.g. Corey's Unicorn Floatie Campaign for hard-to-reach marketing leaders, Antimetal's $15k pizza campaign converting ~75 customers and ~$1M revenue). Should apply the operating principles: think in stories not campaigns, and test small before going big.",
"assertions": [
"Routes to the guerrilla-marketing reference",
"Surfaces the direct-mail 3-rule framework (relevance, relationship-building, precision targeting)",
"Applies the ROI discipline (qualify before spending on high-cost packages)",
"Recommends testing small before scaling",
"Notes deal size justifies high-cost direct mail",
"Pulls named cases as inspiration (e.g. Unicorn Floatie, Antimetal pizza)",
"Applies 'think in stories, not campaigns' principle"
],
"files": []
}
]
}
FILE:references/guerrilla-marketing.md
# Guerrilla Marketing (#121)
Unconventional stunts that earn attention in unexpected places — then get turned into repeatable strategy. The point isn't the one-off spectacle. It's finding a memorable, low-cost way to break through, proving it works small, and running it again.
Two operating principles govern everything below:
- **Think in stories, not campaigns.** People don't share your funnel. They share a story worth retelling. Design the moment so the person who receives it wants to tell someone else.
- **Test small before going big.** A guerrilla idea is a hypothesis. Prove it on a handful of targets before you spend real money scaling it.
---
## Direct Mail: The 3-Rule Framework
Physical mail is the most under-used guerrilla channel in SaaS because most people do it lazily — blasting cheap swag at a bought list. Done right, it cuts through inbox noise precisely because nobody else shows up in the mailbox. Every direct-mail play must satisfy all three rules:
1. **Relevance** — The item connects to your product, your message, or something the recipient specifically cares about. A random branded mug is noise. A gift that lands a joke about their exact problem is a story.
2. **Relationship-building** — The mailer opens or deepens a relationship. It's a gift, not a pitch. If the recipient feels sold to, you've spent money to annoy someone.
3. **Precision targeting** — You know exactly who is receiving it and why. Direct mail is expensive per unit, so it only works when aimed at a short, hand-qualified list.
### The ROI discipline
A $50–200 package sent to an unqualified lead is malpractice. The math only works when the target is qualified enough that a single closed deal pays for dozens of packages.
- **Qualify before you spend.** Reserve high-cost mailers for named accounts and hard-to-reach decision-makers where the deal size justifies the cost.
- **Test small before scaling.** Send to a handful first. Measure reply and meeting rates. Only scale the version that actually earns responses.
### Corey's Unicorn Floatie Campaign
To reach marketing leaders who ignore cold email and screen their calls, Corey mailed **giant inflatable unicorn pool floaties** to a short list of hard-to-reach targets. The floatie is absurd, oversized, and impossible to ignore — it lands as a story, not a pitch (relevance + relationship), aimed at a hand-picked list where each closed deal dwarfs the package cost (precision + ROI discipline). Recipients remembered it, mentioned it, and replied.
---
## Case Library (inspiration fuel)
Steal the *pattern*, not the prop. Each of these turned a small, unconventional act into outsized attention or revenue.
- **WePay's 600-lb ice block** — At PayPal's developer conference, WePay dropped a 600-pound block of ice with money frozen inside and a sign reading **"PayPal Freezes Your Accounts."** A pointed, physical jab at a competitor's real weakness, staged exactly where the audience was.
- **Xero skywriting** — Xero paid for **skywriting over TechCrunch Disrupt**, hijacking a competitor-heavy event's attention from above without buying a booth.
- **Red Bull's Felix Baumgartner space jump** — A supersonic freefall from the edge of space drew **8 million concurrent live viewers** — a story so big it dwarfed any ad buy.
- **Antimetal's $15K pizza campaign** — Antimetal spent roughly **$15,000 sending ~1,000 pizzas** to target accounts, converting **~75 customers** and driving **~$1M in revenue**. Precision-targeted, story-worthy, and measured — the direct-mail framework at scale.
- **Arrows personalized memos** — Personalized, hand-crafted memos sent to specific prospects, treating each as an audience of one.
- **ProfitWell trading cards** — Custom trading cards that turned the team and community into collectible characters, making the brand fun to share.
- **Buzzsprout GIPHY library** — A branded library of GIFs on GIPHY, so the brand rode along inside other people's conversations for free.
- **Wistia "Gear Squad vs Dr. Boring"** — Wistia produced an original, over-the-top branded film — entertainment first, marketing second — because a story people actually want to watch travels further than an ad.
---
## How to Use This With a Client
1. **Pick the story.** What's the one memorable thing a target would retell? Start from the story, then reverse-engineer the tactic.
2. **Qualify the list.** For any high-cost play (direct mail especially), name the exact accounts and confirm the deal size justifies the spend.
3. **Run the 3-rule check** on physical mail: relevance, relationship-building, precision targeting. If it fails any one, redesign it.
4. **Test small.** Ship to a handful, measure replies/meetings/coverage, and only scale what earns a response.
5. **Turn the stunt into a system.** If it works, make it repeatable — a recurring mailer program, a series of films, an ongoing library — rather than a one-time spike.
*Source: Corey Haines, Founding Marketing, ch. 13.*
FILE:references/ideas-by-category.md
# The 139 Marketing Ideas
Complete list of proven marketing approaches organized by category.
## Contents
- Content & SEO (1-10)
- Competitor & Comparison (11-13)
- Free Tools & Engineering (14-22)
- Paid Advertising (23-34)
- Social Media & Community (35-44)
- Email Marketing (45-53)
- Partnerships & Programs (54-64)
- Events & Speaking (65-72)
- PR & Media (73-76)
- Launches & Promotions (77-86)
- Product-Led Growth (87-96)
- Content Formats (97-109)
- Unconventional & Creative (110-122)
- Platforms & Marketplaces (123-130)
- International & Localization (131-132)
- Developer & Technical (133-136)
- Audience-Specific (137-139)
## Content & SEO (1-10)
1. **Easy Keyword Ranking** - Target low-competition keywords where you can rank quickly. Find terms competitors overlook—niche variations, long-tail queries, emerging topics.
2. **SEO Audit** - Conduct comprehensive technical SEO audits of your own site and share findings publicly. Document fixes and improvements to build authority.
3. **Glossary Marketing** - Create comprehensive glossaries defining industry terms. Each term becomes an SEO-optimized page targeting "what is X" searches.
4. **Programmatic SEO** - Build template-driven pages at scale targeting keyword patterns. Location pages, comparison pages, integration pages—any pattern with search volume.
5. **Content Repurposing** - Transform one piece of content into multiple formats. Blog post becomes Twitter thread, YouTube video, podcast episode, infographic.
6. **Proprietary Data Content** - Leverage unique data from your product to create original research and reports. Data competitors can't replicate creates linkable assets.
7. **Internal Linking** - Strategic internal linking distributes authority and improves crawlability. Build topical clusters connecting related content.
8. **Content Refreshing** - Regularly update existing content with fresh data, examples, and insights. Refreshed content often outperforms new content.
9. **Knowledge Base SEO** - Optimize help documentation for search. Support articles targeting problem-solution queries capture users actively seeking solutions.
10. **Parasite SEO** - Publish content on high-authority platforms (Medium, LinkedIn, Substack) that rank faster than your own domain.
---
## Competitor & Comparison (11-13)
11. **Competitor Comparison Pages** - Create detailed comparison pages positioning your product against competitors. "[Your Product] vs [Competitor]" pages capture high-intent searchers.
12. **Marketing Jiu-Jitsu** - Turn competitor weaknesses into your strengths. When competitors raise prices, launch affordability campaigns.
13. **Competitive Ad Research** - Study competitor advertising through tools like SpyFu or Facebook Ad Library. Learn what messaging resonates.
---
## Free Tools & Engineering (14-22)
14. **Side Projects as Marketing** - Build small, useful tools related to your main product. Side projects attract users who may later convert.
15. **Engineering as Marketing** - Build free tools that solve real problems. Calculators, analyzers, generators—useful utilities that naturally lead to your paid product.
16. **Importers as Marketing** - Build import tools for competitor data. "Import from [Competitor]" reduces switching friction.
17. **Quiz Marketing** - Create interactive quizzes that engage users while qualifying leads. Personality quizzes, assessments, and diagnostic tools generate shares.
18. **Calculator Marketing** - Build calculators solving real problems—ROI calculators, pricing estimators, savings tools. Calculators attract links and rank well.
19. **Chrome Extensions** - Create browser extensions providing standalone value. Chrome Web Store becomes another distribution channel.
20. **Microsites** - Build focused microsites for specific campaigns, products, or audiences. Dedicated domains can rank faster.
21. **Scanners** - Build free scanning tools that audit or analyze something. Website scanners, security checkers, performance analyzers.
22. **Public APIs** - Open APIs enable developers to build on your platform, creating an ecosystem.
---
## Paid Advertising (23-34)
23. **Podcast Advertising** - Sponsor relevant podcasts to reach engaged audiences. Host-read ads perform especially well.
24. **Pre-targeting Ads** - Show awareness ads before launching direct response campaigns. Warm audiences convert better.
25. **Facebook Ads** - Meta's detailed targeting reaches specific audiences. Test creative variations and leverage retargeting.
26. **Instagram Ads** - Visual-first advertising for products with strong imagery. Stories and Reels ads capture attention.
27. **Twitter Ads** - Reach engaged professionals discussing industry topics. Promoted tweets and follower campaigns.
28. **LinkedIn Ads** - Target by job title, company size, and industry. Premium CPMs justified by B2B purchase intent.
29. **Reddit Ads** - Reach passionate communities with authentic messaging. Transparency wins on Reddit.
30. **Quora Ads** - Target users actively asking questions your product answers. Intent-rich environment.
31. **Google Ads** - Capture high-intent search queries. Brand terms, competitor terms, and category terms.
32. **YouTube Ads** - Video ads with detailed targeting. Pre-roll and discovery ads reach users consuming related content.
33. **Cross-Platform Retargeting** - Follow users across platforms with consistent messaging.
34. **Click-to-Messenger Ads** - Ads that open direct conversations rather than landing pages.
---
## Social Media & Community (35-44)
35. **Community Marketing** - Build and nurture communities around your product. Slack groups, Discord servers, Facebook groups.
36. **Quora Marketing** - Answer relevant questions with genuine expertise. Include product mentions where naturally appropriate.
37. **Reddit Keyword Research** - Mine Reddit for real language your audience uses. Discover pain points and desires.
38. **Reddit Marketing** - Participate authentically in relevant subreddits. Provide value first.
39. **LinkedIn Audience** - Build personal brands on LinkedIn for B2B reach. Thought leadership builds authority.
40. **Instagram Audience** - Visual storytelling for products with strong aesthetics. Behind-the-scenes and user stories.
41. **X Audience** - Build presence on X/Twitter through consistent value. Threads and insights grow followings.
42. **Short Form Video** - TikTok, Reels, and Shorts reach new audiences with snackable content.
43. **Engagement Pods** - Coordinate with peers to boost each other's content engagement.
44. **Comment Marketing** - Thoughtful comments on relevant content build visibility.
---
## Email Marketing (45-53)
45. **Mistake Email Marketing** - Send "oops" emails when something genuinely goes wrong. Authenticity generates engagement.
46. **Reactivation Emails** - Win back churned or inactive users with targeted campaigns.
47. **Founder Welcome Email** - Personal welcome emails from founders create connection.
48. **Dynamic Email Capture** - Smart email capture that adapts to user behavior. Exit intent, scroll depth triggers.
49. **Monthly Newsletters** - Consistent newsletters keep your brand top-of-mind.
50. **Inbox Placement** - Technical email optimization for deliverability. Authentication and list hygiene.
51. **Onboarding Emails** - Guide new users to activation with targeted sequences.
52. **Win-back Emails** - Re-engage churned users with compelling reasons to return.
53. **Trial Reactivation** - Expired trials aren't lost causes. Targeted campaigns can recover them.
---
## Partnerships & Programs (54-64)
54. **Affiliate Discovery Through Backlinks** - Find potential affiliates by analyzing who links to competitors.
55. **Influencer Whitelisting** - Run ads through influencer accounts for authentic reach.
56. **Reseller Programs** - Enable agencies to resell your product. White-label options create distribution partners.
57. **Expert Networks** - Build networks of certified experts who implement your product.
58. **Newsletter Swaps** - Exchange promotional mentions with complementary newsletters.
59. **Article Quotes** - Contribute expert quotes to journalists. HARO connects experts with writers.
60. **Pixel Sharing** - Partner with complementary companies to share remarketing audiences.
61. **Shared Slack Channels** - Create shared channels with partners and customers.
62. **Affiliate Program** - Structured commission programs for referrers.
63. **Integration Marketing** - Joint marketing with integration partners.
64. **Community Sponsorship** - Sponsor relevant communities, newsletters, or publications.
---
## Events & Speaking (65-72)
65. **Live Webinars** - Educational webinars demonstrate expertise while generating leads.
66. **Virtual Summits** - Multi-speaker online events attract audiences through varied perspectives.
67. **Roadshows** - Take your product on the road to meet customers directly.
68. **Local Meetups** - Host or attend local meetups in key markets.
69. **Meetup Sponsorship** - Sponsor relevant meetups to reach engaged local audiences.
70. **Conference Speaking** - Speak at industry conferences to reach engaged audiences.
71. **Conferences** - Host your own conference to become the center of your industry.
72. **Conference Sponsorship** - Sponsor relevant conferences for brand visibility.
---
## PR & Media (73-76)
73. **Media Acquisitions as Marketing** - Acquire newsletters, podcasts, or publications in your space.
74. **Press Coverage** - Pitch newsworthy stories to relevant publications.
75. **Fundraising PR** - Leverage funding announcements for press coverage.
76. **Documentaries** - Create documentary content exploring your industry or customers.
---
## Launches & Promotions (77-86)
77. **Black Friday Promotions** - Annual deals create urgency and acquisition spikes.
78. **Product Hunt Launch** - Structured Product Hunt launches reach early adopters.
79. **Early-Access Referrals** - Reward referrals with earlier access during launches.
80. **New Year Promotions** - New Year brings fresh budgets and goal-setting energy.
81. **Early Access Pricing** - Launch with discounted early access tiers.
82. **Product Hunt Alternatives** - Launch on BetaList, Launching Next, AlternativeTo.
83. **Twitter Giveaways** - Engagement-boosting giveaways that require follows or retweets.
84. **Giveaways** - Strategic giveaways attract attention and capture leads.
85. **Vacation Giveaways** - Grand prize giveaways generate massive engagement.
86. **Lifetime Deals** - One-time payment deals generate cash and users.
---
## Product-Led Growth (87-96)
87. **Powered By Marketing** - "Powered by [Your Product]" badges create free impressions.
88. **Free Migrations** - Offer free migration services from competitors.
89. **Contract Buyouts** - Pay to exit competitor contracts.
90. **One-Click Registration** - Minimize signup friction with OAuth options.
91. **In-App Upsells** - Strategic upgrade prompts within the product experience.
92. **Newsletter Referrals** - Built-in referral programs for newsletters.
93. **Viral Loops** - Product mechanics that naturally encourage sharing.
94. **Offboarding Flows** - Optimize cancellation flows to retain or learn.
95. **Concierge Setup** - White-glove onboarding for high-value accounts.
96. **Onboarding Optimization** - Continuous improvement of new user experience.
---
## Content Formats (97-109)
97. **Playlists as Marketing** - Create Spotify playlists for your audience.
98. **Template Marketing** - Offer free templates users can immediately use.
99. **Graphic Novel Marketing** - Transform complex stories into visual narratives.
100. **Promo Videos** - High-quality promotional videos showcase your product.
101. **Industry Interviews** - Interview customers, experts, and thought leaders.
102. **Social Screenshots** - Design shareable screenshot templates for social proof.
103. **Online Courses** - Educational courses establish authority while generating leads.
104. **Book Marketing** - Author a book establishing expertise in your domain.
105. **Annual Reports** - Publish annual reports showcasing industry data and trends.
106. **End of Year Wraps** - Personalized year-end summaries users want to share.
107. **Podcasts** - Launch a podcast reaching audiences during commutes.
108. **Changelogs** - Public changelogs showcase product momentum.
109. **Public Demos** - Live product demonstrations showing real usage.
---
## Unconventional & Creative (110-122)
110. **Awards as Marketing** - Create industry awards positioning your brand as tastemaker.
111. **Challenges as Marketing** - Launch viral challenges that spread organically.
112. **Reality TV Marketing** - Create reality-show style content following real customers.
113. **Controversy as Marketing** - Strategic positioning against industry norms.
114. **Moneyball Marketing** - Data-driven marketing finding undervalued channels.
115. **Curation as Marketing** - Curate valuable resources for your audience.
116. **Grants as Marketing** - Offer grants to customers or community members.
117. **Product Competitions** - Sponsor competitions using your product.
118. **Cameo Marketing** - Use Cameo celebrities for personalized messages.
119. **OOH Advertising** - Out-of-home advertising—billboards, transit ads.
120. **Marketing Stunts** - Bold, attention-grabbing marketing moments.
121. **Guerrilla Marketing** - Unconventional, low-cost marketing in unexpected places. Deep dive: direct-mail 3-rule framework (relevance / relationship / precision), ROI discipline, and a named case library in [guerrilla-marketing.md](guerrilla-marketing.md).
122. **Humor Marketing** - Use humor to stand out and create memorability.
---
## Platforms & Marketplaces (123-130)
123. **Open Source as Marketing** - Open-source components or tools build developer goodwill.
124. **App Store Optimization** - Optimize app store listings for discoverability.
125. **App Marketplaces** - List in Salesforce AppExchange, Shopify App Store, etc.
126. **YouTube Reviews** - Get YouTubers to review your product.
127. **YouTube Channel** - Build a YouTube presence with tutorials and thought leadership.
128. **Source Platforms** - Submit to G2, Capterra, GetApp, and similar directories.
129. **Review Sites** - Actively manage presence on review platforms.
130. **Live Audio** - Host Twitter Spaces, Clubhouse, or LinkedIn Audio discussions.
---
## International & Localization (131-132)
131. **International Expansion** - Expand to new geographic markets with localization.
132. **Price Localization** - Adjust pricing for local purchasing power.
---
## Developer & Technical (133-136)
133. **Investor Marketing** - Market to investors for portfolio introductions.
134. **Certifications** - Create certification programs validating expertise.
135. **Support as Marketing** - Exceptional support creates stories customers share.
136. **Developer Relations** - Build relationships with developer communities.
---
## Audience-Specific (137-139)
137. **Two-Sided Referrals** - Reward both referrer and referred.
138. **Podcast Tours** - Guest on multiple podcasts reaching your target audience.
139. **Customer Language** - Use the exact words your customers use in marketing.
Khởi chạy N subagent song song trong các git worktree cô lập để cạnh tranh giải cùng một nhiệm vụ của phiên.
---
name: "spawn"
description: "Launch N parallel subagents in isolated git worktrees to compete on the session task."
command: /hub:spawn
---
# /hub:spawn — Launch Parallel Agents
Spawn N subagents that work on the same task in parallel, each in an isolated git worktree.
## Usage
```
/hub:spawn # Spawn agents for the latest session
/hub:spawn 20260317-143022 # Spawn agents for a specific session
/hub:spawn --template optimizer # Use optimizer template for dispatch prompts
/hub:spawn --template refactorer # Use refactorer template
```
## Templates
When `--template <name>` is provided, use the dispatch prompt from `references/agent-templates.md` instead of the default prompt below. Available templates:
| Template | Pattern | Use Case |
|----------|---------|----------|
| `optimizer` | Edit → eval → keep/discard → repeat x10 | Performance, latency, size reduction |
| `refactorer` | Restructure → test → iterate until green | Code quality, tech debt |
| `test-writer` | Write tests → measure coverage → repeat | Test coverage gaps |
| `bug-fixer` | Reproduce → diagnose → fix → verify | Bug fix with competing approaches |
When using a template, replace all `{variables}` with values from the session config. Assign each agent a **different strategy** appropriate to the template and task — diverse strategies maximize the value of parallel exploration.
## What It Does
1. Load session config from `.agenthub/sessions/{session-id}/config.yaml`
2. For each agent 1..N:
- Write task assignment to `.agenthub/board/dispatch/`
- Build agent prompt with task, constraints, and board write instructions
3. Launch ALL agents in a **single message** with multiple Agent tool calls:
```
Agent(
prompt: "You are agent-{i} in hub session {session-id}.
Your task: {task}
Read your full assignment at .agenthub/board/dispatch/{seq}-agent-{i}.md
Instructions:
1. Work in your worktree — make changes, run tests, iterate
2. Commit all changes with descriptive messages
3. Write your result summary to .agenthub/board/results/agent-{i}-result.md
Include: approach taken, files changed, metric if available, confidence level
4. Exit when done
Constraints:
- Do NOT read or modify other agents' work
- Do NOT access .agenthub/board/results/ for other agents
- Commit early and often with descriptive messages
- If you hit a dead end, commit what you have and explain in your result",
isolation: "worktree"
)
```
4. Update session state to `running` via:
```bash
python {skill_path}/scripts/session_manager.py --update {session-id} --state running
```
## Critical Rules
- **All agents in ONE message** — spawn all Agent tool calls simultaneously for true parallelism
- **isolation: "worktree"** is mandatory — each agent needs its own filesystem
- **Never modify session config** after spawn — agents rely on stable configuration
- **Each agent gets a unique board post** — dispatch posts are numbered sequentially
## After Spawn
Tell the user:
- {N} agents launched in parallel
- Each working in an isolated worktree
- Monitor with `/hub:status`
- Evaluate when done with `/hub:eval`
Thiết lập quy trình marketing lặp lại tự chạy do AI agent thực hiện theo chu kỳ hoặc sự kiện kích hoạt, thay vì làm một lần.
---
name: marketing-loops
description: "When the user wants to set up a recurring, self-running marketing workflow — a repeatable loop an AI agent runs on a cadence (weekly, daily, on a trigger) rather than a one-off task. Also use when the user mentions 'marketing loop,' 'recurring marketing workflow,' 'automate my marketing,' 'marketing on autopilot,' 'weekly marketing review,' 'ad fatigue check,' 'content refresh loop,' 'churn watch,' 'ranking drop alert,' 'always-on marketing,' 'marketing automation workflow,' or 'run this every week.' Use this to pick, adapt, and schedule an ongoing marketing loop that orchestrates the other marketing skills. For one-off marketing ideas, see marketing-ideas. For the experimentation loop specifically, see ab-testing."
metadata:
version: 1.2.0
---
# Marketing Loops
You help set up **marketing loops** — repeatable marketing workflows an AI agent runs on a cadence, each with a defined trigger, a bounded set of steps, a self-check, and an explicit stopping condition. A loop turns a marketing task you'd otherwise do manually (and forget) into an always-on system: the weekly SEO opportunity scan, the ad-fatigue refresh, the churn-signal watch.
This is the operational cousin of `marketing-ideas`. Ideas tell you *what to try once*. Loops tell you *what to keep doing on a schedule* — and wire the other marketing skills together to do it.
## How to Use This Skill
**Check for product marketing context first:** if `.agents/product-marketing.md` exists (or `.claude/product-marketing.md`, or the legacy `product-marketing-context.md`), read it before asking questions. Use that context and only ask for what's missing.
Then:
1. **Clarify the job.** What outcome should this loop protect or grow? (rankings, ad efficiency, activation, retention, revenue, referrals)
2. **Pick a loop** from the catalog in `references/loop-catalog.md` — or adapt the closest one.
3. **Tune the cadence** to how fast the underlying signal actually changes (see the cadence rule below).
4. **Confirm the human checkpoint.** Decide what the loop does autonomously vs. what it stages for human approval before publishing or spending — see `references/loop-guardrails.md`.
5. **Schedule it** (see "Scheduling a loop" below).
Building more than one loop, or a whole marketing operating system? See `references/loop-orchestration.md` for how loops compose and the order to adopt them (start with tracking + a weekly review; don't build 43 at once).
## Anatomy of a Marketing Loop
Every loop in the catalog has these nine parts. When you author or adapt one, fill all of them — a loop missing a stop condition, a self-check, or its state handling is a liability, not an asset.
| Part | What it defines |
|------|-----------------|
| **Check cadence** | How often the loop *looks* (weekly / daily / on-trigger). Match it to signal speed. |
| **Acts when** | The action condition — what must be true to actually *do* something, vs. just check and skip. Most runs of a good loop are "checked, nothing to do." |
| **Purpose** | The one outcome this loop exists to move. |
| **Skills used** | Which marketing skills the loop orchestrates each iteration. |
| **Loop body** | The ordered steps run each iteration. |
| **Self-check** | The verification done *before* acting — so the loop doesn't act on noise, seasonality, or a tracking bug. |
| **State / idempotency** | What the loop remembers between runs: last-run marker, dedupe key, cooldown window, "already handled" set. Without this, loops double-act, re-nag the same people, or re-alert the same thing. Non-negotiable for anything scheduled — see `references/loop-state.md` for where state lives and the idempotency patterns. |
| **Stop / bail-out** | When the loop skips, halts, escalates to a human, or disables itself — plus what it does on error. Every loop needs one, including heartbeat loops (their stop is "manual disable + error-halt," never "n/a"). |
| **Output** | Where results go: a file, a PR, a staged draft, a notification, a report. |
The **Check cadence / Acts when** split matters: a churn-signal loop might *check* daily but only *act* when an account crosses a risk threshold it hasn't been contacted about inside the cooldown window. Conflating the two produces loops that either miss the window or spam.
## The cadence rule
Match cadence to how fast the signal actually changes — not to how often you'd *like* an update.
| Signal | Realistic cadence | Why |
|--------|-------------------|-----|
| Rankings, backlinks, domain authority | Weekly | Move slowly; daily checks are noise |
| Ad creative fatigue, CPA drift | Every 2–3 days | Meta/Google feedback loops are days, not hours |
| Activation / onboarding funnel | Weekly | Needs enough signups to be significant |
| Churn signals | Daily or on-trigger | Early intervention window is short |
| Content / copy decay | Monthly | Traffic erosion is gradual |
| Competitor changes | Weekly | Pricing/positioning shifts are infrequent but matter |
| Social listening / mentions | Daily | Engagement windows close fast |
Over-frequent loops are the most common failure mode: they generate busywork, burn budget, and train you to ignore the output.
## When NOT to loop
Not everything should be automated on a cadence. Skip a loop — or add a mandatory human checkpoint — when:
- **Strategy or creative direction is the real work.** Loops maintain and optimize; they don't set positioning, invent campaigns, or make brand calls.
- **The action publishes or spends without review.** Auto-*drafting* an ad, email, or post is fine. Auto-*publishing* or auto-*shifting budget* needs a human checkpoint unless the user has explicitly authorized autonomous action and set guardrails (caps, allowlists).
- **The signal is too sparse to be significant.** A weekly conversion-rate loop on 40 visitors/week is measuring noise.
- **It's a vanity loop.** If nobody acts on the output, delete the loop. A loop that emails a dashboard nobody reads is worse than nothing.
For any loop that sends, spends, publishes, or touches personal data, apply `references/loop-guardrails.md` — the two-tier action model (autonomous-safe vs. gated), spend/send caps, CAN-SPAM/GDPR/FTC/ToS rules, the always-escalate list, and a required kill switch.
## Scheduling a loop
These loops are agent-agnostic — the *body* works in any agent. The *scheduling* depends on your environment:
- **Claude Code** — native options: `/loop` (self-paced, until a condition), `ScheduleWakeup` (dynamic pacing that reacts to state), and `CronCreate` (fixed cron schedule). If you have a loop-mechanics skill such as `loopify` installed, use it to choose between them and tune delays; otherwise the guidance below is enough.
- **Any agent + cron** — wrap the loop body as a scheduled prompt/script (`0 9 * * 1` for Mondays 9am, etc.).
- **Manual cadence** — for high-judgment loops, "run this skill every Monday" is a perfectly good loop. The value is the repeatable *body*, not the automation.
Default to time-of-day cron for review-style loops (weekly review, ranking watch) and dynamic pacing for monitor-until-threshold loops (churn watch, launch-day tracking).
## The Catalog
`references/loop-catalog.md` holds the full library — 43 marketing loops with thorough funnel coverage: SEO & Content, Paid, Earned/Social/Partnerships, Activation, Retention, Revenue, Referral & Advocacy, and Ongoing Ops. Each is a complete, adaptable spec. Start there, pick the closest match, and tune it to the user's product, stage, and tooling.
## Authoring a new loop
When nothing in the catalog fits, author a new loop from `references/loop-template.md` — a copy-paste template with fill-in prompts, a worked before/after example, and a ship checklist. Fill all nine anatomy parts; if you can't answer the self-check, state/idempotency, and stop/bail-out concretely, the loop isn't ready to run.
## Anti-patterns
- Looping without a stop condition → runaway spend or infinite churn.
- Same cadence for every loop → most run too often and get ignored.
- No self-check → the loop acts on noise, seasonality, or a tracking bug.
- No human checkpoint on spend/publish actions.
- Building 10 loops at once → start with one, prove it earns its keep, then add the next.
## Banned vocabulary
Avoid: "set it and forget it," "fully autonomous marketing," "AI does everything," "10x on autopilot," "growth hacking machine." Loops are disciplined systems with checkpoints, not magic. Describe them honestly.
## Related Skills
- **marketing-ideas** — one-off tactics and inspiration (what to try). Loops operationalize the ones worth repeating.
- **ab-testing** — the experimentation loop specifically (hypothesis → test → promote winner → repeat).
- **analytics** — most loops read from analytics to decide whether to act.
- Individual channel skills (`ads`, `seo-audit`, `emails`, `social`, `churn-prevention`, `pricing`, `referrals`) — the loop bodies orchestrate these.
FILE:evals/evals.json
{
"skill_name": "marketing-loops",
"evals": [
{
"id": 1,
"prompt": "I want to set up a recurring loop that watches our SEO and tells me what to do each week. We're a B2B SaaS with a content-heavy site.",
"expected_output": "Should check for product-marketing.md first. Should recognize this maps to SEO loops in the catalog (keyword-gap, ranking-drop watch, content-decay) and propose the closest match(es). Should present the loop using the nine-part anatomy: check cadence, acts-when (action condition), purpose, skills used, loop body, self-check, state/idempotency, stop/bail-out, and output. Should set a weekly cadence (matching how slowly rankings move) and explain the cadence rule. Should include a stop/bail-out and note that most runs may find no action. Should reference the seo-audit skill for execution.",
"assertions": [
"Checks for product-marketing.md",
"Selects appropriate SEO loop(s) from the catalog",
"Presents the loop with the nine-part anatomy",
"Includes state/idempotency (last-run marker or dedupe)",
"Includes an explicit stop/bail-out condition",
"Sets a weekly cadence and justifies it via the cadence rule",
"References seo-audit or related skills for execution"
],
"files": []
},
{
"id": 2,
"prompt": "I want to automate my marketing so it basically runs itself. Where do I start?",
"expected_output": "Should clarify the outcome to protect or grow before dumping loops (acquisition, activation, retention, revenue, referral). Should push back gently on 'runs itself' framing and avoid the banned vocabulary ('set it and forget it', 'fully autonomous'). Should recommend starting with ONE loop and proving it earns its keep rather than building many at once. Should describe loops as disciplined systems with checkpoints, not autopilot. Should likely suggest the weekly-marketing-review loop as a good first heartbeat loop, and explain human checkpoints for anything that spends or publishes.",
"assertions": [
"Asks about the target outcome / funnel stage before recommending",
"Avoids banned autopilot vocabulary",
"Recommends starting with one loop, not many",
"Frames loops as systems with checkpoints, not magic",
"Emphasizes human checkpoints for spend/publish actions",
"May suggest the weekly-marketing-review loop as a starting point"
],
"files": []
},
{
"id": 3,
"prompt": "Set up a newsjacking loop that finds trending stories and automatically posts our take to X and pitches journalists.",
"expected_output": "Should build the newsjacking loop but refuse to make it fully autonomous. Must include the veto list (skip tragedies, deaths, disasters, active crises, politically/socially charged stories unless the brand takes such stances, and legal/medical/financial-sensitive topics) and require verified sources. Must require human approval before any pitch or post rather than auto-posting. Should include state/idempotency (dedupe per story, one angle per story) and note most days will correctly skip. Should reference the public-relations skill.",
"assertions": [
"Builds the newsjacking loop with the nine-part anatomy",
"Includes an explicit veto list for sensitive story types",
"Requires human approval before pitching or posting",
"Does not enable fully autonomous posting",
"Includes dedupe/state so stories aren't re-pitched",
"References the public-relations skill"
],
"files": []
},
{
"id": 4,
"prompt": "How do I actually schedule these loops so they run on their own?",
"expected_output": "Should explain that the loop body is agent-agnostic but scheduling depends on the environment. Should cover the options: Claude Code native primitives (/loop, ScheduleWakeup, CronCreate), any-agent + cron (with an example cron expression), and manual cadence for high-judgment loops. Should default to time-of-day cron for review-style loops and dynamic pacing for monitor-until-threshold loops. Should treat a dedicated loop-mechanics skill like loopify as an optional companion, not a hard dependency, and not assume it is installed.",
"assertions": [
"Explains scheduling is environment-dependent",
"Covers Claude Code native options and plain cron",
"Includes manual cadence as a valid option",
"Gives cron vs dynamic-pacing default guidance",
"Treats loopify as optional, not a required dependency"
],
"files": []
},
{
"id": 5,
"prompt": "Can you make me a loop that continuously improves our brand positioning and comes up with our big campaign ideas?",
"expected_output": "Should push back: strategy, positioning, and creative direction are the 'when NOT to loop' cases. Loops maintain and optimize; they don't set positioning or invent campaigns. Should explain why (high-judgment, low-frequency, not signal-driven) and redirect: those belong in strategy work, while loops can operationalize the executional follow-through (e.g., competitor-watch to inform positioning, content-calendar refill, campaign-postmortem to compound learnings). Should not fabricate a positioning loop as if it were a good fit.",
"assertions": [
"Identifies this as a 'when NOT to loop' case",
"Explains loops optimize/maintain rather than set strategy or creative",
"Redirects strategy/creative to the appropriate non-loop work",
"Suggests adjacent operational loops that genuinely fit",
"Does not force an ill-fitting positioning/campaign-ideation loop"
],
"files": []
},
{
"id": 6,
"prompt": "We keep losing customers to failed payments and expired cards. Set up something to recover that revenue automatically.",
"expected_output": "Should select the failed-payment/dunning loop. Should trigger on payment failure or upcoming card expiration, run a retry schedule with escalating update-card messaging, and include state (track dunning stage per account, stop on recovery). Must include a stop/bail-out so it doesn't loop forever (escalate/deactivate after final retry) and a self-check to distinguish involuntary failures from intentional cancellations (don't dun someone who chose to leave). Should reference revops and emails skills. May note this is often the highest-ROI retention loop.",
"assertions": [
"Selects the failed-payment/dunning loop",
"Triggers on failed payment or card expiration",
"Includes a retry schedule with dunning stage state",
"Has a stop condition so it does not loop forever",
"Self-check separates involuntary failures from intentional cancels",
"References revops and/or emails skills"
],
"files": []
},
{
"id": 7,
"prompt": "I want a loop that keeps our A/B testing program running — generating hypotheses, running tests, and analyzing results every week.",
"expected_output": "Should recognize heavy overlap with the ab-testing skill and treat the experiment-backlog loop as a thin wrapper only. The loop should maintain and re-rank the hypothesis backlog and manage hand-off cadence, but defer test design, statistical analysis, and velocity management to the ab-testing skill rather than duplicating them. Should include ICE prioritization for the backlog and dedupe of incoming hypotheses, and avoid starting conflicting tests.",
"assertions": [
"Recognizes overlap with the ab-testing skill",
"Keeps the loop as a thin wrapper that defers to ab-testing",
"Does not duplicate test design or statistical analysis in the loop",
"Includes backlog prioritization (ICE) and hypothesis dedupe",
"References the ab-testing skill as the owner of execution"
],
"files": []
}
]
}
FILE:references/loop-catalog.md
# Marketing Loop Catalog
A library of repeatable marketing loops with thorough coverage across the funnel. Each is a complete, adaptable spec. Pick the closest match, then tune the cadence, thresholds, state handling, and human checkpoints to the user's product, stage, and tooling.
Every loop lists nine parts: **Check cadence · Acts when · Purpose · Skills used · Loop body · Self-check · State / idempotency · Stop / bail-out · Output**. See `SKILL.md` for the anatomy, the cadence rule, and when not to loop.
Two rules that apply to every entry:
- **Most runs should do nothing.** A healthy loop checks, finds nothing worth acting on, logs "no action," and exits. Loops that act every run are usually acting on noise.
- **State prevents harm.** Every loop tracks what it already did (last-run marker, dedupe key, cooldown) so it never double-acts, re-nags the same person, or re-alerts the same issue.
Loops are grouped by function. Naming follows the "The X loop" convention.
---
## SEO & Content
### The keyword-gap loop
- **Check cadence**: Weekly
- **Acts when**: A striking-distance keyword (positions 5–20) or a rising query has no adequate page.
- **Purpose**: Surface new ranking opportunities before competitors take them.
- **Skills used**: `seo-audit`, `programmatic-seo`, `content-strategy`
- **Loop body**:
1. Pull ranking + impression data (Search Console / rank tracker).
2. Diff vs. last run: new striking-distance keywords, rising queries with no matching page.
3. Classify each gap: quick on-page win / net-new page / programmatic template candidate.
4. Draft briefs for the top 3.
- **Self-check**: Movement real vs. seasonal? Compare to the same period last month, not just last week.
- **State / idempotency**: Store the set of gaps already briefed; don't re-brief an open one.
- **Stop / bail-out**: No gap clears a minimum impression threshold → log "no action." Halt on data-source outage rather than acting on partial data.
- **Output**: Up to 3 content briefs staged for review + a one-line movement summary.
### The ranking-drop watch loop
- **Check cadence**: Weekly
- **Acts when**: A priority keyword or page drops more than N positions vs. baseline.
- **Purpose**: Catch and diagnose SEO regressions before they compound.
- **Skills used**: `seo-audit`, `analytics`
- **Loop body**:
1. Track positions for priority keywords/pages.
2. Flag material drops; diff what changed (content, links, SERP layout, algo-update timing).
3. Diagnose likely cause + propose a fix.
- **Self-check**: Rule out a SERP-feature change or one-off volatility before declaring a real loss.
- **State / idempotency**: Remember which drops are already open as issues; update rather than re-file.
- **Stop / bail-out**: No material drop → log "stable." Escalate suspected algo hits to a human rather than mass-editing.
- **Output**: A regression report with a recommended fix.
### The content-decay loop
- **Check cadence**: Monthly
- **Acts when**: A page's traffic/rankings declined materially over the trailing 90 days.
- **Purpose**: Refresh decaying content before it slides out of rankings.
- **Skills used**: `copy-editing`, `seo-audit`, `content-strategy`
- **Loop body**:
1. Find pages with declining trailing-90-day traffic/rankings.
2. Pick the highest-value decayers.
3. Draft a refresh plan (update stats, expand thin sections, fix intent match, re-link).
- **Self-check**: Decay from the page itself, or from a SERP/seasonality shift? Refresh only what a refresh can fix.
- **State / idempotency**: Track last-refresh date per page; don't re-queue a page refreshed within the cooldown.
- **Stop / bail-out**: No meaningful decayers → skip.
- **Output**: A prioritized refresh list with per-page plans.
### The internal-linking loop
- **Check cadence**: On new/updated content, or weekly
- **Acts when**: A published page has fewer relevant internal links (in or out) than it should.
- **Purpose**: Distribute link equity and help new content get discovered and rank.
- **Skills used**: `seo-audit`, `site-architecture`, `content-strategy`
- **Loop body**:
1. Identify recently published/updated pages.
2. Find relevant existing pages that should link to them (and vice versa).
3. Draft the specific link insertions with anchor text.
- **Self-check**: Is each link contextually relevant, or link-stuffing? Skip forced links.
- **State / idempotency**: Track which page pairs are already linked; never suggest a duplicate.
- **Stop / bail-out**: No relevant link targets → skip. Stage edits for review; don't mass-edit live pages autonomously.
- **Output**: A list of specific internal-link edits.
### The programmatic-SEO quality loop
- **Check cadence**: Monthly
- **Acts when**: Template pages show indexation gaps, thin content, duplication, or cannibalization.
- **Purpose**: Keep large templated page sets healthy so they don't drag the whole domain.
- **Skills used**: `programmatic-seo`, `seo-audit`
- **Loop body**:
1. Sample the template page set; check indexation, word/data uniqueness, and query overlap.
2. Flag thin, duplicate, cannibalizing, or deindexed pages.
3. Recommend fix, consolidate, noindex, or prune.
- **Self-check**: Is low traffic a quality problem or just low demand? Don't prune pages that serve real long-tail intent.
- **State / idempotency**: Track pages already flagged/actioned; re-check only on the next cycle.
- **Stop / bail-out**: Set healthy → log and skip. Escalate mass-noindex/prune decisions to a human.
- **Output**: A quality report with per-bucket actions.
### The content-repurposing loop
- **Check cadence**: Weekly
- **Acts when**: A long-form asset (post/video/podcast) hasn't been repurposed yet.
- **Purpose**: Turn every long-form asset into a week of channel-native content.
- **Skills used**: `social`, `content-strategy`, `copywriting`
- **Loop body**:
1. Find the newest un-repurposed asset.
2. Extract the 3–5 strongest ideas.
3. Draft channel-native versions (LinkedIn post, X thread, short-form script).
4. Stage in the scheduling queue.
- **Self-check**: Does each piece stand alone, or read like a link-dump? Rewrite anything that only works with the original open.
- **State / idempotency**: Mark assets as repurposed; never re-process one.
- **Stop / bail-out**: Nothing new published → skip.
- **Output**: Drafts in the social queue for approval.
### The content-calendar refill loop
- **Check cadence**: Weekly
- **Acts when**: The editorial pipeline has fewer than N weeks of planned content queued.
- **Purpose**: Keep the content pipeline from running dry.
- **Skills used**: `content-strategy`, `marketing-ideas`, `seo-audit`
- **Loop body**:
1. Count planned/drafted pieces remaining in the calendar.
2. If below the buffer, generate new topic ideas from the keyword-gap output, customer questions, and pillar plan.
3. Prioritize and slot them.
- **Self-check**: Do new topics map to real search demand or audience questions, not just "content for content's sake"?
- **State / idempotency**: Dedupe proposed topics against the existing calendar and published archive.
- **Stop / bail-out**: Pipeline above buffer → skip.
- **Output**: New prioritized topics added to the calendar.
---
## Paid
### The ad-fatigue loop
- **Check cadence**: Every 2–3 days
- **Acts when**: An ad shows rising frequency + declining CTR/CVR past a real significance bar.
- **Purpose**: Refresh creative before CPA drifts up as ads fatigue.
- **Skills used**: `ads`, `ad-creative`, `analytics`
- **Loop body**:
1. Pull per-ad metrics: CTR, frequency, CPA, spend, trend vs. baseline.
2. Flag fatiguing ads and clear winners.
3. Generate 3–5 fresh variants off the winning angle.
4. Stage variants; recommend budget shift fatigued → winning.
- **Self-check**: Enough spend, impressions, and conversions to read CPA past the attribution window, and the ad is out of the learning phase. Rising frequency alone with thin conversion data is not fatigue evidence — wait.
- **State / idempotency**: Track per-ad last-refresh date; don't regenerate variants for an ad refreshed within the cooldown.
- **Stop / bail-out**: Never auto-shift budget or publish without a human checkpoint unless spend caps + an allowlist are explicitly authorized. Halt if daily spend exceeds its cap.
- **Output**: Staged creative drafts + a recommended budget move.
### The daily-creative-drop loop
- **Check cadence**: Daily (early morning, so the batch is ready when the media buyer sits down)
- **Acts when**: The grounded inputs corpus exists and the required inputs are populated — `inputs/winning-ads/` and `inputs/reviews/` (required; `inputs/comments/` and `brand/` strongly recommended, matching ad-creative's grounding rules). If a required input is empty, the loop asks for inputs instead of generating.
- **Purpose**: Keep creative volume ahead of fatigue — a standing batch of fresh static concepts to test, so scaling never stalls waiting on production.
- **Skills used**: `ad-creative` (Mode 3 + static ad template library), `customer-research`
- **Loop body**:
1. Read the inputs corpus: `inputs/winning-ads/`, `inputs/reviews/`, `inputs/comments/`, and `brand/`.
2. Generate the batch (e.g., 50 concepts) cycling all 15 static templates, 3-4 variations each, every concept grounded in a cited source.
3. Generate images if an image tool is configured; otherwise deliver concepts + image prompts.
4. Save to `outputs/YYYY-MM-DD/` with an `INDEX.md` (template type + grounding per concept).
- **Self-check**: Are concepts actually grounded (spot-check citations against sources)? Is template coverage spread across the library, not clustered on 2-3? Does copy match the brand voice doc rather than generic DR voice?
- **State / idempotency**: One batch per day — skip if today's output folder already exists. Track angle/headline hashes across recent batches to avoid regenerating near-duplicates of concepts already delivered.
- **Stop / bail-out**: Missing or empty required inputs → stop and request them; never generate ungrounded. Human picks the 5-10 to upload — this loop stages creative and **never publishes to the ad account**. If batches go unreviewed for a week, pause and ask whether to continue (unpicked batches are a vanity loop).
- **Output**: A dated folder of grounded static ad concepts + index, ready for human selection.
- **Input freshness (companion cadence)**: Weekly, refresh `inputs/winning-ads/` with anything that scaled and prune stale examples; monthly, refresh `inputs/reviews/` and `inputs/comments/` and re-check the voice doc. Stale inputs are this loop's failure mode — output quality tracks input freshness, not run count.
### The monthly-creative-retro loop
- **Check cadence**: Monthly (first business day, reading the prior month)
- **Acts when**: The account had meaningful creative activity last month — new concepts launched with enough delivery to judge (respect the impression/spend thresholds in `ads`). If nothing launched or nothing cleared thresholds, note that and skip.
- **Purpose**: Close the creative strategy loop — turn last month's results into next month's evidence-ranked slate, so the roadmap learns instead of drifting.
- **Skills used**: `ad-creative` (Mode 4 + creative-roadmap reference), `ads` (decision thresholds), `analytics`
- **Loop body**:
1. Pull last month's ad performance via the platform CLIs; map results to the month's roadmap concepts.
2. Draft the retro artifact (`retros/YYYY-MM.md`): winners with the why, losers with funnel-stage diagnosis, single-metric wins, learnings, kills.
3. Update the roadmap: re-rank icebox evidence, write learnings in as new/revised concepts, draft next month's capacity-checked slate.
4. Flag the account-state call (exploration vs. scaling) for human confirmation — the mix recommendation depends on it.
- **Self-check**: Are verdicts on concepts (not single executions)? Did every learning land somewhere — icebox update, re-rank, or kill? Did anything clear thresholds, or is this month a skip?
- **State / idempotency**: One retro per month — skip if `retros/YYYY-MM.md` exists. The roadmap file is the shared state; never fork it.
- **Stop / bail-out**: Stages analysis and a draft slate only — the human approves the slate and the account-state call; the loop **never launches or pauses ads**. If retros go unread for two cycles, pause and ask.
- **Output**: The monthly retro artifact + an updated roadmap with a draft slate for the coming month.
### The paid-search query-mining loop
- **Check cadence**: Weekly
- **Acts when**: Search-term reports reveal wasted spend or new intent.
- **Purpose**: Continuously refine keywords, negatives, and landing-page mapping.
- **Skills used**: `ads`, `analytics`
- **Loop body**:
1. Pull the search-terms report.
2. Identify irrelevant terms (→ negatives), high-performing terms (→ new exact-match), and terms whose landing page is a poor match.
3. Stage keyword/negative changes and landing-page notes.
- **Self-check**: Enough clicks/conversions per term to justify a change? Don't negate on a single click.
- **State / idempotency**: Track already-added negatives/keywords; never re-add.
- **Stop / bail-out**: No terms clear thresholds → skip. Stage changes for review before pushing to the account.
- **Output**: A staged list of negatives, new keywords, and LP mismatches.
### The retargeting-hygiene loop
- **Check cadence**: Weekly
- **Acts when**: Audiences are stale, too small, over-frequent, or missing exclusions.
- **Purpose**: Keep retargeting efficient and non-annoying.
- **Skills used**: `ads`, `analytics`
- **Loop body**:
1. Review retargeting audiences: size, recency, frequency, exclusions, creative sequencing.
2. Flag issues (converters not excluded, audiences too small to serve, frequency too high).
3. Recommend fixes.
- **Self-check**: Is the audience actually underperforming, or just small-but-valuable? Don't kill high-intent segments for size.
- **State / idempotency**: Track which audiences were already fixed this cycle.
- **Stop / bail-out**: All healthy → skip. Human-approve audience deletions.
- **Output**: A hygiene report with recommended audience changes.
### The landing-page regression loop
- **Check cadence**: Weekly (or on deploy)
- **Acts when**: A top acquisition page regresses on conversion, speed, tracking, or form function.
- **Purpose**: Catch silent breakage on the pages that receive paid/organic traffic.
- **Skills used**: `cro`, `analytics`
- **Loop body**:
1. Monitor top acquisition pages: conversion rate, load speed, form submits, tracking fires.
2. Flag regressions vs. baseline; correlate with recent deploys/changes.
3. Diagnose and propose a fix.
- **Self-check**: Rule out tracking breakage vs. a real conversion drop before raising an alarm — and vice versa.
- **State / idempotency**: Track open regressions; update rather than re-file.
- **Stop / bail-out**: No regression → log "stable." Escalate a live-revenue-page break immediately, don't wait for the next run.
- **Output**: A regression alert with cause + fix.
---
## Earned, Social & Partnerships
### The newsjacking loop
- **Check cadence**: Daily
- **Acts when**: A trending story matches the brand's space, clears newsworthiness + fit, **and** passes the veto list.
- **Purpose**: Ride relevant news with a timely angle before the window closes.
- **Skills used**: `public-relations`, `social`
- **Loop body**:
1. Scan news/HN/Reddit/X for stories intersecting the product's space.
2. Score newsworthiness + fit + reach.
3. Run the veto list. For a surviving top story, draft an angle (post, pitch, or commentary).
- **Self-check**: Is the angle genuinely additive, or forced? Kill forced takes — they cost credibility.
- **Veto list (skip immediately)**: tragedies, deaths, disasters, active crises; politically or socially charged stories unless the brand explicitly takes such stances; legal/medical/financial-sensitive topics; anything sourced from an unverified/single unreliable source.
- **State / idempotency**: Dedupe on story ID; one angle per story; never re-pitch a covered story.
- **Stop / bail-out**: Any veto trip → skip. Always require human approval before pitching/posting. Most days will skip — that's correct.
- **Output**: A staged post/pitch for human approval, or nothing.
### The social-listening loop
- **Check cadence**: Daily
- **Acts when**: A thread/mention clears the ICP-fit + intent + reach score.
- **Purpose**: Surface the highest-value conversations to engage in, instead of scrolling feeds.
- **Skills used**: `social` (see its `references/listening.md`), `community-marketing`
- **Loop body**:
1. Pull mentions and relevant threads across configured sources.
2. Score by ICP fit, intent, reach, and comment opportunity.
3. Draft comments/replies for the top handful.
- **Self-check**: Would a human recognize each reply as genuinely useful, not promotional?
- **State / idempotency**: Track already-engaged threads; never double-reply. Respect a per-account interaction cooldown.
- **Stop / bail-out**: Nothing clears the threshold → skip. Stage replies for human post (don't auto-post — bot-detection + brand risk).
- **Output**: A short list of threads with drafted, on-brand replies.
### The community-engagement loop
- **Check cadence**: Daily
- **Acts when**: A target community (subreddit/Slack/Discord/forum) has a relevant thread where a helpful, non-promotional reply fits.
- **Purpose**: Build durable presence and trust in the communities where the ICP lives.
- **Skills used**: `community-marketing`, `social`
- **Loop body**:
1. Scan configured communities for relevant threads/questions.
2. Score for genuine help opportunity (not just keyword match).
3. Draft value-first replies; note any that warrant a longer resource.
- **Self-check**: Does the reply lead with help and respect community norms? Self-promo ratio stays low.
- **State / idempotency**: Track engaged threads + per-community posting cadence to avoid over-posting.
- **Stop / bail-out**: No genuine-help opportunity → skip. Stage for human review where communities are strict about vendors.
- **Output**: Drafted community replies + resource ideas.
### The competitor-watch loop
- **Check cadence**: Weekly
- **Acts when**: A competitor makes a substantive pricing, positioning, product, or messaging change.
- **Purpose**: Catch competitor moves early enough to respond.
- **Skills used**: `competitor-profiling`, `competitors`, `product-marketing`
- **Loop body**:
1. Fetch competitor pricing pages, homepages, changelogs, recent posts.
2. Diff vs. last snapshot.
3. Summarize meaningful changes; flag anything needing a response (comparison-page update, counter-messaging).
- **Self-check**: Substantive vs. cosmetic? Don't raise a copy tweak as a strategic shift.
- **State / idempotency**: Store per-competitor snapshots; diff against the last, and don't re-flag a known change.
- **Stop / bail-out**: No meaningful diffs → log "no change."
- **Output**: A change digest + recommended responses.
### The backlink-prospecting loop
- **Check cadence**: Weekly
- **Acts when**: New relevant link/guest-post/mention targets appear (or the pipeline is thin).
- **Purpose**: Keep a steady flow of link-building and earned-mention opportunities.
- **Skills used**: `public-relations`, `seo-audit`
- **Loop body**:
1. Find new prospects: sites linking to competitors, relevant roundups, unlinked brand mentions, resource pages.
2. Qualify by relevance + authority.
3. Draft outreach angles for the top targets.
- **Self-check**: Is the target genuinely relevant, or a low-quality link that could hurt? Skip spammy sites.
- **State / idempotency**: Track already-contacted targets + outcomes; respect a follow-up cadence, don't re-pitch cold.
- **Stop / bail-out**: No qualified new targets → skip. Human-approve outreach sends.
- **Output**: A qualified prospect list with drafted outreach.
### The directory-submission loop
- **Check cadence**: Monthly
- **Acts when**: A relevant new directory/launch platform/marketplace exists that the product isn't listed on.
- **Purpose**: Steadily expand distribution and referral/SEO footprint via directories.
- **Skills used**: `directory-submissions`
- **Loop body**:
1. Check for new/relevant directories, launch sites, and marketplaces.
2. Qualify by relevance, authority, and audience fit.
3. Prepare listing copy/assets for the top ones.
- **Self-check**: Real audience/SEO value, or a link farm? Skip low-quality directories.
- **State / idempotency**: Maintain a submitted-directories list; never resubmit.
- **Stop / bail-out**: No worthwhile new directories → skip.
- **Output**: Prepared listings staged for submission.
### The partner-pipeline loop
- **Check cadence**: Monthly
- **Acts when**: A viable co-marketing, integration, affiliate, or newsletter-swap opportunity surfaces (or the pipeline is thin).
- **Purpose**: Keep a fresh pipeline of partnership and co-marketing opportunities.
- **Skills used**: `co-marketing`, `referrals`
- **Loop body**:
1. Scan for potential partners (complementary tools, aligned audiences, active newsletters, integration targets).
2. Qualify by audience overlap + reach + fit.
3. Draft partnership/swap outreach for the top prospects.
- **Self-check**: Real audience overlap and mutual value, or a one-sided ask? Skip mismatches.
- **State / idempotency**: Track contacted partners + status; respect follow-up cadence.
- **Stop / bail-out**: No qualified opportunities → skip. Human-approve outreach.
- **Output**: A qualified partner list with drafted outreach.
---
## Activation
### The onboarding drop-off loop
- **Check cadence**: Weekly
- **Acts when**: An onboarding step's drop exceeds benchmark or regresses vs. last period.
- **Purpose**: Find and fix the biggest leak between signup and first value.
- **Skills used**: `onboarding`, `analytics`, `cro`
- **Loop body**:
1. Pull the activation funnel step-by-step (signup → key action → aha).
2. Identify the worst-dropping step vs. benchmark and last period.
3. Diagnose likely cause; propose one focused fix + how to measure it.
- **Self-check**: Enough new users through the funnel for step rates to be significant?
- **State / idempotency**: Track which fixes were already proposed/shipped; measure their effect before re-touching.
- **Stop / bail-out**: Sample too small → widen window or skip.
- **Output**: One prioritized activation fix with a measurement plan.
### The signup-funnel-leak loop
- **Check cadence**: Weekly
- **Acts when**: A signup/checkout step regresses vs. baseline.
- **Purpose**: Keep the signup/checkout path converting as the site changes.
- **Skills used**: `signup`, `cro`, `analytics`, `ab-testing`
- **Loop body**:
1. Pull conversion by step across the signup/checkout flow.
2. Compare to baseline; flag regressions (a deploy or copy change may have hurt it).
3. Draft a hypothesis + test for the worst step (hand test execution to `ab-testing`).
- **Self-check**: Rule out tracking breakage before declaring a real drop.
- **State / idempotency**: Track open regressions + running tests; don't start a conflicting test.
- **Stop / bail-out**: No regression and no test-worthy idea → skip.
- **Output**: A prioritized experiment brief for `ab-testing`.
### The lead-capture-asset loop
- **Check cadence**: Monthly
- **Acts when**: A lead magnet, free tool, or opt-in underperforms on capture rate.
- **Purpose**: Keep top-of-funnel capture assets (lead magnets + free tools) converting visitors to leads.
- **Skills used**: `lead-magnets`, `free-tools`, `cro`, `popups`
- **Loop body**:
1. Pull view → capture conversion for each lead magnet, free tool, and opt-in.
2. Flag underperformers vs. benchmark; diagnose (offer, placement, form friction, targeting).
3. Propose a fix or refresh (new angle, better placement, reduced friction).
- **Self-check**: Enough traffic per asset for the capture rate to be meaningful?
- **State / idempotency**: Track last-optimized date per asset; cooldown before re-touching.
- **Stop / bail-out**: All assets healthy → skip.
- **Output**: A prioritized fix per underperforming asset.
### The feature-adoption loop
- **Check cadence**: Weekly
- **Acts when**: A sticky/valuable feature is underused by a segment that would benefit.
- **Purpose**: Drive adoption of the features that correlate with retention.
- **Skills used**: `onboarding`, `emails`, `analytics`
- **Loop body**:
1. Identify high-retention-correlated features and the segments not using them.
2. Pick the highest-leverage feature × segment.
3. Draft an in-app nudge or email to drive adoption.
- **Self-check**: Is the feature genuinely valuable to that segment, or would the nudge be noise? Don't push features people rationally skip.
- **State / idempotency**: Track who's already been nudged for which feature; enforce a cooldown; suppress adopters.
- **Stop / bail-out**: No clear feature × segment gap → skip.
- **Output**: A staged adoption nudge.
---
## Retention
### The churn-signal loop
- **Check cadence**: Daily (or on-trigger)
- **Acts when**: An account newly crosses a churn-risk threshold and isn't already in an intervention.
- **Purpose**: Intervene inside the short window before an at-risk account leaves.
- **Skills used**: `churn-prevention`, `analytics`, `emails`
- **Loop body**:
1. Score accounts on churn-risk signals (usage decline, seat drop, dunning, support escalations).
2. Segment newly at-risk accounts.
3. Match each to the right intervention (re-engagement email, CS outreach, offer); stage it.
- **Self-check**: Is the "drop" a real trend or a weekend/holiday dip? Compare to the account's own baseline.
- **State / idempotency**: Never re-trigger on an account already in an active intervention; enforce a cooldown between attempts.
- **Stop / bail-out**: No newly at-risk accounts → skip. Escalate high-value accounts to a human rather than auto-emailing.
- **Output**: A prioritized at-risk list with staged interventions.
### The lifecycle-email-refresh loop
- **Check cadence**: Monthly
- **Acts when**: A sequence email underperforms on real engagement or contains stale content.
- **Purpose**: Keep automated sequences performing as the product and audience evolve.
- **Skills used**: `emails`, `analytics`, `copy-editing`
- **Loop body**:
1. Pull per-email performance — clicks, conversions, replies, unsubscribes, spam complaints, bounces (**not opens** — open tracking is unreliable post-privacy-changes).
2. Flag weak performers and stale references (old features, dates, pricing).
3. Draft rewrites or subject-line tests for the bottom performers.
- **Self-check**: Enough sends per email for rates to be meaningful?
- **State / idempotency**: Track last-revised date per email; cooldown before re-testing.
- **Stop / bail-out**: All sequences healthy → skip. **Pause and escalate any sequence with rising complaint/bounce rates — that's a deliverability emergency, not a copy tweak.**
- **Output**: Staged email rewrites + subject-line tests.
### The re-engagement loop
- **Check cadence**: Weekly
- **Acts when**: A user newly crosses the inactivity threshold.
- **Purpose**: Win back dormant users before they're gone for good.
- **Skills used**: `emails`, `sms`, `offers`
- **Loop body**:
1. Identify users newly crossing the inactivity threshold.
2. Pick the win-back angle (new feature, offer, "we miss you," sunset warning).
3. Draft the message; set suppression so they aren't re-hit next week.
- **Self-check**: Truly dormant, or just low-frequency-by-design users? Don't nag healthy accounts.
- **State / idempotency**: Track win-back attempts per user; suppress after each send for the cooldown.
- **Stop / bail-out**: After N unsuccessful attempts, move to sunset — not another email.
- **Output**: A staged win-back message + updated suppression list.
### The email-deliverability loop
- **Check cadence**: Weekly
- **Acts when**: Bounce, complaint, or unsubscribe rates rise, or list-hygiene decays.
- **Purpose**: Protect sender reputation and inbox placement.
- **Skills used**: `emails`, `analytics`
- **Loop body**:
1. Monitor bounces, spam complaints, unsubscribes, domain/DKIM/SPF/DMARC health, and inbox-placement signals.
2. Flag rising problem rates or authentication issues.
3. Recommend actions: suppress hard bounces, sunset chronically unengaged, fix auth, throttle.
- **Self-check**: Is a spike a one-off send or a trend? Correlate with recent campaigns.
- **State / idempotency**: Track already-suppressed addresses + last hygiene sweep date.
- **Stop / bail-out**: All metrics healthy → log and skip. **Escalate a complaint-rate spike immediately** — reputation damage compounds fast.
- **Output**: A deliverability report + a suppression/hygiene action list.
### The voice-of-customer loop
- **Check cadence**: Weekly
- **Acts when**: New feedback (NPS, surveys, support tickets, reviews, calls) has arrived.
- **Purpose**: Route feedback to the right action **and** mine it for marketing inputs.
- **Skills used**: `customer-research`, `churn-prevention`, `referrals`, `copywriting`
- **Loop body**:
1. Collect new feedback across sources.
2. Route: detractors/at-risk → save motion (`churn-prevention`); promoters → referral/review ask (`referrals`); recurring pain/desire → experiment + copy inputs.
3. Extract verbatim customer language for copy, FAQ, and objection-handling.
- **Self-check**: Is a theme a real pattern or one loud voice? Require a minimum count before acting on it.
- **State / idempotency**: Track processed feedback IDs; never double-route the same item.
- **Stop / bail-out**: No new feedback → skip. Escalate sensitive/legal complaints to a human.
- **Output**: Routed actions + a language/insight digest for marketing.
---
## Revenue
### The trial-conversion loop
- **Check cadence**: Daily
- **Acts when**: A trial user reaches a conversion-relevant moment (mid-trial, near-expiry, activated-but-not-paid).
- **Purpose**: Move more trials to paid with well-timed nudges.
- **Skills used**: `emails`, `paywalls`, `analytics`, `offers`
- **Loop body**:
1. Segment active trials by stage and activation level.
2. Match each to the right nudge (value recap, use-case tip, near-expiry push, offer).
3. Stage the nudge.
- **Self-check**: Is the user activated enough for a paid push to land, or do they need more value first?
- **State / idempotency**: Track nudges sent per trial; enforce cadence; suppress converters.
- **Stop / bail-out**: No trials at an actionable stage → skip. Don't over-message a single trial.
- **Output**: Staged, stage-appropriate trial nudges.
### The PQL / upgrade-intent loop
- **Check cadence**: Daily
- **Acts when**: A free/trial user shows product-qualified buying intent (usage limits, key-feature use, team invites).
- **Purpose**: Catch high-intent users and stage upgrade outreach at the right moment.
- **Skills used**: `analytics`, `sales-enablement`, `revops`
- **Loop body**:
1. Score free/trial users on PQL signals.
2. Surface newly qualified users.
3. Stage the right motion (in-app upgrade prompt, sales-assist for high-value, targeted email).
- **Self-check**: Is the signal genuine buying intent or incidental usage? Calibrate the threshold to avoid false positives.
- **State / idempotency**: Track already-actioned PQLs; don't re-route within the cooldown.
- **Stop / bail-out**: No newly qualified users → skip. Route high-value accounts to a human, don't auto-close.
- **Output**: A prioritized PQL list with staged motions.
### The pricing-page-experiment loop
- **Check cadence**: Monthly (tests run longer)
- **Acts when**: No test is running on the page and there's a worthwhile hypothesis — or a running test has concluded.
- **Purpose**: Improve pricing-page conversion **and revenue quality**, continuously.
- **Skills used**: `pricing`, `ab-testing`, `cro`
- **Loop body**:
1. Review pricing-page conversion, plan mix, and revenue-per-visitor.
2. Generate one pricing/packaging/copy hypothesis, or read a concluded test.
3. Hand design/analysis to `ab-testing`; promote a clean winner.
- **Self-check**: Judge winners on **revenue per visitor, plan mix, refunds, downgrades, churn, and support load — not conversion rate alone.** Is the running test statistically done before you call it?
- **State / idempotency**: Track the running test + concluded-test log; never start a conflicting test on the same page.
- **Stop / bail-out**: A test is in flight → hold. **Do not promote a variant that lifts conversion but lowers revenue-per-visitor or raises refunds/churn.**
- **Output**: A test result + next hypothesis.
### The paywall-optimization loop
- **Check cadence**: Monthly
- **Acts when**: No paywall test is running and there's a hypothesis — or one has concluded.
- **Purpose**: Improve in-app upgrade conversion without degrading revenue quality.
- **Skills used**: `paywalls`, `ab-testing`, `analytics`
- **Loop body**:
1. Pull paywall view → upgrade conversion and bounce points.
2. Form one hypothesis (trigger timing, framing, plan anchor), or read a concluded test.
3. Hand execution to `ab-testing`.
- **Self-check**: Segment by plan/cohort — an aggregate number can hide a segment that's tanking. Watch refunds/downgrades alongside conversion.
- **State / idempotency**: Track running/concluded tests; no conflicting tests.
- **Stop / bail-out**: Test in flight → hold. Don't promote a conversion win that raises refunds or churn.
- **Output**: A test result + next hypothesis.
### The expansion / upsell loop
- **Check cadence**: Weekly
- **Acts when**: An existing paid account hits an expansion signal (usage near limits, added seats, new use case).
- **Purpose**: Grow revenue from existing customers via well-timed upsell/cross-sell.
- **Skills used**: `revops`, `sales-enablement`, `emails`
- **Loop body**:
1. Score paid accounts on expansion signals.
2. Surface newly expansion-ready accounts.
3. Stage the right motion (usage-based upgrade prompt, CSM outreach, cross-sell offer).
- **Self-check**: Is the account healthy enough that an upsell won't sour the relationship? Don't upsell an at-risk account — that's a churn loop's job.
- **State / idempotency**: Track upsell touches per account; enforce cadence.
- **Stop / bail-out**: No expansion-ready accounts → skip. Route strategic accounts to a human.
- **Output**: A prioritized expansion list with staged motions.
### The failed-payment / dunning loop
- **Check cadence**: Daily
- **Acts when**: A payment fails or a card is about to expire.
- **Purpose**: Recover involuntary churn — often the highest-ROI retention work.
- **Skills used**: `revops`, `emails`
- **Loop body**:
1. Detect failed payments and upcoming card expirations.
2. Trigger the dunning sequence (retry schedule + escalating update-card messaging).
3. Route persistent failures to a human/CS.
- **Self-check**: Is the failure involuntary (card issue) vs. an intentional cancel? Don't dun someone who chose to leave.
- **State / idempotency**: Track dunning stage per account; follow the retry schedule; stop on recovery.
- **Stop / bail-out**: After the final retry, escalate/deactivate per policy — don't loop forever.
- **Output**: An active dunning queue + recovery status.
---
## Referral & Advocacy
### The referral-nudge loop
- **Check cadence**: Weekly
- **Acts when**: A user hits a "happy moment" (milestone, positive NPS) and hasn't been asked recently.
- **Purpose**: Ask for referrals when users are most delighted.
- **Skills used**: `referrals`, `emails`
- **Loop body**:
1. Identify users who just hit a happy moment and aren't in the ask-cooldown.
2. Match to the right ask (share link, incentive, review request).
3. Stage the ask.
- **Self-check**: Genuinely a happy moment, or just any event? A bad-timing ask erodes goodwill.
- **State / idempotency**: Enforce a cooldown — never ask the same user twice in the window.
- **Stop / bail-out**: No one at a happy moment → skip.
- **Output**: A staged, well-timed referral ask.
### The review-and-UGC-harvest loop
- **Check cadence**: Weekly
- **Acts when**: New reviews, testimonials, or user-generated content have appeared.
- **Purpose**: Keep a steady flow of social proof and route it into marketing.
- **Skills used**: `social`, `referrals`, `sales-enablement`, `cro`
- **Loop body**:
1. Collect new reviews/testimonials/UGC/mentions since last run.
2. Sort by strength and relevance.
3. Draft where each should go (site proof section, ad, social post, sales deck).
4. Flag anything negative for a human response.
- **Self-check**: Is it genuinely strong and on-message? Don't force weak proof into prime placement.
- **State / idempotency**: Track already-harvested items; never re-use the same one twice.
- **Stop / bail-out**: **Verify consent and platform ToS before public reuse; add FTC-required disclosure for incentivized content.** No verifiable consent, or platform prohibits reuse → don't use. Negative/sensitive → escalate to a human, don't auto-publish.
- **Output**: New proof assets routed to their destinations.
### The review-site-management loop
- **Check cadence**: Weekly
- **Acts when**: New reviews land on G2/Capterra/app stores, or listings drift out of date.
- **Purpose**: Maintain reputation and conversion on third-party review platforms.
- **Skills used**: `sales-enablement`, `social`, `cro`
- **Loop body**:
1. Track new reviews across review sites/app stores.
2. Draft responses (thank promoters, address detractors constructively).
3. Flag listing updates needed (screenshots, features, pricing).
- **Self-check**: Is the response specific and non-defensive? Never argue publicly with a reviewer.
- **State / idempotency**: Track responded reviews; never double-respond.
- **Stop / bail-out**: No new reviews/updates → skip. Human-approve responses to negative/legal-sensitive reviews.
- **Output**: Drafted responses + a listing-update checklist.
### The case-study-sourcing loop
- **Check cadence**: Monthly
- **Acts when**: A customer hits case-study-worthy success (strong results, milestone, enthusiastic feedback).
- **Purpose**: Keep a pipeline of case studies and customer stories.
- **Skills used**: `sales-enablement`, `customer-research`, `referrals`
- **Loop body**:
1. Identify customers with standout results/engagement.
2. Qualify for a case study (results, willingness, logo value).
3. Draft the outreach + interview questions.
- **Self-check**: Are the results real and attributable, or coincidental? Verify before pitching a story.
- **State / idempotency**: Track approached customers + status; respect a no-repeat cooldown.
- **Stop / bail-out**: No qualified candidates → skip. Human-approve customer outreach.
- **Output**: A candidate list with drafted outreach.
---
## Ongoing Ops / Meta
### The weekly-marketing-review loop
- **Check cadence**: Weekly (Mon 9am)
- **Acts when**: Always runs — this is the heartbeat. It "acts" by flagging the week's notable movers.
- **Purpose**: One standing full-funnel pulse so nothing drifts unnoticed.
- **Skills used**: `analytics`, `marketing-plan`, `marketing-ideas`
- **Loop body**:
1. Pull top-line AARRR metrics vs. last week and vs. plan.
2. Flag the biggest mover (good and bad) per stage.
3. Tie each flag to the loop or skill that should act on it; surface 1–2 experiment ideas.
- **Self-check**: Distinguish trend from noise before raising an alarm.
- **State / idempotency**: Store each week's snapshot for accurate week-over-week deltas.
- **Stop / bail-out**: Manual disable + error-halt. On a data-source outage, report "stale data," never fabricated movement. (Not "n/a" — even the heartbeat needs an off switch and an error path.)
- **Output**: A one-page weekly digest with owners/next actions.
### The experiment-backlog loop
- **Check cadence**: Weekly
- **Acts when**: New hypotheses exist to log, the backlog needs re-ranking, or a test slot is free.
- **Purpose**: Keep the experiment pipeline full and prioritized. **Thin wrapper — defer all test design, statistical analysis, and velocity management to `ab-testing`.**
- **Skills used**: `ab-testing` (owner), `cro`, `analytics`
- **Loop body**:
1. Harvest new hypotheses from the week (data, research, competitors, support, other loops).
2. Re-rank the backlog with ICE.
3. If a slot is free, hand the top idea to `ab-testing`; if a test concluded there, log the learning.
- **Self-check**: Is the top idea actually testable with current traffic, or ICE-inflated?
- **State / idempotency**: Dedupe incoming hypotheses against the backlog; track which tests are live.
- **Stop / bail-out**: Backlog full and a test running → just log new ideas. Don't duplicate `ab-testing`'s job.
- **Output**: An updated, ranked backlog (the source of record lives with `ab-testing`).
### The analytics-anomaly loop
- **Check cadence**: Daily
- **Acts when**: A tracked metric breaks its expected band (spike or drop beyond normal variance).
- **Purpose**: Catch anything breaking — good or bad — before it runs for days unnoticed.
- **Skills used**: `analytics`
- **Loop body**:
1. Check key metrics (traffic, signups, conversion, revenue, spend) against their normal range.
2. Flag anomalies; separate "real event" from "tracking artifact."
3. Route each to the responsible loop/owner for diagnosis.
- **Self-check**: Is the anomaly real or a tracking/seasonality artifact? Check for known causes (holiday, launch, deploy) before alarming.
- **State / idempotency**: Track already-alerted anomalies; don't re-alert the same ongoing one daily.
- **Stop / bail-out**: All metrics in-band → silent (no alert = good). Escalate a revenue/spend anomaly immediately.
- **Output**: An anomaly alert routed to an owner, or nothing.
### The brand-mention / reputation loop
- **Check cadence**: Daily
- **Acts when**: A meaningful brand mention appears anywhere (not just where you're listening for engagement).
- **Purpose**: Monitor and protect reputation; respond where it matters.
- **Skills used**: `social`, `public-relations`
- **Loop body**:
1. Scan the open web/social/forums for brand mentions.
2. Classify sentiment + reach + risk.
3. Route: positive → amplify/thank; negative/risky → drafted response for human review; unlinked mention → backlink-prospecting.
- **Self-check**: Does a negative mention need a response, or would engaging amplify it? Judge reach + legitimacy.
- **State / idempotency**: Dedupe on mention ID; track handled mentions.
- **Stop / bail-out**: No meaningful mentions → skip. **Always human-approve responses to negative/crisis mentions** — never auto-reply to a complaint.
- **Output**: A mention digest with routed actions.
### The tracking-QA loop
- **Check cadence**: Weekly (and on deploy / campaign launch)
- **Acts when**: Analytics, pixels, UTMs, or conversion events are missing, misfiring, or misconfigured.
- **Purpose**: Keep the measurement layer trustworthy — every other loop depends on it.
- **Skills used**: `analytics`
- **Loop body**:
1. Verify key events fire correctly, pixels are present, UTMs are consistent, and conversions attribute.
2. Flag broken/missing/duplicate tracking, especially after deploys or new campaigns.
3. Recommend fixes.
- **Self-check**: Is it truly broken, or an expected change? Confirm against a known-good baseline.
- **State / idempotency**: Track open tracking issues; update rather than re-file.
- **Stop / bail-out**: All tracking healthy → log "clean." **Escalate a broken revenue/conversion event immediately** — every downstream loop is blind until it's fixed.
- **Output**: A tracking-QA report with prioritized fixes.
### The campaign-postmortem loop
- **Check cadence**: On campaign end (event-based)
- **Acts when**: A campaign (launch, promo, seasonal push) concludes.
- **Purpose**: Capture results, lessons, and reusable assets so each campaign compounds.
- **Skills used**: `analytics`, `marketing-plan`
- **Loop body**:
1. Pull final campaign results vs. goals.
2. Capture what worked, what didn't, and why; save reusable assets (copy, creative, workflows).
3. Feed learnings into the experiment backlog and the next plan; log follow-ups.
- **Self-check**: Are conclusions supported by the data, or hindsight narrative? Separate correlation from cause.
- **State / idempotency**: One postmortem per campaign; don't re-run on an already-documented campaign.
- **Stop / bail-out**: No concluded campaign → skip.
- **Output**: A postmortem doc + backlog/plan inputs.
---
## Adapting and authoring loops
To adapt a loop: keep all nine anatomy parts, swap skills/thresholds for the user's stack, and re-tune cadence to signal speed. To author a brand-new one: use `loop-template.md` (copy-paste template + fill-in prompts + worked example + ship checklist). Either way, do not ship a loop until every part is filled — especially **State / idempotency**, **Self-check**, and **Stop / bail-out**. A loop without those isn't a system; it's a way to do the wrong thing on a schedule, repeatedly, to the same people.
FILE:references/loop-guardrails.md
# Loop Guardrails & Compliance
Loops act on a schedule, often on customer data, sometimes with money or a public voice. This reference consolidates the safety rules that keep autonomous loops from doing harm. Apply it to every loop that sends, spends, publishes, or touches personal data.
## The two-tier action model
Classify every action a loop can take:
**Tier 1 — Autonomous-safe** (a loop may do these unattended):
read data, analyze, diff, score, **draft**, and **stage** work for review.
**Tier 2 — Gated** (require a human checkpoint by default):
**spend** money, **shift budget**, **send** messages, **publish** anything public, **delete/suppress** records, **change** live account settings.
A Tier-2 action may run without a per-action human check only if the user has **explicitly authorized** it *and* it's bounded by caps + an allowlist (below). Absent that, the loop stages a draft and a human approves.
## Spend guardrails (ad-fatigue, paid-search, retargeting, expansion)
- **Hard caps**: a daily/weekly spend ceiling the loop can never exceed; halt and alert if approached.
- **Per-run change limit**: cap how much budget can move in one run (e.g., ≤20%), so a bad read can't reallocate everything.
- **Allowlist**: only specified accounts/campaigns are eligible for autonomous changes; everything else is staged.
- **Directional guardrails**: judge paid changes on revenue/ROAS, not just CTR/CPA — never optimize a proxy metric into a revenue loss.
## Publish & send guardrails (email, social, PR, community, reviews)
- **Default to a staging queue** + human approval for anything public or outbound. Auto-*drafting* is fine; auto-*publishing* is not, unless explicitly authorized.
- **Volume caps**: per-run and per-recipient limits so a loop can't blast a list or over-post a channel.
- **Suppression first**: always check suppression/unsubscribe/do-not-contact lists before sending.
- **No auto-posting where detection/ToS bites**: owned social, press pitches, and community replies are staged for a human (bot detection + brand risk).
## Compliance
Match each rule to the loops it governs:
- **CAN-SPAM / CASL (email/SMS loops — lifecycle, re-engagement, churn, trial, dunning, referral)**: honor unsubscribes immediately and permanently; include a working unsubscribe + physical address; identify the sender; don't email/text without a lawful basis or consent; scrub against suppression every send.
- **GDPR / CCPA (any loop touching personal data)**: process on a lawful basis; get consent for EU marketing; honor deletion and opt-out requests; minimize data pulled and retained; don't repurpose data beyond its collected purpose.
- **FTC (review-and-UGC-harvest, referral, social)**: disclose material connections and incentives (#ad, "I was compensated"); only use testimonials with permission; no fabricated or cherry-picked-to-mislead claims.
- **Platform ToS (social-listening, community-engagement, review-site-management, scraping-based loops)**: respect rate limits and automation rules; follow review-platform response policies; don't scrape or auto-act where prohibited.
When a loop can't confirm consent, permission, or ToS-compatibility, its stop condition is **don't act** — stage for a human instead.
## PII handling
- Don't log raw PII in loop **state** or **run logs** — use internal IDs or hashes.
- Pull the minimum personal data needed to make the decision; don't hoard it in state.
- Keep exports and drafts out of shared/synced locations unless intended.
## Always-escalate list
These never run fully autonomously — route to a human regardless of authorization:
- Negative or crisis brand mentions; responses to complaints or legal/medical/financial-sensitive issues.
- Newsjacking angles (see the veto list in the catalog) — human approval before any pitch/post.
- High-value or strategic accounts (enterprise, at-risk logos).
- Anomalies in **revenue** or **ad spend** — flag immediately, don't self-correct.
- Anything that would delete data or contact a large audience at once.
## Kill switch
Every scheduled loop needs a manual off switch, and you should know how to stop **all** loops fast (disable the schedule / cron, or a global flag the loop bodies check). Document it where the loops are scheduled. A loop you can't stop quickly is a liability.
## Pre-launch guardrail checklist
Before scheduling any loop that sends, spends, publishes, or touches personal data:
- [ ] Every action is classified Tier 1 (auto) or Tier 2 (gated).
- [ ] Tier-2 actions are staged for approval — or bounded by explicit authorization + caps + allowlist.
- [ ] Spend loops have a hard cap and a per-run change limit.
- [ ] Send loops check suppression/unsubscribe and have volume caps.
- [ ] Applicable compliance rules (CAN-SPAM/GDPR/FTC/ToS) are satisfied, with "don't act" as the fallback.
- [ ] No raw PII in state or logs.
- [ ] The always-escalate cases route to a human.
- [ ] There's a documented kill switch.
FILE:references/loop-orchestration.md
# Loop Orchestration & Rollout
Loops aren't independent scripts — they compose into a marketing operating system. This reference covers how they fit together and the order to adopt them so you never build 43 at once.
## The system view
Loops fall into four layers. Data flows down and learnings flow back up.
```
SENSING analytics-anomaly · tracking-QA · weekly-marketing-review
│ (detect what changed; trust the numbers first)
▼
DIAGNOSTIC per-stage watchers — onboarding drop-off, churn-signal,
ranking-drop, landing-page regression, ad-fatigue, …
│ (figure out what to do about it)
▼
ACTION staged drafts, nudges, outreach, budget moves
│ (mostly human-checkpointed)
▼
LEARNING experiment-backlog · campaign-postmortem · voice-of-customer
│ (capture what worked)
└──────────────► feeds back into SENSING & DIAGNOSTIC
```
Key connective tissue:
- **weekly-marketing-review is the router.** It reads top-line metrics and dispatches each notable mover to the loop that owns it. It's the one loop that sees the whole board.
- **tracking-QA + analytics-anomaly are the foundation.** Every other loop reads from analytics. If tracking is broken, every downstream loop acts on lies. These come first.
- **experiment-backlog is the sink.** Hypotheses generated by many loops (signup-leak, pricing, onboarding, voice-of-customer) converge here, then hand off to `ab-testing`. Don't let each loop run its own tests.
- **voice-of-customer is a source.** Customer language it mines feeds copy for ad-fatigue, lifecycle-email, landing-page, and pricing loops.
- **campaign-postmortem closes the loop.** Its learnings become next quarter's hypotheses and plan inputs.
Avoid duplicate ownership: when two loops could act on the same signal, one owns the action and the other just flags. (E.g., an at-risk account belongs to churn-signal, not expansion/upsell — never upsell an account that's churning.)
## Rollout path (adopt in this order)
Add a loop only when the loops before it are running and earning their keep. Each stage assumes the previous one is solid.
**Stage 0 — Foundation (trust the data + see the board).**
`tracking-QA`, `weekly-marketing-review`.
You cannot run any loop responsibly on untrustworthy data or without a full-funnel pulse. This is non-negotiable and comes first.
**Stage 1 — Plug the leaks (highest ROI, protects existing revenue).**
`failed-payment/dunning`, `churn-signal`, `lifecycle-email-refresh`.
Recovering customers you already have is cheaper than acquiring new ones. Dunning alone often pays for the whole system.
**Stage 2 — Convert what you already get (fix the bucket before adding water).**
`onboarding drop-off`, `signup-funnel-leak`, `trial-conversion`.
More traffic into a leaky funnel is waste. Seal activation and conversion next.
**Stage 3 — Grow the top (now scale acquisition).**
`keyword-gap`, `content-repurposing`, `ad-fatigue`, `social-listening`, `analytics-anomaly`.
With the bucket sealed, turn on demand generation and the safety-net anomaly watcher.
**Stage 4 — Optimize monetization.**
`pricing-page-experiment`, `paywall-optimization`, `PQL/upgrade-intent`, `expansion/upsell`.
Once volume is healthy, tune revenue per user — judged on revenue quality, not conversion alone.
**Stage 5 — Compounding & advocacy.**
`referral-nudge`, `review-and-UGC-harvest`, `review-site-management`, `case-study-sourcing`, `partner-pipeline`, `brand-mention/reputation`, `experiment-backlog`, `campaign-postmortem`.
The flywheel: happy customers and earned media that feed back into acquisition, plus the learning loops that make everything compound.
The remaining catalog loops (content-decay, internal-linking, programmatic-SEO quality, content-calendar refill, paid-search query-mining, retargeting-hygiene, landing-page regression, community-engagement, competitor-watch, backlink-prospecting, directory-submission, feature-adoption, lead-capture-asset, email-deliverability, voice-of-customer) slot into the stage that matches their function as each channel becomes a priority.
## Rollout rules
- **One at a time.** Prove a loop earns its keep (someone acts on its output, it moves its metric) before adding the next.
- **Foundation before growth.** Acquisition loops before solid tracking + retention = pouring water into a leaky bucket.
- **Cap the total.** If you're running more loops than you can review the output of, you have vanity loops. Retire the ones nobody acts on.
- **Re-audit quarterly.** Recalibrate thresholds, kill dead loops, promote the ones that consistently drive action.
FILE:references/loop-state.md
# Loop State & Run Logging
Idempotency is only real if the loop can remember what it already did between runs. This reference defines where that state lives and how to log runs — so loops don't double-act, re-nag the same people, or re-alert the same issue.
## Where state lives
Persist each loop's state in a file under `.agents/loops/` — the same `.agents/` convention this repo uses for `product-marketing.md` and `listening-sources.md`. One state file per loop:
```
.agents/loops/<loop-name>.json # the loop's memory
.agents/loops/<loop-name>.log # append-only run log
```
If your scheduler or platform provides its own dedupe/cursor storage, use that instead — the point is durable state, not the specific file. Never keep state only in memory; a loop that forgets on restart will repeat itself.
## What to store
A state file holds whatever the loop needs to not repeat itself:
```json
{
"loop": "churn-signal",
"last_run": "2026-07-01T09:00:00Z",
"cursor": "2026-06-30T23:59:59Z", // watermark — only process items newer than this
"handled": ["acct_1042", "acct_1077"], // dedupe keys already acted on
"cooldowns": { // entity -> next-eligible timestamp
"acct_1042": "2026-07-15T00:00:00Z"
},
"in_flight": ["exp_pricing_v3"], // actions/tests currently open
"counters": { "acct_1042_attempts": 2 } // e.g. dunning/win-back attempt counts
}
```
- **cursor / watermark** — the high-water mark of what's been processed (a timestamp or last ID). The loop only looks at items past it.
- **handled** — dedupe keys for items already acted on, so re-runs skip them.
- **cooldowns** — per-entity suppression windows so you never re-contact someone inside the window.
- **in_flight** — open items (running tests, active interventions) so the loop doesn't start a conflicting one.
- **counters** — attempt counts that drive stop conditions (e.g., "after 2 win-back emails, stop").
Keep state small and prune it: expire old `handled`/`cooldown` entries once they're past their window.
## Idempotency patterns
- **Watermark**: process only items newer than `cursor`; advance `cursor` at the end of a successful run. Safe to re-run — it won't reprocess.
- **Dedupe set**: before acting on an item, check its key against `handled`; add it after acting.
- **Cooldown map**: before contacting an entity, check `cooldowns[entity]`; set it after contact.
- **In-flight guard**: before starting an action that shouldn't overlap (a test, an intervention), check `in_flight`.
## Run logging
Append one line per run, whether or not it acted. This is the audit trail and the vanity-loop detector.
```
2026-07-01T09:00Z checked=312 acted=2 note="2 accounts newly at-risk, interventions staged"
2026-07-02T09:00Z checked=298 acted=0 note="no action"
2026-07-03T09:00Z checked=305 acted=0 note="no action"
```
Log at minimum: timestamp, how many items checked, how many acted on, and a short note. Use it to answer two questions:
- **Is it a vanity loop?** If every run is `acted=0` for weeks and nobody misses it — or it acts every run (a sign it's chasing noise) — reconsider it.
- **Did it double-act?** Two runs acting on the same entity means the dedupe/cooldown state isn't working.
## Resetting & backfilling safely
- To **reset** a loop, clear its `cursor`/`handled` — but keep `cooldowns` so a reset doesn't spam people who were recently contacted.
- On **first run** (no state yet), set the watermark to "now" rather than processing all history, or you'll blast every historical item. If you genuinely want a backfill, do a dry run first (log what it *would* do, act on nothing) and respect cooldowns.
- Never log raw PII in state or run logs — use IDs or hashes (see `loop-guardrails.md`).
FILE:references/loop-template.md
# Loop Template
A copy-paste template for authoring your own marketing loop. Fill every one of the nine parts — a loop missing its **state/idempotency**, **self-check**, or **stop/bail-out** isn't a system, it's a way to do the wrong thing on a schedule.
Before you start, sanity-check that this *should* be a loop at all (see "When NOT to loop" in `SKILL.md`): it's recurring, signal-driven, and doesn't require human judgment to set strategy or creative direction each run.
---
## Blank template (copy this)
```markdown
### The <name> loop
- **Check cadence**: <how often it looks — match to how fast the signal changes, not how often you'd like an update>
- **Acts when**: <the action condition — what must be true to actually DO something vs. just check and skip. Most runs should skip.>
- **Purpose**: <the ONE outcome this loop exists to move>
- **Skills used**: <which marketing skills the loop orchestrates each run>
- **Loop body**:
1. <step — usually: pull data / diff vs. last run>
2. <step — identify what, if anything, crossed the action condition>
3. <step — draft or stage the response>
- **Self-check**: <the verification done BEFORE acting — is the signal real vs. noise/seasonality/tracking bug? Is the sample big enough to be significant?>
- **State / idempotency**: <what it remembers between runs — last-run marker, dedupe key, cooldown window, "already handled" set — so it doesn't double-act or re-nag the same people>
- **Stop / bail-out**: <when it skips, halts, escalates to a human, or disables itself — plus what it does on error. Include a human checkpoint before anything that spends money or publishes.>
- **Output**: <where results go — a file, a PR, a staged draft, a notification, a report>
```
---
## Fill-in prompts (answer these, in order)
1. **What outcome does this protect or grow?** (rankings, ad efficiency, activation, retention, revenue, referrals) → *Purpose*
2. **How fast does that signal actually change?** (hours / days / weeks / months) → *Check cadence*
3. **What has to be true before it's worth acting?** (a threshold crossed, a new item appeared, a regression vs. baseline) → *Acts when*
4. **What data does it read and what does it produce each run?** → *Loop body* + *Output*
5. **What would make it act on a false signal?** (noise, seasonality, a tracking break, too-small a sample) → *Self-check*
6. **What must it remember so it doesn't repeat itself?** (dedupe key, cooldown, last-run marker) → *State / idempotency*
7. **When should it stop, skip, or hand off to a human?** (no action needed, error, spend/publish decision, N failed attempts) → *Stop / bail-out*
If you can't answer 5, 6, and 7 concretely, the loop isn't ready to run.
---
## Worked example (blank → filled)
Say you sell a freemium API tool and want to stop losing signups who never make their first API call.
```markdown
### The first-call activation loop
- **Check cadence**: Daily
- **Acts when**: A user who signed up 48h ago still hasn't made a successful API call and isn't already in this nudge sequence.
- **Purpose**: Increase the share of new signups that reach first value (first successful API call).
- **Skills used**: `onboarding`, `emails`, `analytics`
- **Loop body**:
1. Pull signups from ~48h ago and their first-call status.
2. Filter to those with zero successful calls and no active nudge.
3. Draft a targeted "get your first call working" email (docs link, common blocker, offer to help).
- **Self-check**: Is "no call" a real activation gap, or a tracking gap (calls firing but not logged)? Confirm against server logs before emailing.
- **State / idempotency**: Track which users have entered this sequence; suppress anyone who has made a call since; one nudge per user per stage.
- **Stop / bail-out**: After 2 nudges with no call, stop and route to the broader re-engagement loop — don't keep emailing. Skip the run entirely if the events pipeline looks stale.
- **Output**: A staged activation email per qualifying user + a daily count of new activations.
```
Notice what makes it safe: the **self-check** guards against a tracking bug emailing active users, the **state** stops it re-nagging, and the **stop** caps attempts and hands off instead of looping forever.
---
## Ship checklist
Before you schedule a new loop, confirm:
- [ ] All nine parts are filled — especially self-check, state, and stop.
- [ ] Cadence matches signal speed (you're not checking daily for a weekly-moving signal).
- [ ] It's designed so **most runs do nothing** — it acts only on a real condition.
- [ ] Anything that **spends money or publishes** has a human checkpoint (unless caps + an allowlist are explicitly authorized).
- [ ] State prevents double-acting and re-nagging the same people.
- [ ] There's an error path (stale data → report "stale," don't fabricate movement) and a manual off switch.
- [ ] For scheduling mechanics, see the "Scheduling a loop" section in `SKILL.md`.
Once it runs, give it a few cycles and ask the "is this a vanity loop?" question: if nobody acts on the output, delete it.
Áp dụng nguyên lý tâm lý, mô hình tư duy và khoa học hành vi vào marketing: thiên kiến nhận thức, thuyết phục, hành vi người tiêu dùng.
---
name: marketing-psychology
description: "When the user wants to apply psychological principles, mental models, or behavioral science to marketing. Also use when the user mentions 'psychology,' 'mental models,' 'cognitive bias,' 'persuasion,' 'behavioral science,' 'why people buy,' 'decision-making,' 'consumer behavior,' 'anchoring,' 'social proof,' 'scarcity,' 'loss aversion,' 'framing,' or 'nudge.' Use this whenever someone wants to understand or leverage how people think and make decisions in a marketing context. For applying psychology to specific pages, see cro; for pricing tactics, see pricing; for copy framing, see copywriting."
metadata:
version: 2.0.0
---
# Marketing Psychology & Mental Models
You are an expert in applying psychological principles and mental models to marketing. Your goal is to help users understand why people buy, how to influence behavior ethically, and how to make better marketing decisions.
## How to Use This Skill
**Check for product marketing context first:**
If `.agents/product-marketing.md` exists (or `.claude/product-marketing.md`, or the legacy `product-marketing-context.md` filename, in older setups), read it before applying mental models. Use that context to tailor recommendations to the specific product and audience.
Mental models are thinking tools that help you make better decisions, understand customer behavior, and create more effective marketing. When helping users:
1. Identify which mental models apply to their situation
2. Explain the psychology behind the model
3. Provide specific marketing applications
4. Suggest how to implement ethically
---
## Foundational Thinking Models
These models sharpen your strategy and help you solve the right problems.
### First Principles
Break problems down to basic truths and build solutions from there. Instead of copying competitors, ask "why" repeatedly to find root causes. Use the 5 Whys technique to tunnel down to what really matters.
**Marketing application**: Don't assume you need content marketing because competitors do. Ask why you need it, what problem it solves, and whether there's a better solution.
### Jobs to Be Done
People don't buy products—they "hire" them to get a job done. Focus on the outcome customers want, not features.
**Marketing application**: A drill buyer doesn't want a drill—they want a hole. Frame your product around the job it accomplishes, not its specifications.
### Circle of Competence
Know what you're good at and stay within it. Venture outside only with proper learning or expert help.
**Marketing application**: Don't chase every channel. Double down where you have genuine expertise and competitive advantage.
### Inversion
Instead of asking "How do I succeed?", ask "What would guarantee failure?" Then avoid those things.
**Marketing application**: List everything that would make your campaign fail—confusing messaging, wrong audience, slow landing page—then systematically prevent each.
### Occam's Razor
The simplest explanation is usually correct. Avoid overcomplicating strategies or attributing results to complex causes when simple ones suffice.
**Marketing application**: If conversions dropped, check the obvious first (broken form, page speed) before assuming complex attribution issues.
### Pareto Principle (80/20 Rule)
Roughly 80% of results come from 20% of efforts. Identify and focus on the vital few.
**Marketing application**: Find the 20% of channels, customers, or content driving 80% of results. Cut or reduce the rest.
### Local vs. Global Optima
A local optimum is the best solution nearby, but a global optimum is the best overall. Don't get stuck optimizing the wrong thing.
**Marketing application**: Optimizing email subject lines (local) won't help if email isn't the right channel (global). Zoom out before zooming in.
### Theory of Constraints
Every system has one bottleneck limiting throughput. Find and fix that constraint before optimizing elsewhere.
**Marketing application**: If your funnel converts well but traffic is low, more conversion optimization won't help. Fix the traffic bottleneck first.
### Opportunity Cost
Every choice has a cost—what you give up by not choosing alternatives. Consider what you're saying no to.
**Marketing application**: Time spent on a low-ROI channel is time not spent on high-ROI activities. Always compare against alternatives.
### Law of Diminishing Returns
After a point, additional investment yields progressively smaller gains.
**Marketing application**: The 10th blog post won't have the same impact as the first. Know when to diversify rather than double down.
### Second-Order Thinking
Consider not just immediate effects, but the effects of those effects.
**Marketing application**: A flash sale boosts revenue (first order) but may train customers to wait for discounts (second order).
### Map ≠ Territory
Models and data represent reality but aren't reality itself. Don't confuse your analytics dashboard with actual customer experience.
**Marketing application**: Your customer persona is a useful model, but real customers are more complex. Stay in touch with actual users.
### Probabilistic Thinking
Think in probabilities, not certainties. Estimate likelihoods and plan for multiple outcomes.
**Marketing application**: Don't bet everything on one campaign. Spread risk and plan for scenarios where your primary strategy underperforms.
### Barbell Strategy
Combine extreme safety with small high-risk/high-reward bets. Avoid the mediocre middle.
**Marketing application**: Put 80% of budget into proven channels, 20% into experimental bets. Avoid moderate-risk, moderate-reward middle.
---
## Understanding Buyers & Human Psychology
These models explain how customers think, decide, and behave.
### Fundamental Attribution Error
People attribute others' behavior to character, not circumstances. "They didn't buy because they're not serious" vs. "The checkout was confusing."
**Marketing application**: When customers don't convert, examine your process before blaming them. The problem is usually situational, not personal.
### Mere Exposure Effect
People prefer things they've seen before. Familiarity breeds liking.
**Marketing application**: Consistent brand presence builds preference over time. Repetition across channels creates comfort and trust.
### Availability Heuristic
People judge likelihood by how easily examples come to mind. Recent or vivid events seem more common.
**Marketing application**: Case studies and testimonials make success feel more achievable. Make positive outcomes easy to imagine.
### Confirmation Bias
People seek information confirming existing beliefs and ignore contradictory evidence.
**Marketing application**: Understand what your audience already believes and align messaging accordingly. Fighting beliefs head-on rarely works.
### The Lindy Effect
The longer something has survived, the longer it's likely to continue. Old ideas often outlast new ones.
**Marketing application**: Proven marketing principles (clear value props, social proof) outlast trendy tactics. Don't abandon fundamentals for fads.
### Mimetic Desire
People want things because others want them. Desire is socially contagious.
**Marketing application**: Show that desirable people want your product. Waitlists, exclusivity, and social proof trigger mimetic desire.
### Sunk Cost Fallacy
People continue investing in something because of past investment, even when it's no longer rational.
**Marketing application**: Know when to kill underperforming campaigns. Past spend shouldn't justify future spend if results aren't there.
### Endowment Effect
People value things more once they own them.
**Marketing application**: Free trials, samples, and freemium models let customers "own" the product, making them reluctant to give it up.
### IKEA Effect
People value things more when they've put effort into creating them.
**Marketing application**: Let customers customize, configure, or build something. Their investment increases perceived value and commitment.
### Zero-Price Effect
Free isn't just a low price—it's psychologically different. "Free" triggers irrational preference.
**Marketing application**: Free tiers, free trials, and free shipping have disproportionate appeal. The jump from $1 to $0 is bigger than $2 to $1.
### Hyperbolic Discounting / Present Bias
People strongly prefer immediate rewards over future ones, even when waiting is more rational.
**Marketing application**: Emphasize immediate benefits ("Start saving time today") over future ones ("You'll see ROI in 6 months").
### Status-Quo Bias
People prefer the current state of affairs. Change requires effort and feels risky.
**Marketing application**: Reduce friction to switch. Make the transition feel safe and easy. "Import your data in one click."
### Default Effect
People tend to accept pre-selected options. Defaults are powerful.
**Marketing application**: Pre-select the plan you want customers to choose. Opt-out beats opt-in for subscriptions (ethically applied).
### Paradox of Choice
Too many options overwhelm and paralyze. Fewer choices often lead to more decisions.
**Marketing application**: Limit options. Three pricing tiers beat seven. Recommend a single "best for most" option.
### Goal-Gradient Effect
People accelerate effort as they approach a goal. Progress visualization motivates action.
**Marketing application**: Show progress bars, completion percentages, and "almost there" messaging to drive completion.
### Peak-End Rule
People judge experiences by the peak (best or worst moment) and the end, not the average.
**Marketing application**: Design memorable peaks (surprise upgrades, delightful moments) and strong endings (thank you pages, follow-up emails).
### Zeigarnik Effect
Unfinished tasks occupy the mind more than completed ones. Open loops create tension.
**Marketing application**: "You're 80% done" creates pull to finish. Incomplete profiles, abandoned carts, and cliffhangers leverage this.
### Pratfall Effect
Competent people become more likable when they show a small flaw. Perfection is less relatable.
**Marketing application**: Admitting a weakness ("We're not the cheapest, but...") can increase trust and differentiation.
### Curse of Knowledge
Once you know something, you can't imagine not knowing it. Experts struggle to explain simply.
**Marketing application**: Your product seems obvious to you but confusing to newcomers. Test copy with people unfamiliar with your space.
### Mental Accounting
People treat money differently based on its source or intended use, even though money is fungible.
**Marketing application**: Frame costs in favorable mental accounts. "$3/day" feels different than "$90/month" even though it's the same.
### Regret Aversion
People avoid actions that might cause regret, even if the expected outcome is positive.
**Marketing application**: Address regret directly. Money-back guarantees, free trials, and "no commitment" messaging reduce regret fear.
### Bandwagon Effect / Social Proof
People follow what others are doing. Popularity signals quality and safety.
**Marketing application**: Show customer counts, testimonials, logos, reviews, and "trending" indicators. Numbers create confidence.
---
## Influencing Behavior & Persuasion
These models help you ethically influence customer decisions.
### Reciprocity Principle
People feel obligated to return favors. Give first, and people want to give back.
**Marketing application**: Free content, free tools, and generous free tiers create reciprocal obligation. Give value before asking for anything.
### Commitment & Consistency
Once people commit to something, they want to stay consistent with that commitment.
**Marketing application**: Get small commitments first (email signup, free trial). People who've taken one step are more likely to take the next.
### Authority Bias
People defer to experts and authority figures. Credentials and expertise create trust.
**Marketing application**: Feature expert endorsements, certifications, "featured in" logos, and thought leadership content.
### Liking / Similarity Bias
People say yes to those they like and those similar to themselves.
**Marketing application**: Use relatable spokespeople, founder stories, and community language. "Built by marketers for marketers" signals similarity.
### Unity Principle
Shared identity drives influence. "One of us" is powerful.
**Marketing application**: Position your brand as part of the customer's tribe. Use insider language and shared values.
### Scarcity / Urgency Heuristic
Limited availability increases perceived value. Scarcity signals desirability.
**Marketing application**: Limited-time offers, low-stock warnings, and exclusive access create urgency. Only use when genuine.
### Foot-in-the-Door Technique
Start with a small request, then escalate. Compliance with small requests leads to compliance with larger ones.
**Marketing application**: Free trial → paid plan → annual plan → enterprise. Each step builds on the last.
### Door-in-the-Face Technique
Start with an unreasonably large request, then retreat to what you actually want. The contrast makes the second request seem reasonable.
**Marketing application**: Show enterprise pricing first, then reveal the affordable starter plan. The contrast makes it feel like a deal.
### Loss Aversion / Prospect Theory
Losses feel roughly twice as painful as equivalent gains feel good. People will work harder to avoid losing than to gain.
**Marketing application**: Frame in terms of what they'll lose by not acting. "Don't miss out" beats "You could gain."
### Anchoring Effect
The first number people see heavily influences subsequent judgments.
**Marketing application**: Show the higher price first (original price, competitor price, enterprise tier) to anchor expectations.
### Decoy Effect
Adding a third, inferior option makes one of the original two look better.
**Marketing application**: A "decoy" pricing tier that's clearly worse value makes your preferred tier look like the obvious choice.
### Framing Effect
How something is presented changes how it's perceived. Same facts, different frames.
**Marketing application**: "90% success rate" vs. "10% failure rate" are identical but feel different. Frame positively.
### Contrast Effect
Things seem different depending on what they're compared to.
**Marketing application**: Show the "before" state clearly. The contrast with your "after" makes improvements vivid.
---
## Pricing Psychology
These models specifically address how people perceive and respond to prices.
### Charm Pricing / Left-Digit Effect
Prices ending in 9 seem significantly lower than the next round number. $99 feels much cheaper than $100.
**Marketing application**: Use .99 or .95 endings for value-focused products. The left digit dominates perception.
### Rounded-Price (Fluency) Effect
Round numbers feel premium and are easier to process. $100 signals quality; $99 signals value.
**Marketing application**: Use round prices for premium products ($500/month), charm prices for value products ($497/month).
### Rule of 100
For prices under $100, percentage discounts seem larger ("20% off"). For prices over $100, absolute discounts seem larger ("$50 off").
**Marketing application**: $80 product: "20% off" beats "$16 off." $500 product: "$100 off" beats "20% off."
### Price Relativity / Good-Better-Best
People judge prices relative to options presented. A middle tier seems reasonable between cheap and expensive.
**Marketing application**: Three tiers where the middle is your target. The expensive tier makes it look reasonable; the cheap tier provides an anchor.
### Mental Accounting (Pricing)
Framing the same price differently changes perception.
**Marketing application**: "$1/day" feels cheaper than "$30/month." "Less than your morning coffee" reframes the expense.
---
## Design & Delivery Models
These models help you design effective marketing systems.
### Hick's Law
Decision time increases with the number and complexity of choices. More options = slower decisions = more abandonment.
**Marketing application**: Simplify choices. One clear CTA beats three. Fewer form fields beat more.
### AIDA Funnel
Attention → Interest → Desire → Action. The classic customer journey model.
**Marketing application**: Structure pages and campaigns to move through each stage. Capture attention before building desire.
### Rule of 7
Prospects need roughly 7 touchpoints before converting. One ad rarely converts; sustained presence does.
**Marketing application**: Build multi-touch campaigns across channels. Retargeting, email sequences, and consistent presence compound.
### Nudge Theory / Choice Architecture
Small changes in how choices are presented significantly influence decisions.
**Marketing application**: Default selections, strategic ordering, and friction reduction guide behavior without restricting choice.
### BJ Fogg Behavior Model
Behavior = Motivation × Ability × Prompt. All three must be present for action.
**Marketing application**: High motivation but hard to do = won't happen. Easy to do but no prompt = won't happen. Design for all three.
### EAST Framework
Make desired behaviors: Easy, Attractive, Social, Timely.
**Marketing application**: Reduce friction (easy), make it appealing (attractive), show others doing it (social), ask at the right moment (timely).
### COM-B Model
Behavior requires: Capability, Opportunity, Motivation.
**Marketing application**: Can they do it (capability)? Is the path clear (opportunity)? Do they want to (motivation)? Address all three.
### Activation Energy
The initial energy required to start something. High activation energy prevents action even if the task is easy overall.
**Marketing application**: Reduce starting friction. Pre-fill forms, offer templates, show quick wins. Make the first step trivially easy.
### North Star Metric
One metric that best captures the value you deliver to customers. Focus creates alignment.
**Marketing application**: Identify your North Star (active users, completed projects, revenue per customer) and align all efforts toward it.
### The Cobra Effect
When incentives backfire and produce the opposite of intended results.
**Marketing application**: Test incentive structures. A referral bonus might attract low-quality referrals gaming the system.
---
## Growth & Scaling Models
These models explain how marketing compounds and scales.
### Feedback Loops
Output becomes input, creating cycles. Positive loops accelerate growth; negative loops create decline.
**Marketing application**: Build virtuous cycles: more users → more content → better SEO → more users. Identify and strengthen positive loops.
### Compounding
Small, consistent gains accumulate into large results over time. Early gains matter most.
**Marketing application**: Consistent content, SEO, and brand building compound. Start early; benefits accumulate exponentially.
### Network Effects
A product becomes more valuable as more people use it.
**Marketing application**: Design features that improve with more users: shared workspaces, integrations, marketplaces, communities.
### Flywheel Effect
Sustained effort creates momentum that eventually maintains itself. Hard to start, easy to maintain.
**Marketing application**: Content → traffic → leads → customers → case studies → more content. Each element powers the next.
### Switching Costs
The price (time, money, effort, data) of changing to a competitor. High switching costs create retention.
**Marketing application**: Increase switching costs ethically: integrations, data accumulation, workflow customization, team adoption.
### Exploration vs. Exploitation
Balance trying new things (exploration) with optimizing what works (exploitation).
**Marketing application**: Don't abandon working channels for shiny new ones, but allocate some budget to experiments.
### Critical Mass / Tipping Point
The threshold after which growth becomes self-sustaining.
**Marketing application**: Focus resources on reaching critical mass in one segment before expanding. Depth before breadth.
### Survivorship Bias
Focusing on successes while ignoring failures that aren't visible.
**Marketing application**: Study failed campaigns, not just successful ones. The viral hit you're copying had 99 failures you didn't see.
---
## Quick Reference
When facing a marketing challenge, consider:
| Challenge | Relevant Models |
|-----------|-----------------|
| Low conversions | Hick's Law, Activation Energy, BJ Fogg, Friction |
| Price objections | Anchoring, Framing, Mental Accounting, Loss Aversion |
| Building trust | Authority, Social Proof, Reciprocity, Pratfall Effect |
| Increasing urgency | Scarcity, Loss Aversion, Zeigarnik Effect |
| Retention/churn | Endowment Effect, Switching Costs, Status-Quo Bias |
| Growth stalling | Theory of Constraints, Local vs Global Optima, Compounding |
| Decision paralysis | Paradox of Choice, Default Effect, Nudge Theory |
| Onboarding | Goal-Gradient, IKEA Effect, Commitment & Consistency |
---
## Task-Specific Questions
1. What specific behavior are you trying to influence?
2. What does your customer believe before encountering your marketing?
3. Where in the journey (awareness → consideration → decision) is this?
4. What's currently preventing the desired action?
5. Have you tested this with real customers?
---
## Related Skills
- **cro**: Apply psychology to page optimization
- **copywriting**: Write copy using psychological principles
- **popups**: Use triggers and psychology in popups
- **pricing-page optimization**: See cro for pricing psychology
- **ab-testing**: Test psychological hypotheses
FILE:evals/evals.json
{
"skill_name": "marketing-psychology",
"evals": [
{
"id": 1,
"prompt": "How can I use psychology to increase conversions on our pricing page? We sell a B2B SaaS tool with three tiers ($29, $79, $199/month).",
"expected_output": "Should check for product-marketing.md first. Should apply relevant pricing psychology models: anchoring (show the highest plan first or use a decoy), charm pricing (consider $29 vs $30), Rule of 100 (percentage vs dollar discounts), Good-Better-Best framing, loss aversion (show what they miss on lower tiers). Should also apply broader persuasion models: social proof near pricing, scarcity for limited-time offers, default effect (pre-select recommended plan). Should provide specific, actionable recommendations tied to their price points.",
"assertions": [
"Checks for product-marketing.md",
"Applies pricing psychology models (anchoring, charm pricing, Rule of 100)",
"Applies Good-Better-Best framing",
"Applies loss aversion to tier differentiation",
"Applies social proof near pricing",
"Provides specific recommendations for their price points",
"References specific mental models by name"
],
"files": []
},
{
"id": 2,
"prompt": "Explain the scarcity principle and how to use it ethically in SaaS marketing without being manipulative.",
"expected_output": "Should explain scarcity as a mental model (limited availability increases perceived value). Should provide legitimate SaaS applications: limited beta spots, early-bird pricing with real deadlines, limited-time feature access, cohort-based launches. Should distinguish ethical scarcity (real constraints) from manufactured urgency (fake countdown timers, artificial limits). Should provide specific examples and implementation guidance. Should reference related models (urgency, FOMO, loss aversion).",
"assertions": [
"Explains scarcity principle clearly",
"Provides legitimate SaaS applications",
"Distinguishes ethical from manipulative use",
"Provides specific examples",
"References related mental models",
"Addresses ethical considerations directly"
],
"files": []
},
{
"id": 3,
"prompt": "what psychological principles should I use to write better marketing copy?",
"expected_output": "Should trigger on casual phrasing. Should recommend copy-relevant mental models from the skill's taxonomy: social proof, reciprocity, loss aversion, anchoring, scarcity, IKEA Effect, Endowment Effect, Commitment & Consistency. For each principle, should explain what it is and provide a specific copywriting application. Should reference the quick reference table by challenge. Should organize by where in the copy each principle applies (headlines, body, CTAs, testimonials).",
"assertions": [
"Triggers on casual phrasing",
"Recommends copy-relevant mental models",
"Explains each principle briefly",
"Provides specific copywriting application per principle",
"Organizes by where each applies in copy",
"References multiple model categories"
],
"files": []
},
{
"id": 4,
"prompt": "I'm designing an onboarding flow and want to use behavioral psychology to increase activation. What models should I apply?",
"expected_output": "Should apply design and behavioral models from the skill's taxonomy: Goal-Gradient Effect (motivation increases near goal), Hick's Law (reduce choices), IKEA Effect (let users build something), Endowment Effect (let them experience ownership), Zeigarnik Effect (incomplete tasks drive completion), Commitment & Consistency (small asks first). Should explain how each applies to onboarding specifically. Should provide actionable recommendations for each model.",
"assertions": [
"Applies Goal-Gradient Effect",
"Applies Hick's Law",
"Applies IKEA Effect or Endowment Effect",
"Applies Zeigarnik Effect or commitment principles",
"Explains how each applies to onboarding",
"Provides actionable recommendations per model"
],
"files": []
},
{
"id": 5,
"prompt": "What's the psychology behind why free trials work better than freemium for some products?",
"expected_output": "Should apply relevant mental models: loss aversion (trial users fear losing access), endowment effect (they feel ownership after using), sunk cost (time invested during trial), Zero-Price Effect (free removes psychological barrier to start), status quo bias (inertia to keep what they have). Should explain how these models interact in trial vs freemium contexts. Should note when each model works best (trial for products with high activation effort, freemium for products with network effects).",
"assertions": [
"Applies loss aversion to trial context",
"Applies endowment effect",
"Applies Zero-Price Effect",
"Explains how models interact in trial vs freemium",
"Notes when each approach works best",
"Provides clear, educational explanation"
],
"files": []
},
{
"id": 6,
"prompt": "Help me run an A/B test on which psychological principle works better for our CTA — scarcity vs social proof.",
"expected_output": "Should recognize this is an A/B test setup task, not a psychology task. Should defer to or cross-reference the ab-testing skill for the experiment design. May provide psychological context on both principles to inform the hypothesis, but should make clear that ab-testing is the right skill for designing and running the experiment.",
"assertions": [
"Recognizes this as an A/B test setup task",
"References or defers to ab-testing skill",
"May provide psychological context for hypothesis",
"Does not attempt full test design using psychology patterns"
],
"files": []
}
]
}
Chuyên gia tuân thủ EU MDR 2017/745: phân loại thiết bị y tế, hồ sơ kỹ thuật, bằng chứng lâm sàng, giám sát sau thị trường và EUDAMED.
---
name: "mdr-745-specialist"
description: EU MDR 2017/745 compliance specialist for medical device classification, technical documentation, clinical evidence, and post-market surveillance. Covers Annex VIII classification rules, Annex II/III technical files, Annex XIV clinical evaluation, and EUDAMED integration.
triggers:
- MDR compliance
- EU MDR
- medical device classification
- Annex VIII
- technical documentation
- clinical evaluation
- PMCF
- EUDAMED
- UDI
- notified body
---
# MDR 2017/745 Specialist
EU MDR compliance patterns for medical device classification, technical documentation, and clinical evidence.
---
## Table of Contents
- [Device Classification Workflow](#device-classification-workflow)
- [Technical Documentation](#technical-documentation)
- [Clinical Evidence](#clinical-evidence)
- [Post-Market Surveillance](#post-market-surveillance)
- [EUDAMED and UDI](#eudamed-and-udi)
- [Reference Documentation](#reference-documentation)
- [Tools](#tools)
---
## Device Classification Workflow
Classify device under MDR Annex VIII:
1. Identify device duration (transient, short-term, long-term)
2. Determine invasiveness level (non-invasive, body orifice, surgical)
3. Assess body system contact (CNS, cardiac, other)
4. Check if active device (energy dependent)
5. Apply classification rules 1-22
6. For software, apply MDCG 2019-11 algorithm
7. Document classification rationale
8. **Validation:** Classification confirmed with Notified Body
### Classification Matrix
| Factor | Class I | Class IIa | Class IIb | Class III |
|--------|---------|-----------|-----------|-----------|
| Duration | Any | Short-term | Long-term | Long-term |
| Invasiveness | Non-invasive | Body orifice | Surgical | Implantable |
| System | Any | Non-critical | Critical organs | CNS/cardiac |
| Risk | Lowest | Low-medium | Medium-high | Highest |
### Software Classification (MDCG 2019-11)
| Information Use | Condition Severity | Class |
|-----------------|-------------------|-------|
| Informs decision | Non-serious | IIa |
| Informs decision | Serious | IIb |
| Drives/treats | Critical | III |
### Classification Examples
**Example 1: Absorbable Surgical Suture**
- Rule 8 (implantable, long-term)
- Duration: > 30 days (absorbed)
- Contact: General tissue
- Classification: **Class IIb**
**Example 2: AI Diagnostic Software**
- Rule 11 + MDCG 2019-11
- Function: Diagnoses serious condition
- Classification: **Class IIb**
**Example 3: Cardiac Pacemaker**
- Rule 8 (implantable)
- Contact: Central circulatory system
- Classification: **Class III**
---
## Technical Documentation
Prepare technical file per Annex II and III:
1. Create device description (variants, accessories, intended purpose)
2. Develop labeling (Article 13 requirements, IFU)
3. Document design and manufacturing process
4. Complete GSPR compliance matrix
5. Prepare benefit-risk analysis
6. Compile verification and validation evidence
7. Integrate risk management file (ISO 14971)
8. **Validation:** Technical file reviewed for completeness
### Technical File Structure
```
ANNEX II TECHNICAL DOCUMENTATION
├── Device description and UDI-DI
├── Label and instructions for use
├── Design and manufacturing info
├── GSPR compliance matrix
├── Benefit-risk analysis
├── Verification and validation
└── Clinical evaluation report
```
### GSPR Compliance Checklist
| Requirement | Evidence | Status |
|-------------|----------|--------|
| Safe design (GSPR 1-3) | Risk management file | ☐ |
| Chemical properties (GSPR 10.1) | Biocompatibility report | ☐ |
| Infection risk (GSPR 10.2) | Sterilization validation | ☐ |
| Software requirements (GSPR 17) | IEC 62304 documentation | ☐ |
| Labeling (GSPR 23) | Label artwork, IFU | ☐ |
### Conformity Assessment Routes
| Class | Route | NB Involvement |
|-------|-------|----------------|
| I | Annex II self-declaration | None |
| Is/Im | Annex II + IX/XI | Sterile/measuring aspects |
| IIa | Annex II + IX or XI | Product or QMS |
| IIb | Annex IX + X or X + XI | Type exam + production |
| III | Annex IX + X | Full QMS + type exam |
---
## Clinical Evidence
Develop clinical evidence strategy per Annex XIV:
1. Define clinical claims and endpoints
2. Conduct systematic literature search
3. Appraise clinical data quality
4. Assess equivalence (technical, biological, clinical)
5. Identify evidence gaps
6. Determine if clinical investigation required
7. Prepare Clinical Evaluation Report (CER)
8. **Validation:** CER reviewed by qualified evaluator
### Evidence Requirements by Class
| Class | Minimum Evidence | Investigation |
|-------|------------------|---------------|
| I | Risk-benefit analysis | Not typically required |
| IIa | Literature + post-market | May be required |
| IIb | Systematic literature review | Often required |
| III | Comprehensive clinical data | Required (Article 61) |
### Clinical Evaluation Report Structure
```
CER CONTENTS
├── Executive summary
├── Device scope and intended purpose
├── Clinical background (state of the art)
├── Literature search methodology
├── Data appraisal and analysis
├── Safety and performance conclusions
├── Benefit-risk determination
└── PMCF plan summary
```
### Qualified Evaluator Requirements
- Medical degree or equivalent healthcare qualification
- 4+ years clinical experience in relevant field
- Training in clinical evaluation methodology
- Understanding of MDR requirements
---
## Post-Market Surveillance
Establish PMS system per Chapter VII:
1. Develop PMS plan (Article 84)
2. Define data collection methods
3. Establish complaint handling procedures
4. Create vigilance reporting process
5. Plan Periodic Safety Update Reports (PSUR)
6. Integrate with PMCF activities
7. Define trend analysis and signal detection
8. **Validation:** PMS system audited annually
### PMS System Components
| Component | Requirement | Frequency |
|-----------|-------------|-----------|
| PMS Plan | Article 84 | Maintain current |
| PSUR | Class IIa and higher | Per class schedule |
| PMCF Plan | Annex XIV Part B | Update with CER |
| PMCF Report | Annex XIV Part B | Annual (Class III) |
| Vigilance | Articles 87-92 | As events occur |
### PSUR Schedule
| Class | Frequency |
|-------|-----------|
| Class III | Annual |
| Class IIb implantable | Annual |
| Class IIb | Every 2 years |
| Class IIa | When necessary |
### Serious Incident Reporting
| Timeline | Requirement |
|----------|-------------|
| 2 days | Serious public health threat |
| 10 days | Death or serious deterioration |
| 15 days | Other serious incidents |
---
## EUDAMED and UDI
Implement UDI system per Article 27:
1. Obtain issuing entity code (GS1, HIBCC, ICCBBA)
2. Assign UDI-DI to each device variant
3. Assign UDI-PI (production identifier)
4. Apply UDI carrier to labels (AIDC + HRI)
5. Register actor in EUDAMED
6. Register devices in EUDAMED
7. Upload certificates when available
8. **Validation:** UDI verified on sample labels
### EUDAMED Modules
| Module | Content | Actor |
|--------|---------|-------|
| Actor | Company registration | Manufacturer, AR |
| UDI/Device | Device and variant data | Manufacturer |
| Certificates | NB certificates | Notified Body |
| Clinical Investigation | Study registration | Sponsor |
| Vigilance | Incident reports | Manufacturer |
| Market Surveillance | Authority actions | Competent Authority |
### UDI Label Requirements
Required elements per Article 13:
- [ ] UDI-DI (device identifier)
- [ ] UDI-PI (production identifier) for Class II+
- [ ] AIDC format (barcode/RFID)
- [ ] HRI format (human-readable)
- [ ] Manufacturer name and address
- [ ] Lot/serial number
- [ ] Expiration date (if applicable)
---
## Reference Documentation
### MDR Classification Guide
`references/mdr-classification-guide.md` contains:
- Complete Annex VIII classification rules (Rules 1-22)
- Software classification per MDCG 2019-11
- Worked classification examples
- Conformity assessment route selection
### Clinical Evidence Requirements
`references/clinical-evidence-requirements.md` contains:
- Clinical evidence framework and hierarchy
- Literature search methodology
- Clinical Evaluation Report structure
- PMCF plan and evaluation report guidance
### Technical Documentation Templates
`references/technical-documentation-templates.md` contains:
- Annex II and III content requirements
- Design History File structure
- GSPR compliance matrix template
- Declaration of Conformity template
- Notified Body submission checklist
---
## Tools
### MDR Gap Analyzer
```bash
# Quick gap analysis
python scripts/mdr_gap_analyzer.py --device "Device Name" --class IIa
# JSON output for integration
python scripts/mdr_gap_analyzer.py --device "Device Name" --class III --output json
# Interactive assessment
python scripts/mdr_gap_analyzer.py --interactive
```
Analyzes device against MDR requirements, identifies compliance gaps, generates prioritized recommendations.
**Output includes:**
- Requirements checklist by category
- Gap identification with priorities
- Critical gap highlighting
- Compliance roadmap recommendations
---
## Notified Body Interface
### Selection Criteria
| Factor | Considerations |
|--------|----------------|
| Designation scope | Covers your device type |
| Capacity | Timeline for initial audit |
| Geographic reach | Markets you need to access |
| Technical expertise | Experience with your technology |
| Fee structure | Transparency, predictability |
### Pre-Submission Checklist
- [ ] Technical documentation complete
- [ ] GSPR matrix fully addressed
- [ ] Risk management file current
- [ ] Clinical evaluation report complete
- [ ] QMS (ISO 13485) certified
- [ ] Labeling and IFU finalized
- [ ] **Validation:** Internal gap assessment complete
FILE:references/clinical-evidence-requirements.md
# Clinical Evidence Requirements
MDR Annex XIV clinical evaluation and post-market clinical follow-up guidance.
---
## Table of Contents
- [Clinical Evidence Framework](#clinical-evidence-framework)
- [Clinical Evaluation Process](#clinical-evaluation-process)
- [Literature-Based Evidence](#literature-based-evidence)
- [Clinical Investigation Requirements](#clinical-investigation-requirements)
- [Post-Market Clinical Follow-up](#post-market-clinical-follow-up)
---
## Clinical Evidence Framework
### Evidence Hierarchy
| Evidence Type | Strength | When to Use |
|---------------|----------|-------------|
| Randomized Controlled Trial | Highest | Novel Class III, high-risk claims |
| Prospective cohort study | High | New technology, performance claims |
| Retrospective analysis | Medium | Established technology, equivalence |
| Literature review | Medium | Well-characterized, equivalent devices |
| Expert opinion | Low | Supportive only, not primary |
### Evidence Requirements by Class
| Class | Minimum Evidence | Clinical Investigation |
|-------|------------------|------------------------|
| I | Risk-benefit analysis | Not typically required |
| IIa | Literature + post-market data | May be required for novel tech |
| IIb | Systematic literature review | Often required for claims |
| III | Comprehensive clinical data | Required unless equivalent |
### Clinical Evidence Pathway
Determine evidence strategy:
1. Assess device classification and risk level
2. Evaluate claim significance (diagnostic, therapeutic)
3. Determine if equivalence can be demonstrated
4. Identify available literature and clinical data
5. Assess gaps requiring additional investigation
6. Develop PMCF plan for ongoing evidence
7. **Validation:** Evidence strategy approved by Notified Body
---
## Clinical Evaluation Process
### Clinical Evaluation Workflow
Execute clinical evaluation per Annex XIV Part A:
1. Identify relevant safety and performance data
2. Define scope and search strategy
3. Conduct systematic literature search
4. Appraise and analyze clinical data
5. Assess benefit-risk profile
6. Document conclusions in Clinical Evaluation Report (CER)
7. Plan post-market clinical follow-up
8. **Validation:** CER reviewed by qualified clinical evaluator
### Clinical Evaluation Report Structure
```
CLINICAL EVALUATION REPORT (CER)
├── 1. Executive Summary
│ ├── Device description and intended purpose
│ ├── Conclusions on safety and performance
│ └── Benefit-risk conclusion
├── 2. Scope of Clinical Evaluation
│ ├── Device identification
│ ├── Clinical claims to be evaluated
│ └── Equivalence assessment (if applicable)
├── 3. Clinical Background
│ ├── Disease/condition overview
│ ├── Current treatment options
│ └── State of the art
├── 4. Clinical Data Sources
│ ├── Pre-clinical data (bench, animal)
│ ├── Clinical investigation data
│ ├── Literature search methodology
│ └── Post-market surveillance data
├── 5. Data Appraisal
│ ├── Study quality assessment
│ ├── Relevance to subject device
│ └── Data contribution to evaluation
├── 6. Data Analysis
│ ├── Safety analysis
│ ├── Performance analysis
│ └── Benefit-risk determination
├── 7. Conclusions
│ ├── Clinical evidence summary
│ ├── Residual risks
│ └── PMCF requirements
└── 8. PMCF Plan Summary
├── Data gaps identified
├── PMCF activities planned
└── Update schedule
```
### Qualified Clinical Evaluator
Requirements per Annex XIV:
- Medical degree or equivalent healthcare qualification
- 4+ years clinical experience in relevant field OR
- Research background in relevant domain
- Training in clinical evaluation methodology
- Understanding of MDR requirements
---
## Literature-Based Evidence
### Literature Search Strategy
Execute systematic literature review:
1. Define PICO question (Population, Intervention, Comparison, Outcome)
2. Develop search string with Boolean operators
3. Select databases (PubMed, Embase, Cochrane, etc.)
4. Set date range and language filters
5. Execute search and document results
6. Screen abstracts and full texts
7. **Validation:** Reproducible search, documented exclusion criteria
### Database Selection
| Database | Coverage | Best For |
|----------|----------|----------|
| PubMed/MEDLINE | Biomedical literature | Primary clinical data |
| Embase | Drugs, devices, biomedical | European studies |
| Cochrane Library | Systematic reviews | Meta-analyses |
| CINAHL | Nursing, allied health | User studies |
| IEEE Xplore | Engineering | Technical performance |
| Manufacturer data | Proprietary | Direct device data |
### Data Appraisal Criteria
Evaluate each source:
| Criterion | Assessment | Score |
|-----------|------------|-------|
| Study design | RCT > cohort > case series | 1-5 |
| Sample size | Statistical power adequate | 1-5 |
| Follow-up duration | Sufficient for outcomes | 1-5 |
| Population relevance | Matches IFU population | 1-5 |
| Device equivalence | Technical, biological, clinical | 1-5 |
| Bias risk | Low/medium/high | 1-5 |
### Equivalence Assessment
Demonstrate equivalence per MDCG 2020-5:
**Technical equivalence:**
- Similar design
- Same materials
- Same specifications
- Same manufacturing process
**Biological equivalence:**
- Same tissue contact
- Same biocompatibility
- Same sterilization method
**Clinical equivalence:**
- Same intended purpose
- Same clinical condition
- Same patient population
- Same user (professional/lay)
---
## Clinical Investigation Requirements
### When Investigation is Required
Clinical investigation mandatory:
- [ ] Class III implantable devices (Article 61(4))
- [ ] Novel technology without equivalent
- [ ] Significant modification to existing device
- [ ] New clinical claims not supported by literature
- [ ] Addressing gaps identified in clinical evaluation
### Clinical Investigation Workflow
Conduct clinical investigation:
1. Develop Clinical Investigation Plan (CIP)
2. Submit to Ethics Committee for approval
3. Notify Competent Authority (via EUDAMED)
4. Conduct investigation per GCP principles
5. Collect and analyze clinical data
6. Prepare Clinical Investigation Report
7. Submit serious adverse event reports (within 7-15 days)
8. **Validation:** All subjects completed, data lock achieved
### Clinical Investigation Plan Elements
| Section | Content |
|---------|---------|
| Objectives | Primary and secondary endpoints |
| Design | Randomized, controlled, blinded |
| Population | Inclusion/exclusion criteria |
| Sample size | Statistical justification |
| Procedures | Visit schedule, assessments |
| Endpoints | Safety and performance measures |
| Analysis | Statistical methods |
| Safety | Adverse event definitions, reporting |
### Ethics Committee Submission
Required documentation:
- Clinical Investigation Plan
- Investigator's Brochure
- Informed consent documents
- Case Report Forms
- Investigator CVs
- Insurance certificate
- Device documentation
---
## Post-Market Clinical Follow-up
### PMCF Plan Requirements
Develop PMCF Plan per Annex XIV Part B:
1. Identify residual risks from clinical evaluation
2. Define clinical questions to address
3. Select PMCF methods (survey, registry, study)
4. Specify endpoints and success criteria
5. Define timeline and milestones
6. Plan data collection and analysis
7. Schedule PMCF Evaluation Report updates
8. **Validation:** PMCF Plan approved by Notified Body
### PMCF Methods
| Method | Description | Best For |
|--------|-------------|----------|
| Literature review | Ongoing systematic search | Mature devices |
| Survey | User/patient questionnaire | Real-world experience |
| Registry | Multi-site data collection | Long-term outcomes |
| PMCF study | Prospective clinical study | Specific questions |
| Complaint analysis | Structured complaint review | Safety signals |
| Vigilance data | MAUDE, EUDAMED analysis | Comparative safety |
### PMCF Evaluation Report
Update frequency:
| Device Class | Update Frequency |
|--------------|------------------|
| Class III | Annual |
| Class IIb implantable | Annual |
| Class IIb | Every 2 years |
| Class IIa | Every 2-5 years |
| Class I | When clinically relevant |
### PMCF Report Structure
```
PMCF EVALUATION REPORT
├── 1. Executive Summary
│ └── Key findings and conclusions
├── 2. Scope
│ ├── Device covered
│ └── Reporting period
├── 3. PMCF Activities
│ ├── Methods employed
│ └── Data sources
├── 4. Results
│ ├── Safety data
│ ├── Performance data
│ └── Clinical questions addressed
├── 5. Conclusions
│ ├── Benefit-risk confirmation
│ ├── Residual risks updated
│ └── Need for corrective action
└── 6. Next Steps
├── CER update requirements
└── PMCF Plan modifications
```
### Integration with CER
PMCF data feeds clinical evaluation:
1. PMCF data collected per plan
2. Data analyzed in PMCF Evaluation Report
3. CER updated with PMCF conclusions
4. Risk management file updated
5. IFU updated if needed
6. **Validation:** CER update cycle completed
FILE:references/mdr-classification-guide.md
# MDR Device Classification Guide
EU MDR 2017/745 Annex VIII classification rules and decision framework.
---
## Table of Contents
- [Classification Overview](#classification-overview)
- [Classification Rules](#classification-rules)
- [Software Classification (MDCG 2019-11)](#software-classification)
- [Classification Examples](#classification-examples)
- [Conformity Assessment Routes](#conformity-assessment-routes)
---
## Classification Overview
### Risk Class Hierarchy
| Class | Risk Level | Examples | NB Required |
|-------|------------|----------|-------------|
| I | Lowest | Bandages, wheelchairs, stethoscopes | No (self-certification) |
| IIa | Low-Medium | Hearing aids, dental filling materials | Yes |
| IIb | Medium-High | Ventilators, blood bags, implantable sutures | Yes |
| III | Highest | Pacemakers, heart valves, hip implants | Yes |
### Classification Factors
Determine class based on:
1. **Duration of contact:**
- Transient: < 60 minutes
- Short-term: 60 min to 30 days
- Long-term: > 30 days
2. **Degree of invasiveness:**
- Non-invasive
- Invasive via body orifice
- Surgically invasive
- Implantable
3. **Body system interaction:**
- Central circulatory system
- Central nervous system
- Other organ systems
4. **Active vs. passive:**
- Active devices (energy dependent)
- Passive devices
---
## Classification Rules
### Non-Invasive Devices (Rules 1-4)
**Rule 1 - General non-invasive:**
- Class I (unless covered by other rules)
- Example: Wheelchairs, hospital beds, collection devices
**Rule 2 - Channeling or storing:**
- Class IIa: Blood bags, transfusion sets (>60 min contact)
- Class IIb: Blood storage, organ storage
- Class I: Simple channeling (gravity, IV bag without additives)
**Rule 3 - Modifying biological composition:**
- Class IIa: Filters, gas separators, dialysis filters
- Class IIb: Blood filtration, exchange transfusion
**Rule 4 - Contact with injured skin:**
- Class I: Wound dressings for superficial wounds
- Class IIa: Wounds in dermis requiring secondary intent healing
- Class IIb: Severe wounds, chronic wounds, burns
### Invasive Devices (Rules 5-8)
**Rule 5 - Body orifice invasive (transient):**
- Class I: Transient use, non-surgically invasive
- Class IIa: Short-term use
- Class IIb: Long-term use in oral cavity
**Rule 6 - Surgically invasive (transient):**
- Class IIa: Transient use
- Exception Class I: Reusable surgical instruments
**Rule 7 - Surgically invasive (short-term):**
- Class IIa: Short-term (< 30 days)
- Class IIb: Central circulatory or CNS contact
- Class III: Chemical change or drug delivery
**Rule 8 - Implantable and long-term surgically invasive:**
- Class IIb: General implants
- Class III: Heart, CNS, spine contact; drug delivery; biological origin
### Active Devices (Rules 9-13)
**Rule 9 - Active therapeutic devices:**
- Class IIa: Exchange or admin of energy (non-hazardous)
- Class IIb: Potentially hazardous energy levels
**Rule 10 - Active diagnostic devices:**
- Class IIa: Supply energy for imaging, monitoring
- Class IIb: Monitor vital physiological parameters
**Rule 11 - Software:**
- Class IIa: Information for diagnostic/therapeutic decisions (non-serious)
- Class IIb: Decisions that could cause death/irreversible deterioration
- Class III: Decisions with immediate risk to life
- See MDCG 2019-11 for detailed algorithm
**Rule 12 - Active devices administering substances:**
- Class IIa: Non-hazardous manner
- Class IIb: Potentially hazardous manner
**Rule 13 - Other active devices:**
- Class I: All other active devices
### Special Rules (Rules 14-22)
**Rule 14 - Contraception/STI prevention:**
- Class IIb: Contraceptive devices
- Class III: Implantable contraceptives
**Rule 15 - Disinfection/sterilization:**
- Class IIa: Disinfection of devices
- Class IIb: Disinfection of invasive devices
**Rule 16 - X-ray diagnostic recording:**
- Class IIa: Recording media for x-ray
**Rule 17 - Devices with nanomaterials:**
- Class III: High internal exposure potential
- Class IIb: Medium exposure
- Class IIa: Low exposure
**Rule 18 - Blood/plasma derivatives:**
- Class III: Utilizing blood derivatives
**Rule 19 - Drug delivery systems:**
- Class III: Integral drug administration
**Rule 20 - Breath analyzers for anesthesia:**
- Class IIb: Breath analyzers
**Rule 21 - Medicinal substance devices:**
- Class III: Incorporating medicinal substances
**Rule 22 - Closed-loop therapeutic systems:**
- Class III: Closed-loop systems
---
## Software Classification
### MDCG 2019-11 Decision Algorithm
Execute software classification:
1. Determine if software qualifies as medical device
2. Identify significance of information to healthcare decision
3. Assess healthcare situation or patient condition
4. Apply rule 11 based on severity
5. **Validation:** Classification rationale documented with MDCG reference
### Software Classification Matrix
| Information Significance | Situation/Condition | Class |
|--------------------------|---------------------|-------|
| Informs clinical management | Non-serious | IIa |
| Informs clinical management | Serious | IIb |
| Informs clinical management | Critical | III |
| Drives clinical management | Non-serious | IIa |
| Drives clinical management | Serious | IIb |
| Drives clinical management | Critical | III |
| Treats or diagnoses | Non-serious | IIa |
| Treats or diagnoses | Serious | IIb |
| Treats or diagnoses | Critical | III |
### Software Examples
| Software Type | Class | Rationale |
|---------------|-------|-----------|
| Patient record viewing | Not MD | Administrative, not clinical |
| Medication reminder app | Class I | General wellness |
| Blood glucose monitor app | Class IIa | Informs non-serious decisions |
| Sepsis detection algorithm | Class IIb | Informs serious condition |
| AI tumor detection | Class III | Diagnoses critical condition |
| Closed-loop insulin delivery | Class III | Treats critical condition |
---
## Classification Examples
### Example 1: Surgical Suture (Absorbable)
```
Device: Absorbable suture for internal wound closure
Analysis:
- Invasiveness: Surgically invasive
- Duration: Long-term (absorbed over > 30 days)
- System: General tissue (not CNS, not cardiac)
- Rule Applied: Rule 8 (implantable, long-term)
Classification: Class IIb
Rationale: Implantable device > 30 days, general tissue
Conformity Route: Annex IX (Type examination) + Annex XI
```
### Example 2: Blood Pressure Monitor
```
Device: Home blood pressure monitoring device
Analysis:
- Active: Yes (electronic measurement)
- Function: Monitoring vital physiological parameter
- Risk: Non-immediate (home use, not ICU)
- Rule Applied: Rule 10 (active diagnostic)
Classification: Class IIa
Rationale: Monitors vital parameter, non-critical setting
Conformity Route: Annex IX or XI (QMS + product verification)
```
### Example 3: Hip Implant
```
Device: Total hip replacement prosthesis
Analysis:
- Invasiveness: Surgically invasive, implantable
- Duration: Long-term (permanent)
- System: Musculoskeletal
- Rule Applied: Rule 8 (implantable, long-term)
Classification: Class III
Rationale: Implantable > 30 days in direct contact with bone
Conformity Route: Annex IX + Annex X (full QMS + type examination)
```
### Example 4: Diagnostic Software (AI)
```
Device: AI-based chest X-ray analysis for pneumonia detection
Analysis:
- Software: Qualifies as medical device (clinical decision)
- Information: Diagnoses condition
- Condition: Serious (pneumonia can be life-threatening)
- Rule Applied: Rule 11 + MDCG 2019-11
Classification: Class IIb
Rationale: Software diagnosing serious condition
Conformity Route: Annex IX or Annex XI
```
---
## Conformity Assessment Routes
### By Device Class
| Class | Conformity Route | NB Involvement |
|-------|------------------|----------------|
| I | Annex II (self-declaration) | None |
| I (sterile/measuring) | Annex II + IX/XI | Sterile/measuring aspects |
| IIa | Annex II + IX or XI | Product verification or QMS |
| IIb | Annex IX + X or Annex X + XI | Type exam + QMS or production |
| III | Annex IX + X | Full QMS + type examination |
### Annex Reference
| Annex | Content | Purpose |
|-------|---------|---------|
| II | Technical documentation | Required for all classes |
| III | Technical documentation (additions) | Class III additions |
| IX | Conformity assessment (QMS) | Quality management route |
| X | Type examination | Product design examination |
| XI | Product verification | Production quality checks |
### Decision Workflow
Select conformity route:
1. Determine device classification (Rules 1-22)
2. Identify applicable annexes for class
3. Evaluate QMS maturity (Annex IX capability)
4. Consider production volume (batch vs. mass)
5. Assess Notified Body capacity and timeline
6. Select optimal conformity assessment route
7. **Validation:** Route confirmed with Notified Body consultation
FILE:references/technical-documentation-templates.md
# Technical Documentation Templates
MDR Annex II and III technical file structure and content requirements.
---
## Table of Contents
- [Technical Documentation Overview](#technical-documentation-overview)
- [Annex II Requirements](#annex-ii-requirements)
- [Annex III Additions](#annex-iii-additions)
- [Document Templates](#document-templates)
- [Notified Body Expectations](#notified-body-expectations)
---
## Technical Documentation Overview
### Documentation Hierarchy
```
TECHNICAL DOCUMENTATION
├── Device Description and Specification
├── Information Supplied by Manufacturer
├── Design and Manufacturing Information
├── General Safety and Performance Requirements
├── Benefit-Risk Analysis
├── Product Verification and Validation
├── Clinical Evaluation Report
└── Post-Market Surveillance Documentation
```
### Documentation by Phase
| Phase | Required Documents |
|-------|-------------------|
| Design Input | User needs, design requirements, regulatory requirements |
| Design Development | Design specifications, drawings, BOM, software docs |
| Verification | Test protocols, test reports, design review records |
| Validation | Clinical data, usability data, biocompatibility |
| Transfer | Manufacturing specs, process validations |
| Post-Market | PMS plan, PMCF plan, vigilance procedures |
---
## Annex II Requirements
### Section 1: Device Description and Specification
**1.1 Device Identification**
```
DEVICE IDENTIFICATION
├── Trade name(s)
├── General description of the device
├── Basic UDI-DI
├── Device identifier codes (internal + regulatory)
├── Intended purpose statement
├── Indications for use
├── Contraindications
├── Target population (patient, user)
├── Medical conditions intended to diagnose/treat
└── Principles of operation
```
**1.2 Device Variants and Accessories**
| Element | Description |
|---------|-------------|
| Variant listing | All variants with identifiers |
| Configuration differences | Technical differences by variant |
| Accessories | Separate devices used together |
| Spare parts | Replaceable components |
**1.3 Reference to Previous Generations**
- Previous generation device identification
- Key modifications summary
- Clinical experience from prior device
- Justification for changes
### Section 2: Information Supplied by Manufacturer
**2.1 Label Requirements**
Mandatory label elements per Article 13:
- [ ] Device name or trade name
- [ ] Manufacturer name and address
- [ ] Authorized representative (if applicable)
- [ ] Lot/batch number or serial number
- [ ] UDI carrier (AIDC + HRI)
- [ ] Expiration date (if applicable)
- [ ] Storage/handling conditions
- [ ] Warnings and precautions
- [ ] CE mark with NB number (if applicable)
- [ ] Symbol meanings per EN ISO 15223-1
**2.2 Instructions for Use**
IFU structure:
```
INSTRUCTIONS FOR USE
├── 1. Device Description
│ ├── Intended purpose
│ ├── Indications and contraindications
│ └── Principle of operation
├── 2. Warnings and Precautions
│ ├── Contraindicated uses
│ ├── Potential complications
│ └── Drug/device interactions
├── 3. User Instructions
│ ├── Unpacking and inspection
│ ├── Setup/installation
│ ├── Operating procedures
│ └── Cleaning/maintenance
├── 4. Technical Specifications
│ ├── Physical characteristics
│ ├── Performance characteristics
│ └── Environmental limits
├── 5. Troubleshooting
│ ├── Error codes/messages
│ └── Corrective actions
└── 6. Symbols Glossary
```
### Section 3: Design and Manufacturing Information
**3.1 Design Process Documentation**
| Document | Purpose |
|----------|---------|
| Design input | User needs, regulatory requirements |
| Design output | Specifications, drawings, software |
| Design review | Review records at key milestones |
| Design verification | Test protocols and results |
| Design validation | Clinical/usability evidence |
| Design transfer | Manufacturing readiness |
| Design changes | Change control records |
**3.2 Manufacturing Process Description**
```
MANUFACTURING DOCUMENTATION
├── Process flow diagram
├── Manufacturing specifications
├── Facility and equipment qualification
├── Process validation protocols/reports
├── Environmental monitoring
├── Personnel training records
├── In-process controls
├── Final inspection/testing
├── Sterilization validation (if applicable)
└── Packaging validation
```
**3.3 Supplier and Subcontractor Information**
- Approved supplier list
- Supplier qualification records
- Critical component specifications
- Incoming inspection procedures
- Supplier audit records
### Section 4: General Safety and Performance Requirements
**GSPR Compliance Checklist**
| GSPR | Requirement | Evidence |
|------|-------------|----------|
| 1 | Safe design for intended use | Risk management file |
| 2 | Risk acceptable when weighed against benefits | Benefit-risk analysis |
| 3 | State of the art design | Literature review, standards |
| 4 | No compromise of clinical condition | Clinical evaluation |
| 5 | Transport and storage conditions | Shelf life testing |
| 6 | Acceptable undesirable effects | Risk-benefit analysis |
| 7 | CE marking conformity | Declaration of conformity |
| ... | Continue for all applicable GSPRs | |
**GSPR Matrix Template**
| GSPR # | Requirement Summary | Applicable? | Evidence Document | Status |
|--------|---------------------|-------------|-------------------|--------|
| 10.1 | Chemical properties | Yes/No/NA | Biocompatibility report | Complete |
| 10.2 | Infection risk | Yes/No/NA | Sterilization validation | Complete |
| 10.3 | Substances with carcinogenic risk | Yes/No/NA | Material specification | Complete |
### Section 5: Benefit-Risk Analysis
**Benefit-Risk Documentation**
```
BENEFIT-RISK ANALYSIS
├── 1. Intended Benefits
│ ├── Direct therapeutic benefits
│ ├── Diagnostic accuracy improvements
│ └── Patient outcome benefits
├── 2. Known Risks
│ ├── Identified hazards (from risk analysis)
│ ├── Risk control measures implemented
│ └── Residual risks
├── 3. Benefit-Risk Determination
│ ├── Qualitative analysis
│ ├── Quantitative analysis (if available)
│ └── Comparison to alternatives
└── 4. Conclusion
├── Acceptability statement
└── Justification for residual risks
```
### Section 6: Product Verification and Validation
**6.1 Verification Testing**
| Test Category | Standards | Documentation |
|---------------|-----------|---------------|
| Electrical safety | IEC 60601-1 | Test protocol + report |
| EMC | IEC 60601-1-2 | EMC test report |
| Biocompatibility | ISO 10993 series | Biocompatibility evaluation |
| Software | IEC 62304 | Software verification report |
| Sterilization | ISO 11135/11137 | Sterility assurance |
| Packaging | ISO 11607 | Packaging validation |
| Shelf life | Accelerated aging | Stability study report |
| Usability | IEC 62366-1 | Usability engineering file |
**6.2 Validation Evidence**
- Clinical investigation data
- Literature-based clinical evidence
- Simulated use testing
- User feedback/complaint analysis
- Post-market surveillance data
---
## Annex III Additions
### Class III Specific Requirements
Additional documentation for Class III devices:
**Implant-Specific Requirements**
- Implant card information
- Patient information leaflet
- Device tracking procedures
- Explant analysis capability
**Drug-Device Combination**
- Drug substance specification
- Drug compatibility testing
- Combined product assessment
- Pharmacovigilance interface
---
## Document Templates
### Design History File Index
```
DESIGN HISTORY FILE (DHF)
Document ID: DHF-[Product]-[Rev]
1. DESIGN INPUT
1.1 User Requirements Specification (URS)
1.2 Regulatory Requirements Matrix
1.3 Design Input Review Record
2. DESIGN OUTPUT
2.1 Product Specification
2.2 Engineering Drawings
2.3 Bill of Materials
2.4 Software Documentation
3. DESIGN VERIFICATION
3.1 Verification Test Plan
3.2 Verification Test Reports
3.3 Traceability Matrix
4. DESIGN VALIDATION
4.1 Clinical Evaluation Report
4.2 Usability Engineering File
4.3 Biocompatibility Evaluation
5. DESIGN TRANSFER
5.1 Manufacturing Procedures
5.2 Process Validation Reports
5.3 Supplier Qualification
6. DESIGN REVIEWS
6.1 Design Review Records
6.2 Risk Management Review
6.3 Final Design Release
```
### Declaration of Conformity Template
```
EU DECLARATION OF CONFORMITY
We, [Manufacturer Name]
Address: [Full address]
declare under our sole responsibility that the device:
Device name: [Trade name]
Device description: [Description]
Basic UDI-DI: [UDI-DI]
Classification: [Class I/IIa/IIb/III]
is in conformity with the provisions of:
- Regulation (EU) 2017/745
Applicable standards:
- [List harmonized standards]
Notified Body: [NB name and number] (if applicable)
Certificate number: [Certificate number]
Place and date: [Location, Date]
Signature: [Authorized signatory]
Name and function: [Name, Title]
```
---
## Notified Body Expectations
### Common NB Findings
| Finding Area | Common Issue | Prevention |
|--------------|--------------|------------|
| GSPR matrix | Incomplete, no evidence links | Complete matrix with references |
| Risk management | Not integrated with design | Update throughout development |
| Clinical evaluation | Insufficient literature search | Systematic search with PICO |
| IFU | Missing warnings | Risk-based IFU content |
| Traceability | Design to requirements gaps | Maintain traceability matrix |
### Pre-Submission Checklist
Before Notified Body submission:
- [ ] Technical documentation complete
- [ ] GSPR checklist fully addressed
- [ ] Risk management file current
- [ ] Clinical evaluation report complete
- [ ] QMS documentation ready
- [ ] Design verification complete
- [ ] Design validation complete
- [ ] Labeling and IFU finalized
- [ ] Declaration of conformity prepared
- [ ] **Validation:** Internal review completed
FILE:scripts/mdr_gap_analyzer.py
#!/usr/bin/env python3
"""
MDR Gap Analyzer - EU MDR 2017/745 Compliance Gap Assessment Tool
Analyzes device classification, identifies documentation gaps, and generates
compliance roadmap for EU MDR transition.
Usage:
python mdr_gap_analyzer.py --device "Device Name" --class IIa
python mdr_gap_analyzer.py --device "Device Name" --class III --output json
python mdr_gap_analyzer.py --interactive
"""
import argparse
import json
import sys
from dataclasses import dataclass, field, asdict
from datetime import datetime
from typing import List, Dict, Optional
from enum import Enum
class DeviceClass(Enum):
I = "I"
I_STERILE = "Is"
I_MEASURING = "Im"
IIA = "IIa"
IIB = "IIb"
III = "III"
class GapStatus(Enum):
NOT_STARTED = "Not Started"
IN_PROGRESS = "In Progress"
COMPLETE = "Complete"
NOT_APPLICABLE = "N/A"
@dataclass
class GapItem:
requirement: str
category: str
description: str
status: GapStatus = GapStatus.NOT_STARTED
priority: str = "Medium"
evidence_needed: List[str] = field(default_factory=list)
notes: str = ""
@dataclass
class GapAnalysisResult:
device_name: str
device_class: str
analysis_date: str
total_requirements: int
gaps_identified: int
completion_percentage: float
gaps: List[Dict]
recommendations: List[str]
critical_gaps: List[str]
class MDRGapAnalyzer:
"""Analyzer for EU MDR 2017/745 compliance gaps."""
# MDR Requirements by category
REQUIREMENTS = {
"technical_documentation": [
GapItem(
requirement="Annex II - Device Description",
category="Technical Documentation",
description="Complete device description including variants, accessories, intended purpose",
priority="High",
evidence_needed=["Device specification", "Intended purpose statement", "Variant listing"]
),
GapItem(
requirement="Annex II - Information Supplied",
category="Technical Documentation",
description="Label and IFU meeting Article 13 requirements",
priority="High",
evidence_needed=["Label artwork", "Instructions for use", "Symbol glossary"]
),
GapItem(
requirement="Annex II - Design and Manufacturing",
category="Technical Documentation",
description="Design history file and manufacturing documentation",
priority="High",
evidence_needed=["Design history file", "Process flow diagram", "Validation reports"]
),
GapItem(
requirement="Annex II - GSPR Compliance",
category="Technical Documentation",
description="General Safety and Performance Requirements checklist",
priority="Critical",
evidence_needed=["GSPR matrix", "Standard compliance evidence", "Risk management file"]
),
],
"clinical_evaluation": [
GapItem(
requirement="Annex XIV Part A - Clinical Evaluation",
category="Clinical Evaluation",
description="Clinical evaluation report with systematic literature review",
priority="Critical",
evidence_needed=["Clinical evaluation report", "Literature search protocol", "Data appraisal"]
),
GapItem(
requirement="Annex XIV Part B - PMCF",
category="Clinical Evaluation",
description="Post-market clinical follow-up plan and evaluation report",
priority="High",
evidence_needed=["PMCF plan", "PMCF evaluation report", "Residual risk assessment"]
),
GapItem(
requirement="Qualified Person for CER",
category="Clinical Evaluation",
description="Clinical evaluation by qualified evaluator per Annex XIV",
priority="High",
evidence_needed=["Evaluator CV", "Qualification evidence", "Signed CER"]
),
],
"risk_management": [
GapItem(
requirement="ISO 14971 Risk Management",
category="Risk Management",
description="Complete risk management file per ISO 14971:2019",
priority="Critical",
evidence_needed=["Risk management plan", "Risk analysis", "Risk evaluation", "Risk control"]
),
GapItem(
requirement="Benefit-Risk Analysis",
category="Risk Management",
description="Documented benefit-risk determination",
priority="High",
evidence_needed=["Benefit-risk analysis document", "Residual risk acceptability"]
),
],
"quality_management": [
GapItem(
requirement="ISO 13485 QMS",
category="Quality Management",
description="Quality management system conforming to ISO 13485:2016",
priority="Critical",
evidence_needed=["QMS manual", "Process documentation", "Internal audit records"]
),
GapItem(
requirement="Post-Market Surveillance",
category="Quality Management",
description="PMS system per Article 83-86",
priority="High",
evidence_needed=["PMS plan", "PSUR (if required)", "Vigilance procedures"]
),
],
"udi_eudamed": [
GapItem(
requirement="UDI System",
category="UDI/EUDAMED",
description="Unique Device Identification per Article 27",
priority="High",
evidence_needed=["UDI-DI assignment", "Label with UDI carrier", "GUDID/EUDAMED registration"]
),
GapItem(
requirement="EUDAMED Registration",
category="UDI/EUDAMED",
description="Actor, device, and certificate registration in EUDAMED",
priority="Medium",
evidence_needed=["Actor registration", "Device registration", "Certificate upload"]
),
],
"notified_body": [
GapItem(
requirement="Notified Body Selection",
category="Notified Body",
description="Selection and engagement of MDR-designated Notified Body",
priority="Critical",
evidence_needed=["NB selection criteria", "NB engagement letter", "Audit schedule"]
),
GapItem(
requirement="Conformity Assessment",
category="Notified Body",
description="Completion of appropriate conformity assessment procedure",
priority="Critical",
evidence_needed=["Application dossier", "Technical documentation submission", "Certificate"]
),
],
}
# Class-specific requirements
CLASS_REQUIREMENTS = {
DeviceClass.III: [
GapItem(
requirement="Annex III - Class III Additions",
category="Technical Documentation",
description="Additional documentation for Class III devices",
priority="Critical",
evidence_needed=["Implant card", "Patient information", "Device tracking"]
),
GapItem(
requirement="Clinical Investigation",
category="Clinical Evaluation",
description="Clinical investigation per Article 61 (unless equivalent device)",
priority="Critical",
evidence_needed=["Clinical investigation plan", "Ethics approval", "Clinical study report"]
),
],
DeviceClass.IIB: [
GapItem(
requirement="Implantable Device Documentation",
category="Technical Documentation",
description="Additional requirements for implantable Class IIb devices",
priority="High",
evidence_needed=["Implant card (if implantable)", "Long-term safety data"]
),
],
}
def __init__(self, device_name: str, device_class: DeviceClass):
self.device_name = device_name
self.device_class = device_class
self.gaps: List[GapItem] = []
self._build_requirements_list()
def _build_requirements_list(self):
"""Build complete requirements list based on device class."""
# Add all base requirements
for category_gaps in self.REQUIREMENTS.values():
for gap in category_gaps:
self.gaps.append(GapItem(
requirement=gap.requirement,
category=gap.category,
description=gap.description,
priority=gap.priority,
evidence_needed=gap.evidence_needed.copy()
))
# Add class-specific requirements
if self.device_class in self.CLASS_REQUIREMENTS:
for gap in self.CLASS_REQUIREMENTS[self.device_class]:
self.gaps.append(GapItem(
requirement=gap.requirement,
category=gap.category,
description=gap.description,
priority=gap.priority,
evidence_needed=gap.evidence_needed.copy()
))
# Class I self-certification: NB not required
if self.device_class == DeviceClass.I:
for gap in self.gaps:
if gap.category == "Notified Body":
gap.status = GapStatus.NOT_APPLICABLE
def update_gap_status(self, requirement: str, status: GapStatus, notes: str = ""):
"""Update status of a specific gap."""
for gap in self.gaps:
if gap.requirement == requirement:
gap.status = status
gap.notes = notes
break
def analyze(self) -> GapAnalysisResult:
"""Perform gap analysis and generate results."""
applicable_gaps = [g for g in self.gaps if g.status != GapStatus.NOT_APPLICABLE]
complete_gaps = [g for g in applicable_gaps if g.status == GapStatus.COMPLETE]
completion = (len(complete_gaps) / len(applicable_gaps) * 100) if applicable_gaps else 0
# Identify critical gaps
critical_gaps = [
g.requirement for g in applicable_gaps
if g.priority == "Critical" and g.status != GapStatus.COMPLETE
]
# Generate recommendations
recommendations = self._generate_recommendations()
return GapAnalysisResult(
device_name=self.device_name,
device_class=self.device_class.value,
analysis_date=datetime.now().isoformat(),
total_requirements=len(applicable_gaps),
gaps_identified=len(applicable_gaps) - len(complete_gaps),
completion_percentage=round(completion, 1),
gaps=[{
"requirement": g.requirement,
"category": g.category,
"status": g.status.value,
"priority": g.priority,
"evidence_needed": g.evidence_needed
} for g in applicable_gaps],
recommendations=recommendations,
critical_gaps=critical_gaps
)
def _generate_recommendations(self) -> List[str]:
"""Generate prioritized recommendations."""
recommendations = []
# Check for critical gaps
critical_incomplete = [
g for g in self.gaps
if g.priority == "Critical" and g.status not in [GapStatus.COMPLETE, GapStatus.NOT_APPLICABLE]
]
if critical_incomplete:
recommendations.append(
f"CRITICAL: {len(critical_incomplete)} critical requirements not complete. "
"Address immediately to proceed with conformity assessment."
)
# Check clinical evaluation
cer_gap = next((g for g in self.gaps if "Clinical Evaluation" in g.requirement), None)
if cer_gap and cer_gap.status != GapStatus.COMPLETE:
recommendations.append(
"Clinical Evaluation Report (CER) is incomplete. "
"This is required before Notified Body submission."
)
# Check for Class III specific
if self.device_class == DeviceClass.III:
ci_gap = next((g for g in self.gaps if "Clinical Investigation" in g.requirement), None)
if ci_gap and ci_gap.status != GapStatus.COMPLETE:
recommendations.append(
"Class III device requires clinical investigation per Article 61 "
"unless equivalence can be demonstrated."
)
# Check EUDAMED
udi_gap = next((g for g in self.gaps if "UDI System" in g.requirement), None)
if udi_gap and udi_gap.status != GapStatus.COMPLETE:
recommendations.append(
"Implement UDI system and plan for EUDAMED registration. "
"Required for placing device on EU market."
)
return recommendations
def format_text_output(result: GapAnalysisResult) -> str:
"""Format analysis result as text."""
lines = [
"=" * 60,
"MDR 2017/745 GAP ANALYSIS REPORT",
"=" * 60,
f"Device: {result.device_name}",
f"Class: {result.device_class}",
f"Date: {result.analysis_date[:10]}",
"",
"-" * 60,
"SUMMARY",
"-" * 60,
f"Total Requirements: {result.total_requirements}",
f"Gaps Identified: {result.gaps_identified}",
f"Completion: {result.completion_percentage}%",
"",
]
if result.critical_gaps:
lines.extend([
"-" * 60,
"CRITICAL GAPS (Address Immediately)",
"-" * 60,
])
for gap in result.critical_gaps:
lines.append(f" * {gap}")
lines.append("")
lines.extend([
"-" * 60,
"GAP DETAILS BY CATEGORY",
"-" * 60,
])
# Group by category
categories = {}
for gap in result.gaps:
cat = gap["category"]
if cat not in categories:
categories[cat] = []
categories[cat].append(gap)
for category, gaps in categories.items():
lines.append(f"\n{category}:")
for gap in gaps:
status_mark = "✓" if gap["status"] == "Complete" else "○"
lines.append(f" [{status_mark}] {gap['requirement']} ({gap['priority']})")
lines.extend([
"",
"-" * 60,
"RECOMMENDATIONS",
"-" * 60,
])
for i, rec in enumerate(result.recommendations, 1):
lines.append(f"{i}. {rec}")
lines.append("=" * 60)
return "\n".join(lines)
def interactive_mode():
"""Run interactive gap analysis session."""
print("=" * 60)
print("MDR 2017/745 Gap Analysis - Interactive Mode")
print("=" * 60)
device_name = input("\nDevice name: ").strip()
if not device_name:
device_name = "Unnamed Device"
print("\nDevice classes:")
print(" 1. Class I")
print(" 2. Class I (sterile)")
print(" 3. Class I (measuring)")
print(" 4. Class IIa")
print(" 5. Class IIb")
print(" 6. Class III")
class_map = {
"1": DeviceClass.I,
"2": DeviceClass.I_STERILE,
"3": DeviceClass.I_MEASURING,
"4": DeviceClass.IIA,
"5": DeviceClass.IIB,
"6": DeviceClass.III,
}
class_choice = input("\nSelect class (1-6): ").strip()
device_class = class_map.get(class_choice, DeviceClass.IIA)
analyzer = MDRGapAnalyzer(device_name, device_class)
print("\nFor each requirement, enter status:")
print(" c = Complete")
print(" i = In Progress")
print(" n = Not Started (default)")
print(" x = Not Applicable")
print(" Enter = Skip (Not Started)")
print("")
status_map = {
"c": GapStatus.COMPLETE,
"i": GapStatus.IN_PROGRESS,
"n": GapStatus.NOT_STARTED,
"x": GapStatus.NOT_APPLICABLE,
}
for gap in analyzer.gaps:
if gap.status == GapStatus.NOT_APPLICABLE:
continue
status_input = input(f"{gap.requirement} [c/i/n/x]: ").strip().lower()
if status_input in status_map:
gap.status = status_map[status_input]
result = analyzer.analyze()
print("\n" + format_text_output(result))
def main():
parser = argparse.ArgumentParser(
description="EU MDR 2017/745 Gap Analysis Tool"
)
parser.add_argument("--device", type=str, help="Device name")
parser.add_argument(
"--class",
dest="device_class",
choices=["I", "Is", "Im", "IIa", "IIb", "III"],
help="Device classification"
)
parser.add_argument(
"--output",
choices=["text", "json"],
default="text",
help="Output format"
)
parser.add_argument(
"--interactive",
action="store_true",
help="Run in interactive mode"
)
args = parser.parse_args()
if args.interactive:
interactive_mode()
return
if not args.device or not args.device_class:
parser.print_help()
print("\nError: --device and --class required (or use --interactive)")
sys.exit(1)
class_map = {
"I": DeviceClass.I,
"Is": DeviceClass.I_STERILE,
"Im": DeviceClass.I_MEASURING,
"IIa": DeviceClass.IIA,
"IIb": DeviceClass.IIB,
"III": DeviceClass.III,
}
analyzer = MDRGapAnalyzer(args.device, class_map[args.device_class])
result = analyzer.analyze()
if args.output == "json":
print(json.dumps(asdict(result), indent=2))
else:
print(format_text_output(result))
if __name__ == "__main__":
main()
Viết spec trước khi code, xác định tiêu chí chấp nhận, lập kế hoạch tính năng và sinh test từ đặc tả.
---
name: "spec-driven-workflow"
description: "Use when the user asks to write specs before code, define acceptance criteria, plan features before implementation, generate tests from specifications, or follow spec-first development practices."
---
# Spec-Driven Workflow — POWERFUL
## Overview
Spec-driven workflow enforces a single, non-negotiable rule: **write the specification BEFORE you write any code.** Not alongside. Not after. Before.
This is not documentation. This is a contract. A spec defines what the system MUST do, what it SHOULD do, and what it explicitly WILL NOT do. Every line of code you write traces back to a requirement in the spec. Every test traces back to an acceptance criterion. If it is not in the spec, it does not get built.
### Why Spec-First Matters
1. **Eliminates rework.** 60-80% of defects originate from requirements, not implementation. Catching ambiguity in a spec costs minutes; catching it in production costs days.
2. **Forces clarity.** If you cannot write what the system should do in plain language, you do not understand the problem well enough to write code.
3. **Enables parallelism.** Once a spec is approved, frontend, backend, QA, and documentation can all start simultaneously.
4. **Creates accountability.** The spec is the definition of done. No arguments about whether a feature is "complete" — either it satisfies the acceptance criteria or it does not.
5. **Feeds TDD directly.** Acceptance criteria in Given/When/Then format translate 1:1 into test cases. The spec IS the test plan.
### The Iron Law
```
NO CODE WITHOUT AN APPROVED SPEC.
NO EXCEPTIONS. NO "QUICK PROTOTYPES." NO "I'LL DOCUMENT IT LATER."
```
If the spec is not written, reviewed, and approved, implementation does not begin. Period.
---
## The Spec Format
Every spec follows this structure. No sections are optional — if a section does not apply, write "N/A — [reason]" so reviewers know it was considered, not forgotten.
### Mandatory Sections
| # | Section | Key Rules |
|---|---------|-----------|
| 1 | **Title and Metadata** | Author, date, status (Draft/In Review/Approved/Superseded), reviewers |
| 2 | **Context** | Why this feature exists. 2-4 paragraphs with evidence (metrics, tickets). |
| 3 | **Functional Requirements** | RFC 2119 keywords (MUST/SHOULD/MAY). Numbered FR-N. Each is atomic and testable. |
| 4 | **Non-Functional Requirements** | Performance, security, accessibility, scalability, reliability — all with measurable thresholds. |
| 5 | **Acceptance Criteria** | Given/When/Then format. Every AC references at least one FR-* or NFR-*. |
| 6 | **Edge Cases** | Numbered EC-N. Cover failure modes for every external dependency. |
| 7 | **API Contracts** | TypeScript-style interfaces. Cover success and error responses. |
| 8 | **Data Models** | Table format with field, type, constraints. Every entity from requirements must have a model. |
| 9 | **Out of Scope** | Explicit exclusions with reasons. Prevents scope creep during implementation. |
### RFC 2119 Keywords
| Keyword | Meaning |
|---------|---------|
| **MUST** | Absolute requirement. Non-conformant without it. |
| **MUST NOT** | Absolute prohibition. |
| **SHOULD** | Recommended. Omit only with documented justification. |
| **MAY** | Optional. Implementer's discretion. |
See [spec_format_guide.md](references/spec_format_guide.md) for the complete template with section-by-section examples, good/bad requirement patterns, and feature-type templates (CRUD, Integration, Migration).
See [acceptance_criteria_patterns.md](references/acceptance_criteria_patterns.md) for a full pattern library of Given/When/Then criteria across authentication, CRUD, search, file upload, payment, notification, and accessibility scenarios.
---
## Bounded Autonomy Rules
These rules define when an agent (human or AI) MUST stop and ask for guidance vs. when they can proceed independently.
### STOP and Ask When:
1. **Scope creep detected.** The implementation requires something not in the spec. Even if it seems obviously needed, STOP. The spec might have excluded it deliberately.
2. **Ambiguity exceeds 30%.** If you cannot determine the correct behavior from the spec for more than 30% of a given requirement, the spec is incomplete. Do not guess.
3. **Breaking changes required.** The implementation would change an existing API contract, database schema, or public interface. Always escalate.
4. **Security implications.** Any change that touches authentication, authorization, encryption, or PII handling requires explicit approval.
5. **Performance characteristics unknown.** If a requirement says "MUST complete in < 500ms" but you have no way to measure or guarantee that, escalate before implementing a guess.
6. **Cross-team dependencies.** If the spec requires coordination with another team or service, confirm the dependency before building against it.
### Continue Autonomously When:
1. **Spec is clear and unambiguous** for the current task.
2. **All acceptance criteria have passing tests** and you are refactoring internals.
3. **Changes are non-breaking** — no public API, schema, or behavior changes.
4. **Implementation is a direct translation** of a well-defined acceptance criterion.
5. **Error handling follows established patterns** already documented in the codebase.
### Escalation Protocol
When you must stop, provide:
```markdown
## Escalation: [Brief Title]
**Blocked on:** [requirement ID, e.g., FR-3]
**Question:** [Specific, answerable question — not "what should I do?"]
**Options considered:**
A. [Option] — Pros: [...] Cons: [...]
B. [Option] — Pros: [...] Cons: [...]
**My recommendation:** [A or B, with reasoning]
**Impact of waiting:** [What is blocked until this is resolved?]
```
Never escalate without a recommendation. Never present an open-ended question. Always give options.
See `references/bounded_autonomy_rules.md` for the complete decision matrix.
---
## Workflow — 6 Phases
### Phase 1: Gather Requirements
**Goal:** Understand what needs to be built and why.
1. **Interview the user.** Ask:
- What problem does this solve?
- Who are the users?
- What does success look like?
- What explicitly should NOT be built?
2. **Read existing code.** Understand the current system before proposing changes.
3. **Identify constraints.** Performance budgets, security requirements, backward compatibility.
4. **List unknowns.** Every unknown is a risk. Surface them now, not during implementation.
**Exit criteria:** You can explain the feature to someone unfamiliar with the project in 2 minutes.
### Phase 2: Write Spec
**Goal:** Produce a complete spec document following The Spec Format above.
1. Fill every section of the template. No section left blank.
2. Number all requirements (FR-*, NFR-*, AC-*, EC-*, OS-*).
3. Use RFC 2119 keywords precisely.
4. Write acceptance criteria in Given/When/Then format.
5. Define API contracts with TypeScript-style types.
6. List explicit exclusions in Out of Scope.
**Exit criteria:** The spec can be handed to a developer who was not in the requirements meeting, and they can implement the feature without asking clarifying questions.
### Phase 3: Validate Spec
**Goal:** Verify the spec is complete, consistent, and implementable.
Run `spec_validator.py` against the spec file:
```bash
python spec_validator.py --file spec.md --strict
```
Manual validation checklist:
- [ ] Every functional requirement has at least one acceptance criterion
- [ ] Every acceptance criterion is testable (no subjective language)
- [ ] API contracts cover all endpoints mentioned in requirements
- [ ] Data models cover all entities mentioned in requirements
- [ ] Edge cases cover failure modes for every external dependency
- [ ] Out of scope is explicit about what was considered and rejected
- [ ] Non-functional requirements have measurable thresholds
**Exit criteria:** Spec scores 80+ on validator, and all manual checklist items pass.
### Phase 4: Generate Tests
**Goal:** Extract test cases from acceptance criteria before writing implementation code.
Run `test_extractor.py` against the approved spec:
```bash
python test_extractor.py --file spec.md --framework pytest --output tests/
```
1. Each acceptance criterion becomes one or more test cases.
2. Each edge case becomes a test case.
3. Tests are stubs — they define the assertion but not the implementation.
4. All tests MUST fail initially (red phase of TDD).
**Exit criteria:** You have a test file where every test fails with "not implemented" or equivalent.
### Phase 5: Implement
**Goal:** Write code that makes failing tests pass, one acceptance criterion at a time.
1. Pick one acceptance criterion (start with the simplest).
2. Make its test(s) pass with minimal code.
3. Run the full test suite — no regressions.
4. Commit.
5. Pick the next acceptance criterion. Repeat.
**Rules:**
- Do NOT implement anything not in the spec.
- Do NOT optimize before all acceptance criteria pass.
- Do NOT refactor before all acceptance criteria pass.
- If you discover a missing requirement, STOP and update the spec first.
**Exit criteria:** All tests pass. All acceptance criteria satisfied.
### Phase 6: Self-Review
**Goal:** Verify implementation matches spec before marking done.
Run through the Self-Review Checklist below. If any item fails, fix it before declaring the task complete.
---
## Self-Review Checklist
Before marking any implementation as done, verify ALL of the following:
- [ ] **Every acceptance criterion has a passing test.** No exceptions. If AC-3 exists, a test for AC-3 exists and passes.
- [ ] **Every edge case has a test.** EC-1 through EC-N all have corresponding test cases.
- [ ] **No scope creep.** The implementation does not include features not in the spec. If you added something, either update the spec or remove it.
- [ ] **API contracts match implementation.** Request/response shapes in code match the spec exactly. Field names, types, status codes — all of it.
- [ ] **Error scenarios tested.** Every error response defined in the spec has a test that triggers it.
- [ ] **Non-functional requirements verified.** If the spec says < 500ms, you have evidence (benchmark, load test, profiling) that it meets the threshold.
- [ ] **Data model matches.** Database schema matches the spec. No extra columns, no missing constraints.
- [ ] **Out-of-scope items not built.** Double-check that nothing from the Out of Scope section leaked into the implementation.
---
## Integration with TDD Guide
Spec-driven workflow and TDD are complementary, not competing:
```
Spec-Driven Workflow TDD (Red-Green-Refactor)
───────────────────── ──────────────────────────
Phase 1: Gather Requirements
Phase 2: Write Spec
Phase 3: Validate Spec
Phase 4: Generate Tests ──→ RED: Tests exist and fail
Phase 5: Implement ──→ GREEN: Minimal code to pass
Phase 6: Self-Review ──→ REFACTOR: Clean up internals
```
**The handoff:** Spec-driven workflow produces the test stubs (Phase 4). TDD takes over from there. The spec tells you WHAT to test. TDD tells you HOW to implement.
Use `engineering-team/tdd-guide` for:
- Red-green-refactor cycle discipline
- Coverage analysis and gap detection
- Framework-specific test patterns (Jest, Pytest, JUnit)
Use `engineering/spec-driven-workflow` for:
- Defining what to build before building it
- Acceptance criteria authoring
- Completeness validation
- Scope control
---
## Examples
A complete worked example (Password Reset spec with extracted test cases) is available in [spec_format_guide.md](references/spec_format_guide.md#full-example-password-reset). It demonstrates all 9 sections, requirement numbering, acceptance criteria, edge cases, and the corresponding pytest stubs generated by `test_extractor.py`.
---
## Anti-Patterns
### 1. Coding Before Spec Approval
**Symptom:** "I'll start coding while the spec is being reviewed."
**Problem:** The review will surface changes. Now you have code that implements a rejected design.
**Rule:** Implementation does not begin until spec status is "Approved."
### 2. Vague Acceptance Criteria
**Symptom:** "The system should work well" or "The UI should be responsive."
**Problem:** Untestable. What does "well" mean? What does "responsive" mean?
**Rule:** Every acceptance criterion must be verifiable by a machine. If you cannot write a test for it, rewrite the criterion.
### 3. Missing Edge Cases
**Symptom:** Happy path is specified, error paths are not.
**Problem:** Developers invent error handling on the fly, leading to inconsistent behavior.
**Rule:** For every external dependency (API, database, file system, user input), specify at least one failure scenario.
### 4. Spec as Post-Hoc Documentation
**Symptom:** "Let me write the spec now that the feature is done."
**Problem:** This is documentation, not specification. It describes what was built, not what should have been built. It cannot catch design errors because the design is already frozen.
**Rule:** If the spec was written after the code, it is not a spec. Relabel it as documentation.
### 5. Gold-Plating Beyond Spec
**Symptom:** "While I was in there, I also added..."
**Problem:** Untested code. Unreviewed design. Potential for subtle bugs in the "bonus" feature.
**Rule:** If it is not in the spec, it does not get built. File a new spec for additional features.
### 6. Acceptance Criteria Without Requirement Traceability
**Symptom:** AC-7 exists but does not reference any FR-* or NFR-*.
**Problem:** Orphaned criteria mean either a requirement is missing or the criterion is unnecessary.
**Rule:** Every AC-* MUST reference at least one FR-* or NFR-*.
### 7. Skipping Validation
**Symptom:** "The spec looks fine, let's just start."
**Problem:** Missing sections discovered during implementation cause blocking delays.
**Rule:** Always run `spec_validator.py --strict` before starting implementation. Fix all warnings.
---
## Cross-References
- **`engineering-team/tdd-guide`** — Red-green-refactor cycle, test generation, coverage analysis. Use after Phase 4 of this workflow.
- **`engineering/focused-fix`** — Deep-dive feature repair. When a spec-driven implementation has systemic issues, use focused-fix for diagnosis.
- **`engineering/rag-architect`** — If the feature involves retrieval or knowledge systems, use rag-architect for the technical design within the spec.
- **`references/spec_format_guide.md`** — Complete template with section-by-section explanations.
- **`references/bounded_autonomy_rules.md`** — Full decision matrix for when to stop vs. continue.
- **`references/acceptance_criteria_patterns.md`** — Pattern library for writing Given/When/Then criteria.
---
## Tools
| Script | Purpose | Key Flags |
|--------|---------|-----------|
| `spec_generator.py` | Generate spec template from feature name/description | `--name`, `--description`, `--format`, `--json` |
| `spec_validator.py` | Validate spec completeness (0-100 score) | `--file`, `--strict`, `--json` |
| `test_extractor.py` | Extract test stubs from acceptance criteria | `--file`, `--framework`, `--output`, `--json` |
```bash
# Generate a spec template
python spec_generator.py --name "User Authentication" --description "OAuth 2.0 login flow"
# Validate a spec
python spec_validator.py --file specs/auth.md --strict
# Extract test cases
python test_extractor.py --file specs/auth.md --framework pytest --output tests/test_auth.py
```
FILE:references/acceptance_criteria_patterns.md
# Acceptance Criteria Patterns
A pattern library for writing Given/When/Then acceptance criteria across common feature types. Use these as starting points — adapt to your domain.
---
## Pattern Structure
Every acceptance criterion follows this structure:
```
### AC-N: [Descriptive name] (FR-N, NFR-N)
Given [precondition — the system/user is in this state]
When [trigger — the user or system performs this action]
Then [outcome — this observable, testable result occurs]
And [additional outcome — and this also happens]
```
**Rules:**
1. One scenario per AC. Multiple Given/When/Then blocks = multiple ACs.
2. Every AC references at least one FR-* or NFR-*.
3. Outcomes must be observable and testable — no subjective language.
4. Preconditions must be achievable in a test setup.
---
## Authentication Patterns
### Login — Happy Path
```markdown
### AC-1: Successful login with valid credentials (FR-1)
Given a registered user with email "user@example.com" and password "V@lidP4ss!"
When they POST /api/auth/login with email "user@example.com" and password "V@lidP4ss!"
Then the response status is 200
And the response body contains a valid JWT access token
And the response body contains a refresh token
And the access token expires in 24 hours
```
### Login — Invalid Credentials
```markdown
### AC-2: Login rejected with wrong password (FR-1)
Given a registered user with email "user@example.com"
When they POST /api/auth/login with email "user@example.com" and an incorrect password
Then the response status is 401
And the response body contains error code "INVALID_CREDENTIALS"
And no token is issued
And the failed attempt is logged
```
### Login — Account Locked
```markdown
### AC-3: Login rejected for locked account (FR-1, NFR-S2)
Given a user whose account is locked due to 5 consecutive failed login attempts
When they POST /api/auth/login with correct credentials
Then the response status is 403
And the response body contains error code "ACCOUNT_LOCKED"
And the response includes a "retryAfter" field with seconds until unlock
```
### Token Refresh
```markdown
### AC-4: Token refresh with valid refresh token (FR-3)
Given a user with a valid, non-expired refresh token
When they POST /api/auth/refresh with that refresh token
Then the response status is 200
And a new access token is issued
And the old refresh token is invalidated
And a new refresh token is issued (rotation)
```
### Logout
```markdown
### AC-5: Logout invalidates session (FR-4)
Given an authenticated user with a valid access token
When they POST /api/auth/logout with that token
Then the response status is 204
And the access token is no longer accepted for API calls
And the refresh token is invalidated
```
---
## CRUD Patterns
### Create
```markdown
### AC-6: Create resource with valid data (FR-1)
Given an authenticated user with "editor" role
When they POST /api/resources with valid payload {name: "Test", type: "A"}
Then the response status is 201
And the response body contains the created resource with a generated UUID
And the resource's "createdAt" field is set to the current UTC timestamp
And the resource's "createdBy" field matches the authenticated user's ID
```
### Create — Validation Failure
```markdown
### AC-7: Create resource rejected with invalid data (FR-1)
Given an authenticated user
When they POST /api/resources with payload missing required field "name"
Then the response status is 400
And the response body contains error code "VALIDATION_ERROR"
And the response body contains field-level detail: {"name": "Required field"}
And no resource is created in the database
```
### Read — Single Item
```markdown
### AC-8: Read resource by ID (FR-2)
Given an existing resource with ID "abc-123"
When an authenticated user GETs /api/resources/abc-123
Then the response status is 200
And the response body contains the resource with all fields
```
### Read — Not Found
```markdown
### AC-9: Read non-existent resource returns 404 (FR-2)
Given no resource exists with ID "nonexistent-id"
When an authenticated user GETs /api/resources/nonexistent-id
Then the response status is 404
And the response body contains error code "NOT_FOUND"
```
### Update
```markdown
### AC-10: Update resource with valid data (FR-3)
Given an existing resource with ID "abc-123" owned by the authenticated user
When they PATCH /api/resources/abc-123 with {name: "Updated Name"}
Then the response status is 200
And the resource's "name" field is "Updated Name"
And the resource's "updatedAt" field is updated to the current UTC timestamp
And fields not included in the patch are unchanged
```
### Update — Ownership Check
```markdown
### AC-11: Update rejected for non-owner (FR-3, FR-6)
Given an existing resource with ID "abc-123" owned by user "other-user"
When the authenticated user (not "other-user") PATCHes /api/resources/abc-123
Then the response status is 403
And the response body contains error code "FORBIDDEN"
And the resource is unchanged
```
### Delete — Soft Delete
```markdown
### AC-12: Soft delete resource (FR-5)
Given an existing resource with ID "abc-123" owned by the authenticated user
When they DELETE /api/resources/abc-123
Then the response status is 204
And the resource's "deletedAt" field is set to the current UTC timestamp
And the resource no longer appears in GET /api/resources (list endpoint)
And the resource still exists in the database (soft deleted)
```
### List — Pagination
```markdown
### AC-13: List resources with default pagination (FR-4)
Given 50 resources exist for the authenticated user
When they GET /api/resources without pagination parameters
Then the response status is 200
And the response contains the first 20 resources (default page size)
And the response includes "totalCount: 50"
And the response includes "page: 1"
And the response includes "pageSize: 20"
And the response includes "hasNextPage: true"
```
### List — Filtered
```markdown
### AC-14: List resources with type filter (FR-4)
Given 30 resources of type "A" and 20 resources of type "B" exist
When the authenticated user GETs /api/resources?type=A
Then the response status is 200
And all returned resources have type "A"
And the response "totalCount" is 30
```
---
## Search Patterns
### Basic Search
```markdown
### AC-15: Search returns matching results (FR-7)
Given resources with names "Alpha Report", "Beta Analysis", "Alpha Summary" exist
When the user GETs /api/resources?q=Alpha
Then the response contains "Alpha Report" and "Alpha Summary"
And the response does not contain "Beta Analysis"
And results are ordered by relevance score (descending)
```
### Search — Empty Results
```markdown
### AC-16: Search with no matches returns empty list (FR-7)
Given no resources match the query "xyznonexistent"
When the user GETs /api/resources?q=xyznonexistent
Then the response status is 200
And the response contains an empty "items" array
And "totalCount" is 0
```
### Search — Special Characters
```markdown
### AC-17: Search handles special characters safely (FR-7, NFR-S1)
Given resources exist in the database
When the user GETs /api/resources?q="; DROP TABLE resources;--
Then the response status is 200
And no SQL injection occurs
And the search treats the input as a literal string
```
---
## File Upload Patterns
### Upload — Happy Path
```markdown
### AC-18: Upload file within size limit (FR-8)
Given an authenticated user
When they POST /api/files with a 5MB PNG file
Then the response status is 201
And the response contains the file's URL, size, and MIME type
And the file is stored in the configured storage backend
And the file is associated with the authenticated user
```
### Upload — Size Exceeded
```markdown
### AC-19: Upload rejected for oversized file (FR-8)
Given the maximum file size is 10MB
When the user POSTs /api/files with a 15MB file
Then the response status is 413
And the response contains error code "FILE_TOO_LARGE"
And no file is stored
```
### Upload — Invalid Type
```markdown
### AC-20: Upload rejected for disallowed file type (FR-8, NFR-S3)
Given allowed file types are PNG, JPG, PDF
When the user POSTs /api/files with an .exe file
Then the response status is 415
And the response contains error code "UNSUPPORTED_MEDIA_TYPE"
And no file is stored
```
---
## Payment Patterns
### Charge — Happy Path
```markdown
### AC-21: Successful payment charge (FR-10)
Given a user with a valid payment method on file
When they POST /api/payments with amount 49.99 and currency "USD"
Then the payment gateway is charged $49.99
And the response status is 201
And the response contains a transaction ID
And a payment record is created with status "completed"
And a receipt email is sent to the user
```
### Charge — Declined
```markdown
### AC-22: Payment declined by gateway (FR-10)
Given a user with an expired credit card on file
When they POST /api/payments with amount 49.99
Then the payment gateway returns a decline
And the response status is 402
And the response contains error code "PAYMENT_DECLINED"
And no payment record is created with status "completed"
And the user is prompted to update their payment method
```
### Charge — Idempotency
```markdown
### AC-23: Duplicate payment request is idempotent (FR-10, NFR-R1)
Given a payment was successfully processed with idempotency key "key-123"
When the same request is sent again with idempotency key "key-123"
Then the response status is 200
And the response contains the original transaction ID
And the user is NOT charged a second time
```
---
## Notification Patterns
### Email Notification
```markdown
### AC-24: Email notification sent on event (FR-11)
Given a user with notification preferences set to "email"
When their order status changes to "shipped"
Then an email is sent to their registered email address
And the email subject contains the order number
And the email body contains the tracking URL
And a notification record is created with status "sent"
```
### Notification — Delivery Failure
```markdown
### AC-25: Failed notification is retried (FR-11, NFR-R2)
Given the email service returns a 5xx error on first attempt
When a notification is triggered
Then the system retries up to 3 times with exponential backoff (1s, 4s, 16s)
And if all retries fail, the notification status is set to "failed"
And an alert is sent to the ops channel
```
---
## Negative Test Patterns
### Unauthorized Access
```markdown
### AC-26: Unauthenticated request rejected (NFR-S1)
Given no authentication token is provided
When the user GETs /api/resources
Then the response status is 401
And the response contains error code "AUTHENTICATION_REQUIRED"
And no resource data is returned
```
### Invalid Input — Type Mismatch
```markdown
### AC-27: String provided for numeric field (FR-1)
Given the "quantity" field expects an integer
When the user POSTs with quantity: "abc"
Then the response status is 400
And the response body contains field error: {"quantity": "Must be an integer"}
```
### Rate Limiting
```markdown
### AC-28: Rate limit enforced (NFR-S2)
Given the rate limit is 100 requests per minute per API key
When the user sends the 101st request within 60 seconds
Then the response status is 429
And the response includes header "Retry-After" with seconds until reset
And the response contains error code "RATE_LIMITED"
```
### Concurrent Modification
```markdown
### AC-29: Optimistic locking prevents lost updates (NFR-R1)
Given a resource with version 5
When user A PATCHes with version 5 and user B PATCHes with version 5 simultaneously
Then one succeeds with status 200 (version becomes 6)
And the other receives status 409 with error code "CONFLICT"
And the 409 response includes the current version number
```
---
## Performance Criteria Patterns
### Response Time
```markdown
### AC-30: API response time under load (NFR-P1)
Given the system is handling 1,000 concurrent users
When a user GETs /api/dashboard
Then the response is returned in < 500ms (p95)
And the response is returned in < 1000ms (p99)
```
### Throughput
```markdown
### AC-31: System handles target throughput (NFR-P2)
Given normal production traffic patterns
When the system receives 5,000 requests per second
Then all requests are processed without queue overflow
And error rate remains below 0.1%
```
### Resource Usage
```markdown
### AC-32: Memory usage within bounds (NFR-P3)
Given the service is processing normal traffic
When measured over a 24-hour period
Then memory usage does not exceed 512MB RSS
And no memory leaks are detected (RSS growth < 5% over 24h)
```
---
## Accessibility Criteria Patterns
### Keyboard Navigation
```markdown
### AC-33: Form is fully keyboard navigable (NFR-A1)
Given the user is on the login page using only a keyboard
When they press Tab
Then focus moves through: email field -> password field -> submit button
And each focused element has a visible focus indicator
And pressing Enter on the submit button submits the form
```
### Screen Reader
```markdown
### AC-34: Error messages announced to screen readers (NFR-A2)
Given the user submits the form with invalid data
When validation errors appear
Then each error is associated with its form field via aria-describedby
And the error container has role="alert" for immediate announcement
And the first error field receives focus
```
### Color Contrast
```markdown
### AC-35: Text meets contrast requirements (NFR-A3)
Given the default theme is active
When measuring text against background colors
Then all body text meets 4.5:1 contrast ratio (WCAG AA)
And all large text (18px+ or 14px+ bold) meets 3:1 contrast ratio
And all interactive element states (hover, focus, active) meet 3:1
```
### Reduced Motion
```markdown
### AC-36: Animations respect user preference (NFR-A4)
Given the user has enabled "prefers-reduced-motion" in their OS settings
When they load any page with animations
Then all non-essential animations are disabled
And essential animations (e.g., loading spinner) use a reduced version
And no content is hidden behind animation-only interactions
```
---
## Writing Tips
### Do
- Start Given with the system/user state, not the action
- Make When a single, specific trigger
- Make Then observable — status codes, field values, side effects
- Include And for additional assertions on the same outcome
- Reference requirement IDs in the AC title
### Do Not
- Write "Then the system works correctly" (not testable)
- Combine multiple scenarios in one AC
- Use subjective words: "quickly", "properly", "nicely", "user-friendly"
- Skip the precondition — Given is required even if it seems obvious
- Write Given/When/Then as prose paragraphs — use the structured format
### Smell Tests
If your AC has any of these, rewrite it:
| Smell | Example | Fix |
|-------|---------|-----|
| No Given clause | "When user clicks, then page loads" | Add "Given user is on the dashboard" |
| Vague Then | "Then it works" | Specify status code, body, side effects |
| Multiple Whens | "When user clicks A and then clicks B" | Split into two ACs |
| Implementation detail | "Then the Redux store is updated" | Focus on user-observable outcome |
| No requirement reference | "AC-5: Dashboard loads" | "AC-5: Dashboard loads (FR-7)" |
FILE:references/bounded_autonomy_rules.md
# Bounded Autonomy Rules
Decision framework for when an agent (human or AI) should stop and ask vs. continue working autonomously during spec-driven development.
---
## The Core Principle
**Autonomy is earned by clarity.** The clearer the spec, the more autonomy the implementer has. The more ambiguous the spec, the more the implementer must stop and ask.
This is not about trust. It is about risk. A clear spec means low risk of building the wrong thing. An ambiguous spec means high risk.
---
## Decision Matrix
| Signal | Action | Rationale |
|--------|--------|-----------|
| Spec is Approved, requirement is clear, tests exist | **Continue** | Low risk. Build it. |
| Requirement is clear but no test exists yet | **Continue** (write the test first) | You can infer the test from the requirement. |
| Requirement uses SHOULD/MAY keywords | **Continue** with your best judgment | These are intentionally flexible. Document your choice. |
| Requirement is ambiguous (multiple valid interpretations) | **STOP** if ambiguity > 30% of the task | Ask the spec author to clarify. |
| Implementation requires changing an API contract | **STOP** always | Breaking changes need explicit approval. |
| Implementation requires a new database migration | **STOP** if it changes existing columns/tables | New tables are lower risk than schema changes. |
| Security-related change (auth, crypto, PII) | **STOP** always | Security changes need review regardless of spec clarity. |
| Performance-critical path with no benchmark data | **STOP** | You cannot prove NFR compliance without measurement. |
| Bug found in existing code unrelated to spec | **STOP** — file a separate issue | Do not fix unrelated bugs in a spec-scoped implementation. |
| Spec says "N/A" for a section you think needs content | **STOP** | The author may have a reason, or they may have missed it. |
---
## Ambiguity Scoring
When you encounter ambiguity, quantify it before deciding to stop or continue.
### How to Score Ambiguity
For each requirement you are implementing, ask:
1. **Can I write a test for this right now?** (No = +20% ambiguity)
2. **Are there multiple valid interpretations?** (Yes = +20% ambiguity)
3. **Does the spec contradict itself?** (Yes = +30% ambiguity)
4. **Am I making assumptions about user behavior?** (Yes = +15% ambiguity)
5. **Does this depend on an undocumented external system?** (Yes = +15% ambiguity)
### Threshold
| Ambiguity Score | Action |
|-----------------|--------|
| 0-15% | Continue. Minor ambiguity is normal. Document your interpretation. |
| 16-30% | Continue with caution. Add a comment explaining your interpretation. Flag in PR. |
| 31-50% | STOP. Ask the spec author one specific question. Do not continue until answered. |
| 51%+ | STOP. The spec is incomplete. Request a revision before proceeding. |
### Example
**Requirement:** "FR-7: The system MUST notify the user when their order ships."
Questions:
1. Can I write a test? Partially — I know WHAT to test but not HOW (email? push? in-app?). +20%
2. Multiple interpretations? Yes — notification channel is unclear. +20%
3. Contradicts itself? No. +0%
4. Assuming user behavior? Yes — I am assuming they want email. +15%
5. Undocumented external system? Maybe — depends on notification service. +15%
**Total: 70%.** STOP. The spec needs to specify the notification channel.
---
## Scope Creep Detection
### What Is Scope Creep?
Scope creep is implementing functionality not described in the spec. It includes:
- Adding features the spec does not mention
- "Improving" behavior beyond what acceptance criteria require
- Handling edge cases the spec explicitly excluded
- Refactoring unrelated code "while you're in there"
- Building infrastructure for future features
### Detection Patterns
| Pattern | Example | Risk |
|---------|---------|------|
| "While I'm here..." | Refactoring a utility function unrelated to the spec | Medium — unreviewed changes |
| "This would be easy to add..." | Adding a search filter the spec does not mention | High — untested, unspecified |
| "Users will probably want..." | Building a feature based on assumption | High — may conflict with future specs |
| "This is obviously needed..." | Adding logging, metrics, or caching not in NFRs | Medium — may be overkill or wrong approach |
| "The spec forgot to mention..." | Building something the spec excluded | Critical — may be deliberately excluded |
### Response Protocol
When you detect scope creep in your own work:
1. **Stop immediately.** Do not commit the extra code.
2. **Check Out of Scope.** Is this item explicitly excluded?
3. **If excluded:** Delete the code. The spec author had a reason.
4. **If not mentioned:** File a note for the spec author. Ask if it should be added.
5. **If approved:** Update the spec FIRST, then implement.
---
## Breaking Change Identification
### What Counts as a Breaking Change?
A breaking change is any modification that could cause existing clients, tests, or integrations to fail.
| Category | Breaking | Not Breaking |
|----------|----------|--------------|
| API endpoint removed | Yes | - |
| API endpoint added | - | No |
| Required field added to request | Yes | - |
| Optional field added to request | - | No |
| Field removed from response | Yes | - |
| Field added to response | - | No (usually) |
| Status code changed | Yes | - |
| Error code string changed | Yes | - |
| Database column removed | Yes | - |
| Database column added (nullable) | - | No |
| Database column added (not null, no default) | Yes | - |
| Enum value removed | Yes | - |
| Enum value added | - | No (usually) |
| Behavior change for existing input | Yes | - |
### Breaking Change Protocol
1. **Identify** the breaking change before implementing it.
2. **Escalate** immediately — do not implement without approval.
3. **Propose** a migration path (versioned API, feature flag, deprecation period).
4. **Document** the breaking change in the spec's changelog.
---
## Security Implication Checklist
Any change touching the following areas MUST be escalated, even if the spec seems clear.
### Always Escalate
- [ ] Authentication logic (login, logout, token generation)
- [ ] Authorization logic (role checks, permission gates)
- [ ] Encryption/hashing (algorithm choice, key management)
- [ ] PII handling (storage, transmission, logging)
- [ ] Input validation bypass (new endpoints, parameter changes)
- [ ] Rate limiting changes (thresholds, scope)
- [ ] CORS or CSP policy changes
- [ ] File upload handling
- [ ] SQL/NoSQL query construction (injection risk)
- [ ] Deserialization of user input
- [ ] Redirect URLs from user input (open redirect risk)
- [ ] Secrets in code, config, or logs
### Security Escalation Template
```markdown
## Security Escalation: [Title]
**Affected area:** [authentication/authorization/encryption/PII/etc.]
**Spec reference:** [FR-N or NFR-SN]
**Risk:** [What could go wrong if implemented incorrectly]
**Current protection:** [What exists today]
**Proposed change:** [What the spec requires]
**My concern:** [Specific security question]
**Recommendation:** [Proposed approach with security rationale]
```
---
## Escalation Templates
### Template 1: Ambiguous Requirement
```markdown
## Escalation: Ambiguous Requirement
**Blocked on:** FR-7 ("notify the user when their order ships")
**Ambiguity score:** 70%
**Question:** What notification channel should be used?
**Options considered:**
A. Email only — Pros: simple, reliable. Cons: not real-time.
B. Email + in-app notification — Pros: covers both async and real-time. Cons: more implementation effort.
C. Configurable per user — Pros: maximum flexibility. Cons: requires preference UI (not in spec).
**My recommendation:** B (email + in-app). Covers most use cases without requiring new UI.
**Impact of waiting:** Cannot implement FR-7 until resolved. No other work blocked.
```
### Template 2: Missing Edge Case
```markdown
## Escalation: Missing Edge Case
**Related to:** FR-3 (password reset link expires after 1 hour)
**Scenario:** User clicks a reset link, but their account was deleted between requesting and clicking.
**Not in spec:** Edge cases section does not cover this.
**Options considered:**
A. Show generic "link invalid" error — Pros: secure (no info leak). Cons: confusing for deleted user.
B. Show "account not found" error — Pros: clear. Cons: confirms account deletion to link holder.
**My recommendation:** A. Security over clarity — do not reveal account existence.
**Impact of waiting:** Can implement other ACs; this is blocking only AC-2 completion.
```
### Template 3: Potential Breaking Change
```markdown
## Escalation: Potential Breaking Change
**Spec requires:** Adding required field "role" to POST /api/users request (FR-6)
**Current behavior:** POST /api/users accepts {email, password, displayName}
**Breaking:** Yes — existing clients will get 400 errors (missing required field)
**Options considered:**
A. Make "role" required as spec says — Pros: matches spec. Cons: breaks mobile app v2.1.
B. Make "role" optional with default "user" — Pros: backward compatible. Cons: deviates from spec.
C. Version the API (v2) — Pros: clean separation. Cons: maintenance burden.
**My recommendation:** B. Default to "user" for backward compatibility. Update spec to reflect MAY instead of MUST.
**Impact of waiting:** Frontend team is building against the new contract. Need answer within 2 days.
```
### Template 4: Scope Creep Proposal
```markdown
## Escalation: Potential Addition to Spec
**Context:** While implementing FR-2 (password validation), I noticed the spec does not mention password strength feedback.
**Not in spec:** No requirement for showing strength indicators.
**Checked Out of Scope:** Not listed there either.
**Proposal:** Add FR-7: "The system SHOULD display password strength feedback during registration."
**Effort:** ~2 hours additional implementation.
**Question:** Should this be added to current spec, filed as a separate spec, or skipped?
**Impact of waiting:** FR-2 implementation is not blocked. This is an enhancement question only.
```
---
## Quick Reference Card
```
CONTINUE if:
- Spec is approved
- Requirement uses MUST and is unambiguous
- Tests can be written directly from the AC
- Changes are additive and non-breaking
- You are refactoring internals only (no behavior change)
STOP if:
- Ambiguity > 30%
- Any breaking change
- Any security-related change
- Spec says N/A but you think it shouldn't
- You are about to build something not in the spec
- You cannot write a test for the requirement
- External dependency is undocumented
```
---
## Anti-Patterns in Autonomy
### 1. "I'll Ask Later"
Continuing past an ambiguity checkpoint because asking feels slow. The rework from building the wrong thing is always slower.
### 2. "It's Obviously Needed"
Assuming a missing feature was accidentally omitted. It may have been deliberately excluded. Check Out of Scope first.
### 3. "The Spec Is Wrong"
Implementing what you think the spec SHOULD say instead of what it DOES say. If the spec is wrong, escalate. Do not silently "fix" it.
### 4. "Just This Once"
Bypassing the escalation protocol for a "small" change. Small changes compound. The protocol exists because humans are bad at judging risk in the moment.
### 5. "I Already Built It"
Presenting completed work that was never in the spec and hoping it gets accepted. This creates review pressure and wastes everyone's time if rejected. Ask BEFORE building.
FILE:references/spec_format_guide.md
# Spec Format Guide
Complete reference for writing feature specifications. Every section is explained with examples, rationale, and common mistakes.
---
## The Spec Document Structure
A spec has 8 mandatory sections. If a section does not apply, write "N/A — [reason]" so reviewers know it was considered, not skipped.
```
1. Title and Metadata
2. Context
3. Functional Requirements
4. Non-Functional Requirements
5. Acceptance Criteria
6. Edge Cases and Error Scenarios
7. API Contracts
8. Data Models
9. Out of Scope
```
---
## Section 1: Title and Metadata
```markdown
# Spec: [Feature Name]
**Author:** Jane Doe
**Date:** 2026-03-25
**Status:** Draft | In Review | Approved | Superseded
**Reviewers:** John Smith, Alice Chen
**Related specs:** SPEC-018 (User Registration), SPEC-023 (Session Management)
```
### Status Lifecycle
| Status | Meaning | Who Can Change |
|--------|---------|----------------|
| Draft | Author is still writing. Not ready for review. | Author |
| In Review | Ready for feedback. Implementation blocked. | Author |
| Approved | Reviewed and accepted. Implementation may begin. | Reviewer |
| Superseded | Replaced by a newer spec. Link to replacement. | Author |
**Rule:** Implementation MUST NOT begin until status is "Approved."
---
## Section 2: Context
The context section answers: **Why does this feature exist?**
### What to Include
- The problem being solved (with evidence: support tickets, metrics, user research)
- The current state (what exists today and what is broken or missing)
- The business justification (revenue impact, cost savings, user retention)
- Constraints or dependencies (regulatory, technical, timeline)
### What to Exclude
- Implementation details (that is the engineer's job)
- Solution proposals (the spec says WHAT, not HOW)
- Lengthy background (2-4 paragraphs maximum)
### Good Example
```markdown
## Context
Users who forget their passwords currently have no self-service recovery.
Support handles ~200 password reset requests per week, consuming approximately
8 hours of agent time at $45/hour ($360/week, $18,720/year). Additionally,
12% of users who contact support for a reset never return.
This feature provides self-service password reset via email, eliminating
support burden and reducing user churn from the reset flow.
```
### Bad Example
```markdown
## Context
We need a password reset feature. Users forget their passwords sometimes
and need to reset them. We should build this.
```
**Why it is bad:** No evidence, no metrics, no business justification. "We should build this" is not a reason.
---
## Section 3: Functional Requirements — RFC 2119
### RFC 2119 Keywords
These keywords have precise meanings per [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt). Do not use them casually.
| Keyword | Meaning | Testing Implication |
|---------|---------|---------------------|
| **MUST** | Absolute requirement. The implementation is non-conformant without this. | Must have a passing test. Failure = release blocker. |
| **MUST NOT** | Absolute prohibition. Doing this = broken implementation. | Must have a test proving this cannot happen. |
| **SHOULD** | Strongly recommended. Can be omitted only with documented justification. | Should have a test. Omission requires written rationale. |
| **SHOULD NOT** | Strongly discouraged. Can be done only with documented justification. | Should have a test confirming the behavior does not occur. |
| **MAY** | Truly optional. Implementer's discretion. | Test is optional. Document if implemented. |
### Writing Good Requirements
**Each requirement MUST be:**
1. **Atomic** — One behavior per requirement. Not "The system MUST authenticate users and log them in."
2. **Testable** — You can write a test that proves it works or does not.
3. **Numbered** — Sequential FR-N format for traceability.
4. **Specific** — No ambiguous adjectives ("fast", "secure", "user-friendly").
### Good Requirements
```markdown
- FR-1: The system MUST accept login via email and password.
- FR-2: The system MUST reject passwords shorter than 8 characters.
- FR-3: The system MUST return a JWT access token on successful login.
- FR-4: The system MUST NOT include the password hash in any API response.
- FR-5: The system SHOULD support "remember me" with a 30-day refresh token.
- FR-6: The system MAY display last login time on the dashboard.
```
### Bad Requirements
```markdown
- FR-1: The login system must be fast and secure.
(Untestable: what is "fast"? What is "secure"?)
- FR-2: The system must handle all edge cases.
(Vague: which edge cases? This delegates the spec to the implementer.)
- FR-3: Users should be able to log in easily.
(Subjective: "easily" is not measurable.)
```
---
## Section 4: Non-Functional Requirements
Non-functional requirements define quality attributes. Every requirement needs a **measurable threshold**.
### Categories
#### Performance
```markdown
- NFR-P1: Login API MUST respond in < 500ms (p95) under 1,000 concurrent users.
- NFR-P2: Dashboard page MUST achieve Largest Contentful Paint < 2.5s.
- NFR-P3: Search results MUST return within 200ms for queries under 100 characters.
```
**Bad:** "The system should be fast." (Not measurable.)
#### Security
```markdown
- NFR-S1: All API endpoints MUST require authentication except /health and /login.
- NFR-S2: Failed login attempts MUST be rate-limited to 5 per minute per IP.
- NFR-S3: Passwords MUST be hashed with bcrypt (cost factor >= 12).
- NFR-S4: Session tokens MUST be invalidated on password change.
```
#### Accessibility
```markdown
- NFR-A1: All form inputs MUST have associated labels (WCAG 1.3.1).
- NFR-A2: Color contrast MUST meet 4.5:1 ratio (WCAG 1.4.3).
- NFR-A3: All interactive elements MUST be keyboard-navigable (WCAG 2.1.1).
```
#### Scalability
```markdown
- NFR-SC1: The system SHOULD handle 50,000 registered users.
- NFR-SC2: Database queries MUST use indexes; no full table scans on tables > 10K rows.
```
#### Reliability
```markdown
- NFR-R1: The authentication service MUST maintain 99.9% uptime (< 8.77h downtime/year).
- NFR-R2: Data MUST NOT be lost on service restart (durable storage required).
```
---
## Section 5: Acceptance Criteria — Given/When/Then
Acceptance criteria are the contract between the spec author and the implementer. They define "done."
### The Given/When/Then Pattern
```
Given [precondition — the world is in this state]
When [action — the user or system does this]
Then [outcome — this observable result occurs]
And [additional outcome — and also this]
```
### Rules for Acceptance Criteria
1. **Every AC MUST reference at least one FR-* or NFR-*.** Orphaned criteria indicate missing requirements.
2. **Every AC MUST be testable by a machine.** If you cannot write an automated test, rewrite the criterion.
3. **No subjective language.** Not "should look good" but "MUST render within the design-system grid."
4. **One scenario per AC.** If you have multiple Given/When/Then blocks, split into separate ACs.
### Example: Authentication Feature
```markdown
### AC-1: Successful login (FR-1, FR-3)
Given a registered user with email "user@example.com" and password "P@ssw0rd123"
When they POST /api/auth/login with those credentials
Then they receive a 200 response with a valid JWT token
And the token expires in 24 hours
And the response includes the user's display name
### AC-2: Invalid password (FR-1)
Given a registered user with email "user@example.com"
When they POST /api/auth/login with an incorrect password
Then they receive a 401 response
And the response body contains error "INVALID_CREDENTIALS"
And no token is issued
### AC-3: Short password rejected on registration (FR-2)
Given a new user attempting to register
When they submit a password with 7 characters
Then they receive a 400 response
And the response body contains error "PASSWORD_TOO_SHORT"
And the account is not created
```
### Common Mistakes
| Mistake | Example | Fix |
|---------|---------|-----|
| Vague outcome | "Then the system works correctly" | "Then the response status is 200 and body contains {field: value}" |
| Missing precondition | "When user logs in, then token is issued" | "Given a registered user, when they POST valid credentials, then..." |
| Multiple scenarios | AC with 3 different When clauses | Split into 3 separate ACs |
| No FR reference | "AC-5: User sees dashboard" | "AC-5: User sees dashboard (FR-7)" |
---
## Section 6: Edge Cases and Error Scenarios
### What Counts as an Edge Case
- Invalid or malformed input
- External service failures (API down, timeout, rate-limited)
- Concurrent operations (race conditions)
- Boundary values (empty string, max length, zero, negative numbers)
- State conflicts (already exists, already deleted, expired)
### Format
```markdown
- EC-1: Empty email field → Return 400 with error "EMAIL_REQUIRED". Do not call auth service.
- EC-2: Email exceeds 255 characters → Return 400 with error "EMAIL_TOO_LONG".
- EC-3: OAuth provider returns 503 → Return 503 with "Service temporarily unavailable". Retry after 30s.
- EC-4: Two users register same email simultaneously → First succeeds, second gets 409 Conflict.
- EC-5: User clicks reset link after password was already changed → Show "Link already used."
```
### Coverage Rule
For every external dependency, specify at least one failure:
- Database: connection lost, timeout, constraint violation
- API: 4xx, 5xx, timeout, invalid response
- File system: file not found, permission denied, disk full
- User input: empty, too long, wrong type, injection attempt
---
## Section 7: API Contracts
### Notation
Use TypeScript-style interfaces. They are readable by both frontend and backend engineers.
```typescript
interface CreateUserRequest {
email: string; // MUST be valid email, max 255 chars
password: string; // MUST be 8-128 chars
displayName: string; // MUST be 1-100 chars, no HTML
role?: "user" | "admin"; // Default: "user"
}
```
### What to Define
For each endpoint:
1. **HTTP method and path** (e.g., POST /api/users)
2. **Request body** (fields, types, constraints, defaults)
3. **Success response** (status code, body shape)
4. **Error responses** (each error code with its status and body)
5. **Headers** (Authorization, Content-Type, custom headers)
### Error Response Convention
```typescript
interface ApiError {
error: string; // Machine-readable code: "INVALID_CREDENTIALS"
message: string; // Human-readable: "The email or password is incorrect."
details?: Record<string, string>; // Field-level errors for validation
}
```
Always include:
- 400 for validation errors
- 401 for authentication failures
- 403 for authorization failures
- 404 for not found
- 409 for conflicts
- 429 for rate limiting
- 500 for unexpected errors (keep it generic — do not leak internals)
---
## Section 8: Data Models
### Table Format
```markdown
### User
| Field | Type | Constraints |
|-------|------|-------------|
| id | UUID | PK, auto-generated, immutable |
| email | varchar(255) | Unique, not null, valid email |
| passwordHash | varchar(60) | Not null, bcrypt, never in API responses |
| displayName | varchar(100) | Not null |
| role | enum('user','admin') | Default: 'user' |
| createdAt | timestamp | UTC, immutable, auto-set |
| updatedAt | timestamp | UTC, auto-updated |
| deletedAt | timestamp | Null unless soft-deleted |
```
### Rules
1. **Every entity in requirements MUST have a data model.** If FR-1 mentions "users", there must be a User model.
2. **Constraints MUST match requirements.** If FR-2 says passwords >= 8 chars, the model must note that.
3. **Include indexes.** If NFR-P1 says < 500ms queries, note which fields need indexes.
4. **Specify soft vs. hard delete.** State it explicitly.
---
## Section 9: Out of Scope
### Why This Section Matters
Out of Scope prevents scope creep during implementation. When someone says "while you're in there, could you also..." — point them to this section.
### Format
```markdown
- OS-1: Multi-factor authentication — Planned for Q3 (SPEC-045).
- OS-2: Social login beyond Google/GitHub — Insufficient user demand (< 2% requests).
- OS-3: Admin impersonation — Security review pending. Separate spec required.
- OS-4: Password strength meter UI — Nice-to-have, deferred to design sprint 12.
```
### Rules
1. **Every feature discussed and rejected MUST be listed.** This creates a paper trail.
2. **Include the reason.** "Not now" is not a reason. "Insufficient demand (< 2% of requests)" is.
3. **Link to future specs** when the exclusion is a deferral, not a rejection.
---
## Feature-Type Templates
### CRUD Feature
Focus on: all 4 operations, validation rules, authorization, pagination for list endpoints.
```markdown
- FR-1: Users MUST be able to create a [resource] with [required fields].
- FR-2: Users MUST be able to read a [resource] by ID.
- FR-3: Users MUST be able to list [resources] with pagination (default: 20/page).
- FR-4: Users MUST be able to update [mutable fields] of their own [resources].
- FR-5: Users MUST be able to delete their own [resources] (soft delete).
- FR-6: Users MUST NOT be able to modify or delete other users' [resources].
```
### Integration Feature
Focus on: external API contract, retry/fallback behavior, data mapping, error propagation.
```markdown
- FR-1: The system MUST call [external API] to [purpose].
- FR-2: The system MUST retry failed calls up to 3 times with exponential backoff.
- FR-3: The system MUST map [external field] to [internal field].
- FR-4: The system MUST NOT expose external API errors directly to users.
- EC-1: External API returns 5xx → Log error, return cached data if < 1h old, else 503.
- EC-2: External API response schema changes → Log warning, reject unmappable fields.
```
### Migration Feature
Focus on: backward compatibility, rollback plan, data integrity, zero-downtime deployment.
```markdown
- FR-1: The migration MUST transform [old schema] to [new schema].
- FR-2: The migration MUST be reversible (rollback script required).
- FR-3: The migration MUST NOT cause downtime exceeding 30 seconds.
- FR-4: The migration MUST validate data integrity post-run (row count, checksum).
- EC-1: Migration fails mid-way → Automatic rollback, alert ops team.
- EC-2: New schema has stricter constraints → Log invalid rows, quarantine for manual review.
```
---
## Checklist: Is This Spec Ready for Review?
- [ ] Every section is filled (or marked N/A with reason)
- [ ] All requirements use FR-N, NFR-N numbering
- [ ] RFC 2119 keywords are UPPERCASE
- [ ] Every AC references at least one requirement
- [ ] Every AC uses Given/When/Then
- [ ] Edge cases cover each external dependency failure
- [ ] API contracts define success AND error responses
- [ ] Data models include all entities from requirements
- [ ] Out of Scope lists items discussed and rejected
- [ ] No placeholder text remains
- [ ] Context includes evidence (metrics, tickets, research)
- [ ] Status is "In Review" (not still "Draft")
---
## Full Example: Password Reset
A complete spec demonstrating all sections, followed by extracted test stubs.
### The Spec
```markdown
# Spec: Password Reset Flow
**Author:** Engineering Team
**Date:** 2026-03-25
**Status:** Approved
## Context
Users who forget their passwords currently have no self-service recovery option.
Support receives ~200 password reset requests per week, costing approximately
8 hours of support time. This feature eliminates that burden entirely.
## Functional Requirements
- FR-1: The system MUST allow users to request a password reset via email.
- FR-2: The system MUST send a reset link that expires after 1 hour.
- FR-3: The system MUST invalidate all previous reset links when a new one is requested.
- FR-4: The system MUST enforce minimum password length of 8 characters on reset.
- FR-5: The system MUST NOT reveal whether an email exists in the system.
- FR-6: The system SHOULD log all reset attempts for audit purposes.
## Acceptance Criteria
### AC-1: Request reset (FR-1, FR-5)
Given a user on the password reset page
When they enter any email address and submit
Then they see "If an account exists, a reset link has been sent"
And the response is identical whether the email exists or not
### AC-2: Valid reset link (FR-2)
Given a user who received a reset email 30 minutes ago
When they click the reset link
Then they see the password reset form
### AC-3: Expired reset link (FR-2)
Given a user who received a reset email 2 hours ago
When they click the reset link
Then they see "This link has expired. Please request a new one."
### AC-4: Previous links invalidated (FR-3)
Given a user who requested two reset emails
When they click the link from the first email
Then they see "This link is no longer valid."
## Edge Cases
- EC-1: User submits reset for non-existent email → Same success message (FR-5).
- EC-2: User clicks reset link twice → Second click shows "already used" if password was changed.
- EC-3: Email delivery fails → Log error, do not retry automatically.
- EC-4: User requests reset while already logged in → Allow it, do not force logout.
## Out of Scope
- OS-1: Security questions as alternative reset method.
- OS-2: SMS-based password reset.
- OS-3: Admin-initiated password reset (separate spec).
```
### Extracted Test Cases
Generated by `test_extractor.py --framework pytest`:
```python
class TestPasswordReset:
def test_ac1_request_reset_existing_email(self):
"""AC-1: Request reset with existing email shows generic message."""
# Given a user on the password reset page
# When they enter a registered email and submit
# Then they see "If an account exists, a reset link has been sent"
raise NotImplementedError("Implement this test")
def test_ac1_request_reset_nonexistent_email(self):
"""AC-1: Request reset with unknown email shows same generic message."""
# Given a user on the password reset page
# When they enter an unregistered email and submit
# Then they see identical response to existing email case
raise NotImplementedError("Implement this test")
def test_ac2_valid_reset_link(self):
"""AC-2: Reset link works within expiry window."""
raise NotImplementedError("Implement this test")
def test_ac3_expired_reset_link(self):
"""AC-3: Reset link rejected after 1 hour."""
raise NotImplementedError("Implement this test")
def test_ac4_previous_links_invalidated(self):
"""AC-4: Old reset links stop working when new one is requested."""
raise NotImplementedError("Implement this test")
def test_ec1_nonexistent_email_same_response(self):
"""EC-1: Non-existent email produces identical response."""
raise NotImplementedError("Implement this test")
def test_ec2_reset_link_used_twice(self):
"""EC-2: Already-used reset link shows appropriate message."""
raise NotImplementedError("Implement this test")
```
FILE:scripts/spec_generator.py
#!/usr/bin/env python3
"""
Spec Generator - Generates a feature specification template from a name and description.
Produces a complete spec document with all required sections pre-filled with
guidance prompts. Output can be markdown or structured JSON.
No external dependencies - uses only Python standard library.
"""
import argparse
import json
import sys
import textwrap
from datetime import date
from pathlib import Path
from typing import Dict, Any, Optional
SPEC_TEMPLATE = """\
# Spec: {name}
**Author:** [your name]
**Date:** {date}
**Status:** Draft
**Reviewers:** [list reviewers]
**Related specs:** [links to related specs, or "None"]
---
## Context
{context_prompt}
---
## Functional Requirements
_Use RFC 2119 keywords: MUST, MUST NOT, SHOULD, SHOULD NOT, MAY._
_Each requirement is a single, testable statement. Number sequentially._
- FR-1: The system MUST [describe required behavior].
- FR-2: The system MUST [describe another required behavior].
- FR-3: The system SHOULD [describe recommended behavior].
- FR-4: The system MAY [describe optional behavior].
- FR-5: The system MUST NOT [describe prohibited behavior].
---
## Non-Functional Requirements
### Performance
- NFR-P1: [Operation] MUST complete in < [threshold] (p95) under [conditions].
- NFR-P2: [Operation] SHOULD handle [throughput] requests per second.
### Security
- NFR-S1: All data in transit MUST be encrypted via TLS 1.2+.
- NFR-S2: The system MUST rate-limit [operation] to [limit] per [period] per [scope].
### Accessibility
- NFR-A1: [UI component] MUST meet WCAG 2.1 AA standards.
- NFR-A2: Error messages MUST be announced to screen readers.
### Scalability
- NFR-SC1: The system SHOULD handle [number] concurrent [entities].
### Reliability
- NFR-R1: The [service] MUST maintain [percentage]% uptime.
---
## Acceptance Criteria
_Write in Given/When/Then (Gherkin) format._
_Each criterion MUST reference at least one FR-* or NFR-*._
### AC-1: [Descriptive name] (FR-1)
Given [precondition]
When [action]
Then [expected result]
And [additional assertion]
### AC-2: [Descriptive name] (FR-2)
Given [precondition]
When [action]
Then [expected result]
### AC-3: [Descriptive name] (NFR-S2)
Given [precondition]
When [action]
Then [expected result]
And [additional assertion]
---
## Edge Cases
_For every external dependency (API, database, file system, user input), specify at least one failure scenario._
- EC-1: [Input/condition] -> [expected behavior].
- EC-2: [Input/condition] -> [expected behavior].
- EC-3: [External service] is unavailable -> [expected behavior].
- EC-4: [Concurrent/race condition] -> [expected behavior].
- EC-5: [Boundary value] -> [expected behavior].
---
## API Contracts
_Define request/response shapes using TypeScript-style notation._
_Cover all endpoints referenced in functional requirements._
### [METHOD] [endpoint]
Request:
```typescript
interface [Name]Request {{
field: string; // Description, constraints
optional?: number; // Default: [value]
}}
```
Success Response ([status code]):
```typescript
interface [Name]Response {{
id: string;
field: string;
createdAt: string; // ISO 8601
}}
```
Error Response ([status code]):
```typescript
interface [Name]Error {{
error: "[ERROR_CODE]";
message: string;
}}
```
---
## Data Models
_Define all entities referenced in requirements._
### [Entity Name]
| Field | Type | Constraints |
|-------|------|-------------|
| id | UUID | Primary key, auto-generated |
| [field] | [type] | [constraints] |
| createdAt | timestamp | UTC, immutable |
| updatedAt | timestamp | UTC, auto-updated |
---
## Out of Scope
_Explicit exclusions prevent scope creep. If someone asks for these during implementation, point them here._
- OS-1: [Feature/capability] — [reason for exclusion or link to future spec].
- OS-2: [Feature/capability] — [reason for exclusion].
- OS-3: [Feature/capability] — deferred to [version/sprint].
---
## Open Questions
_Track unresolved questions here. Each must be resolved before status moves to "Approved"._
- [ ] Q1: [Question] — Owner: [name], Due: [date]
- [ ] Q2: [Question] — Owner: [name], Due: [date]
"""
def generate_context_prompt(description: str) -> str:
"""Generate a context section prompt based on the provided description."""
if description:
return textwrap.dedent(f"""\
{description}
_Expand this context section to include:_
_- Why does this feature exist? What problem does it solve?_
_- What is the business motivation? (link to user research, support tickets, metrics)_
_- What is the current state? (what exists today, what pain points exist)_
_- 2-4 paragraphs maximum._""")
return textwrap.dedent("""\
_Why does this feature exist? What problem does it solve? What is the business
motivation? Include links to user research, support tickets, or metrics that
justify this work. 2-4 paragraphs maximum._""")
def generate_spec(name: str, description: str) -> str:
"""Generate a spec document from name and description."""
context_prompt = generate_context_prompt(description)
return SPEC_TEMPLATE.format(
name=name,
date=date.today().isoformat(),
context_prompt=context_prompt,
)
def generate_spec_json(name: str, description: str) -> Dict[str, Any]:
"""Generate structured JSON representation of the spec template."""
return {
"spec": {
"title": f"Spec: {name}",
"metadata": {
"author": "[your name]",
"date": date.today().isoformat(),
"status": "Draft",
"reviewers": [],
"related_specs": [],
},
"context": description or "[Describe why this feature exists]",
"functional_requirements": [
{"id": "FR-1", "keyword": "MUST", "description": "[describe required behavior]"},
{"id": "FR-2", "keyword": "MUST", "description": "[describe another required behavior]"},
{"id": "FR-3", "keyword": "SHOULD", "description": "[describe recommended behavior]"},
{"id": "FR-4", "keyword": "MAY", "description": "[describe optional behavior]"},
{"id": "FR-5", "keyword": "MUST NOT", "description": "[describe prohibited behavior]"},
],
"non_functional_requirements": {
"performance": [
{"id": "NFR-P1", "description": "[operation] MUST complete in < [threshold]"},
],
"security": [
{"id": "NFR-S1", "description": "All data in transit MUST be encrypted via TLS 1.2+"},
],
"accessibility": [
{"id": "NFR-A1", "description": "[UI component] MUST meet WCAG 2.1 AA"},
],
"scalability": [
{"id": "NFR-SC1", "description": "[system] SHOULD handle [N] concurrent [entities]"},
],
"reliability": [
{"id": "NFR-R1", "description": "[service] MUST maintain [N]% uptime"},
],
},
"acceptance_criteria": [
{
"id": "AC-1",
"name": "[descriptive name]",
"references": ["FR-1"],
"given": "[precondition]",
"when": "[action]",
"then": "[expected result]",
},
],
"edge_cases": [
{"id": "EC-1", "condition": "[input/condition]", "behavior": "[expected behavior]"},
],
"api_contracts": [
{
"method": "[METHOD]",
"endpoint": "[/api/path]",
"request_fields": [{"name": "field", "type": "string", "constraints": "[description]"}],
"success_response": {"status": 200, "fields": []},
"error_response": {"status": 400, "fields": []},
},
],
"data_models": [
{
"name": "[Entity]",
"fields": [
{"name": "id", "type": "UUID", "constraints": "Primary key, auto-generated"},
],
},
],
"out_of_scope": [
{"id": "OS-1", "description": "[feature/capability]", "reason": "[reason]"},
],
"open_questions": [],
},
"metadata": {
"generated_by": "spec_generator.py",
"feature_name": name,
"feature_description": description,
},
}
def main():
parser = argparse.ArgumentParser(
description="Generate a feature specification template from a name and description.",
epilog="Example: python spec_generator.py --name 'User Auth' --description 'OAuth 2.0 login flow'",
)
parser.add_argument(
"--name",
required=True,
help="Feature name (used as spec title)",
)
parser.add_argument(
"--description",
default="",
help="Brief feature description (used to seed the context section)",
)
parser.add_argument(
"--output",
"-o",
default=None,
help="Output file path (default: stdout)",
)
parser.add_argument(
"--format",
choices=["md", "json"],
default="md",
help="Output format: md (markdown) or json (default: md)",
)
parser.add_argument(
"--json",
action="store_true",
dest="json_flag",
help="Shorthand for --format json",
)
args = parser.parse_args()
output_format = "json" if args.json_flag else args.format
if output_format == "json":
result = generate_spec_json(args.name, args.description)
output = json.dumps(result, indent=2)
else:
output = generate_spec(args.name, args.description)
if args.output:
out_path = Path(args.output)
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_text(output, encoding="utf-8")
print(f"Spec template written to {out_path}", file=sys.stderr)
else:
print(output)
sys.exit(0)
if __name__ == "__main__":
main()
FILE:scripts/spec_validator.py
#!/usr/bin/env python3
"""
Spec Validator - Validates a feature specification for completeness and quality.
Checks that a spec document contains all required sections, uses RFC 2119 keywords
correctly, has acceptance criteria in Given/When/Then format, and scores overall
completeness from 0-100.
Sections checked:
- Context, Functional Requirements, Non-Functional Requirements
- Acceptance Criteria, Edge Cases, API Contracts, Data Models, Out of Scope
Exit codes: 0 = pass, 1 = warnings, 2 = critical (or --strict with score < 80)
No external dependencies - uses only Python standard library.
"""
import argparse
import json
import re
import sys
from pathlib import Path
from typing import Dict, List, Any, Tuple
# Section definitions: (key, display_name, required_header_patterns, weight)
SECTIONS = [
("context", "Context", [r"^##\s+Context"], 10),
("functional_requirements", "Functional Requirements", [r"^##\s+Functional\s+Requirements"], 15),
("non_functional_requirements", "Non-Functional Requirements", [r"^##\s+Non-Functional\s+Requirements"], 10),
("acceptance_criteria", "Acceptance Criteria", [r"^##\s+Acceptance\s+Criteria"], 20),
("edge_cases", "Edge Cases", [r"^##\s+Edge\s+Cases"], 10),
("api_contracts", "API Contracts", [r"^##\s+API\s+Contracts"], 10),
("data_models", "Data Models", [r"^##\s+Data\s+Models"], 10),
("out_of_scope", "Out of Scope", [r"^##\s+Out\s+of\s+Scope"], 10),
("metadata", "Metadata (Author/Date/Status)", [r"\*\*Author:\*\*", r"\*\*Date:\*\*", r"\*\*Status:\*\*"], 5),
]
RFC_KEYWORDS = ["MUST", "MUST NOT", "SHOULD", "SHOULD NOT", "MAY"]
# Patterns that indicate placeholder/unfilled content
PLACEHOLDER_PATTERNS = [
r"\[your\s+name\]",
r"\[list\s+reviewers\]",
r"\[describe\s+",
r"\[input/condition\]",
r"\[precondition\]",
r"\[action\]",
r"\[expected\s+result\]",
r"\[feature/capability\]",
r"\[operation\]",
r"\[threshold\]",
r"\[UI\s+component\]",
r"\[service\]",
r"\[percentage\]",
r"\[number\]",
r"\[METHOD\]",
r"\[endpoint\]",
r"\[Name\]",
r"\[Entity\s+Name\]",
r"\[type\]",
r"\[constraints\]",
r"\[field\]",
r"\[reason\]",
]
class SpecValidator:
"""Validates a spec document for completeness and quality."""
def __init__(self, content: str, file_path: str = ""):
self.content = content
self.file_path = file_path
self.lines = content.split("\n")
self.findings: List[Dict[str, Any]] = []
self.section_scores: Dict[str, Dict[str, Any]] = {}
def validate(self) -> Dict[str, Any]:
"""Run all validation checks and return results."""
self._check_sections_present()
self._check_functional_requirements()
self._check_acceptance_criteria()
self._check_edge_cases()
self._check_rfc_keywords()
self._check_api_contracts()
self._check_data_models()
self._check_out_of_scope()
self._check_placeholders()
self._check_traceability()
total_score = self._calculate_score()
return {
"file": self.file_path,
"score": total_score,
"grade": self._score_to_grade(total_score),
"sections": self.section_scores,
"findings": self.findings,
"summary": self._build_summary(total_score),
}
def _add_finding(self, severity: str, section: str, message: str):
"""Record a validation finding."""
self.findings.append({
"severity": severity, # "error", "warning", "info"
"section": section,
"message": message,
})
def _find_section_content(self, header_pattern: str) -> str:
"""Extract content between a section header and the next ## header."""
in_section = False
section_lines = []
for line in self.lines:
if re.match(header_pattern, line, re.IGNORECASE):
in_section = True
continue
if in_section and re.match(r"^##\s+", line):
break
if in_section:
section_lines.append(line)
return "\n".join(section_lines)
def _check_sections_present(self):
"""Check that all required sections exist."""
for key, name, patterns, weight in SECTIONS:
found = False
for pattern in patterns:
for line in self.lines:
if re.search(pattern, line, re.IGNORECASE):
found = True
break
if found:
break
if found:
self.section_scores[key] = {"name": name, "present": True, "score": weight, "max": weight}
else:
self.section_scores[key] = {"name": name, "present": False, "score": 0, "max": weight}
self._add_finding("error", key, f"Missing section: {name}")
def _check_functional_requirements(self):
"""Validate functional requirements format and content."""
content = self._find_section_content(r"^##\s+Functional\s+Requirements")
if not content.strip():
return
fr_pattern = re.compile(r"-\s+FR-(\d+):")
matches = fr_pattern.findall(content)
if not matches:
self._add_finding("error", "functional_requirements", "No numbered requirements found (expected FR-N: format)")
if "functional_requirements" in self.section_scores:
self.section_scores["functional_requirements"]["score"] = max(
0, self.section_scores["functional_requirements"]["score"] - 10
)
return
fr_count = len(matches)
if fr_count < 3:
self._add_finding("warning", "functional_requirements", f"Only {fr_count} requirements found. Most features need 3+.")
# Check for RFC keywords
has_keyword = False
for kw in RFC_KEYWORDS:
if kw in content:
has_keyword = True
break
if not has_keyword:
self._add_finding("warning", "functional_requirements", "No RFC 2119 keywords (MUST/SHOULD/MAY) found.")
def _check_acceptance_criteria(self):
"""Validate acceptance criteria use Given/When/Then format."""
content = self._find_section_content(r"^##\s+Acceptance\s+Criteria")
if not content.strip():
return
ac_pattern = re.compile(r"###\s+AC-(\d+):")
matches = ac_pattern.findall(content)
if not matches:
self._add_finding("error", "acceptance_criteria", "No numbered acceptance criteria found (expected ### AC-N: format)")
if "acceptance_criteria" in self.section_scores:
self.section_scores["acceptance_criteria"]["score"] = max(
0, self.section_scores["acceptance_criteria"]["score"] - 15
)
return
ac_count = len(matches)
# Check Given/When/Then
given_count = len(re.findall(r"(?i)\bgiven\b", content))
when_count = len(re.findall(r"(?i)\bwhen\b", content))
then_count = len(re.findall(r"(?i)\bthen\b", content))
if given_count < ac_count:
self._add_finding("warning", "acceptance_criteria",
f"Found {ac_count} criteria but only {given_count} 'Given' clauses. Each AC needs Given/When/Then.")
if when_count < ac_count:
self._add_finding("warning", "acceptance_criteria",
f"Found {ac_count} criteria but only {when_count} 'When' clauses.")
if then_count < ac_count:
self._add_finding("warning", "acceptance_criteria",
f"Found {ac_count} criteria but only {then_count} 'Then' clauses.")
# Check for FR references
fr_refs = re.findall(r"\(FR-\d+", content)
if not fr_refs:
self._add_finding("warning", "acceptance_criteria",
"No acceptance criteria reference functional requirements (expected (FR-N) in title).")
def _check_edge_cases(self):
"""Validate edge cases section."""
content = self._find_section_content(r"^##\s+Edge\s+Cases")
if not content.strip():
return
ec_pattern = re.compile(r"-\s+EC-(\d+):")
matches = ec_pattern.findall(content)
if not matches:
self._add_finding("warning", "edge_cases", "No numbered edge cases found (expected EC-N: format)")
elif len(matches) < 3:
self._add_finding("warning", "edge_cases", f"Only {len(matches)} edge cases. Consider failure modes for each external dependency.")
def _check_rfc_keywords(self):
"""Check RFC 2119 keywords are used consistently (capitalized)."""
# Look for lowercase must/should/may that might be intended as RFC keywords
context_content = self._find_section_content(r"^##\s+Functional\s+Requirements")
context_content += self._find_section_content(r"^##\s+Non-Functional\s+Requirements")
for kw in ["must", "should", "may"]:
# Find lowercase usage in requirement-like sentences
pattern = rf"(?:system|service|API|endpoint)\s+{kw}\s+"
if re.search(pattern, context_content):
self._add_finding("warning", "rfc_keywords",
f"Found lowercase '{kw}' in requirements. RFC 2119 keywords should be UPPERCASE: {kw.upper()}")
def _check_api_contracts(self):
"""Validate API contracts section."""
content = self._find_section_content(r"^##\s+API\s+Contracts")
if not content.strip():
return
# Check for at least one endpoint definition
has_endpoint = bool(re.search(r"(GET|POST|PUT|PATCH|DELETE)\s+/", content))
if not has_endpoint:
self._add_finding("warning", "api_contracts", "No HTTP method + path found (expected e.g., POST /api/endpoint)")
# Check for request/response definitions
has_interface = bool(re.search(r"interface\s+\w+", content))
if not has_interface:
self._add_finding("info", "api_contracts", "No TypeScript interfaces found. Consider defining request/response shapes.")
def _check_data_models(self):
"""Validate data models section."""
content = self._find_section_content(r"^##\s+Data\s+Models")
if not content.strip():
return
# Check for table format
has_table = bool(re.search(r"\|.*\|.*\|", content))
if not has_table:
self._add_finding("warning", "data_models", "No table-formatted data models found. Use | Field | Type | Constraints | format.")
def _check_out_of_scope(self):
"""Validate out of scope section."""
content = self._find_section_content(r"^##\s+Out\s+of\s+Scope")
if not content.strip():
return
os_pattern = re.compile(r"-\s+OS-(\d+):")
matches = os_pattern.findall(content)
if not matches:
self._add_finding("warning", "out_of_scope", "No numbered exclusions found (expected OS-N: format)")
elif len(matches) < 2:
self._add_finding("info", "out_of_scope", "Only 1 exclusion listed. Consider what was deliberately left out.")
def _check_placeholders(self):
"""Check for unfilled placeholder text."""
placeholder_count = 0
for pattern in PLACEHOLDER_PATTERNS:
matches = re.findall(pattern, self.content, re.IGNORECASE)
placeholder_count += len(matches)
if placeholder_count > 0:
self._add_finding("warning", "placeholders",
f"Found {placeholder_count} placeholder(s) that need to be filled in (e.g., [your name], [describe ...]).")
# Deduct from overall score proportionally
for key in self.section_scores:
if self.section_scores[key]["present"]:
deduction = min(3, self.section_scores[key]["score"])
self.section_scores[key]["score"] = max(0, self.section_scores[key]["score"] - deduction)
def _check_traceability(self):
"""Check that acceptance criteria reference functional requirements."""
ac_content = self._find_section_content(r"^##\s+Acceptance\s+Criteria")
fr_content = self._find_section_content(r"^##\s+Functional\s+Requirements")
if not ac_content.strip() or not fr_content.strip():
return
# Extract FR IDs
fr_ids = set(re.findall(r"FR-(\d+)", fr_content))
# Extract FR references from AC
ac_fr_refs = set(re.findall(r"FR-(\d+)", ac_content))
unreferenced = fr_ids - ac_fr_refs
if unreferenced:
unreferenced_list = ", ".join(f"FR-{i}" for i in sorted(unreferenced))
self._add_finding("warning", "traceability",
f"Functional requirements without acceptance criteria: {unreferenced_list}")
def _calculate_score(self) -> int:
"""Calculate the total completeness score."""
total = sum(s["score"] for s in self.section_scores.values())
maximum = sum(s["max"] for s in self.section_scores.values())
if maximum == 0:
return 0
# Apply finding-based deductions
error_count = sum(1 for f in self.findings if f["severity"] == "error")
warning_count = sum(1 for f in self.findings if f["severity"] == "warning")
base_score = round((total / maximum) * 100)
deduction = (error_count * 5) + (warning_count * 2)
return max(0, min(100, base_score - deduction))
@staticmethod
def _score_to_grade(score: int) -> str:
"""Convert score to letter grade."""
if score >= 90:
return "A"
if score >= 80:
return "B"
if score >= 70:
return "C"
if score >= 60:
return "D"
return "F"
def _build_summary(self, score: int) -> str:
"""Build human-readable summary."""
errors = [f for f in self.findings if f["severity"] == "error"]
warnings = [f for f in self.findings if f["severity"] == "warning"]
infos = [f for f in self.findings if f["severity"] == "info"]
lines = [
f"Spec Completeness Score: {score}/100 (Grade: {self._score_to_grade(score)})",
f"Errors: {len(errors)}, Warnings: {len(warnings)}, Info: {len(infos)}",
"",
]
if errors:
lines.append("ERRORS (must fix):")
for e in errors:
lines.append(f" [{e['section']}] {e['message']}")
lines.append("")
if warnings:
lines.append("WARNINGS (should fix):")
for w in warnings:
lines.append(f" [{w['section']}] {w['message']}")
lines.append("")
if infos:
lines.append("INFO:")
for i in infos:
lines.append(f" [{i['section']}] {i['message']}")
lines.append("")
# Section breakdown
lines.append("Section Breakdown:")
for key, data in self.section_scores.items():
status = "PRESENT" if data["present"] else "MISSING"
lines.append(f" {data['name']}: {data['score']}/{data['max']} ({status})")
return "\n".join(lines)
def format_human(result: Dict[str, Any]) -> str:
"""Format validation result for human reading."""
lines = [
"=" * 60,
"SPEC VALIDATION REPORT",
"=" * 60,
"",
]
if result["file"]:
lines.append(f"File: {result['file']}")
lines.append("")
lines.append(result["summary"])
return "\n".join(lines)
def main():
parser = argparse.ArgumentParser(
description="Validate a feature specification for completeness and quality.",
epilog="Example: python spec_validator.py --file spec.md --strict",
)
parser.add_argument(
"--file",
"-f",
required=True,
help="Path to the spec markdown file",
)
parser.add_argument(
"--strict",
action="store_true",
help="Exit with code 2 if score is below 80",
)
parser.add_argument(
"--json",
action="store_true",
dest="json_flag",
help="Output results as JSON",
)
args = parser.parse_args()
file_path = Path(args.file)
if not file_path.exists():
print(f"Error: File not found: {file_path}", file=sys.stderr)
sys.exit(2)
content = file_path.read_text(encoding="utf-8")
if not content.strip():
print(f"Error: File is empty: {file_path}", file=sys.stderr)
sys.exit(2)
validator = SpecValidator(content, str(file_path))
result = validator.validate()
if args.json_flag:
print(json.dumps(result, indent=2))
else:
print(format_human(result))
# Determine exit code
score = result["score"]
has_errors = any(f["severity"] == "error" for f in result["findings"])
has_warnings = any(f["severity"] == "warning" for f in result["findings"])
if args.strict and score < 80:
sys.exit(2)
elif has_errors:
sys.exit(2)
elif has_warnings:
sys.exit(1)
else:
sys.exit(0)
if __name__ == "__main__":
main()
FILE:scripts/test_extractor.py
#!/usr/bin/env python3
"""
Test Extractor - Extracts test case stubs from a feature specification.
Parses acceptance criteria (Given/When/Then) and edge cases from a spec
document, then generates test stubs for the specified framework.
Supported frameworks: pytest, jest, go-test
Exit codes: 0 = success, 1 = warnings (some criteria unparseable), 2 = critical error
No external dependencies - uses only Python standard library.
"""
import argparse
import json
import re
import sys
import textwrap
from pathlib import Path
from typing import Dict, List, Any, Optional, Tuple
class SpecParser:
"""Parses spec documents to extract testable criteria."""
def __init__(self, content: str):
self.content = content
self.lines = content.split("\n")
def extract_acceptance_criteria(self) -> List[Dict[str, Any]]:
"""Extract AC-N blocks with Given/When/Then clauses."""
criteria = []
ac_pattern = re.compile(r"###\s+AC-(\d+):\s*(.+?)(?:\s*\(([^)]+)\))?\s*$")
in_ac = False
current_ac: Optional[Dict[str, Any]] = None
body_lines: List[str] = []
for line in self.lines:
match = ac_pattern.match(line)
if match:
# Save previous AC
if current_ac is not None:
current_ac["body"] = "\n".join(body_lines).strip()
self._parse_gwt(current_ac)
criteria.append(current_ac)
ac_id = int(match.group(1))
name = match.group(2).strip()
refs = match.group(3).strip() if match.group(3) else ""
current_ac = {
"id": f"AC-{ac_id}",
"name": name,
"references": [r.strip() for r in refs.split(",") if r.strip()] if refs else [],
"given": "",
"when": "",
"then": [],
"body": "",
}
body_lines = []
in_ac = True
elif in_ac:
# Check if we hit another ## section
if re.match(r"^##\s+", line) and not re.match(r"^###\s+", line):
in_ac = False
if current_ac is not None:
current_ac["body"] = "\n".join(body_lines).strip()
self._parse_gwt(current_ac)
criteria.append(current_ac)
current_ac = None
else:
body_lines.append(line)
# Don't forget the last one
if current_ac is not None:
current_ac["body"] = "\n".join(body_lines).strip()
self._parse_gwt(current_ac)
criteria.append(current_ac)
return criteria
def extract_edge_cases(self) -> List[Dict[str, Any]]:
"""Extract EC-N edge case items."""
edge_cases = []
ec_pattern = re.compile(r"-\s+EC-(\d+):\s*(.+?)(?:\s*->\s*|\s*->\s*|\s*→\s*)(.+)")
in_section = False
for line in self.lines:
if re.match(r"^##\s+Edge\s+Cases", line, re.IGNORECASE):
in_section = True
continue
if in_section and re.match(r"^##\s+", line):
break
if in_section:
match = ec_pattern.match(line.strip())
if match:
edge_cases.append({
"id": f"EC-{match.group(1)}",
"condition": match.group(2).strip().rstrip("."),
"behavior": match.group(3).strip().rstrip("."),
})
return edge_cases
def extract_spec_title(self) -> str:
"""Extract the spec title from the first H1."""
for line in self.lines:
match = re.match(r"^#\s+(?:Spec:\s*)?(.+)", line)
if match:
return match.group(1).strip()
return "UnknownFeature"
@staticmethod
def _parse_gwt(ac: Dict[str, Any]):
"""Parse Given/When/Then from the AC body text."""
body = ac["body"]
lines = body.split("\n")
current_section = None
for line in lines:
stripped = line.strip()
if not stripped:
continue
lower = stripped.lower()
if lower.startswith("given "):
current_section = "given"
ac["given"] = stripped[6:].strip()
elif lower.startswith("when "):
current_section = "when"
ac["when"] = stripped[5:].strip()
elif lower.startswith("then "):
current_section = "then"
ac["then"].append(stripped[5:].strip())
elif lower.startswith("and "):
if current_section == "then":
ac["then"].append(stripped[4:].strip())
elif current_section == "given":
ac["given"] += " AND " + stripped[4:].strip()
elif current_section == "when":
ac["when"] += " AND " + stripped[4:].strip()
def _sanitize_name(name: str) -> str:
"""Convert a human-readable name to a valid function/method name."""
# Remove parenthetical references like (FR-1)
name = re.sub(r"\([^)]*\)", "", name)
# Replace non-alphanumeric with underscore
name = re.sub(r"[^a-zA-Z0-9]+", "_", name)
# Remove leading/trailing underscores
name = name.strip("_").lower()
return name or "unnamed"
def _to_pascal_case(name: str) -> str:
"""Convert to PascalCase for Go test names."""
parts = _sanitize_name(name).split("_")
return "".join(p.capitalize() for p in parts if p)
class PytestGenerator:
"""Generates pytest test stubs."""
def generate(self, title: str, criteria: List[Dict], edge_cases: List[Dict]) -> str:
class_name = "Test" + _to_pascal_case(title)
lines = [
'"""',
f"Test suite for: {title}",
f"Auto-generated from spec. {len(criteria)} acceptance criteria, {len(edge_cases)} edge cases.",
"",
"All tests are stubs — implement the test body to make them pass.",
'"""',
"",
"import pytest",
"",
"",
f"class {class_name}:",
f' """Tests for {title}."""',
"",
]
for ac in criteria:
method_name = f"test_{ac['id'].lower().replace('-', '')}_{_sanitize_name(ac['name'])}"
docstring = f'{ac["id"]}: {ac["name"]}'
ref_str = f" [{', '.join(ac['references'])}]" if ac["references"] else ""
lines.append(f" def {method_name}(self):")
lines.append(f' """{docstring}{ref_str}"""')
if ac["given"]:
lines.append(f" # Given {ac['given']}")
if ac["when"]:
lines.append(f" # When {ac['when']}")
for t in ac["then"]:
lines.append(f" # Then {t}")
lines.append(' raise NotImplementedError("Implement this test")')
lines.append("")
if edge_cases:
lines.append(" # --- Edge Cases ---")
lines.append("")
for ec in edge_cases:
method_name = f"test_{ec['id'].lower().replace('-', '')}_{_sanitize_name(ec['condition'])}"
lines.append(f" def {method_name}(self):")
lines.append(f' """{ec["id"]}: {ec["condition"]} -> {ec["behavior"]}"""')
lines.append(f" # Condition: {ec['condition']}")
lines.append(f" # Expected: {ec['behavior']}")
lines.append(' raise NotImplementedError("Implement this test")')
lines.append("")
return "\n".join(lines)
class JestGenerator:
"""Generates Jest/Vitest test stubs (TypeScript)."""
def generate(self, title: str, criteria: List[Dict], edge_cases: List[Dict]) -> str:
lines = [
f"/**",
f" * Test suite for: {title}",
f" * Auto-generated from spec. {len(criteria)} acceptance criteria, {len(edge_cases)} edge cases.",
f" *",
f" * All tests are stubs — implement the test body to make them pass.",
f" */",
"",
f'describe("{title}", () => {{',
]
for ac in criteria:
ref_str = f" [{', '.join(ac['references'])}]" if ac["references"] else ""
test_name = f"{ac['id']}: {ac['name']}{ref_str}"
lines.append(f' it("{test_name}", () => {{')
if ac["given"]:
lines.append(f" // Given {ac['given']}")
if ac["when"]:
lines.append(f" // When {ac['when']}")
for t in ac["then"]:
lines.append(f" // Then {t}")
lines.append("")
lines.append(' throw new Error("Not implemented");')
lines.append(" });")
lines.append("")
if edge_cases:
lines.append(" // --- Edge Cases ---")
lines.append("")
for ec in edge_cases:
test_name = f"{ec['id']}: {ec['condition']}"
lines.append(f' it("{test_name}", () => {{')
lines.append(f" // Condition: {ec['condition']}")
lines.append(f" // Expected: {ec['behavior']}")
lines.append("")
lines.append(' throw new Error("Not implemented");')
lines.append(" });")
lines.append("")
lines.append("});")
lines.append("")
return "\n".join(lines)
class GoTestGenerator:
"""Generates Go test stubs."""
def generate(self, title: str, criteria: List[Dict], edge_cases: List[Dict]) -> str:
package_name = _sanitize_name(title).split("_")[0] or "feature"
lines = [
f"package {package_name}_test",
"",
"import (",
'\t"testing"',
")",
"",
f"// Test suite for: {title}",
f"// Auto-generated from spec. {len(criteria)} acceptance criteria, {len(edge_cases)} edge cases.",
f"// All tests are stubs — implement the test body to make them pass.",
"",
]
for ac in criteria:
func_name = "Test" + _to_pascal_case(ac["id"] + " " + ac["name"])
ref_str = f" [{', '.join(ac['references'])}]" if ac["references"] else ""
lines.append(f"// {ac['id']}: {ac['name']}{ref_str}")
lines.append(f"func {func_name}(t *testing.T) {{")
if ac["given"]:
lines.append(f"\t// Given {ac['given']}")
if ac["when"]:
lines.append(f"\t// When {ac['when']}")
for then_clause in ac["then"]:
lines.append(f"\t// Then {then_clause}")
lines.append("")
lines.append('\tt.Fatal("Not implemented")')
lines.append("}")
lines.append("")
if edge_cases:
lines.append("// --- Edge Cases ---")
lines.append("")
for ec in edge_cases:
func_name = "Test" + _to_pascal_case(ec["id"] + " " + ec["condition"])
lines.append(f"// {ec['id']}: {ec['condition']} -> {ec['behavior']}")
lines.append(f"func {func_name}(t *testing.T) {{")
lines.append(f"\t// Condition: {ec['condition']}")
lines.append(f"\t// Expected: {ec['behavior']}")
lines.append("")
lines.append('\tt.Fatal("Not implemented")')
lines.append("}")
lines.append("")
return "\n".join(lines)
GENERATORS = {
"pytest": PytestGenerator,
"jest": JestGenerator,
"go-test": GoTestGenerator,
}
FILE_EXTENSIONS = {
"pytest": ".py",
"jest": ".test.ts",
"go-test": "_test.go",
}
def main():
parser = argparse.ArgumentParser(
description="Extract test case stubs from a feature specification.",
epilog="Example: python test_extractor.py --file spec.md --framework pytest --output tests/test_feature.py",
)
parser.add_argument(
"--file",
"-f",
required=True,
help="Path to the spec markdown file",
)
parser.add_argument(
"--framework",
choices=list(GENERATORS.keys()),
default="pytest",
help="Target test framework (default: pytest)",
)
parser.add_argument(
"--output",
"-o",
default=None,
help="Output file path (default: stdout)",
)
parser.add_argument(
"--json",
action="store_true",
dest="json_flag",
help="Output extracted criteria as JSON instead of test code",
)
args = parser.parse_args()
file_path = Path(args.file)
if not file_path.exists():
print(f"Error: File not found: {file_path}", file=sys.stderr)
sys.exit(2)
content = file_path.read_text(encoding="utf-8")
if not content.strip():
print(f"Error: File is empty: {file_path}", file=sys.stderr)
sys.exit(2)
spec_parser = SpecParser(content)
title = spec_parser.extract_spec_title()
criteria = spec_parser.extract_acceptance_criteria()
edge_cases = spec_parser.extract_edge_cases()
if not criteria and not edge_cases:
print("Error: No acceptance criteria or edge cases found in spec.", file=sys.stderr)
sys.exit(2)
warnings = []
for ac in criteria:
if not ac["given"] and not ac["when"]:
warnings.append(f"{ac['id']}: Could not parse Given/When/Then — check format.")
if args.json_flag:
result = {
"spec_title": title,
"framework": args.framework,
"acceptance_criteria": criteria,
"edge_cases": edge_cases,
"warnings": warnings,
"counts": {
"acceptance_criteria": len(criteria),
"edge_cases": len(edge_cases),
"total_test_cases": len(criteria) + len(edge_cases),
},
}
output = json.dumps(result, indent=2)
else:
generator_class = GENERATORS[args.framework]
generator = generator_class()
output = generator.generate(title, criteria, edge_cases)
if args.output:
out_path = Path(args.output)
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_text(output, encoding="utf-8")
total = len(criteria) + len(edge_cases)
print(f"Generated {total} test stubs -> {out_path}", file=sys.stderr)
else:
print(output)
if warnings:
for w in warnings:
print(f"Warning: {w}", file=sys.stderr)
sys.exit(1)
sys.exit(0)
if __name__ == "__main__":
main()
Thiết kế và cải thiện offer: định khung giá trị, xếp bonus, bảo đảm, khan hiếm/khẩn cấp, đặt tên và cấu trúc thanh toán.
---
name: offers
description: "When the user wants to design, construct, or improve an offer — the thing they actually sell — including value framing, bonus stacking, guarantee design, scarcity/urgency, naming, and payment structure. Also use when the user mentions 'offer,' 'offer design,' 'build an offer,' 'grand slam offer,' 'irresistible offer,' 'value stack,' 'bonus stack,' 'guarantee,' 'risk reversal,' 'money-back guarantee,' 'scarcity,' 'urgency,' 'high-ticket offer,' 'productize a service,' 'naming an offer,' 'payment plan,' 'down-sell,' 'upsell offer,' or 'why isn't my offer converting.' Best for services, agencies, courses, coaching, info products, high-ticket B2B, and direct-response. If you run pure self-serve SaaS, read pricing first — tiers and packaging do more work there. For price level itself (tiers, freemium, value metric), see pricing. For the page that presents the offer, see copywriting. For the launch moment, see launch. For sales collateral, see sales-enablement."
metadata:
version: 1.0.1
---
# Offer Design
You are an expert in offer construction. Your goal is to help the user build offers that move — not by writing better copy on a worse offer, but by improving the offer itself.
## Before Starting
**Check for product marketing context first:**
If `.agents/product-marketing.md` exists (or `.claude/product-marketing.md`, or the legacy `product-marketing-context.md` filename, in older setups), read it before asking questions. Use that context and only ask for information not already covered or specific to this task.
---
## Core Philosophy
**The offer is the thing, not the page.** Better copy on a weak offer compounds slowly. A stronger offer with average copy converts immediately. Most "we need better copy" requests are actually "we need a better offer" requests in disguise.
This skill exists because the rest of the repo handles the *expression* of an offer — `copywriting` writes the sales page, `cro` optimizes the conversion path, `pricing` sets the tier structure, `launch` orchestrates the moment, `paywalls` shapes the upgrade prompt. None of them ask the deeper question: **is the offer underneath any of that actually good?**
### When this skill matters
You sell:
- **Services** — consulting, freelance, agency retainers, productized services
- **Courses** — async, cohort-based, live
- **Coaching** — 1:1, group, mastermind
- **Info products** — guides, swipe files, templates, communities
- **High-ticket B2B** — $5K+ ACV with a sales conversation
- **Direct-response** — e-com promo offers, infomercial-style, paid-traffic-to-VSL
### When `pricing` does more of the work
You sell:
- **Self-serve SaaS** with tiered subscriptions — the levers are mostly tier structure, value metric, and packaging; offer construction (bonuses, guarantees) is secondary
- **Marketplaces** — the offer is structural, not constructed
Skim this skill in those cases for the value equation framing, then go to `pricing`.
---
## The Value Equation
The single most useful frame for offer design. Originally from Alex Hormozi's *$100M Offers* — internalized broadly across direct-response and creator-economy training since.
```
Dream Outcome × Perceived Likelihood of Achievement
Value = ─────────────────────────────────────────────────────────
Time Delay × Effort & Sacrifice
```
You move the four levers like this:
| Lever | What it means | How to increase value |
|-------|---------------|-----------------------|
| **Dream outcome** ↑ | What the customer actually wants | Connect to the bigger goal behind the surface ask. Specify and name it. |
| **Perceived likelihood** ↑ | Do they believe they'll get it | Proof (case studies, named customers, data), guarantees, methodology specificity |
| **Time delay** ↓ | How long until result | Faster onboarding, faster first win, faster end-to-end timeline |
| **Effort & sacrifice** ↓ | What it costs them in time/work/risk besides money | Done-for-you, simpler process, fewer decisions, lower learning curve |
**Implication for offer construction**: most "lower the price" requests are actually "raise the numerator or lower the denominator" requests. Price is the comparison, not the value.
**For the full framework, examples, and how to diagnose which lever is broken:** see [references/value-equation.md](references/value-equation.md)
---
## The Anatomy of a Complete Offer
A complete offer has six components. Skip any one and conversion suffers.
| # | Component | Question it answers |
|---|-----------|---------------------|
| 1 | **Core deliverable** | What do they get? |
| 2 | **Bonus stack** | What else do they get that makes the core feel undervalued? |
| 3 | **Guarantee** | What happens if it doesn't work? |
| 4 | **Scarcity / urgency** | Why now, not later? |
| 5 | **Name** | What is this thing called? |
| 6 | **Price + payment structure** | What do they pay and how? |
Most weak offers fail on bonuses (none), guarantees (none or wrong type), or scarcity (none, or fake). Most aggressive-to-the-point-of-cringe offers fail on guarantee (over-promising) or scarcity (fake countdown timers).
**For the full anatomy with worked examples:** see [references/offer-anatomy.md](references/offer-anatomy.md)
---
## Reference Library
| Reference | When to read |
|-----------|--------------|
| [value-equation.md](references/value-equation.md) | Diagnosing which lever is broken on a stuck offer |
| [offer-anatomy.md](references/offer-anatomy.md) | Building a complete offer from scratch |
| [guarantee-design.md](references/guarantee-design.md) | Picking the right type of guarantee for your business model |
| [bonus-stacking.md](references/bonus-stacking.md) | Adding bonuses that raise perceived value without devaluing the core |
| [scarcity-urgency.md](references/scarcity-urgency.md) | Creating *real* scarcity (and avoiding the fake patterns that destroy trust) |
| [offer-formats.md](references/offer-formats.md) | Format playbooks by business type — service, course, coaching, info product, SaaS lead magnet, agency retainer, high-ticket B2B |
| [saas-offers.md](references/saas-offers.md) | SaaS specifically — the discount trap (why discounting to acquire backfires) + four SaaS worked offers (AudienceTap, SaberSim, Teachable, Kit) |
| [examples.md](references/examples.md) | Anonymized worked examples — before/after for each business type |
---
## The Diagnostic Loop
When the user says "my offer isn't converting" or "I want to improve my offer":
1. **Identify the business type** — service, course, coaching, info product, SaaS, agency, B2B. The right playbook is type-specific.
2. **State the current offer in plain language** — name, price, what they get, guarantee, deadline. Write it down even if it lives in scattered places now.
3. **Run the value equation** — score each of the four levers 1–10. The lowest is the binding constraint.
4. **Audit the anatomy** — which of the six components is missing or weak?
5. **Pick one lever to fix this iteration** — don't rebuild everything. The biggest lever is usually the one currently scoring lowest.
6. **Draft the changed component** — new bonus, new guarantee, new scarcity, new name, new payment plan
7. **Project the lift, honestly** — most single-component changes deliver 10–40% conversion lift. Anyone promising 5x is selling something. Two consecutive iterations on different levers can stack to 2–3x.
---
## When NOT to Use Offer-Design Tactics
Some offer patterns work but cost more than they're worth:
- **Manipulative scarcity** — fake countdown timers, "only 3 spots left" lies. Short-term lift, long-term trust collapse. Don't.
- **Over-promising guarantees** — "double your revenue or refund + $1,000." Refund risk eats margin; the few cases that fail nuke your reputation publicly.
- **Bonus inflation** — stacking $50K of "bonuses" on a $497 product so it "feels like a steal." Sophisticated buyers see this. Treat bonuses as additive, not exaggerated.
- **Course-bro aesthetic on a serious product** — Gold logos, "secret method," fake urgency. Pattern-matches to scam. Wrong room.
- **Discounting to acquire** — discount-*askers* churn at ~2× the rate of full-price customers, and a coupon anchors the product as cheap. Discount only for upgrades/cross-sells (rewarding existing customers) or real seasonal windows — never to win a new one. Raise value with an offer instead. See [saas-offers.md](references/saas-offers.md).
The repo voice: opinionated, but honest. Building offers well doesn't mean building offers loud.
---
## Banned Vocabulary
When drafting offer language (sales pages, emails, headlines), avoid:
- **"Game-changing," "revolutionary," "disruptive," "next-level," "10x"** — pattern-matches to AI slop / course-bro
- **"Secret," "hidden," "what they don't want you to know"** — clickbait
- **"Limited time" with no actual time limit** — lying
- **"Worth $X" or "$Y value" with no comparable** — inflation
- **"100% guaranteed" without specifying conditions** — legally and brand-wise risky
Use specific numbers, named customers, concrete outcomes, real timelines. Specificity beats superlatives.
---
## Related Skills
- **pricing** — for price levels, tier structure, value metric, packaging, freemium
- **copywriting** — for the page that presents the offer
- **cro** — for optimizing the conversion path the offer travels through
- **launch** — for the moment you ship the offer
- **paywalls** — for in-app upgrade-prompt versions of an offer
- **sales-enablement** — for the deck and one-pager that carry the offer into a sales conversation
- **emails** — for the email sequence that warms up the offer
- **marketing-psychology** — for the cognitive biases that make offers land or bounce
FILE:evals/evals.json
{
"skill_name": "offers",
"evals": [
{
"id": 1,
"prompt": "Our B2B SaaS trial-to-paid conversion is stuck. Our plan is to run a 50% off first 3 months coupon to push more people to convert. It's a $99/month email-tool competitor and the biggest thing prospects say is they're scared to migrate their list and automations off their current provider. Is the discount the right move?",
"expected_output": "Should push back on discounting to acquire and cite that discount-askers churn at roughly 2x the rate of full-price customers, that a coupon anchors the product as cheap, and attracts price-shoppers over value-buyers. Should state the rule: discount only for upgrades/cross-sells or real seasonal windows, never to acquire a new customer. Should reframe 'offers > discounts' — raise value instead of cutting price. Should diagnose that the real objection is switching cost / migration risk, not price, so a discount does nothing about it. Should recommend a real offer that reverses that risk — e.g. a done-for-you 'Painless Switch' style migration offer with a switching-cost guarantee (migration live/verified in N days or it's free) and a bonus that removes the secondary fear (deliverability/re-warm). May reference the value equation (lower effort/sacrifice and raise perceived likelihood rather than lower price). Should tie back to real, honest scarcity (capacity/queue) rather than a fake timer.",
"assertions": [
"Advises against discounting to acquire new customers",
"Cites discount-askers churn at ~2x full-price customers",
"States discount only for upgrades/cross-sells or seasonal moments",
"Frames offers as beating discounts (raise value, not cut price)",
"Identifies migration/switching cost as the real objection, not price",
"Recommends a done-for-you migration offer with a switching-cost guarantee",
"Suggests a bonus or risk-reversal that removes the real fear",
"Uses real scarcity (capacity/queue) rather than a fake timer"
],
"files": []
}
]
}
FILE:references/bonus-stacking.md
# Bonus Stacking
How to add bonuses that raise perceived value without devaluing the core offer.
## What bonuses actually do
Three jobs at once:
1. **Raise perceived value** of the total offer
2. **Lower perceived risk** — even if the core underdelivers, "I still got X for free"
3. **Close specific buying objections** — each bonus can target one objection
The third job is the underrated one. Most weak bonus stacks throw four generic "extras" at the buyer. Strong bonus stacks read the buyer's specific hesitations and close them in order.
---
## The core principle: bonuses-as-objection-handlers
For each major objection your buyer has, add a bonus that closes it.
### Common objections → matching bonus
| Objection | Targeted bonus |
|-----------|---------------|
| "I don't have time to implement this" | Done-for-you setup, week 1 |
| "I don't know which tools to use" | Pre-vetted tool stack with discount codes |
| "What if I get stuck?" | 30-day async Slack support |
| "I'm not sure my team will buy in" | Stakeholder pitch deck |
| "I've tried something like this before and it didn't work" | Case study from someone in your exact situation |
| "What about [edge case in my industry]?" | Industry-specific bonus reference doc |
| "Will I have to learn a bunch of new tools?" | Pre-built templates for the tools we recommend |
| "What if I don't finish it?" | 1:1 accountability check-in at day 30 |
| "My situation is more complex than the average buyer" | 1:1 onboarding call to customize the plan |
| "Will this work in [region/language]?" | Localized version or addendum |
A 4-bonus stack that closes 4 specific objections converts massively better than a 4-bonus stack of generic "extras."
### How to find your buyer's actual objections
1. Read every refund-request email and sales-call transcript from the last 6 months
2. Read your own sales page out loud and write down every doubt that surfaces
3. Ask 3 recent buyers: "What almost made you not buy?"
The answers cluster around 3–6 objections. Build a bonus for each.
---
## The math of bonus value
Each bonus has a stated value (what it would cost if you bought it separately). Bonuses should:
1. **Have a stated value the buyer can verify.** Compare to a comparable product or service. "$497 value — that's what the standalone template pack costs" beats "$5,000 value." (Standalone? Compared to what?)
2. **Total to less than 2x the price** of the core offer. A $1K offer can comfortably have $1.5K in bonuses. A $1K offer with "$25K in bonuses" reads as a scam.
3. **Be things you'd actually sell separately.** If you'd never sell the bonus as a standalone product, the stated value isn't real. Sophisticated buyers can tell.
4. **Each have a specific named outcome.** "Bonus: marketing toolkit" is weak. "Bonus: 12 pre-built Notion templates for your first 90 days, valued at $297 because that's what the standalone template pack sells for at [link]" is strong.
---
## The 4-bonus pattern that works
Most strong offers stack exactly 3–5 bonuses. More starts to feel like padding; fewer leaves objections un-closed.
A common structure:
| # | Type | Purpose | Typical value |
|---|------|---------|---------------|
| 1 | **Speed bonus** | Removes time-delay objection | Templates, swipes, accelerators |
| 2 | **Trust bonus** | Removes likelihood-of-failure objection | Case study, methodology doc, examples library |
| 3 | **Stuck bonus** | Removes "what if I get stuck" objection | Office hours, Slack, on-demand support |
| 4 | **Decision bonus** | Removes "I have to choose between X and Y" objection | Tool stack with discount codes, pre-vetted recommendations |
| 5 (optional) | **Bigger-than-you-asked bonus** | Adds dream-outcome surface area | Adjacent deliverable, related framework, partner offer |
Example for a $2K B2B copywriting course:
| # | Bonus | Closes |
|---|-------|--------|
| 1 | "30 winning sales page templates (last updated 2026-Q2)" — $297 value | "I don't have time to write from scratch" |
| 2 | "9 case studies from agencies that hit $250K MRR using these frameworks" — $0 (proof, not a saleable asset) | "Does this actually work for my situation?" |
| 3 | "60-day Slack access with weekly office hours" — $497 value | "What if I get stuck on a specific project?" |
| 4 | "The tool stack: 5 tools we use + discount codes (saves ~$1,200/yr)" — $1,200 value | "I don't know what to use" |
| 5 | "Bonus session: How to charge $5K+ per project" — $297 value | Pricing confidence (adjacent dream outcome) |
Total stated value: ~$2,300 in bonuses on a $2K core. Math checks out (under 2x). Each bonus closes a real objection.
---
## When bonuses backfire
### Inflated values
"$50,000 in bonuses included today only!" on a $497 product. The asymmetry is the tell — every sophisticated buyer's bullshit detector fires.
Stated values must be defensible. If you can't point to a comparable price, don't quote the value.
### Bonuses that devalue the core
If your core offer is "I'll write your sales page for $5K" and your bonus is "PLUS — bonus sales page edits for free for life!" — the bonus implies the core is incomplete. Now the buyer wonders why they should buy *without* the bonus.
Bonuses should be *additive* to a complete core, not patches on an incomplete one.
### Bonus-stack-as-substitute-for-core
A weak core surrounded by amazing bonuses converts at the moment of sale but produces angry refund requests. The buyer bought the bonuses, got the core, felt cheated.
Order: strong core *first*, then bonuses to address specific objections.
### Stacked too high
5+ bonuses with high stated values starts to read as a course-bro funnel. Premium buyers ignore the bonus list entirely; mid-market buyers feel they're being upsold; new buyers get confused.
3–5 bonuses, each with a specific purpose. Cap it.
### Same-as-everyone-else bonuses
"BONUS! Private community access!" on every course in your category isn't a bonus, it's table stakes. If every competitor offers the same bonuses, none of them are differentiators.
Find bonuses that are specific to your buyer's situation. A SaaS bonus for a SaaS-focused buyer beats a generic "private community" every time.
---
## Bonus delivery: timing matters
A bonus delivered on day 1 closes "what if I never use it?" risk.
A bonus delivered at week 4 maintains momentum.
A bonus delivered at completion rewards finishing.
Mix the timing intentionally:
- Day 1: speed bonuses (templates, swipes, toolkit)
- Week 2–4: support bonuses (Slack, office hours, check-ins)
- Completion: identity bonuses (certificate, alumni access)
A buyer who gets all bonuses up-front is more likely to abandon (they got what they wanted, lost incentive to finish). A buyer who gets some bonuses at completion is more likely to finish (and refer).
---
## The audit
For each existing bonus on a current offer, ask:
1. **What specific buying objection does this close?** If you can't name one, it's filler.
2. **What's its defensible stated value?** If you can't point to a comparable price, drop the dollar amount.
3. **Does the buyer get it on day 1, or at a meaningful point in their journey?** Timing should support the customer outcome, not just the conversion event.
4. **Is this bonus specific to my buyer, or could any competitor offer the same thing?** If it's generic, replace it.
5. **Is the bonus strong enough that the offer would still convert without the core?** If yes, the core is weak. Fix the core, don't lean on the bonus.
Most stuck offers have either zero bonuses or too many generic ones. The right move is usually: cut to 3–5 specific objection-closing bonuses, name each one clearly, and put defensible values on them.
FILE:references/examples.md
# Worked Examples — Before/After Offers
Anonymized examples drawn from real engagements. Each shows the weak version, the diagnostic, and the strong version.
---
## Example 1: Fractional CMO service
### Before
**The offer (as it was):**
> Fractional CMO services. $15K/month. We'll help you grow.
**Diagnostic:**
- Dream outcome: 4 (vague — "grow")
- Perceived likelihood: 3 (no methodology, no case studies)
- Time delay: 4 (no timeline, indefinite engagement)
- Effort & sacrifice: 5 (unclear what the buyer has to do)
- Anatomy: only the core is present. No bonuses, no guarantee, no scarcity, no name.
**Lowest binding constraint:** perceived likelihood. Buyers don't believe an unnamed service will deliver.
### After
| Component | What was added |
|-----------|----------------|
| **Core** | "The 90-Day Marketing Reset" — 8-week audit + 12-week execution plan, delivered by a CMO who's run marketing at 3+ similar-stage companies |
| **Bonuses** | (1) Weekly 1:1s for 12 weeks (~$12K value); (2) Pre-vetted execution-partner intros (priceless); (3) Board-deck marketing strategy section template |
| **Guarantee** | "After the 8-week audit, if you don't have a clear 90-day plan you'd run yourself, you don't pay the audit fee." |
| **Scarcity** | "We take 2 engagements per quarter — next slot opens [date]" |
| **Name** | "The 90-Day Marketing Reset" |
| **Price** | $15K → $5K start, $5K week 8, $5K week 16 |
Same delivery, same person, ~3x close rate, longer engagements (because the buyer is clearer about scope).
**Lesson:** the price didn't move. The structure did.
---
## Example 2: $1,997 cohort-based copywriting course
### Before
**The offer:**
> Learn copywriting. $1,997. Includes 6 modules and Slack access.
**Diagnostic:**
- Dream outcome: 5 ("learn copywriting" — surface ask, not dream outcome)
- Perceived likelihood: 3 (no case studies, no named methodology)
- Time delay: 4 (6-month course, no first-win)
- Effort & sacrifice: 4 (lots of homework, weekly calls, big commitment)
**Lowest binding constraint:** perceived likelihood. Buyers don't believe THEY can do it.
### After
| Component | What changed |
|-----------|--------------|
| **Core** | "Write sales pages clients pay you $5K+ for in 12 weeks" — outcome-framed |
| **Bonuses** | (1) 30 winning sales page templates (last updated Q2 2026) — $297 value; (2) 9 named case studies from copywriters in 6 industries — proof, not pitch; (3) 60-day Slack with weekly office hours — $497 value; (4) The tool stack with discount codes — $1,200 value |
| **Guarantee** | "Complete all 6 modules, submit the final exercise, and if you haven't written a sales page that lands you a $5K+ client within 12 months, refund in full." |
| **Scarcity** | Cohort scarcity — doors close Friday, next cohort in 3 months |
| **Name** | "The $5K Sales Page Bootcamp" |
| **Price** | $1,997 pay-in-full OR $797 × 3 |
Same modules. Same instructor. ~4x conversion. Lower refund rate (conditional guarantee qualifies).
**Lesson:** rename the outcome, add proof, install a real scarcity mechanic.
---
## Example 3: $97 Notion template pack
### Before
**The offer:**
> Notion templates for marketers. $97. 20 templates included.
**Diagnostic:**
- Dream outcome: 6 (clear what you get, less clear what you achieve with it)
- Perceived likelihood: 6 (templates work for some, less for others — no proof)
- Time delay: 8 (instant access)
- Effort & sacrifice: 5 (setup work to customize each template)
**Lowest binding constraint:** perceived likelihood + dream outcome. "Will these actually save me time, for *my* setup?"
### After
| Component | What changed |
|-----------|--------------|
| **Core** | "The Marketing Ops Stack — 20 Notion templates that turn your scattered docs into a working marketing OS in one Saturday" — outcome-framed |
| **Bonuses** | (1) 10-minute "do this first" Loom — speed bonus; (2) "Stack the templates" flowchart (visual setup map); (3) Lifetime updates as templates are added |
| **Guarantee** | "30-day no-questions money-back" — unconditional, fits the price point |
| **Scarcity** | Founding-buyer pricing — $97 for the first 200 buyers, then $147 |
| **Name** | "The Marketing Ops Stack" |
| **Price** | $97 pay-in-full |
Same templates. ~2x close rate from the same traffic. The differentiator was the "in one Saturday" outcome anchor and the Loom that proves the speed claim.
**Lesson:** for low-priced info products, the dream outcome and a fast first-win are the levers. Don't over-engineer the guarantee.
---
## Example 4: $50K B2B SaaS annual contract
### Before
**The offer:**
> Enterprise plan: $50K/year. Includes unlimited users, all features, dedicated support.
**Diagnostic:**
- Dream outcome: 5 (features-listed, not outcome-framed)
- Perceived likelihood: 5 (no roll-out plan, no time-to-value)
- Time delay: 3 (unclear when value starts; sales says "implementation varies")
- Effort & sacrifice: 4 (procurement + security review + IT integration + change management)
**Lowest binding constraint:** time delay. Enterprise buyers can't tolerate "implementation varies."
### After
| Component | What changed |
|-----------|--------------|
| **Core** | "Production-ready in 30 days, ROI by quarter end" — time-anchored |
| **Bonuses** | (1) Dedicated implementation engineer for 30 days; (2) Pre-built integration packs for top 5 platforms; (3) Custom training session for the buyer's team; (4) Quarterly business reviews with the buyer's CSM |
| **Guarantee** | "Not in production by day 30? You don't pay until you are." SLA-based. |
| **Scarcity** | Capacity-based: "We onboard 4 enterprise accounts per quarter. Next slot starts [date]." |
| **Name** | Tier name stayed "Enterprise" but added the engagement name "Strategic Onboarding" |
| **Price** | $50K annual → $50K annual with quarterly billing + paid 30-day pilot |
Same product. ~30% higher close rate, 50% shorter sales cycle. The pilot + SLA combination removed the procurement objection.
**Lesson:** for enterprise B2B, time-to-value IS the offer. Solve it explicitly.
---
## Example 5: $4K group coaching mastermind
### Before
**The offer:**
> Group coaching for founders. $4K/quarter. Includes 12 calls and Slack.
**Diagnostic:**
- Dream outcome: 5 (vague — "be a better founder")
- Perceived likelihood: 4 (one alumni testimonial, no methodology)
- Time delay: 6 (quarterly cadence reasonable)
- Effort & sacrifice: 7 (12 calls is real time)
**Lowest binding constraint:** dream outcome + perceived likelihood.
### After
| Component | What changed |
|-----------|--------------|
| **Core** | "12 founders, 12 weeks, one specific goal each — and a room that's seen it before" — peer-room positioning |
| **Bonuses** | (1) 1:1 onboarding call to set the personal goal; (2) Founder Library — 90 frameworks from past members; (3) 1:1 mid-quarter check-in; (4) Alumni access for 1 year after |
| **Guarantee** | "First two weeks — if it's not the room you wanted, full refund. After that, you're in." |
| **Scarcity** | Cohort size capped at 12 — once full, you're on the waitlist for next quarter |
| **Name** | "The Founders' Quarter" |
| **Price** | $4K/quarter pay-in-full OR $1,500 × 3 |
Same coach, same cadence. Higher close rate. Notably: members renew at ~70% (was ~35% before) because the "alumni access for 1 year" bonus changed the buying decision frame from "quarter" to "year."
**Lesson:** for coaching, the room IS the offer. Position the room, not the curriculum. Renewal-friendly bonuses lock in long-term LTV.
---
## Example 6: Agency retainer — content marketing
### Before
**The offer:**
> Content marketing retainer. $8K/month. 4 articles per month + SEO strategy.
**Diagnostic:**
- Dream outcome: 4 (output-described, not outcome-framed)
- Perceived likelihood: 5 (no case studies linking content to revenue)
- Time delay: 3 (SEO is slow; client expectations misaligned)
- Effort & sacrifice: 6 (interviews, reviews, approvals all on client side)
**Lowest binding constraint:** dream outcome (vague) and time delay (misaligned expectations).
### After
| Component | What changed |
|-----------|--------------|
| **Core** | "We own the content engine. You get organic-driven sales meetings by month 9, with measurable revenue attribution." — outcome + timeline |
| **Bonuses** | (1) Persona research kickoff (one-time); (2) Quarterly content audit + republish list; (3) Pre-vetted freelance writers with QA layer; (4) Quarterly executive readout |
| **Guarantee** | "First 30 days is a paid pilot — 4 published pieces + 3 keyword roadmap. If at the end you don't see a clear 12-month path, we end the engagement, no balance owed." |
| **Scarcity** | Capacity-based: "We take on 3 retainer clients per quarter. Next slot is [date]." |
| **Name** | Tier name: "Growth Retainer"; engagement name: "The 90-Day Content Reset → 9-Month Growth Engine" |
| **Price** | $8K/month, 6-month minimum, OR $7K/month for 12-month commit |
Same writers. Same SEO methodology. ~2x close rate. 60% of pilots convert to 12-month commits.
**Lesson:** for slow-cycle services (SEO, brand, content), the offer has to address the timeline explicitly. "Trust us, results in 6 months" doesn't sell; "paid pilot → milestone at day 30 → ramp" does.
---
## Pattern across all six examples
Look at the changes side-by-side:
| Example | Core change | Most important other change |
|---------|-------------|----------------------------|
| Fractional CMO | Named it, added scope | First-milestone guarantee |
| Copywriting course | Outcome-framed, added proof | Case studies bonus |
| Notion templates | "in one Saturday" anchor | First-step Loom bonus |
| B2B SaaS | Time-to-value commitment | SLA-based guarantee + pilot |
| Coaching mastermind | Positioned the room, not the coach | 1-year alumni access bonus |
| Agency retainer | Outcome + timeline framing | Paid pilot guarantee |
**The pattern:** in every case, the price barely moved (or didn't move at all). What moved was the *structure* of the offer — naming, framing, guaranteeing, sequencing.
The price is the comparison. The value is the offer.
FILE:references/guarantee-design.md
# Guarantee Design
A guarantee directly raises *perceived likelihood of achievement* (the buyer thinks: "they'll only offer this if they're confident") and lowers *effort & sacrifice* (less emotional risk). It's one of the highest-leverage levers in offer design.
The wrong guarantee hurts more than no guarantee. Pick the type that matches your business model.
## The eight guarantee types
| Type | What it promises | When it works | When it backfires |
|------|------------------|---------------|-------------------|
| 1. **Unconditional money-back** | "Refund anytime within X days, no questions" | Low-priced info, high-trust audience | High-priced/high-touch; refund risk eats margin |
| 2. **Conditional money-back** | "Refund if you complete X and still don't see Y" | Courses, programs requiring effort | Sophisticated buyers; harder to honor publicly |
| 3. **Better-than-money-back** | "If it doesn't work, full refund + $X" | Confident delivery, ample margin | If you fail; the few failures explode publicly |
| 4. **Service-level / SLA** | "If we don't deliver X by Y, your money back" | Productized services, agency work | Vague SLAs you can't measure |
| 5. **Performance-based** | "Pay only when X happens" (rev share, results-based) | Sophisticated B2B, high-confidence delivery | Long cycles, hard-to-attribute outcomes |
| 6. **Anti-guarantee** | "No refunds. Make sure you want it." | Premium audiences, mature buyers | Confused / first-time buyers; reads cold |
| 7. **Outcome-or-extension** | "If you don't get X by Y, we continue free" | Coaching, services with extendable time | Open-ended cost; choose with care |
| 8. **Comparison guarantee** | "Beat [competitor]'s result or refund" | When you can credibly compare | When you can't measure the competitor cleanly |
---
## Picking the right one
Decision tree:
1. **What's your buyer's biggest perceived risk?**
- "What if it doesn't work?" → money-back family (1, 2, 3)
- "What if you don't deliver on time?" → SLA (4)
- "What if I pay and get no results?" → performance-based (5) or outcome-or-extension (7)
- "Is this real or scam?" → comparison or specificity-based (8)
2. **What's your refund tolerance?**
- Can absorb refunds at scale → unconditional (1)
- Need to qualify refunders → conditional (2)
- Confident enough to add a bonus on top → better-than-money-back (3)
- Can't afford refunds at all → anti-guarantee (6) or no guarantee + strong proof
3. **What's your buyer sophistication?**
- Premium / mature buyers → anti-guarantee can work; "we don't do refunds" reads as confidence
- First-time-in-category buyers → strong refund guarantee; they need permission to try
- Sophisticated B2B → SLA or performance-based; they expect commercial terms
4. **How measurable is the outcome?**
- Clean and measurable → performance-based, comparison, or outcome-or-extension
- Fuzzy / subjective → money-back family with a conditional gate (you completed the work)
---
## Examples by business type
### Course / cohort
**Strong:** "Complete all six modules within 60 days, submit the final exercise, and if you haven't [specific outcome] we refund in full." Conditional on effort, clear on outcome.
**Weak:** "100% money-back guarantee." No conditions = refund magnet for buyers who never engaged.
### Coaching / consulting
**Strong:** "After the first two sessions, if you don't think the engagement will deliver, we end it and refund the unused balance." Mid-engagement off-ramp builds trust.
**Weak:** "Satisfaction guaranteed." Means nothing.
### Productized service / agency retainer
**Strong:** "First month is a paid pilot. At the end, if you don't see [specific milestone], you don't pay for month 2 and we end on good terms." Clear gate, clear out.
**Weak:** "We'll work until you're happy." Open-ended cost. Don't.
### High-ticket info product (community, mastermind)
**Strong (premium audience):** "No refunds. The application process is rigorous because the value is real. If you're not sure, don't apply yet." Anti-guarantee works here.
**Weak (premium audience):** Generic 30-day refund. Reads cheap.
### Low-ticket info product (template, swipe file)
**Strong:** "30-day no-questions refund." The transactional bar is "I bought it, looked at it, didn't want it." Unconditional fits.
**Weak:** No guarantee. The buyer's risk is too high for the price.
### SaaS
**Strong:** Free trial *or* annual-prepay-with-money-back-in-first-30-days. Reduces friction without locking in unhappy users.
**Weak:** "Cancel anytime" alone — not a guarantee, just standard SaaS terms.
### Direct response / paid traffic
**Strong:** Double-your-money-back or comparable risk inversion. Direct-response buyers expect risk-reversal-heavy offers.
**Weak:** Vanilla 30-day refund. Doesn't differentiate from every other ad on the platform.
---
## Writing the guarantee
The guarantee text matters. Patterns that work:
**Specific terms:**
> If, after completing the first 4 weeks of the program, you can't point to one specific business outcome you've achieved, email us and we'll refund 100%.
**Confident tone:**
> We know this works. If it doesn't for you, we don't want your money.
**Acknowledge the awkwardness:**
> Guarantees feel slimy. Here's ours anyway: if you do the work in modules 1–3 and don't see meaningful traction, we refund.
**Patterns that don't work:**
- "100% satisfaction guaranteed!" — generic, low-trust
- "Lifetime guarantee" — meaningless without conditions
- Multiple stacked guarantees — sophistication-collapsing
- Guarantees full of legalese — buyers skim and assume the worst
---
## Common mistakes
### Promising more than you can deliver
"Double your revenue or your money back + $1,000." If even 1 in 50 buyers fails and gets the bonus refund + writes a public review, the offer is permanently damaged.
Stress-test: what happens if 10% of buyers invoke the guarantee?
### Conditional guarantees with too many conditions
"Refund if you watched all 24 modules, completed the 6 exercises, attended every live call, and posted in the community at least once per week."
Buyers read this as "they made it impossible to actually get a refund." Trust drops.
Two conditions max. Three only if they're closely related (e.g., "completed the course AND submitted the final project AND emailed us a question").
### Hiding the guarantee in the fine print
If your guarantee is your strongest perceived-likelihood lever, *put it on the sales page in 24pt text*. Move it above the buy button.
### Forgetting to test it
Re-read your guarantee text every six months. The wording that worked a year ago may now be undermined by something you've changed about your offer.
### Treating guarantees as a substitute for proof
Strong proof + weak guarantee > strong guarantee + weak proof. Order matters. Build proof first, then layer on the guarantee.
---
## The honest case for NO guarantee
Anti-guarantees ("no refunds, this is final") work when:
- Buyer sophistication is high
- Application or qualification process precedes the sale
- Price is premium-to-luxury
- Brand is established
- Proof is overwhelming
What you're saying: "We don't need a guarantee because the work is real, the buyer has self-qualified, and we won't engage in transactional refund games."
The wrong audience reads this as cold or scammy. The right audience reads it as confidence. Know your buyer.
---
## The diagnostic
When auditing an offer with no guarantee (or a weak one), ask:
1. **What's the buyer's actual risk?** Make it concrete. ("$2K and I might not get more clients.")
2. **What guarantee structure reverses that specific risk?** Match it to one of the eight types.
3. **What's your honest refund tolerance?** Calculate refund rate × refund cost; can you sustain it?
4. **Does the guarantee match your audience sophistication?** Premium buyers want anti-guarantee; first-time buyers want unconditional.
Most offers don't have the wrong guarantee — they have *no* guarantee at all. Adding any guarantee is almost always a lift. Adding the right one is the lever.
FILE:references/offer-anatomy.md
# Offer Anatomy
A complete offer has six components. Skip any one and conversion suffers — usually noticeably.
## The six components
| # | Component | Question it answers | Where it fails |
|---|-----------|---------------------|----------------|
| 1 | **Core deliverable** | What do they get? | Too vague, or pitched as features instead of outcome |
| 2 | **Bonus stack** | What else do they get that makes the core feel undervalued? | Either no bonuses, or inflated/fake bonuses |
| 3 | **Guarantee** | What happens if it doesn't work? | None, wrong type, or over-promising |
| 4 | **Scarcity / urgency** | Why now, not later? | None, fake, or destructively manipulative |
| 5 | **Name** | What is this thing called? | Generic, internal-jargon, or no name at all |
| 6 | **Price + payment structure** | What do they pay and how? | Single number with no payment flexibility |
---
## 1. Core deliverable
The thing they actually get.
### Define it as an outcome, not a feature list
- **Feature-pitched (weak):** "6 modules, 24 lessons, weekly calls, private community."
- **Outcome-pitched (strong):** "A working customer-acquisition system that brings 5 qualified leads per week within 60 days — built with you, not handed to you."
The features still matter — buyers want to know what they're getting — but the *frame* is the outcome. Features support the outcome, they don't replace it.
### Define the scope explicitly
What's in. What's out. What's optional. Buyers buy clarity; ambiguity erodes perceived likelihood.
Example scope statement:
```
Includes:
- 90-day program with weekly live calls (recorded)
- Private Slack with daily founder access
- 12 fill-in-the-blank templates
- 1 90-minute strategy session with a senior strategist
Doesn't include:
- 1:1 calls outside the strategy session
- Implementation of the work (you/your team does this; we coach)
- Tools and software (you provide; we recommend specific stacks)
```
### Match the depth to the buyer's stage of awareness
Sophisticated buyers want the methodology and scope. New-to-category buyers want the dream outcome and proof. Read your audience.
---
## 2. Bonus stack
What you add to make the core feel undervalued at the asking price.
Bonuses do three jobs at once:
1. **Raise perceived value** of the total offer
2. **Lower perceived risk** — even if the core underdelivers, "I got X for free"
3. **Close specific objections** — each bonus can target a different buying objection
### How to construct bonuses
For each major objection your buyer has, add a bonus that closes it:
| Objection | Targeted bonus |
|-----------|---------------|
| "I don't have time to implement this" | Done-for-you setup, day 1 |
| "I don't know which tools to use" | Pre-vetted tool stack with discount codes |
| "What if I get stuck?" | 30-day async support |
| "I'm not sure my team will buy in" | Stakeholder pitch deck for your team |
| "I've tried something like this before and it didn't work" | Case study of someone in your exact situation |
A 4-bonus stack that closes 4 specific objections converts massively better than a 4-bonus stack of generic "extras."
### Don't inflate
"$50,000 in bonuses!" on a $500 offer reads as scam. The asymmetry destroys trust.
Bonuses should:
- Have a stated value the buyer can verify (compare to a comparable product)
- Total to less than 2x the price (e.g., a $1K offer can have ~$1.5K in bonuses comfortably)
- Be things you'd actually sell separately if you wanted
For the full bonus-stacking framework, see [bonus-stacking.md](bonus-stacking.md).
---
## 3. Guarantee
What happens if it doesn't work.
A guarantee directly raises perceived likelihood of achievement (the buyer thinks: "they'll only offer this if they're sure"). It also lowers effort & sacrifice (less emotional risk).
The wrong guarantee can hurt:
- Over-promising guarantees attract refund-seekers
- Generic "100% guaranteed" with no conditions reads as legally unenforceable
- No guarantee at all signals you're not confident
The right type depends on your business model, refund risk tolerance, and buyer sophistication. For the full taxonomy, see [guarantee-design.md](guarantee-design.md).
---
## 4. Scarcity / urgency
The reason to buy now, not later.
Two flavors:
- **Scarcity** — limited *quantity* (cohort size, seats, inventory, batch)
- **Urgency** — limited *time* (cohort deadline, season, bonus expiry)
The bar: **the scarcity has to be real.** Fake countdown timers and "only 3 spots left" lies work once and torch trust permanently. The internet is small; you will be caught.
Common honest scarcity formats:
- Cohort closes Friday (because the cohort actually starts Monday)
- Founding-member pricing for the first 20 customers (because you're capacity-constrained)
- Seasonal product or service (because demand is seasonal)
- Bonus expires at launch end (because the bonus is your time)
- Capacity-based service tier (because you literally can't take more clients)
For full guidance on creating real scarcity, see [scarcity-urgency.md](scarcity-urgency.md).
---
## 5. Name
What this thing is called.
A named offer beats an unnamed offer for three reasons:
1. **Repeatability** — buyers can tell their friend about it
2. **Distinction** — a name makes it a *thing*, not a generic service
3. **Pricing power** — branded offers can charge more than the same delivery sold as a service
### Naming patterns that work
- **Outcome-named:** "The 30-Day Activation Sprint" — names what they get
- **Methodology-named:** "The VAULT Framework" — names how you do it
- **Identity-named:** "Founder Marketing OS" — names who it's for
- **Compression-named:** "5-Day Cohort" — names the timing/structure
### Naming patterns that don't work
- **Generic descriptors:** "Marketing Coaching Program" — forgettable
- **Internal jargon:** "Tier 2 Standard" — buyer can't repeat
- **Course-bro:** "The Money-Making Machine" — pattern-matches to scam
- **Pun-overload:** "GrowthGoGetter" — reads as low-status
### Practical test
Can a buyer text a friend: "I just signed up for *the [name]*. It's $X and you get [one-line outcome]"? If yes, the name works. If no, rename.
---
## 6. Price + payment structure
The price is the obvious part. The structure is the underrated part.
### Price isn't a number, it's a comparison
Buyers compare the price to:
- The dream outcome (does this get me the result I want?)
- The next-best alternative (what else could I buy?)
- The cost of doing nothing (what does the status quo cost me?)
- Other items in your own catalog (anchor pricing)
You can move price perception without changing the number by:
- Showing the cost of doing nothing more vividly
- Anchoring against a higher-priced alternative
- Sequencing other items in your catalog at higher prices first
### Payment structure is its own lever
Same total price, different structures convert very differently:
| Structure | When it works | Trade-off |
|-----------|---------------|-----------|
| **Pay in full** | High-trust buyers, lower price points | Highest perceived commitment, smallest buyer pool |
| **Pay in 2-4 installments** | Mid-range price, hesitant buyers | More buyers, payment defaults |
| **Monthly subscription** | SaaS, ongoing services | Annuity revenue, churn risk |
| **Pay-after-results** | High-confidence delivery, sophisticated buyers | Cash flow lag, fewer disputes |
| **Down payment + balance on delivery** | Services with milestone-based delivery | Balance risk on backend |
| **Free trial → paid** | Low-friction SaaS, info products | Conversion drop-off |
Often the right move isn't lowering price — it's adding a payment plan. Same $6K price, "$6K today" vs "$2K × 3 monthly" converts very differently.
---
## Putting it together: an example
A B2B fractional CMO service.
| Component | Weak version | Strong version |
|-----------|--------------|----------------|
| **Core** | "Fractional CMO services" | "8-week marketing audit + 90-day execution plan, delivered by a CMO who's done it for 3+ similar companies" |
| **Bonuses** | None | (1) 1:1 weekly check-ins for 90 days; (2) pre-vetted execution-partner introductions; (3) board-deck for marketing strategy section |
| **Guarantee** | None | "If after the 8-week audit you don't have a clear 90-day plan you'd run yourself, you don't pay the audit fee" |
| **Scarcity** | None | "We take 2 engagements per quarter — next slot opens [date]" |
| **Name** | "fCMO Consulting" | "The 90-Day Marketing Reset" |
| **Price** | "$15K, paid up front" | "$15K → $5K to start, $5K at week 8, $5K at week 16" |
Same delivery. Same person. Different offer. Different conversion.
The point: most "we need to lower our price" conversations are actually "we have one of six components missing or weak" conversations.
FILE:references/offer-formats.md
# Offer Formats by Business Type
The right offer format depends on what you sell. The same six components (core, bonuses, guarantee, scarcity, name, price) get assembled differently by business type.
This reference is organized by business type. Find yours, then use the format as a starting point — not a fixed recipe.
---
## Service / freelance
You sell your time and skill.
### Default format
| Component | Default |
|-----------|---------|
| **Core** | A scoped engagement with a specific deliverable and timeline |
| **Bonuses** | Templates, frameworks, post-engagement support, tool stack |
| **Guarantee** | First-milestone gate (paid pilot or first-deliverable refund) |
| **Scarcity** | Capacity-based (next slot opens [date]) |
| **Name** | Methodology-named or outcome-named (e.g., "The 30-Day Activation Sprint") |
| **Price** | Project-based with down-payment, or monthly retainer |
### What to watch
- **Naming matters disproportionately** — services without named offers compete on price; named services compete on positioning
- **Scope creep is the offer killer** — define what's in, out, and optional, *in writing*, before the engagement starts
- **Bonuses should compound the deliverable** — templates and frameworks that make the buyer self-sufficient after the engagement, not during
### Productizing the offer
Move from "I sell consulting" to "I sell the 8-Week Marketing Reset." Same delivery, different offer.
The productized version:
- Has a name
- Has a fixed scope and timeline
- Has a fixed price
- Has the same bonuses every time
- Has a defined gate (week 4 check-in, paid pilot, milestone review)
Productizing raises perceived value, simplifies sales, and creates a repeatable case-study factory.
---
## Course (async, cohort-based, live)
You sell structured learning.
### Default format
| Component | Default |
|-----------|---------|
| **Core** | The curriculum + delivery format |
| **Bonuses** | Templates, swipe files, case studies, community access |
| **Guarantee** | Conditional money-back (completion-gated) |
| **Scarcity** | Cohort scarcity (enrollment closes [date]) |
| **Name** | Outcome-named or methodology-named |
| **Price** | Pay-in-full or 2–4 installments |
### Async vs cohort-based
| Decision | Async | Cohort |
|----------|-------|--------|
| **Pricing** | Lower ($297–$1,997) | Higher ($1,497–$5,000+) |
| **Scarcity** | Bonus expiry, price increases | Cohort start date |
| **Guarantee** | Generous unconditional | Conditional on completion |
| **Conversion mechanic** | Email funnel, evergreen webinar | Launch window + cohort deadline |
| **Default bonus** | Templates, swipes | Slack + office hours + 1:1 review |
### What to watch
- **Completion is the marketing asset** — every completer is a case study. Engineer the first win in week 1.
- **Cohort scarcity must be real** — if "doors close Friday" is followed by "doors reopen Monday because we extended the cohort," the trick gets noticed
- **Refund design matters more than refund rate** — a generous-sounding guarantee with smart conditions converts well and refunds rarely
---
## Coaching (1:1, group, mastermind)
You sell access to your expertise applied to their specific situation.
### Default format
| Component | Default |
|-----------|---------|
| **Core** | Sessions + asynchronous access + specific outcome focus |
| **Bonuses** | Resources from your library, intro to network, post-engagement check-ins |
| **Guarantee** | Outcome-or-extension or first-two-sessions out |
| **Scarcity** | Capacity-based (N spots / quarter) |
| **Name** | Identity-named or outcome-named (e.g., "Founder Marketing Mastermind") |
| **Price** | Monthly retainer or 3/6/12-month engagement |
### 1:1 vs group
| Decision | 1:1 | Group / mastermind |
|----------|-----|---------------------|
| **Pricing** | $1,500–$10,000+/mo | $497–$2,500+/mo |
| **Scarcity** | Capacity (4–10 1:1 clients) | Cohort size (8–30 members) |
| **Guarantee** | First-two-sessions out | Trial period, no refunds after |
| **Bonuses** | Custom resources, intros | Group access, peer accountability, library access |
| **Default delivery** | Weekly or biweekly sessions | Monthly group calls + community |
### What to watch
- **Identity is the offer** — group coaching is often more about being in the room with peers than about the coach's instruction. Name the room, not the coach.
- **Onboarding is part of the offer** — a sloppy intake destroys perceived likelihood
- **Renewal is the real conversion event** — design for the 6-month decision, not the first-month decision
---
## Info product (guide, swipe file, template pack, community)
You sell packaged knowledge or assets.
### Default format
| Component | Default |
|-----------|---------|
| **Core** | The asset(s) + lifetime access |
| **Bonuses** | Adjacent assets, walkthroughs, templates |
| **Guarantee** | Generous unconditional (30-day no-questions) |
| **Scarcity** | Bonus expiry, price-increase scheduling |
| **Name** | Outcome-named, often punchy and specific |
| **Price** | $29–$497, pay-in-full |
### What to watch
- **The first-impression matters disproportionately** — the buyer opens it once. If the first 5 minutes don't feel premium, they don't engage with the rest
- **Quick-start is a bonus** — pair the asset with a 10-minute "do this first" walkthrough
- **Lifetime access is implicit pricing** — clarify what "lifetime" means (yours, the product's, until you sunset it)
---
## High-ticket B2B ($5K+ ACV, sales-led)
You sell to companies with a sales conversation.
### Default format
| Component | Default |
|-----------|---------|
| **Core** | A multi-month engagement or annual contract |
| **Bonuses** | Onboarding, training, integration, dedicated CSM |
| **Guarantee** | SLA, performance-based, or pilot-gated |
| **Scarcity** | Quarter-end pricing, capacity (N onboardings/quarter), tier limits |
| **Name** | Internal-stable (e.g., "Enterprise Plan") + named engagement type (e.g., "Strategic Onboarding") |
| **Price** | Annual contract with quarterly payment, often custom |
### What to watch
- **Buying committee, not buyer** — the offer has to land with the champion, the economic buyer, and the influencer simultaneously
- **Procurement is the offer** — your terms (payment, NET 60, security review, MSA) are part of the offer; rigid terms lose deals
- **Pilot offers convert sophisticated buyers** — "30-day paid pilot, decide to continue at end" reduces decision risk
- **The CSM is part of the offer** — buyers consistently rate post-sale relationship as part of the offer-perceived-value
---
## Agency retainer
You sell ongoing service delivery.
### Default format
| Component | Default |
|-----------|---------|
| **Core** | Monthly deliverables + dedicated team + reporting cadence |
| **Bonuses** | Strategy sessions, tool access, audit credits, library access |
| **Guarantee** | Month-1 paid pilot or 90-day out clause |
| **Scarcity** | Capacity (N clients / vertical / quarter) |
| **Name** | Tier-named ("Growth," "Scale," "Enterprise") + service line |
| **Price** | Monthly retainer with discount for annual commit |
### What to watch
- **Onboarding velocity is the offer** — agencies that take 6 weeks to start delivering lose to agencies that deliver something in week 1
- **Reporting is part of the offer** — clean, monthly, action-oriented reporting reduces churn more than additional deliverables
- **Tier upgrades are the easiest revenue** — design tiers with clear value-step-ups so upgrade conversations are obvious
---
## Self-serve SaaS
You sell a tool with tiered subscriptions.
### Default format
| Component | Default |
|-----------|---------|
| **Core** | Tiered subscription with clear feature differentiation |
| **Bonuses** | Free onboarding, templates, integrations, partner discounts |
| **Guarantee** | Free trial OR annual-with-30-day-refund |
| **Scarcity** | Founding-pricing for first N customers, or seasonal launches |
| **Name** | Tier-named ("Starter," "Pro," "Team," "Enterprise") |
| **Price** | Monthly or annual with discount, value-metric-based |
### What to watch
- **Pricing tier > offer construction** — for self-serve SaaS, packaging and value metric do more work than guarantees and bonuses. Use the `pricing` skill.
- **Free trial design IS offer design** — length, gated features, credit-card-required vs not, automatic conversion. Each is an offer decision.
- **Annual prepay is the offer lever** — same product, different commitment, often 20–40% discount. Many SaaS conversion lifts come from improving the annual offer, not the monthly.
For SaaS, this skill is supplemental. Read [`pricing`](../../pricing/SKILL.md) first.
---
## Direct response / paid traffic
You sell from a sales page or VSL to cold traffic.
### Default format
| Component | Default |
|-----------|---------|
| **Core** | The "thing" + clear payoff |
| **Bonuses** | Heavy bonus stack (5–7 bonuses, layered values) |
| **Guarantee** | Aggressive risk reversal (better-than-money-back, double guarantee) |
| **Scarcity** | Real time-bound (launch window, evergreen with hard close) |
| **Name** | Hooky, often pattern-interrupt |
| **Price** | Often single-payment with payment plan offered |
### What to watch
- **Direct-response buyers expect aggression** — quiet, premium-feeling offers convert badly on cold paid traffic. The aesthetic of the page matters as much as the offer.
- **Refund rates can be 10–20%** — bake this into the math. If margin can't survive 15% refunds, restructure.
- **The first 7 seconds determine the rest** — hook, then offer
This format is high-skill. If you're not from a direct-response background, hire someone or partner with someone who is.
---
## Choosing your format
If you're not sure which format applies, pick the closest match and adapt. The biggest mistake is borrowing a format from a different business type (e.g., applying direct-response bonus stacking to a premium B2B service — wrong audience, wrong aesthetic).
Two diagnostic questions:
1. **Who buys it, and how sophisticated are they?** Premium B2B and direct-response cold traffic both buy, but they need different offers.
2. **What's the dominant constraint?** Service businesses are capacity-constrained, SaaS is pricing-tier-constrained, courses are cohort/season constrained. Match the scarcity format to the real constraint.
For worked examples by business type, see [examples.md](examples.md).
FILE:references/saas-offers.md
# SaaS Offers — the discount trap + worked examples
The rest of the offers library skews services, courses, and coaching. This reference covers the SaaS case specifically: why discounting is the wrong acquisition lever, and four worked offers that stack risk-reversal, bonuses, and scarcity for a software business.
Read this alongside [offer-formats.md](offer-formats.md#self-serve-saas). For price level and tier structure, the `pricing` skill still does the heavier lifting — this covers the *offer* wrapped around the price.
---
## The discount trap
The instinct when a SaaS isn't converting is to cut the price — a launch coupon, a "50% off first 3 months," a permanent lower tier. It's the most-reached-for lever and one of the worst.
**Offers beat discounts.** A discount lowers the price. An offer raises the value — a guarantee, a done-for-you migration, a bonus that removes the switching cost. Same net price to the buyer, but one trains them to expect cheap and the other trains them to expect valuable.
### The data
**Discount-*askers* churn at roughly 2× the rate of full-price customers.** The buyer who negotiated their way in is signaling something: price was the reason they bought, not value. When a cheaper option appears — or when the renewal hits full price — they leave. You bought a customer who was never yours.
This compounds. Discounting to acquire also:
- **Anchors the product as cheap** — hard to raise later without churn spikes
- **Attracts the wrong ICP** — price-shoppers, not value-buyers
- **Trains the market to wait** — buyers learn there's always a sale coming, so they never pay full
- Sits next to **trial fatigue and discount fatigue** — the same buyer who's seen a hundred "50% off" banners no longer feels urgency from yours
### The rule
**Never discount to acquire.** Discount only in two moments, where the mechanics actually work for you:
| When | Why it works |
|------|--------------|
| **Upgrades / cross-sells** | The customer already values the product. A discount to move up a tier or add a product rewards commitment instead of buying a stranger. |
| **Seasonal moments** | Black Friday, year-end, an annual-plan push — a real, time-bound, everyone-gets-it window. Not a permanent price cut wearing a costume. |
Everything else is an **offer**, not a discount. If conversion is stuck at the top of the funnel, reverse the risk and stack the value — don't cut the price. See the four examples below.
---
## Four SaaS worked offers
Each shows how to wrap a SaaS price in a real offer — risk-reversal, a switching-cost-killing bonus, and honest scarcity — instead of a coupon.
### 1. AudienceTap — $297 fixed-price pilot
**Business:** audience-analytics SaaS, self-serve, mid-market buyers hesitant to sign an annual before seeing value on their own data.
**The offer instead of a discount:**
| Component | What it is |
|-----------|------------|
| **Core** | A **$297 fixed-price 30-day pilot** — full product, run against the buyer's real audience, not a demo dataset |
| **Risk reversal** | "If the pilot doesn't surface an insight your team acts on, the $297 is refunded — and it credits toward annual if you convert." |
| **Bonus** | A done-with-you setup session (connect sources, first report) so time-to-value is days, not weeks |
| **Scarcity** | Capacity-real: "We onboard 6 pilots a month so each gets the setup session." Not a fake counter. |
**Why it beats a discount:** the pilot is a paid, low-risk *yes* that filters for value-buyers. The $297 isn't a price cut — it's a fee that credits forward, so full-price annual is the default next step, not a negotiation.
### 2. SaberSim — $497 bundle
**Business:** sports-analytics/optimizer SaaS. Individual tools convert fine alone but the full stack is where retention lives.
**The offer instead of a discount:**
| Component | What it is |
|-----------|------------|
| **Core** | A **$497 bundle** of the optimizer + sync + data feed — the tools that only pay off together |
| **Risk reversal** | 14-day "run it on a real slate" guarantee — use it live, full refund if it doesn't beat the buyer's current workflow |
| **Bonus** | Strategy walkthroughs + a starter template library so the bundle produces a result on day one |
| **Scarcity** | Seasonal: bundle priced for the **start of the season**; after kickoff it unbundles to full à-la-carte |
**Why it beats a discount:** the bundle raises perceived value (three tools, one decision) rather than lowering price on one. The season start is *real* seasonal scarcity — an allowed discount moment — not a permanent markdown.
### 3. Teachable — $1,997 accelerator
**Business:** course-platform SaaS. The subscription is cheap; the value (and the switching cost) is getting a course actually launched.
**The offer instead of a discount:**
| Component | What it is |
|-----------|------------|
| **Core** | A **$1,997 "Launch Accelerator"** — the platform plan **plus** a structured 6-week program to ship the buyer's first course |
| **Risk reversal** | Outcome guarantee: "Publish your course in 6 weeks or we work with you free until you do." Tied to a completion condition. |
| **Bonus** | Launch-email templates, a pricing-page teardown, and a cohort Slack — the pieces creators stall on |
| **Scarcity** | Cohort-based: accelerator runs on a start date, capped seat count, next cohort later |
**Why it beats a discount:** the accelerator sells the *outcome* (a launched course) at a price far above the raw subscription — the opposite of discounting. The guarantee de-risks the real fear ("I'll pay and never launch"), and the cohort cap is honest scarcity.
### 4. Kit — "Painless Switch" $997 migration offer
**Business:** email-platform SaaS. The blocker isn't price — it's the terror of migrating a list, sequences, and automations off the incumbent.
**The offer instead of a discount:**
| Component | What it is |
|-----------|------------|
| **Core** | A **$997 "Painless Switch"** — done-for-you migration of list, forms, sequences, and automations |
| **Risk reversal** | "If your migration isn't live and verified in 14 days, it's free." The guarantee is on the *switching cost*, the actual objection |
| **Bonus** | A deliverability audit + a re-warm plan so the switch doesn't tank open rates — removes the second-biggest fear |
| **Scarcity** | Capacity-real: migration engineers handle a fixed number per month; the offer closes when the queue fills |
**Why it beats a discount:** the entire barrier to a SaaS switch is effort and risk, not price. A discount does nothing about migration dread; a done-for-you offer with a switching-cost guarantee removes it. The buyer pays *more* up front and churns *less*, because they're a value-buyer who committed.
---
## The pattern across all four
| Offer | Price | Risk reversal is on… | Scarcity type |
|-------|-------|----------------------|---------------|
| AudienceTap pilot | $297 | Whether it surfaces an actionable insight | Capacity (setup sessions) |
| SaberSim bundle | $497 | Whether it beats the current workflow | Seasonal (season start) |
| Teachable accelerator | $1,997 | Whether the course actually launches | Cohort (start date + cap) |
| Kit Painless Switch | $997 | Whether migration is live in 14 days | Capacity (engineer queue) |
Notice what's *not* here: a coupon, a "% off," a slashed sticker price. Every one raises value and reverses the real risk — and the scarcity is either capacity, cohort, or a genuine seasonal window, never a fake timer.
**The SaaS takeaway:** when conversion is stuck, your instinct will be to discount. Build the offer instead. Discount only to reward existing customers (upgrades, cross-sells) or in a real seasonal window — never to acquire a stranger you'll watch churn at 2×.
FILE:references/scarcity-urgency.md
# Scarcity & Urgency
The reason to buy now, not later.
This is the most misused offer lever in the industry. Done right, it's a meaningful conversion lift and a respect-the-buyer's-time gesture. Done wrong (fake countdown timers, lies about inventory), it converts once and torches trust permanently.
The repo voice: **only ship real scarcity.** If the scarcity is fake, take it off the page.
## Scarcity vs urgency
| | What it limits | Examples |
|---|---|---|
| **Scarcity** | Quantity | Cohort size, seats, inventory, batch, capacity |
| **Urgency** | Time | Cohort deadline, season, bonus expiry, price increase |
Both work via the same mechanism — they convert "I'll think about it" into "decide now." The difference is what enforces the decision: a limit on how many, or a limit on when.
---
## Honest scarcity formats
The bar: **the constraint has to be real.** Here are the formats that work without lying.
### Capacity-based scarcity
You can only deliver to N customers at a time. The next slot opens when one finishes.
> "We take 2 fractional CMO engagements per quarter. Next slot opens September 15."
Works for: services, agencies, coaching, consulting, anything labor-bound.
### Cohort scarcity
The class starts on a date. After the date, the door closes until the next cohort.
> "Cohort 7 starts October 1. Doors close September 28. Next cohort: January."
Works for: courses, programs, group coaching, anything synchronous.
### Founding-member pricing
The first N buyers get a different price. After that, the price goes up.
> "Founding pricing — $497/mo for the first 50 members. After we hit 50, the price moves to $797/mo."
Works for: SaaS, communities, memberships. Critical: actually raise the price when you hit 50. Otherwise it was a lie.
### Inventory-based scarcity
You have N units. When they're gone, they're gone.
> "We're only producing 100 of the print edition. Sold out, no reprints."
Works for: physical products, limited-run digital, bespoke services.
### Seasonal scarcity
Demand or capacity is seasonal.
> "Tax-prep service for Q1 2026 closes January 15. After that, we focus on existing clients only until April."
Works for: tax, retail Q4, events, education enrollment, anything calendar-bound.
### Bonus expiry
A bonus is available only until X date. The core offer stays the same; the *added value* expires.
> "Order by Friday and the 1:1 onboarding session is included. After Friday, the offer is the same but onboarding is +$497."
Works for: anything with a launch window. Critical: actually remove the bonus when the date hits.
### Price-increase scheduling
The price goes up on a date. Communicate it transparently.
> "On November 1, the program moves from $1,997 to $2,497. Anyone enrolled before November 1 keeps the $1,997 price for life."
Works for: courses, SaaS, communities. Builds credibility because the buyer can verify it later.
---
## Fake scarcity to avoid
Pattern-match and rip these off your page:
### Countdown timers that reset
The timer hits 0:00 and refreshes. The buyer comes back tomorrow, same timer, same urgency. Every buyer who notices loses trust.
If you use a countdown, it ends on a real date. After that date, the discount or bonus actually ends — verifiably.
### "Only 3 spots left"
Especially on digital products where there's no capacity constraint. Especially when the number stays at "3" for weeks.
Use this only when the constraint is real (cohort, capacity, inventory) AND when the number reflects reality.
### Manufactured FOMO
"127 people are looking at this page right now."
These can be honest on some platforms (Booking.com etc.) when they reflect actual traffic. They're dishonest when fabricated. Buyers increasingly assume fabrication.
### Bonus "stacking" that's always available
"This bonus expires at midnight!" — but every page on the site says the same thing at midnight every night.
The bonus has to actually expire, or the line is a lie.
### "Last chance" emails for things that aren't actually last chance
Repeated "FINAL HOURS" emails that send weekly. Every recipient who notices unsubscribes or stops opening.
Use "last chance" once. If you use it multiple times, you've taught your list that "last chance" is meaningless.
---
## Why fake scarcity is uniquely costly
Buyers compare notes. The internet is small.
- One Reddit post about a fake countdown becomes the top Google result for your brand
- One screenshot of "only 3 left" staying at 3 for a month becomes a viral thread
- One "last chance" email that wasn't goes into a "marketing fails" newsletter
Real scarcity converts ~the same as fake scarcity at the moment of purchase. The difference shows up at month 6, when fake-scarcity offers are facing trust collapse and real-scarcity offers are still compounding.
If you have to fake it, you don't have an offer-design problem — you have a value-equation problem. Go back to [value-equation.md](value-equation.md).
---
## When scarcity isn't needed
Some offers don't need scarcity:
- **Subscription products** that customers can cancel anytime — the natural friction is low enough
- **Low-priced impulse products** ($5–30) — the deliberation is short; scarcity feels forced
- **Premium / luxury brands** — scarcity is implicit in the positioning; explicit scarcity reads as low-status
- **High-trust audiences** who already know they'll buy — scarcity is unnecessary friction
Don't force scarcity into offers that don't need it. The forced version is worse than no scarcity.
---
## The diagnostic
For an existing offer with weak or no scarcity:
1. **Is there a real constraint?** Capacity, cohort, inventory, season, batch. Find the real one.
2. **What's the honest version of that constraint?** Write it in plain English.
3. **Can the buyer verify it?** If you say "founding pricing for the first 50 members," can the buyer see the count?
4. **Are you willing to actually enforce it?** When the constraint hits, do you have the discipline to actually close the door / raise the price / remove the bonus?
5. **Where on the page does the scarcity appear?** It should be next to the buy button, not buried.
If you can't find a real constraint, don't ship scarcity. The "trick" of fake scarcity is one of the most expensive shortcuts in marketing. The honest version is usually cheaper than people think — every business has *some* real constraint (capacity, calendar, batch, attention).
---
## Pairing with the rest of the offer
Scarcity is the final lever. It only works if the rest of the offer is strong.
- Strong offer + real scarcity = the buyer decides now
- Weak offer + scarcity = the buyer decides not to buy, faster
If conversions are bad, scarcity is rarely the first thing to fix. Run the diagnostic from [value-equation.md](value-equation.md) first.
The order is:
1. Strong dream outcome
2. Specific perceived likelihood (proof + methodology + guarantee)
3. Compressed time delay
4. Reduced effort & sacrifice
5. *Then* scarcity to get the decision now
Scarcity is the close, not the offer.
FILE:references/value-equation.md
# The Value Equation
The single most useful frame for offer design. Originated in Alex Hormozi's *$100M Offers*; the underlying idea (multiply benefits, divide costs) is much older — direct-response copywriters have been doing it for a century.
## The formula
```
Dream Outcome × Perceived Likelihood of Achievement
Value = ─────────────────────────────────────────────────────────
Time Delay × Effort & Sacrifice
```
The customer compares the *Value* score to the *Price*. If Value > Price, they buy. If not, they don't, no matter how good the copy is.
**Price is the comparison, not the value.** Most "lower the price" requests are actually "raise the numerator or lower the denominator" requests.
---
## Lever 1: Dream Outcome (numerator)
What the customer *actually* wants — usually one or two levels above the surface ask.
### Surface ask → dream outcome
| Surface ask | Dream outcome |
|-------------|---------------|
| "I want a website" | "I want more qualified leads I can close" |
| "I want to learn copywriting" | "I want to write copy clients pay me $5K+ per project for" |
| "I want a meal plan" | "I want to feel confident in a swimsuit on a beach in 12 weeks" |
| "I want fitness coaching" | "I want my back pain gone so I can pick up my kids without thinking about it" |
| "I want a Notion template" | "I want to feel in control of my work for the first time in years" |
| "I want to lower CAC" | "I want a marketing engine I can step away from for two weeks without things breaking" |
### How to increase
- **Name it specifically.** "Feel confident in a swimsuit" beats "lose weight." The specific name is the offer.
- **Connect to the bigger goal.** The dream outcome behind the surface ask is almost always emotional or identity-based.
- **Show the future state in concrete sensory terms.** What does the morning of day one *after they have it* actually look like?
### Common mistake
Pitching the surface ask. ("Get a website" instead of "get a website that brings you 5 qualified leads a week.") The bigger the buyer's pain, the more important this is — they're not buying a deliverable, they're buying a future.
---
## Lever 2: Perceived Likelihood of Achievement (numerator)
Do they actually believe they'll get the dream outcome? This is the single most underweighted lever — most offers are perfectly fine on the dream outcome side but the buyer just doesn't think it'll work *for them*.
### How to increase
- **Proof** — case studies with names, numbers, before/after metrics, photos. Specific > glossy.
- **Methodology specificity** — name your process. "The 5-step VAULT framework" beats "our proprietary system." Even if the substance is the same, naming it raises perceived likelihood.
- **Guarantees** — risk reversal directly raises perceived likelihood (more in [guarantee-design.md](guarantee-design.md)).
- **Reduce sample-of-one objection** — show people *like them* who got results. "Other people get results but I'm different" is the universal objection.
- **Pre-empt the failure path** — explicitly address what could go wrong and how you handle it. Builds trust faster than hiding the risk.
### Common mistake
Stacking more features ("we also include X, Y, Z") instead of stacking more proof. Features address dream outcome. Proof addresses likelihood. Most stuck offers need proof, not features.
---
## Lever 3: Time Delay (denominator)
How long from purchase to result. The denominator is doing a lot of work here — slow results don't just feel slow, they erode perceived likelihood (the longer it takes, the less the buyer believes it'll work).
### How to decrease
- **Faster first win** — find the smallest possible early result and engineer it into the first 7 days
- **Onboarding velocity** — replace a 60-minute kickoff with a 10-minute async intake + Loom send
- **Front-load the assets** — give the templates / swipe files / decks on day 1, not "throughout the program"
- **Faster end-to-end timeline** — "in 8 weeks" beats "in 6 months." If you can credibly compress, do.
- **Quick-start path** — explicit "first thing to do today" so they don't lose momentum
### Common mistake
Promising a faster timeline than you can deliver. The first time you miss it, perceived likelihood for every future buyer drops permanently as the failure story spreads.
---
## Lever 4: Effort & Sacrifice (denominator)
What the buyer pays *besides money* — time, learning curve, decisions, willpower, social risk, opportunity cost.
### How to decrease
- **Done-for-you over do-it-yourself** — DFY tiers move the effort to you. Charge accordingly.
- **Fewer decisions** — every decision the buyer makes is friction. Bundle, default, recommend.
- **Lower learning curve** — pre-built templates, examples, defaults. "We did the thinking, you do the executing."
- **Removed risk** — emotional risk (am I dumb if this doesn't work?), social risk (what will my team think?), opportunity cost (what am I not doing while I do this?). Address all three explicitly.
- **Async over live** — for many buyers (founders, executives), removing synchronous commitments is huge value
- **Less willpower required** — automation, accountability, environmental design
### Common mistake
Overestimating how much your buyer *enjoys* the work. They want the outcome, not the process. (Exception: identity-buyers — fitness, learning, mastery. For those, the process *is* part of the dream outcome. Read your buyer.)
---
## Diagnostic: scoring the levers
When an offer is stuck, score each lever 1–10 honestly. The lowest is the binding constraint.
### Quick scoring prompts
- **Dream outcome (1–10)**: Can the buyer picture, in concrete sensory terms, the day after this works? Is the outcome named specifically enough that they can repeat it back to a friend?
- **Perceived likelihood (1–10)**: Do they have at least three named, comparable proof points? Is the methodology specific enough to repeat? Have you addressed the "but my situation is different" objection?
- **Time delay (1–10)**: What's the first concrete win, and how soon do they get it? What's the end-to-end timeline? Are there any "delay surfaces" (onboarding lag, asset drip, week-1 friction)?
- **Effort & sacrifice (1–10)**: How much work does the buyer do? How many decisions? How much learning curve? How much synchronous time? How much emotional/social/opportunity-cost risk?
### Worked example: a stuck $3K copywriting course
Initial scoring:
- Dream outcome: 6 (`become a better copywriter` — too vague)
- Perceived likelihood: 4 (one testimonial, no methodology name)
- Time delay: 5 (6-month course, no first-win mechanic)
- Effort & sacrifice: 4 (lots of homework, live calls, weekly assignments)
Lowest: effort & sacrifice and perceived likelihood are tied. Pick one (perceived likelihood — easier to move and unblocks more downstream work):
- Name the methodology: "The VAULT framework — 5 angles every winning sales page uses"
- Add 8 named-customer case studies with before/after copy + revenue numbers
- Add an "even if you've never written before" cohort with 3 named graduates
After: perceived likelihood goes 4 → 8. Course converts at ~3x baseline. Two months later, attack effort & sacrifice (replace weekly live calls with async + 1 live Q&A).
---
## Key idea to internalize
**The price-vs-value comparison happens in the buyer's head, not yours.** You only know it's working when the buyer can articulate the dream outcome back to you in their own words, and when they don't need to ask "but does this actually work?"
If they're asking either of those questions, you have a value equation problem, not a copy problem.
Chụp toàn bộ trang web, kể cả SPA, ảnh tải chậm và trang rất dài, qua Chrome DevTools Protocol không cần thư viện ngoài.
--- name: "full-page-screenshot" description: "Use when the user asks to capture a full-page screenshot, long screenshot, or complete page capture of a web page. Handles SPA scroll containers, lazy-loaded images, and very tall pages via Chrome DevTools Protocol with zero external dependencies." --- # Full Page Screenshot Capture a full-page screenshot of any web page via Chrome DevTools Protocol. Produces a single PNG that includes all content — even portions that require scrolling. Zero external dependencies beyond Node.js 22+ and Chrome with remote debugging enabled. ## Prerequisites - **Node.js 22+** (uses built-in `WebSocket`) - **Chrome/Chromium** with remote debugging enabled Check environment readiness: ```bash node "SKILL_DIR/scripts/full-page-screenshot.mjs" --check ``` If Chrome check fails, instruct user to open `chrome://inspect/#remote-debugging` and enable **"Allow remote debugging for this browser instance"**. ## Workflow ### Option A: Screenshot an already-open tab (recommended for authenticated pages) 1. List available tabs: ```bash node "SKILL_DIR/scripts/full-page-screenshot.mjs" --list ``` 2. Identify the target by title/URL, then capture: ```bash node "SKILL_DIR/scripts/full-page-screenshot.mjs" <targetId> /tmp/screenshot.png --width 1200 --dpr 1 ``` ### Option B: Screenshot a URL (opens a background tab, captures, closes) ```bash node "SKILL_DIR/scripts/full-page-screenshot.mjs" --url "https://example.com" /tmp/screenshot.png --width 1200 --dpr 1 --wait 15000 ``` > **Note:** `--url` mode creates a background tab. Pages requiring authentication (SSO, login walls) should use Option A instead. ### Parameters | Parameter | Description | Default | |-----------|-------------|---------| | `output` | Output PNG file path | `/tmp/screenshot.png` | | `--width` | Viewport width in CSS pixels (articles: 1200, dashboards: 1440-1920) | 1200 | | `--dpr` | Device pixel ratio (2 = Retina, but 4x file size) | 1 | | `--wait` | Page load timeout in ms (`--url` mode only) | 15000 | | `--css` | Custom CSS to inject before capture (e.g., hide elements) | — | ### Verify Output ```bash # macOS sips -g pixelWidth -g pixelHeight /tmp/screenshot.png # Linux file /tmp/screenshot.png ``` ## Core Capabilities 1. **SPA scroll container expansion** — Detects `overflow-y: auto/scroll` containers, scrolls through them to trigger lazy-loading, then removes overflow constraints (including Tailwind `h-[calc(...)]`) so all content renders in a single pass. 2. **DOM stability detection** — After `readyState=complete`, monitors DOM element count until it stabilizes. This ensures SPA frameworks finish rendering dynamic content. 3. **Lazy-load triggering** — Scrolls the viewport incrementally to fire `IntersectionObserver` callbacks, then waits for all `<img>` elements to complete loading. 4. **Tiled capture for very tall pages** — Pages exceeding 16,000px are captured in 8,000px tiles and automatically stitched using Python PIL. Falls back to saving tiles separately if PIL is unavailable. 5. **Auto-discovery of Chrome** — Reads `DevToolsActivePort` file to find the debugging port. Falls back to probing ports 9222, 9229, 9333. 6. **CDP Proxy fallback** — When a CDP proxy holds the browser WebSocket, the script falls back to proxy API endpoints (`/eval`, `/screenshot`, `/scroll`) for capture. ## How It Works ``` 1. Discover Chrome debugging port 2. Connect via WebSocket (CDP) 3. Attach to target / create background tab 4. Set viewport width via Emulation domain 5. Wait: readyState + DOM stability 6. Detect & expand scroll containers 7. Scroll through page (trigger lazy-load) 8. Wait for images to complete 9. Measure final content height 10. Page.captureScreenshot (or tiled capture) 11. Stitch tiles if needed (PIL) 12. Restore viewport, detach, clean up ``` ## Anti-Patterns | Do NOT | Do instead | |--------|-----------| | Use `--dpr 2` on pages > 10,000px tall | Use `--dpr 1` to avoid Chrome memory issues | | Use `--url` for authenticated/SSO pages | Use `--list` + targetId on a tab where user is logged in | | Set `--wait` below 5000 for SPAs | SPAs need time to fetch data and render; use 10000-15000 | | Capture without checking `--check` first | Always verify Chrome debugging is available | | Hardcode viewport widths for all pages | Use 1200 for articles, 1440+ for dashboards/tables | | Skip output verification | Always verify with `sips` or `file` command after capture | ## Troubleshooting | Symptom | Cause | Fix | |---------|-------|-----| | "Cannot find Chrome debugging port" | Remote debugging not enabled | Open `chrome://inspect/#remote-debugging`, enable it | | "WebSocket connection timeout" | CDP proxy holding the connection | Script auto-falls back to proxy API | | Blank/white screenshot | Page not loaded yet | Increase `--wait` value | | Truncated at bottom | Scroll container not expanded | Script handles this automatically; file an issue if it persists | | Out of memory | Very tall page + high DPR | Reduce `--dpr` to 1 and/or reduce `--width` | | "PIL not available for stitching" | Python Pillow not installed | Install with `pip3 install Pillow` or accept separate tile files | ## Cross-References - [`engineering/browser-automation`](../browser-automation/SKILL.md) — General browser automation patterns via CDP/Playwright - [`engineering/performance-profiler`](../performance-profiler/SKILL.md) — Performance analysis that may complement visual captures FILE:scripts/full-page-screenshot.mjs #!/usr/bin/env node // full-page-screenshot.mjs — Standalone full-page screenshot tool via Chrome CDP // // Modes: // --check Check environment (Node.js 22+, Chrome debugging port) // --list List open browser tabs as JSON // --url <URL> [output] [options] Screenshot a URL (create tab → wait → capture → close) // <targetId> [output] [options] Screenshot an existing tab by target ID // // Options: // --width N Viewport width in CSS pixels (default: 1200) // --dpr N Device pixel ratio (default: 1) // --wait N Page load timeout in ms, --url mode only (default: 15000) // // Requires: Node.js 22+, Chrome with remote debugging enabled import fs from 'fs'; import net from 'net'; import path from 'path'; import { platform, homedir } from 'os'; // ─── Argument parsing ─────────────────────────────────────────────────────── const args = process.argv.slice(2); const flags = {}; const positional = []; const boolFlags = new Set(['check', 'list']); for (let i = 0; i < args.length; i++) { if (args[i].startsWith('--')) { const key = args[i].slice(2); if (boolFlags.has(key)) { flags[key] = true; } else if (i + 1 < args.length) { flags[key] = args[++i]; } } else { positional.push(args[i]); } } const vpWidth = parseInt(flags.width || '1200', 10); const dpr = parseInt(flags.dpr || '1', 10); const loadTimeout = parseInt(flags.wait || '15000', 10); const customCss = flags.css || ''; // ─── Chrome discovery ─────────────────────────────────────────────────────── function getDevToolsActivePortPaths() { const home = homedir(); if (platform() === 'darwin') { return [ path.join(home, 'Library/Application Support/Google/Chrome/DevToolsActivePort'), path.join(home, 'Library/Application Support/Google/Chrome Canary/DevToolsActivePort'), path.join(home, 'Library/Application Support/Chromium/DevToolsActivePort'), ]; } else if (platform() === 'linux') { return [ path.join(home, '.config/google-chrome/DevToolsActivePort'), path.join(home, '.config/chromium/DevToolsActivePort'), ]; } else if (platform() === 'win32') { const local = process.env.LOCALAPPDATA || ''; return [ path.join(local, 'Google/Chrome/User Data/DevToolsActivePort'), path.join(local, 'Chromium/User Data/DevToolsActivePort'), ]; } return []; } function checkPort(port) { return new Promise((resolve) => { const socket = net.createConnection(port, '127.0.0.1'); const timer = setTimeout(() => { socket.destroy(); resolve(false); }, 2000); socket.once('connect', () => { clearTimeout(timer); socket.destroy(); resolve(true); }); socket.once('error', () => { clearTimeout(timer); resolve(false); }); }); } async function discoverChrome() { // 1. Try DevToolsActivePort file for (const p of getDevToolsActivePortPaths()) { try { const lines = fs.readFileSync(p, 'utf-8').trim().split('\n'); const port = parseInt(lines[0], 10); if (port > 0 && port < 65536) { const ok = await checkPort(port); if (ok) { const wsPath = lines[1] || '/devtools/browser'; return { port, wsUrl: `ws://127.0.0.1:portwsPath` }; } } } catch { /* try next */ } } // 2. Fallback: probe common debugging ports for (const port of [9222, 9229, 9333]) { const ok = await checkPort(port); if (ok) { return { port, wsUrl: `ws://127.0.0.1:port/devtools/browser` }; } } return null; } // ─── CDP WebSocket helpers ────────────────────────────────────────────────── let msgId = 0; const pending = new Map(); let ws; function send(method, params = {}, sessionId = null, timeoutMs = 60000) { return new Promise((resolve, reject) => { const id = ++msgId; const msg = { id, method, params }; if (sessionId) msg.sessionId = sessionId; pending.set(id, resolve); ws.send(JSON.stringify(msg)); setTimeout(() => { if (pending.has(id)) { pending.delete(id); reject(new Error(`CDP timeout (timeoutMsms): method`)); } }, timeoutMs); }); } const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); async function connectChrome() { const chrome = await discoverChrome(); if (!chrome) { console.error('Cannot find Chrome debugging port.'); console.error('Open chrome://inspect/#remote-debugging and enable "Allow remote debugging for this browser instance".'); process.exit(1); } ws = new WebSocket(chrome.wsUrl); ws.onmessage = (evt) => { const msg = JSON.parse(typeof evt.data === 'string' ? evt.data : evt.data.toString()); if (msg.id !== undefined && pending.has(msg.id)) { pending.get(msg.id)(msg); pending.delete(msg.id); } }; await new Promise((resolve, reject) => { const timer = setTimeout(() => { ws.close(); reject(new Error('WebSocket connection timeout (10s) — browser WebSocket may be held by proxy')); }, 10000); ws.onopen = () => { clearTimeout(timer); resolve(); }; ws.onerror = () => { clearTimeout(timer); reject(new Error('WebSocket connection to Chrome failed')); }; }); return chrome; } function closeWs() { try { ws?.close(); } catch {} } // ─── Wait for page load ───────────────────────────────────────────────────── async function waitForLoad(sid, timeoutMs) { const start = Date.now(); // Phase 1: wait for readyState=complete while (Date.now() - start < timeoutMs) { try { const resp = await send('Runtime.evaluate', { expression: 'document.readyState', returnByValue: true, }, sid, 5000); if (resp.result?.result?.value === 'complete') break; } catch { /* retry */ } await sleep(500); } // Phase 2: wait for DOM to stabilize (SPA content rendering) // SPAs load shell HTML instantly, then fetch data and render dynamically. // We detect stability by checking if DOM element count stops changing. const stabilityTimeout = Math.min(15000, Math.max(0, timeoutMs - (Date.now() - start))); if (stabilityTimeout > 0) { console.log('Waiting for DOM to stabilize...'); let lastCount = 0; let stableRounds = 0; const stableThreshold = 3; // need 3 consecutive stable checks (1.5s) const checkStart = Date.now(); while (Date.now() - checkStart < stabilityTimeout) { try { const resp = await send('Runtime.evaluate', { expression: 'document.querySelectorAll("*").length', returnByValue: true, }, sid, 5000); const count = resp.result?.result?.value || 0; if (count === lastCount && count > 0) { stableRounds++; if (stableRounds >= stableThreshold) { console.log(`DOM stable at count elements`); return true; } } else { stableRounds = 0; lastCount = count; } } catch { /* retry */ } await sleep(500); } console.log(`DOM stability timeout (last count: lastCount), proceeding`); } return true; } // ─── Core screenshot logic ────────────────────────────────────────────────── async function captureFullPage({ sid, outputFile, width, devicePixelRatio, css }) { // Set target width with a short viewport to measure content height await send('Emulation.setDeviceMetricsOverride', { width, height: 800, deviceScaleFactor: devicePixelRatio, mobile: false, }, sid); await sleep(2000); // Inject custom CSS if provided (e.g. hide sidebars, adjust layout) if (css) { await send('Runtime.evaluate', { expression: `(() => { const s = document.createElement('style'); s.textContent = JSON.stringify(css); document.head.appendChild(s); })()`, returnByValue: true, }, sid); await sleep(500); } // Expand internal scroll containers so their full content is visible in the screenshot. // Many SPAs use overflow-y:auto/scroll on inner divs instead of document-level scrolling. // We detect these, scroll through them to trigger lazy-loading, then remove overflow constraints. const expandResult = await send('Runtime.evaluate', { expression: `(async () => { const containers = []; for (const el of document.querySelectorAll('*')) { const style = getComputedStyle(el); const overflowY = style.overflowY; if ((overflowY === 'auto' || overflowY === 'scroll') && el.scrollHeight > el.clientHeight + 50 && el.clientHeight >= 100) { containers.push(el); } } if (containers.length === 0) return { found: 0 }; // Scroll each container to trigger lazy loading inside it for (const el of containers) { const step = 800; for (let y = 0; y < el.scrollHeight; y += step) { el.scrollTop = y; await new Promise(r => setTimeout(r, 150)); } el.scrollTop = 0; } // Expand containers: remove overflow and fixed-height constraints // Use !important to override CSS class-based constraints (e.g. Tailwind h-[calc(...)]) function expandEl(el) { el.style.setProperty('overflow', 'visible', 'important'); el.style.setProperty('overflow-y', 'visible', 'important'); el.style.setProperty('max-height', 'none', 'important'); // Override computed height if it constrains content const computed = getComputedStyle(el); const computedH = parseFloat(computed.height); if (computedH < el.scrollHeight - 10) { el.style.setProperty('height', 'auto', 'important'); } } let expanded = 0; for (const el of containers) { if (el.scrollHeight > el.clientHeight + 50) { expandEl(el); // Walk up the parent chain and expand anything that clips let parent = el.parentElement; for (let i = 0; i < 10 && parent && parent !== document.body; i++) { const ps = getComputedStyle(parent); if (ps.overflow !== 'visible' || ps.overflowY !== 'visible' || parseFloat(ps.height) < parent.scrollHeight - 10) { expandEl(parent); } parent = parent.parentElement; } expanded++; } } return { found: containers.length, expanded }; })()`, returnByValue: true, awaitPromise: true, }, sid, 60000); const expInfo = expandResult.result?.result?.value; if (expInfo && expInfo.expanded > 0) { console.log(`Expanded expInfo.expanded scroll container(s)`); await sleep(1000); } // Measure content height after expansion const m1 = await send('Page.getLayoutMetrics', {}, sid); const contentH = Math.ceil(m1.result.cssContentSize.height); console.log(`Content: widthxcontentH @ devicePixelRatiox`); // Set viewport height — cap at 8000px to avoid Chrome memory issues on very tall pages. // captureBeyondViewport: true will still capture the full page content. const vpHeight = Math.min(contentH, 8000); await send('Emulation.setDeviceMetricsOverride', { width, height: vpHeight, deviceScaleFactor: devicePixelRatio, mobile: false, }, sid); await sleep(2000); // Scroll through page slowly to trigger window-level lazy loading // Non-fatal: if this times out (common on very tall expanded pages), we skip it try { await send('Runtime.evaluate', { expression: `(async()=>{ const h = document.documentElement.scrollHeight; const step = 800; const maxSteps = Math.ceil(h / step); const deadline = Date.now() + 15000; for(let i=0; i<maxSteps; i++){ if(Date.now()>deadline) break; window.scrollTo(0, i * step); await new Promise(r=>setTimeout(r, 200)); } window.scrollTo(0, document.documentElement.scrollHeight); await new Promise(r=>setTimeout(r, 300)); window.scrollTo(0, 0); })()`, returnByValue: true, awaitPromise: true, }, sid, 20000); } catch (e) { console.warn(`Scroll lazy-load skipped (e.message)`); } // Wait for all images to finish loading (non-fatal timeout) try { const imgResult = await send('Runtime.evaluate', { expression: `(async()=>{ const deadline = Date.now() + 10000; while (Date.now() < deadline) { const imgs = Array.from(document.querySelectorAll('img')); const pending = imgs.filter(i => !i.complete && i.src && !i.src.startsWith('data:')); if (pending.length === 0) return { loaded: imgs.length, waited: false }; await new Promise(r => setTimeout(r, 500)); } const imgs = Array.from(document.querySelectorAll('img')); const still = imgs.filter(i => !i.complete && i.src && !i.src.startsWith('data:')); return { loaded: imgs.length - still.length, pending: still.length, timeout: true }; })()`, returnByValue: true, awaitPromise: true, }, sid, 15000); const imgInfo = imgResult.result?.result?.value; if (imgInfo) { if (imgInfo.timeout) { console.warn(`Warning: imgInfo.pending image(s) still loading after 10s timeout`); } else { console.log(`Images loaded: imgInfo.loaded`); } } } catch (e) { console.warn(`Image wait skipped (e.message)`); } // Final measure (content may have grown after lazy-load) const m2 = await send('Page.getLayoutMetrics', {}, sid); const finalH = Math.ceil(m2.result.cssContentSize.height); if (finalH !== contentH) { console.log(`Height adjusted: contentH → finalH`); const newVpH = Math.min(finalH, 8000); await send('Emulation.setDeviceMetricsOverride', { width, height: newVpH, deviceScaleFactor: devicePixelRatio, mobile: false, }, sid); await sleep(1000); } // Capture — for very tall pages (>16000px), use tiled capture to avoid Chrome timeout const TILE_THRESHOLD = 16000; if (finalH <= TILE_THRESHOLD) { // Single capture console.log(`Capturing widthxfinalH...`); const shot = await send('Page.captureScreenshot', { format: 'png', captureBeyondViewport: true, clip: { x: 0, y: 0, width, height: finalH, scale: 1 }, }, sid, 120000); if (shot.error) { throw new Error(`Screenshot failed: JSON.stringify(shot.error)`); } const buf = Buffer.from(shot.result.data, 'base64'); fs.writeFileSync(outputFile, buf); const mb = (buf.length / 1024 / 1024).toFixed(2); console.log(`Saved: outputFile (width * devicePixelRatioxfinalH * devicePixelRatiopx, mb MB)`); } else { // Tiled capture for very tall pages const tileH = 8000; const tiles = []; for (let y = 0; y < finalH; y += tileH) { const h = Math.min(tileH, finalH - y); console.log(`Capturing tile tiles.length + 1: y=y, h=h...`); const shot = await send('Page.captureScreenshot', { format: 'png', captureBeyondViewport: true, clip: { x: 0, y, width, height: h, scale: 1 }, }, sid, 120000); if (shot.error) { throw new Error(`Tile screenshot failed: JSON.stringify(shot.error)`); } const tilePath = outputFile.replace(/\.png$/, `_tiletiles.length.png`); fs.writeFileSync(tilePath, Buffer.from(shot.result.data, 'base64')); tiles.push({ path: tilePath, y, h }); } // Stitch tiles using Python PIL (available on macOS) console.log(`Stitching tiles.length tiles (widthxfinalH)...`); const { execSync } = await import('child_process'); const stitchScriptPath = outputFile.replace(/\.png$/, '_stitch.py'); const stitchScript = [ 'from PIL import Image', `tiles = [tiles.map(t => `("${t.path", t.y)`).join(',')}]`, `out = Image.new("RGB", (width * devicePixelRatio, finalH * devicePixelRatio))`, 'for path, y in tiles:', ' tile = Image.open(path)', ` out.paste(tile, (0, y * devicePixelRatio))`, ' tile.close()', `out.save("outputFile")`, 'print(f"Stitched: {out.size[0]}x{out.size[1]}")', ].join('\n'); fs.writeFileSync(stitchScriptPath, stitchScript); try { const result = execSync(`python3 "stitchScriptPath"`, { encoding: 'utf-8', timeout: 60000 }); console.log(result.trim()); } catch (e) { // Fallback: if Python/PIL not available, keep tiles as separate files console.warn('PIL not available for stitching. Tiles saved as separate files:'); for (const t of tiles) console.log(` t.path`); // Copy first tile as the output for basic functionality fs.copyFileSync(tiles[0].path, outputFile); } try { fs.unlinkSync(stitchScriptPath); } catch {} // Clean up tile files for (const t of tiles) { try { fs.unlinkSync(t.path); } catch {} } const stat = fs.statSync(outputFile); const mb = (stat.size / 1024 / 1024).toFixed(2); console.log(`Saved: outputFile (width * devicePixelRatioxfinalH * devicePixelRatiopx, mb MB)`); } } // ─── Mode: --check ────────────────────────────────────────────────────────── async function modeCheck() { // Check Node.js version const nodeVer = process.versions.node; const major = parseInt(nodeVer.split('.')[0], 10); if (major >= 22) { console.log(`node: ok (vnodeVer)`); } else { console.log(`node: FAIL (vnodeVer, need 22+)`); process.exit(1); } // Check Chrome debugging port (TCP only, no WebSocket to avoid auth popup) const chrome = await discoverChrome(); if (chrome) { console.log(`chrome: ok (port chrome.port)`); } else { console.log('chrome: FAIL'); console.error('Open chrome://inspect/#remote-debugging and enable "Allow remote debugging for this browser instance".'); process.exit(1); } } // ─── Mode: --list ─────────────────────────────────────────────────────────── async function modeList() { // Try direct CDP first, fall back to proxy API try { await connectChrome(); try { const resp = await send('Target.getTargets'); const pages = resp.result.targetInfos .filter((t) => t.type === 'page') .map(({ targetId, title, url }) => ({ targetId, title, url })); console.log(JSON.stringify(pages, null, 2)); } finally { closeWs(); } } catch { // Direct connection failed — try proxy const proxyUp = await isProxyRunning(); if (!proxyUp) { console.error('Cannot connect to Chrome and no proxy running.'); process.exit(1); } const resp = await fetch(`PROXY_URL/targets`, { signal: AbortSignal.timeout(10000) }); const targets = await resp.json(); const pages = targets .filter((t) => t.type === 'page') .map(({ targetId, title, url }) => ({ targetId, title, url })); console.log(JSON.stringify(pages, null, 2)); } } // ─── Mode: --url ──────────────────────────────────────────────────────────── async function modeUrl() { const url = flags.url; const outputFile = positional[0] || '/tmp/screenshot.png'; // Try direct CDP first let connected = false; try { await connectChrome(); connected = true; } catch { // Browser WebSocket held by proxy } if (connected) { let createdTargetId = null; let sid = null; try { // Create background tab const create = await send('Target.createTarget', { url, background: true }); if (create.error) { throw new Error(`Failed to create tab: JSON.stringify(create.error)`); } createdTargetId = create.result.targetId; console.log(`Tab created: createdTargetId.slice(0, 16)...`); // Attach const attach = await send('Target.attachToTarget', { targetId: createdTargetId, flatten: true }); if (attach.error) { throw new Error(`Attach failed: JSON.stringify(attach.error)`); } sid = attach.result.sessionId; // Wait for page load await send('Page.enable', {}, sid); await waitForLoad(sid, loadTimeout); // Screenshot await captureFullPage({ sid, outputFile, width: vpWidth, devicePixelRatio: dpr, css: customCss }); } finally { if (sid) { try { await send('Emulation.clearDeviceMetricsOverride', {}, sid); } catch {} try { await send('Target.detachFromTarget', { sessionId: sid }); } catch {} } if (createdTargetId) { try { await send('Target.closeTarget', { targetId: createdTargetId }); } catch {} } closeWs(); } return; } // Fallback: use proxy to create tab and screenshot const proxyUp = await isProxyRunning(); if (!proxyUp) { console.error('Cannot connect to Chrome and no proxy running.'); process.exit(1); } console.log('Using proxy to create tab...'); const newResp = await fetch(`PROXY_URL/new?url=encodeURIComponent(url)`, { signal: AbortSignal.timeout(loadTimeout + 5000), }); const newTab = await newResp.json(); const targetId = newTab.targetId; console.log(`Tab created via proxy: targetId.slice(0, 16)...`); // Wait for DOM to stabilize (SPA rendering) console.log('Waiting for DOM to stabilize...'); let lastCount = 0, stableRounds = 0; const stabilityDeadline = Date.now() + 15000; while (Date.now() < stabilityDeadline) { const count = await proxyEval(targetId, 'document.querySelectorAll("*").length'); if (count === lastCount && count > 0) { stableRounds++; if (stableRounds >= 3) { console.log(`DOM stable at count elements`); break; } } else { stableRounds = 0; lastCount = count; } await sleep(500); } try { await captureViaProxy(targetId, outputFile, vpWidth, dpr); } finally { // Close the tab we created try { await fetch(`PROXY_URL/close?target=targetId`, { signal: AbortSignal.timeout(5000) }); } catch {} } } // ─── CDP Proxy helpers (used when proxy is running) ───────────────────────── const PROXY_URL = 'http://localhost:3456'; async function isProxyRunning() { try { const resp = await fetch(`PROXY_URL/health`, { signal: AbortSignal.timeout(1000) }); const data = await resp.json(); return data.status === 'ok'; } catch { return false; } } async function proxyEval(targetId, expr) { const resp = await fetch(`PROXY_URL/eval?target=targetId`, { method: 'POST', body: expr, signal: AbortSignal.timeout(60000), }); return (await resp.json()).value; } async function proxyScreenshot(targetId, filePath) { const resp = await fetch(`PROXY_URL/screenshot?target=targetId&file=encodeURIComponent(filePath)`, { signal: AbortSignal.timeout(30000), }); return await resp.json(); } async function proxyScroll(targetId, y) { await fetch(`PROXY_URL/scroll?target=targetId&y=y`, { signal: AbortSignal.timeout(10000), }); } // Full-page screenshot using proxy API (tiled viewport captures) async function captureViaProxy(targetId, outputFile, width, devicePixelRatio) { console.log('Using proxy API for full-page capture...'); // Detect scroll containers WITHOUT expanding them (expansion causes issues with proxy) const containerInfo = await proxyEval(targetId, `(() => { const containers = []; for (const el of document.querySelectorAll('*')) { const style = getComputedStyle(el); const overflowY = style.overflowY; if ((overflowY === 'auto' || overflowY === 'scroll') && el.scrollHeight > el.clientHeight + 50 && el.clientHeight >= 100) { // Find a unique selector for this element let selector = el.tagName.toLowerCase(); if (el.id) selector = '#' + el.id; else if (el.className) { const cls = el.className.trim().split(/\\s+/).filter(c => !c.includes('[')).slice(0, 3).join('.'); if (cls) selector = el.tagName.toLowerCase() + '.' + cls; } containers.push({ selector, scrollHeight: el.scrollHeight, clientHeight: el.clientHeight, index: containers.length }); } } return containers; })()`); const pageInfo = await proxyEval(targetId, `({ scrollHeight: Math.max(document.body.scrollHeight, document.documentElement.scrollHeight), viewportHeight: window.innerHeight, viewportWidth: window.innerWidth })`); const vpH = pageInfo.viewportHeight; console.log(`Page viewport: pageInfo.viewportWidthxvpH`); // Determine capture strategy const mainContainer = containerInfo && containerInfo.length > 0 ? containerInfo.reduce((a, b) => a.scrollHeight > b.scrollHeight ? a : b) : null; if (mainContainer && mainContainer.scrollHeight > vpH) { // SPA with internal scroll container — scroll the container, capture tiles console.log(`Scroll container: mainContainer.selector (mainContainer.scrollHeightpx content in mainContainer.clientHeightpx view)`); // First, scroll through container to trigger lazy loading await proxyEval(targetId, `(async () => { const containers = []; for (const el of document.querySelectorAll('*')) { const style = getComputedStyle(el); if ((style.overflowY === 'auto' || style.overflowY === 'scroll') && el.scrollHeight > el.clientHeight + 50 && el.clientHeight >= 100) { containers.push(el); } } const el = containers[mainContainer.index]; if (!el) return; const step = 800; for (let y = 0; y < el.scrollHeight; y += step) { el.scrollTop = y; await new Promise(r => setTimeout(r, 150)); } el.scrollTop = 0; })()`); // Wait for images await proxyEval(targetId, `(async () => { const deadline = Date.now() + 8000; while (Date.now() < deadline) { const imgs = Array.from(document.querySelectorAll('img')); const pending = imgs.filter(i => !i.complete && i.src && !i.src.startsWith('data:')); if (pending.length === 0) return; await new Promise(r => setTimeout(r, 500)); } })()`); // Capture tiles by scrolling the container const containerH = mainContainer.clientHeight; const totalScrollH = mainContainer.scrollHeight; const overlap = 20; const stepH = containerH - overlap; const tiles = []; for (let scrollY = 0; scrollY < totalScrollH; scrollY += stepH) { await proxyEval(targetId, `(() => { const containers = []; for (const el of document.querySelectorAll('*')) { const style = getComputedStyle(el); if ((style.overflowY === 'auto' || style.overflowY === 'scroll') && el.scrollHeight > el.clientHeight + 50 && el.clientHeight >= 100) { containers.push(el); } } const el = containers[mainContainer.index]; if (el) el.scrollTop = scrollY; })()`); await sleep(300); const tilePath = outputFile.replace(/\.png$/, `_tiletiles.length.png`); await proxyScreenshot(targetId, tilePath); tiles.push({ path: tilePath, scrollY }); console.log(`Tile tiles.length: scrollY=scrollY`); } // Reset scroll position await proxyEval(targetId, `(() => { const containers = []; for (const el of document.querySelectorAll('*')) { const style = getComputedStyle(el); if ((style.overflowY === 'auto' || style.overflowY === 'scroll') && el.scrollHeight > el.clientHeight + 50 && el.clientHeight >= 100) { containers.push(el); } } const el = containers[mainContainer.index]; if (el) el.scrollTop = 0; })()`); if (tiles.length === 1) { fs.renameSync(tiles[0].path, outputFile); } else { // Stitch tiles console.log(`Stitching tiles.length tiles...`); const { execSync } = await import('child_process'); const sipsOut = execSync(`sips -g pixelWidth -g pixelHeight "tiles[0].path"`, { encoding: 'utf-8' }); const pxW = parseInt(sipsOut.match(/pixelWidth:\s*(\d+)/)?.[1] || '0'); const pxH = parseInt(sipsOut.match(/pixelHeight:\s*(\d+)/)?.[1] || '0'); const dpr = pxH / vpH; // Calculate the pixel offset of the container within the viewport const containerOffset = await proxyEval(targetId, `(() => { const containers = []; for (const el of document.querySelectorAll('*')) { const style = getComputedStyle(el); if ((style.overflowY === 'auto' || style.overflowY === 'scroll') && el.scrollHeight > el.clientHeight + 50 && el.clientHeight >= 100) { containers.push(el); } } const el = containers[mainContainer.index]; if (!el) return 0; const rect = el.getBoundingClientRect(); return rect.top; })()`); const pxContainerTop = Math.round((containerOffset || 0) * dpr); const pxContainerH = Math.round(containerH * dpr); const pxStepH = Math.round(stepH * dpr); const pxTotalContentH = Math.round(totalScrollH * dpr); // Stitch: header (top of first tile) + container content strips from each tile + footer (bottom of last tile) const stitchScriptPath = outputFile.replace(/\.png$/, '_stitch.py'); const stitchLines = [ 'from PIL import Image', `tiles = [tiles.map(t => `"${t.path"`).join(',')}]`, 'imgs = [Image.open(p) for p in tiles]', 'tile_w, tile_h = imgs[0].size', `container_top = pxContainerTop`, `container_h = pxContainerH`, `step_h = pxStepH`, `total_content_h = pxTotalContentH`, 'header_h = container_top', 'footer_h = tile_h - container_top - container_h', 'out_h = header_h + total_content_h + footer_h', 'out = Image.new("RGB", (tile_w, out_h))', 'if header_h > 0:', ' header = imgs[0].crop((0, 0, tile_w, header_h))', ' out.paste(header, (0, 0))', 'for i, img in enumerate(imgs):', ' content_strip = img.crop((0, container_top, tile_w, container_top + container_h))', ' y = header_h + i * step_h', ' paste_h = min(container_h, out_h - footer_h - y)', ' if paste_h < container_h:', ' content_strip = content_strip.crop((0, 0, tile_w, paste_h))', ' if paste_h > 0:', ' out.paste(content_strip, (0, y))', 'if footer_h > 0:', ' footer = imgs[-1].crop((0, tile_h - footer_h, tile_w, tile_h))', ' out.paste(footer, (0, out_h - footer_h))', 'for img in imgs:', ' img.close()', `out.save("outputFile")`, 'print(f"Stitched: {out.size[0]}x{out.size[1]}")', ]; fs.writeFileSync(stitchScriptPath, stitchLines.join('\n')); try { const result = execSync(`python3 "stitchScriptPath"`, { encoding: 'utf-8', timeout: 60000 }); console.log(result.trim()); } catch (e) { console.warn('Stitching failed:', e.message?.slice(0, 200)); fs.copyFileSync(tiles[0].path, outputFile); } // Clean up tiles and stitch script try { fs.unlinkSync(stitchScriptPath); } catch {} for (const t of tiles) { try { fs.unlinkSync(t.path); } catch {} } } } else { // Normal page or small page — single viewport screenshot // Scroll through to trigger lazy loading first await proxyEval(targetId, `(async () => { const h = document.documentElement.scrollHeight; const step = 800; for (let y = 0; y < h; y += step) { window.scrollTo(0, y); await new Promise(r => setTimeout(r, 200)); } window.scrollTo(0, 0); })()`); await proxyScreenshot(targetId, outputFile); } const stat = fs.statSync(outputFile); const mb = (stat.size / 1024 / 1024).toFixed(2); console.log(`Saved: outputFile (mb MB)`); } // ─── Mode: targetId (existing tab) ───────────────────────────────────────── async function modeTarget() { const targetId = positional[0]; const outputFile = positional[1] || '/tmp/screenshot.png'; if (!targetId) { console.error('Usage:'); console.error(' node full-page-screenshot.mjs --check'); console.error(' node full-page-screenshot.mjs --list'); console.error(' node full-page-screenshot.mjs --url <URL> [output] [--width N] [--dpr N]'); console.error(' node full-page-screenshot.mjs <targetId> [output] [--width N] [--dpr N]'); process.exit(1); } // Try direct CDP first. If browser WebSocket is held by proxy, fall back to proxy API. let connected = false; try { await connectChrome(); connected = true; } catch { // Browser WebSocket likely held by proxy } if (connected) { let sid = null; try { const attach = await send('Target.attachToTarget', { targetId, flatten: true }); if (attach.error) { console.error(`Attach failed: JSON.stringify(attach.error)`); console.error('Run with --list to see available targets.'); process.exit(1); } sid = attach.result.sessionId; // Test with Page.enable — if times out, proxy may be interfering try { await send('Page.enable', {}, sid, 10000); } catch { console.warn('Direct session timed out, falling back to proxy...'); try { await send('Target.detachFromTarget', { sessionId: sid }); } catch {} sid = null; closeWs(); connected = false; } if (sid) { await captureFullPage({ sid, outputFile, width: vpWidth, devicePixelRatio: dpr, css: customCss }); } } finally { if (sid) { try { await send('Emulation.clearDeviceMetricsOverride', {}, sid); } catch {} try { await send('Target.detachFromTarget', { sessionId: sid }); } catch {} } if (connected) closeWs(); } } // Fallback to proxy API if (!connected) { const proxyUp = await isProxyRunning(); if (!proxyUp) { console.error('Cannot connect to Chrome (browser WebSocket unavailable) and no proxy running.'); process.exit(1); } await captureViaProxy(targetId, outputFile, vpWidth, dpr); } } // ─── Dispatch ─────────────────────────────────────────────────────────────── async function main() { if (flags.check) return modeCheck(); if (flags.list) return modeList(); if (flags.url) return modeUrl(); return modeTarget(); } main().catch((e) => { console.error('Error:', e.message || e); closeWs(); process.exit(1); });
Sinh chuỗi OKR từ chiến lược công ty xuống mục tiêu từng nhóm.
--- name: okr description: Generate OKR cascades from company strategy to team objectives. Usage: /okr generate <strategy> --- # /okr Generate cascaded OKR frameworks from company-level strategy down to team-level key results. ## Usage ``` /okr generate <strategy> Generate OKR cascade ``` Supported strategies: `growth`, `retention`, `revenue`, `innovation`, `operational` ## Input Format Pass a strategy keyword directly. The generator produces company, department, and team-level OKRs aligned to the chosen strategy. ## Examples ``` /okr generate growth /okr generate retention /okr generate revenue /okr generate innovation /okr generate operational /okr generate growth --json ``` ## Scripts - `product-team/product-strategist/scripts/okr_cascade_generator.py` — OKR cascade generator (`<strategy> [--teams "A,B,C"] [--contribution 0.3] [--json]`) ## Skill Reference > `product-team/product-strategist/SKILL.md`
Phân tích toàn diện quy trình vận hành, KPI/OKR, cơ cấu tổ chức và chiến lược doanh nghiệp thực chiến cho thương mại điện tử và sản xuất.
--- name: phan-tich-nghiep-vu-quan-tri-doanh-nghiep description: Phân tích toàn diện quy trình vận hành, KPI/OKR, cơ cấu tổ chức và chiến lược doanh nghiệp thực chiến (TMĐT & sản xuất). Dùng khi nói "phân tích nghiệp vụ", "tối ưu quy trình", "báo cáo quản trị", "cơ cấu tổ chức". --- # Phân tích nghiệp vụ quản trị doanh nghiệp ## Mục tiêu Phân tích toàn diện các vấn đề quản trị doanh nghiệp — từ quy trình, hiệu suất, tổ chức đến chiến lược — để ra quyết định có căn cứ hoặc trình bày cho các bên liên quan. ## Bối cảnh áp dụng - Chargee: công ty phân phối hàng hóa sàn TMĐT — ưu tiên phân tích vận hành, hiệu suất kênh bán, bottleneck logistics - Elmich (IT): sản xuất & thương mại đồ gia dụng — ưu tiên phân tích quy trình IT, tổ chức phòng ban, hỗ trợ chiến lược công nghệ ## Khi nào dùng - Cần phân tích một vấn đề vận hành đang phát sinh - Chuẩn bị báo cáo cho ban lãnh đạo hoặc họp nội bộ - Đánh giá hiệu suất đội nhóm hoặc quy trình - Ra quyết định tổ chức: tuyển dụng, phân quyền, tái cơ cấu - Phân tích chiến lược trước khi mở rộng hoặc thay đổi hướng đi ## Đầu vào cần cung cấp - Vấn đề hoặc câu hỏi cần phân tích - Công ty liên quan: Chargee hay Elmich (hoặc cả hai) - Loại phân tích: quy trình / hiệu suất / tổ chức / chiến lược - Dữ liệu hiện có: số liệu, mô tả tình huống, bối cảnh - Mục đích đầu ra: quyết định cá nhân / báo cáo nội bộ / trình bày lãnh đạo / lưu tài liệu ## Quy trình xử lý ### A. Phân tích quy trình 1. Vẽ lại quy trình hiện tại (as-is) theo từng bước 2. Xác định bottleneck, điểm lặp, điểm thủ công không cần thiết 3. So sánh với quy trình lý tưởng (to-be) 4. Đề xuất cải tiến có thể triển khai ngay vs. dài hạn ### B. Phân tích hiệu suất (KPI/OKR) 1. Xác định chỉ số đang đo và chỉ số còn thiếu 2. So sánh thực tế vs. mục tiêu — tính % đạt 3. Tìm nguyên nhân gốc rễ nếu có gap (5 Whys) 4. Đề xuất điều chỉnh mục tiêu hoặc hành động khắc phục ### C. Phân tích tổ chức 1. Mapping cơ cấu hiện tại: ai làm gì, báo cáo cho ai 2. Xác định điểm mơ hồ về trách nhiệm (RACI) 3. Đánh giá tải công việc và phân quyền 4. Đề xuất điều chỉnh cơ cấu hoặc phân quyền ### D. Phân tích chiến lược 1. Thu thập dữ liệu nội bộ và thị trường 2. Phân tích SWOT hoặc framework phù hợp 3. Xác định 2–3 lựa chọn chiến lược với pros/cons 4. Đề xuất hướng ưu tiên có căn cứ rõ ràng ## Tiêu chuẩn đầu ra theo mục đích | Mục đích | Định dạng | Độ dài | |---|---|---| | Quyết định cá nhân | Bullet points + kết luận | Ngắn gọn | | Báo cáo nội bộ | Markdown có bảng + biểu đồ mô tả | Trung bình | | Trình bày lãnh đạo | Cấu trúc: vấn đề → phân tích → đề xuất | Súc tích, có số liệu | | Lưu tài liệu | Đầy đủ, có ngày tháng, có giả định | Chi tiết | ## Giả định luôn phải nêu rõ - Số liệu dựa trên dữ liệu bạn cung cấp — không tự bịa - Nếu thiếu dữ liệu: nêu rõ cần thu thập thêm gì - Đề xuất mang tính tham khảo — quyết định cuối thuộc về bạn ## Tránh - Phân tích chung chung không gắn với Chargee hoặc Elmich - Đưa ra kết luận khi chưa đủ dữ liệu - Bỏ qua sự khác biệt giữa hai công ty (TMĐT vs. sản xuất) - Trộn lẫn đầu ra cho các mục đích khác nhau
Hợp nhất nhánh của agent chiến thắng vào nhánh gốc, lưu trữ các nhánh còn lại và dọn dẹp worktree.
---
name: "merge"
description: "Merge the winning agent's branch into base, archive losers, and clean up worktrees."
command: /hub:merge
---
# /hub:merge — Merge Winner
Merge the best agent's branch into the base branch, archive losing branches via git tags, and clean up worktrees.
## Usage
```
/hub:merge # Merge winner of latest session
/hub:merge 20260317-143022 # Merge winner of specific session
/hub:merge 20260317-143022 --agent agent-2 # Explicitly choose winner
```
## What It Does
### 1. Identify Winner
If `--agent` specified, use that. Otherwise, use the #1 ranked agent from the most recent `/hub:eval`.
### 2. Merge Winner
```bash
git checkout {base_branch}
git merge --no-ff hub/{session-id}/{winner}/attempt-1 \
-m "hub: merge {winner} from session {session-id}
Task: {task}
Winner: {winner}
Session: {session-id}"
```
### 3. Archive Losers
For each non-winning agent:
```bash
# Create archive tag (preserves commits forever)
git tag hub/archive/{session-id}/{agent-id} hub/{session-id}/{agent-id}/attempt-1
# Delete branch ref (commits preserved via tag)
git branch -D hub/{session-id}/{agent-id}/attempt-1
```
### 4. Clean Up Worktrees
```bash
python {skill_path}/scripts/session_manager.py --cleanup {session-id}
```
### 5. Post Merge Summary
Write `.agenthub/board/results/merge-summary.md`:
```markdown
---
author: coordinator
timestamp: {now}
channel: results
---
## Merge Summary
- **Session**: {session-id}
- **Winner**: {winner}
- **Merged into**: {base_branch}
- **Archived**: {loser-1}, {loser-2}, ...
- **Worktrees cleaned**: {count}
```
### 6. Update State
```bash
python {skill_path}/scripts/session_manager.py --update {session-id} --state merged
```
## Safety
- **Confirm with user** before merging — show the diff summary first
- **Never force-push** — merge is always `--no-ff` for clear history
- **Archive, don't delete** — losing agents' commits are preserved via tags
- **Clean worktrees** — don't leave orphan directories on disk
## After Merge
Tell the user:
- Winner merged into `{base_branch}`
- Losers archived with tags `hub/archive/{session-id}/agent-{N}`
- Worktrees cleaned up
- Session state: `merged`
Tạo và tối ưu popup, modal, overlay, slide-in và banner để tăng chuyển đổi: exit intent, thu thập email, banner thông báo.
---
name: popups
description: When the user wants to create or optimize popups, modals, overlays, slide-ins, or banners for conversion purposes. Also use when the user mentions "exit intent," "popup conversions," "modal optimization," "lead capture popup," "email popup," "announcement banner," "overlay," "collect emails with a popup," "exit popup," "scroll trigger," "sticky bar," or "notification bar." Use this for any overlay or interrupt-style conversion element. For forms outside of popups, see cro. For general page conversion optimization, see cro.
metadata:
version: 2.0.0
---
# Popup CRO
You are an expert in popup and modal optimization. Your goal is to create popups that convert without annoying users or damaging brand perception.
## Initial Assessment
**Check for product marketing context first:**
If `.agents/product-marketing.md` exists (or `.claude/product-marketing.md`, or the legacy `product-marketing-context.md` filename, in older setups), read it before asking questions. Use that context and only ask for information not already covered or specific to this task.
Before providing recommendations, understand:
1. **Popup Purpose**
- Email/newsletter capture
- Lead magnet delivery
- Discount/promotion
- Announcement
- Exit intent save
- Feature promotion
- Feedback/survey
2. **Current State**
- Existing popup performance?
- What triggers are used?
- User complaints or feedback?
- Mobile experience?
3. **Traffic Context**
- Traffic sources (paid, organic, direct)
- New vs. returning visitors
- Page types where shown
---
## Core Principles
### 1. Timing Is Everything
- Too early = annoying interruption
- Too late = missed opportunity
- Right time = helpful offer at moment of need
### 2. Value Must Be Obvious
- Clear, immediate benefit
- Relevant to page context
- Worth the interruption
### 3. Respect the User
- Easy to dismiss
- Don't trap or trick
- Remember preferences
- Don't ruin the experience
---
## Trigger Strategies
### Time-Based
- **Not recommended**: "Show after 5 seconds"
- **Better**: "Show after 30-60 seconds" (proven engagement)
- Best for: General site visitors
### Scroll-Based
- **Typical**: 25-50% scroll depth
- Indicates: Content engagement
- Best for: Blog posts, long-form content
- Example: "You're halfway through—get more like this"
### Exit Intent
- Detects cursor moving to close/leave
- Last chance to capture value
- Best for: E-commerce, lead gen
- Mobile alternative: Back button or scroll up
### Click-Triggered
- User initiates (clicks button/link)
- Zero annoyance factor
- Best for: Lead magnets, gated content, demos
- Example: "Download PDF" → Popup form
### Page Count / Session-Based
- After visiting X pages
- Indicates research/comparison behavior
- Best for: Multi-page journeys
- Example: "Been comparing? Here's a summary..."
### Behavior-Based
- Add to cart abandonment
- Pricing page visitors
- Repeat page visits
- Best for: High-intent segments
---
## Popup Types
### Email Capture Popup
**Goal**: Newsletter/list subscription
**Best practices:**
- Clear value prop (not just "Subscribe")
- Specific benefit of subscribing
- Single field (email only)
- Consider incentive (discount, content)
**Copy structure:**
- Headline: Benefit or curiosity hook
- Subhead: What they get, how often
- CTA: Specific action ("Get Weekly Tips")
### Lead Magnet Popup
**Goal**: Exchange content for email
**Best practices:**
- Show what they get (cover image, preview)
- Specific, tangible promise
- Minimal fields (email, maybe name)
- Instant delivery expectation
### Discount/Promotion Popup
**Goal**: First purchase or conversion
**Best practices:**
- Clear discount (10%, $20, free shipping)
- Deadline creates urgency
- Single use per visitor
- Easy to apply code
### Exit Intent Popup
**Goal**: Last-chance conversion
**Best practices:**
- Acknowledge they're leaving
- Different offer than entry popup
- Address common objections
- Final compelling reason to stay
**Formats:**
- "Wait! Before you go..."
- "Forget something?"
- "Get 10% off your first order"
- "Questions? Chat with us"
### Announcement Banner
**Goal**: Site-wide communication
**Best practices:**
- Top of page (sticky or static)
- Single, clear message
- Dismissable
- Links to more info
- Time-limited (don't leave forever)
### Slide-In
**Goal**: Less intrusive engagement
**Best practices:**
- Enters from corner/bottom
- Doesn't block content
- Easy to dismiss or minimize
- Good for chat, support, secondary CTAs
---
## Design Best Practices
### Visual Hierarchy
1. Headline (largest, first seen)
2. Value prop/offer (clear benefit)
3. Form/CTA (obvious action)
4. Close option (easy to find)
### Sizing
- Desktop: 400-600px wide typical
- Don't cover entire screen
- Mobile: Full-width bottom or center, not full-screen
- Leave space to close (visible X, click outside)
### Close Button
- Keep visible (top right is convention) — users who can't find the close button will bounce entirely
- Large enough to tap on mobile
- "No thanks" text link as alternative
- Click outside to close
### Mobile Considerations
- Can't detect exit intent (use alternatives)
- Full-screen overlays feel aggressive
- Bottom slide-ups work well
- Larger touch targets
- Easy dismiss gestures
### Imagery
- Product image or preview
- Face if relevant (increases trust)
- Minimal for speed
- Optional—copy can work alone
---
## Copy Formulas
### Headlines
- Benefit-driven: "Get [result] in [timeframe]"
- Question: "Want [desired outcome]?"
- Command: "Don't miss [thing]"
- Social proof: "Join [X] people who..."
- Curiosity: "The one thing [audience] always get wrong about [topic]"
### Subheadlines
- Expand on the promise
- Address objection ("No spam, ever")
- Set expectations ("Weekly tips in 5 min")
### CTA Buttons
- First person works: "Get My Discount" vs "Get Your Discount"
- Specific over generic: "Send Me the Guide" vs "Submit"
- Value-focused: "Claim My 10% Off" vs "Subscribe"
### Decline Options
- Polite, not guilt-trippy
- "No thanks" / "Maybe later" / "I'm not interested"
- Avoid manipulative: "No, I don't want to save money"
---
## Frequency and Rules
### Frequency Capping
- Show maximum once per session
- Remember dismissals (cookie/localStorage)
- 7-30 days before showing again
- Respect user choice
### Audience Targeting
- New vs. returning visitors (different needs)
- By traffic source (match ad message)
- By page type (context-relevant)
- Exclude converted users
- Exclude recently dismissed
### Page Rules
- Exclude checkout/conversion flows
- Consider blog vs. product pages
- Match offer to page context
---
## Compliance and Accessibility
### GDPR/Privacy
- Clear consent language
- Link to privacy policy
- Don't pre-check opt-ins
- Honor unsubscribe/preferences
### Accessibility
- Keyboard navigable (Tab, Enter, Esc)
- Focus trap while open
- Screen reader compatible
- Sufficient color contrast
- Don't rely on color alone
### Google Guidelines
- Intrusive interstitials hurt SEO
- Mobile especially sensitive
- Allow: Cookie notices, age verification, reasonable banners
- Avoid: Full-screen before content on mobile
---
## Measurement
### Key Metrics
- **Impression rate**: Visitors who see popup
- **Conversion rate**: Impressions → Submissions
- **Close rate**: How many dismiss immediately
- **Engagement rate**: Interaction before close
- **Time to close**: How long before dismissing
### What to Track
- Popup views
- Form focus
- Submission attempts
- Successful submissions
- Close button clicks
- Outside clicks
- Escape key
### Benchmarks
- Email popup: 2-5% conversion typical
- Exit intent: 3-10% conversion
- Click-triggered: Higher (10%+, self-selected)
---
## Output Format
### Popup Design
- **Type**: Email capture, lead magnet, etc.
- **Trigger**: When it appears
- **Targeting**: Who sees it
- **Frequency**: How often shown
- **Copy**: Headline, subhead, CTA, decline
- **Design notes**: Layout, imagery, mobile
### Multiple Popup Strategy
If recommending multiple popups:
- Popup 1: [Purpose, trigger, audience]
- Popup 2: [Purpose, trigger, audience]
- Conflict rules: How they don't overlap
### Test Hypotheses
Ideas to A/B test with expected outcomes
---
## Common Popup Strategies
### E-commerce
1. Entry/scroll: First-purchase discount
2. Exit intent: Bigger discount or reminder
3. Cart abandonment: Complete your order
### B2B SaaS
1. Click-triggered: Demo request, lead magnets
2. Scroll: Newsletter/blog subscription
3. Exit intent: Trial reminder or content offer
### Content/Media
1. Scroll-based: Newsletter after engagement
2. Page count: Subscribe after multiple visits
3. Exit intent: Don't miss future content
### Lead Generation
1. Time-delayed: General list building
2. Click-triggered: Specific lead magnets
3. Exit intent: Final capture attempt
---
## Experiment Ideas
### Placement & Format Experiments
**Banner Variations**
- Top bar vs. banner below header
- Sticky banner vs. static banner
- Full-width vs. contained banner
- Banner with countdown timer vs. without
**Popup Formats**
- Center modal vs. slide-in from corner
- Full-screen overlay vs. smaller modal
- Bottom bar vs. corner popup
- Top announcements vs. bottom slideouts
**Position Testing**
- Test popup sizes on desktop and mobile
- Left corner vs. right corner for slide-ins
- Test visibility without blocking content
---
### Trigger Experiments
**Timing Triggers**
- Exit intent vs. 30-second delay vs. 50% scroll depth
- Test optimal time delay (10s vs. 30s vs. 60s)
- Test scroll depth percentage (25% vs. 50% vs. 75%)
- Page count trigger (show after X pages viewed)
**Behavior Triggers**
- Show based on user intent prediction
- Trigger based on specific page visits
- Return visitor vs. new visitor targeting
- Show based on referral source
**Click Triggers**
- Click-triggered popups for lead magnets
- Button-triggered vs. link-triggered modals
- Test in-content triggers vs. sidebar triggers
---
### Messaging & Content Experiments
**Headlines & Copy**
- Test attention-grabbing vs. informational headlines
- "Limited-time offer" vs. "New feature alert" messaging
- Urgency-focused copy vs. value-focused copy
- Test headline length and specificity
**CTAs**
- CTA button text variations
- Button color testing for contrast
- Primary + secondary CTA vs. single CTA
- Test decline text (friendly vs. neutral)
**Visual Content**
- Add countdown timers to create urgency
- Test with/without images
- Product preview vs. generic imagery
- Include social proof in popup
---
### Personalization Experiments
**Dynamic Content**
- Personalize popup based on visitor data
- Show industry-specific content
- Tailor content based on pages visited
- Use progressive profiling (ask more over time)
**Audience Targeting**
- New vs. returning visitor messaging
- Segment by traffic source
- Target based on engagement level
- Exclude already-converted visitors
---
### Frequency & Rules Experiments
- Test frequency capping (once per session vs. once per week)
- Cool-down period after dismissal
- Test different dismiss behaviors
- Show escalating offers over multiple visits
---
## Task-Specific Questions
1. What's the primary goal for this popup?
2. What's your current popup performance (if any)?
3. What traffic sources are you optimizing for?
4. What incentive can you offer?
5. Are there compliance requirements (GDPR, etc.)?
6. Mobile vs. desktop traffic split?
---
## Related Skills
- **lead-magnets**: For planning lead magnets to promote via popups
- **cro**: For optimizing the form inside the popup
- **cro**: For the page context around popups
- **emails**: For what happens after popup conversion
- **ab-testing**: For testing popup variations
FILE:evals/evals.json
{
"skill_name": "popups",
"evals": [
{
"id": 1,
"prompt": "Help me create an exit-intent popup for our SaaS landing page. We want to capture emails from visitors who are about to leave without signing up. Our product is a social media scheduling tool.",
"expected_output": "Should check for product-marketing.md first. Should identify the popup type as exit-intent email capture. Should apply the exit-intent popup design guidance: compelling headline (address why they're leaving or offer additional value), lead magnet or incentive (discount, free resource, extended trial), minimal form fields (email only), clear CTA, and easy close option. Should apply copy formulas from the skill. Should address trigger configuration (exit intent detection). Should recommend frequency rules (don't show again if dismissed). Should include benchmarks (exit intent popups typically 3-10% conversion).",
"assertions": [
"Checks for product-marketing.md",
"Identifies as exit-intent popup type",
"Includes compelling headline",
"Includes lead magnet or incentive",
"Minimal form fields (email only)",
"Applies copy formulas from the skill",
"Addresses trigger configuration",
"Recommends frequency rules",
"Includes conversion benchmarks"
],
"files": []
},
{
"id": 2,
"prompt": "We want to offer a 10% discount to first-time visitors via a popup. When should we show it and what should it say?",
"expected_output": "Should identify this as a discount/offer popup type. Should apply trigger strategy guidance: recommend against showing immediately on page load (too aggressive). Should suggest time-based delay (30-60 seconds), scroll-based trigger (50%+ page scroll), or exit intent as better alternatives. Should apply the copy formula for discount popups: headline that frames the value, clear offer terms, urgency element, email capture, and CTA. Should address compliance (GDPR cookie consent if applicable). Should recommend frequency capping.",
"assertions": [
"Identifies as discount popup type",
"Recommends against immediate page load trigger",
"Suggests better trigger alternatives (time, scroll, exit)",
"Applies copy formula for discount popups",
"Includes urgency element",
"Addresses frequency capping",
"Addresses compliance considerations"
],
"files": []
},
{
"id": 3,
"prompt": "our popups are annoying everyone. we keep getting complaints but we also get a lot of email signups from them. how do we balance this?",
"expected_output": "Should trigger on casual phrasing. Should apply the frequency and rules guidance. Should address the balance: reduce annoyance while preserving conversions. Should recommend: frequency capping (once per session or once per X days), don't show to returning visitors who already dismissed, don't show to existing subscribers, respect 'close' action, consider less intrusive formats (slide-in instead of full modal, announcement bar instead of overlay). Should address compliance and accessibility requirements. Should suggest A/B testing different triggers and formats to find the best balance.",
"assertions": [
"Triggers on casual phrasing",
"Applies frequency and rules guidance",
"Addresses balance between conversions and UX",
"Recommends frequency capping",
"Suggests excluding existing subscribers",
"Recommends less intrusive alternatives",
"Suggests A/B testing to optimize"
],
"files": []
},
{
"id": 4,
"prompt": "What types of popups should we use on our blog? We publish content about email marketing and want to grow our email list.",
"expected_output": "Should recommend blog-appropriate popup types: scroll-triggered popup (show after 50-70% scroll indicating engagement), exit-intent popup, slide-in (less intrusive than modal), and inline content upgrades. Should recommend lead magnets relevant to the blog topic (email marketing templates, checklist, swipe file). Should address different popup placements: mid-content, end of post, sidebar slide-in. Should recommend behavior-based triggers over time-based for blog content. Should apply copy formulas with blog-specific hooks.",
"assertions": [
"Recommends blog-appropriate popup types",
"Includes scroll-triggered and exit-intent",
"Suggests less intrusive formats (slide-in)",
"Recommends relevant lead magnets",
"Addresses popup placement on blog pages",
"Recommends behavior-based triggers for blog",
"Applies copy formulas"
],
"files": []
},
{
"id": 5,
"prompt": "Design an announcement banner for our new feature launch. We want it to show at the top of the site for 2 weeks.",
"expected_output": "Should identify this as an announcement banner popup type. Should apply banner design guidance: short, clear headline announcing the feature, brief description of benefit, CTA to learn more or try it, dismiss option. Should recommend banner positioning (top of page, sticky or static). Should address duration (2 weeks as stated). Should recommend targeting (show to existing users who'd benefit, not just everyone). Should provide copy recommendations.",
"assertions": [
"Identifies as announcement banner type",
"Provides short, clear headline",
"Includes brief benefit description and CTA",
"Includes dismiss option",
"Addresses banner positioning",
"Recommends audience targeting",
"Provides copy recommendations"
],
"files": []
},
{
"id": 6,
"prompt": "We need to optimize the lead capture form inside our popup. It currently asks for name, email, company, and phone number. Too many fields?",
"expected_output": "Should recognize this overlaps with form optimization. Should defer to or cross-reference the cro skill, which handles form field optimization, layout, and conversion. May provide popup-specific context (popups need minimal fields due to fleeting attention) but should make clear that cro is the right skill for detailed form optimization.",
"assertions": [
"Recognizes overlap with form optimization",
"References or defers to cro skill",
"Notes popups need minimal fields due to context",
"Does not attempt detailed form redesign"
],
"files": []
}
]
}
Phân tích trung thực những gì đã sai sau một sự cố hoặc thất bại.
--- name: "postmortem" description: "/em -postmortem — Honest Analysis of What Went Wrong" --- # /em:postmortem — Honest Analysis of What Went Wrong **Command:** `/em:postmortem <event>` Not blame. Understanding. The failed deal, the missed quarter, the feature that flopped, the hire that didn't work out. What actually happened, why, and what changes as a result. --- ## Why Most Post-Mortems Fail They become one of two things: **The blame session** — someone gets scapegoated, defensive walls go up, actual causes don't get examined, and the same problem happens again in a different form. **The whitewash** — "We learned a lot, we're going to do better, here are 12 vague action items." Nothing changes. Same problem, different quarter. A real post-mortem is neither. It's a rigorous investigation into a system failure. Not "whose fault was it" but "what conditions made this outcome predictable in hindsight?" **The purpose:** extract the maximum learning value from a failure so you can prevent recurrence and improve the system. --- ## The Framework ### Step 1: Define the Event Precisely Before analysis: describe exactly what happened. - What was the expected outcome? - What was the actual outcome? - When was the gap first visible? - What was the impact (financial, operational, reputational)? Precision matters. "We missed Q3 revenue" is not precise enough. "We closed $420K in new ARR vs $680K target — a $260K miss driven primarily by three deals that slipped to Q4 and one deal that was lost to a competitor" is precise. ### Step 2: The 5 Whys — Done Properly The goal: get from **what happened** (the symptom) to **why it happened** (the root cause). Standard bad 5 Whys: - Why did we miss revenue? Because deals slipped. - Why did deals slip? Because the sales cycle was longer than expected. - Why? Because the customer buying process is complex. - Why? Because we're selling to enterprise. - Why? That's just how enterprise sales works. → Conclusion: Nothing to do. It's just enterprise. Real 5 Whys: - Why did we miss revenue? Three deals slipped out of quarter. - Why did those deals slip? None of them had identified a champion with budget authority. - Why did we progress deals without a champion? Our qualification criteria didn't require it. - Why didn't our qualification criteria require it? When we built the criteria 8 months ago, we were in SMB, not enterprise. - Why haven't we updated qualification criteria as ICP shifted? No owner, no process for criteria review. → Root cause: Qualification criteria outdated, no owner, no review process. → Fix: Update criteria, assign owner, add quarterly review. **The test for a good root cause:** Could you prevent recurrence with a specific, concrete change? If yes, you've found something real. ### Step 3: Distinguish Contributing Factors from Root Cause Most events have multiple contributing factors. Not all are root causes. **Contributing factor:** Made it worse, but isn't the core reason. If removed, the outcome might have been different — but the same class of problem would recur. **Root cause:** The fundamental condition that made the outcome probable. Fix this, and this class of problem doesn't recur. Example — failed hire: - Contributing factors: rushed process, reference checks skipped, team under pressure to staff up - Root cause: No defined competency framework, so interview process varied by who happened to conduct interviews **The distinction matters.** If you address only contributing factors, you'll have a different-looking but structurally identical failure next time. ### Step 4: Identify the Warning Signs That Were Ignored Every failure has precursors. In hindsight, they're obvious. The value of this step is making them obvious prospectively. Ask: - At what point was the negative outcome predictable? - What signals were visible at that point? - Who saw them? What happened when they raised them? - Why weren't they acted on? **Common patterns:** - Signal was raised but dismissed by a senior person - Signal wasn't raised because nobody felt safe saying it - Signal was seen but no one had clear ownership to act on it - Data was available but nobody was looking at it - The team was too optimistic to take negative signals seriously This step is particularly important for systemic issues — "we didn't feel safe raising the concern" is a much deeper root cause than "the deal qualification was off." ### Step 5: Distinguish What Was in Control vs. Out of Control Some failures happen despite correct decisions. Some happen because of incorrect decisions. Knowing the difference prevents both overcorrection and undercorrection. - **In control:** Process, criteria, team capability, resource allocation, decisions made - **Out of control:** Market conditions, customer decisions, competitor actions, macro events For things out of control: what can be done to be more resilient to similar events? For things in control: what specifically needs to change? **Warning:** "It was outside our control" is sometimes used to avoid accountability. Be rigorous. ### Step 6: Build the Change Register Every post-mortem ends with a change register — specific commitments, owned and dated. **Bad action items:** - "We'll improve our qualification process" - "Communication will be better" - "We'll be more rigorous about forecasting" **Good action items:** - "Ravi owns rewriting qualification criteria by March 15 to include champion identification as hard requirement. New criteria reviewed in weekly sales standup starting March 22." - "By March 10, Elena adds deal-slippage risk flag to CRM for any open opportunity >60 days without a product demo" - "Maria runs a 30-min retrospective with enterprise sales team every 6 weeks starting April 1, reviews win/loss data" **For each action:** - What exactly is changing? - Who owns it? - By when? - How will you verify it worked? ### Step 7: Verification Date The most commonly skipped step. Post-mortems are useless if nobody checks whether the changes actually happened and actually worked. Set a verification date: "We'll review whether qualification criteria have been updated and whether deal slippage rate has improved at the June board meeting." Without this, post-mortems are theater. --- ## Post-Mortem Output Format ``` EVENT: [Name and date] EXPECTED: [What was supposed to happen] ACTUAL: [What happened] IMPACT: [Quantified] TIMELINE [Date]: [What happened or was visible] [Date]: ... 5 WHYS 1. [Why did X happen?] → Because [Y] 2. [Why did Y happen?] → Because [Z] 3. [Why did Z happen?] → Because [A] 4. [Why did A happen?] → Because [B] 5. [Why did B happen?] → Because [ROOT CAUSE] ROOT CAUSE: [One clear sentence] CONTRIBUTING FACTORS • [Factor] — how it contributed • [Factor] — how it contributed WARNING SIGNS MISSED • [Signal visible at what date] — why it wasn't acted on WHAT WAS IN CONTROL: [List] WHAT WASN'T: [List] CHANGE REGISTER | Action | Owner | Due Date | Verification | |--------|-------|----------|-------------| | [Specific change] | [Name] | [Date] | [How to verify] | VERIFICATION DATE: [Date of check-in] ``` --- ## The Tone of Good Post-Mortems Blame is cheap. Understanding is hard. The goal isn't to establish that someone made a mistake. The goal is to understand why the system produced that outcome — so the system can be improved. "The salesperson didn't qualify the deal properly" is blame. "Our qualification framework hadn't been updated when we moved upmarket, and no one owned keeping it current" is understanding. The first version fires or shames someone. The second version builds a more resilient organization. Both might be true simultaneously. The distinction is: which one actually prevents recurrence?
Lệnh tạo nhanh tài liệu yêu cầu sản phẩm (PRD) từ một tính năng hoặc vấn đề.
--- name: prd description: Quick PRD generation command. Usage: /prd <feature-or-problem> --- # /prd Generate a concise product requirements document for a feature, initiative, or problem statement. ## Usage ```bash /prd <feature-or-problem> ``` ## Output Structure - Problem statement - Goals and non-goals - User stories and acceptance criteria - Metrics and success thresholds - Scope and timeline assumptions ## Skill Reference - `product-team/product-manager-toolkit/SKILL.md`
Lập kế hoạch di chuyển không gián đoạn, kiểm tra tương thích và chiến lược rollback cho di chuyển hệ thống, cơ sở dữ liệu và hạ tầng.
---
name: "migration-architect"
description: "Zero-downtime migration planning, compatibility validation, and rollback strategy generation. Tools for system, database, and infrastructure migrations with minimal business impact. Use when planning a database migration, infrastructure cutover, system replacement, or any high-risk transition that needs explicit rollback paths."
---
# Migration Architect
**Tier:** POWERFUL
**Category:** Engineering - Migration Strategy
**Purpose:** Zero-downtime migration planning, compatibility validation, and rollback strategy generation
## Overview
The Migration Architect skill provides comprehensive tools and methodologies for planning, executing, and validating complex system migrations with minimal business impact. This skill combines proven migration patterns with automated planning tools to ensure successful transitions between systems, databases, and infrastructure.
## Core Capabilities
### 1. Migration Strategy Planning
- **Phased Migration Planning:** Break complex migrations into manageable phases with clear validation gates
- **Risk Assessment:** Identify potential failure points and mitigation strategies before execution
- **Timeline Estimation:** Generate realistic timelines based on migration complexity and resource constraints
- **Stakeholder Communication:** Create communication templates and progress dashboards
### 2. Compatibility Analysis
- **Schema Evolution:** Analyze database schema changes for backward compatibility issues
- **API Versioning:** Detect breaking changes in REST/GraphQL APIs and microservice interfaces
- **Data Type Validation:** Identify data format mismatches and conversion requirements
- **Constraint Analysis:** Validate referential integrity and business rule changes
### 3. Rollback Strategy Generation
- **Automated Rollback Plans:** Generate comprehensive rollback procedures for each migration phase
- **Data Recovery Scripts:** Create point-in-time data restoration procedures
- **Service Rollback:** Plan service version rollbacks with traffic management
- **Validation Checkpoints:** Define success criteria and rollback triggers
## Migration Patterns
### Database Migrations
#### Schema Evolution Patterns
1. **Expand-Contract Pattern**
- **Expand:** Add new columns/tables alongside existing schema
- **Dual Write:** Application writes to both old and new schema
- **Migration:** Backfill historical data to new schema
- **Contract:** Remove old columns/tables after validation
2. **Parallel Schema Pattern**
- Run new schema in parallel with existing schema
- Use feature flags to route traffic between schemas
- Validate data consistency between parallel systems
- Cutover when confidence is high
3. **Event Sourcing Migration**
- Capture all changes as events during migration window
- Apply events to new schema for consistency
- Enable replay capability for rollback scenarios
#### Data Migration Strategies
1. **Bulk Data Migration**
- **Snapshot Approach:** Full data copy during maintenance window
- **Incremental Sync:** Continuous data synchronization with change tracking
- **Stream Processing:** Real-time data transformation pipelines
2. **Dual-Write Pattern**
- Write to both source and target systems during migration
- Implement compensation patterns for write failures
- Use distributed transactions where consistency is critical
3. **Change Data Capture (CDC)**
- Stream database changes to target system
- Maintain eventual consistency during migration
- Enable zero-downtime migrations for large datasets
### Service Migrations
#### Strangler Fig Pattern
1. **Intercept Requests:** Route traffic through proxy/gateway
2. **Gradually Replace:** Implement new service functionality incrementally
3. **Legacy Retirement:** Remove old service components as new ones prove stable
4. **Monitoring:** Track performance and error rates throughout transition
```mermaid
graph TD
A[Client Requests] --> B[API Gateway]
B --> C{Route Decision}
C -->|Legacy Path| D[Legacy Service]
C -->|New Path| E[New Service]
D --> F[Legacy Database]
E --> G[New Database]
```
#### Parallel Run Pattern
1. **Dual Execution:** Run both old and new services simultaneously
2. **Shadow Traffic:** Route production traffic to both systems
3. **Result Comparison:** Compare outputs to validate correctness
4. **Gradual Cutover:** Shift traffic percentage based on confidence
#### Canary Deployment Pattern
1. **Limited Rollout:** Deploy new service to small percentage of users
2. **Monitoring:** Track key metrics (latency, errors, business KPIs)
3. **Gradual Increase:** Increase traffic percentage as confidence grows
4. **Full Rollout:** Complete migration once validation passes
### Infrastructure Migrations
#### Cloud-to-Cloud Migration
1. **Assessment Phase**
- Inventory existing resources and dependencies
- Map services to target cloud equivalents
- Identify vendor-specific features requiring refactoring
2. **Pilot Migration**
- Migrate non-critical workloads first
- Validate performance and cost models
- Refine migration procedures
3. **Production Migration**
- Use infrastructure as code for consistency
- Implement cross-cloud networking during transition
- Maintain disaster recovery capabilities
#### On-Premises to Cloud Migration
1. **Lift and Shift**
- Minimal changes to existing applications
- Quick migration with optimization later
- Use cloud migration tools and services
2. **Re-architecture**
- Redesign applications for cloud-native patterns
- Adopt microservices, containers, and serverless
- Implement cloud security and scaling practices
3. **Hybrid Approach**
- Keep sensitive data on-premises
- Migrate compute workloads to cloud
- Implement secure connectivity between environments
## Feature Flags for Migrations
### Progressive Feature Rollout
```python
# Example feature flag implementation
class MigrationFeatureFlag:
def __init__(self, flag_name, rollout_percentage=0):
self.flag_name = flag_name
self.rollout_percentage = rollout_percentage
def is_enabled_for_user(self, user_id):
hash_value = hash(f"{self.flag_name}:{user_id}")
return (hash_value % 100) < self.rollout_percentage
def gradual_rollout(self, target_percentage, step_size=10):
while self.rollout_percentage < target_percentage:
self.rollout_percentage = min(
self.rollout_percentage + step_size,
target_percentage
)
yield self.rollout_percentage
```
### Circuit Breaker Pattern
Implement automatic fallback to legacy systems when new systems show degraded performance:
```python
class MigrationCircuitBreaker:
def __init__(self, failure_threshold=5, timeout=60):
self.failure_count = 0
self.failure_threshold = failure_threshold
self.timeout = timeout
self.last_failure_time = None
self.state = 'CLOSED' # CLOSED, OPEN, HALF_OPEN
def call_new_service(self, request):
if self.state == 'OPEN':
if self.should_attempt_reset():
self.state = 'HALF_OPEN'
else:
return self.fallback_to_legacy(request)
try:
response = self.new_service.process(request)
self.on_success()
return response
except Exception as e:
self.on_failure()
return self.fallback_to_legacy(request)
```
## Data Validation and Reconciliation
### Validation Strategies
1. **Row Count Validation**
- Compare record counts between source and target
- Account for soft deletes and filtered records
- Implement threshold-based alerting
2. **Checksums and Hashing**
- Generate checksums for critical data subsets
- Compare hash values to detect data drift
- Use sampling for large datasets
3. **Business Logic Validation**
- Run critical business queries on both systems
- Compare aggregate results (sums, counts, averages)
- Validate derived data and calculations
### Reconciliation Patterns
1. **Delta Detection**
```sql
-- Example delta query for reconciliation
SELECT 'missing_in_target' as issue_type, source_id
FROM source_table s
WHERE NOT EXISTS (
SELECT 1 FROM target_table t
WHERE t.id = s.id
)
UNION ALL
SELECT 'extra_in_target' as issue_type, target_id
FROM target_table t
WHERE NOT EXISTS (
SELECT 1 FROM source_table s
WHERE s.id = t.id
);
```
2. **Automated Correction**
- Implement data repair scripts for common issues
- Use idempotent operations for safe re-execution
- Log all correction actions for audit trails
## Rollback Strategies
### Database Rollback
1. **Schema Rollback**
- Maintain schema version control
- Use backward-compatible migrations when possible
- Keep rollback scripts for each migration step
2. **Data Rollback**
- Point-in-time recovery using database backups
- Transaction log replay for precise rollback points
- Maintain data snapshots at migration checkpoints
### Service Rollback
1. **Blue-Green Deployment**
- Keep previous service version running during migration
- Switch traffic back to blue environment if issues arise
- Maintain parallel infrastructure during migration window
2. **Rolling Rollback**
- Gradually shift traffic back to previous version
- Monitor system health during rollback process
- Implement automated rollback triggers
### Infrastructure Rollback
1. **Infrastructure as Code**
- Version control all infrastructure definitions
- Maintain rollback terraform/CloudFormation templates
- Test rollback procedures in staging environments
2. **Data Persistence**
- Preserve data in original location during migration
- Implement data sync back to original systems
- Maintain backup strategies across both environments
## Risk Assessment Framework
### Risk Categories
1. **Technical Risks**
- Data loss or corruption
- Service downtime or degraded performance
- Integration failures with dependent systems
- Scalability issues under production load
2. **Business Risks**
- Revenue impact from service disruption
- Customer experience degradation
- Compliance and regulatory concerns
- Brand reputation impact
3. **Operational Risks**
- Team knowledge gaps
- Insufficient testing coverage
- Inadequate monitoring and alerting
- Communication breakdowns
### Risk Mitigation Strategies
1. **Technical Mitigations**
- Comprehensive testing (unit, integration, load, chaos)
- Gradual rollout with automated rollback triggers
- Data validation and reconciliation processes
- Performance monitoring and alerting
2. **Business Mitigations**
- Stakeholder communication plans
- Business continuity procedures
- Customer notification strategies
- Revenue protection measures
3. **Operational Mitigations**
- Team training and documentation
- Runbook creation and testing
- On-call rotation planning
- Post-migration review processes
## Migration Runbooks
### Pre-Migration Checklist
- [ ] Migration plan reviewed and approved
- [ ] Rollback procedures tested and validated
- [ ] Monitoring and alerting configured
- [ ] Team roles and responsibilities defined
- [ ] Stakeholder communication plan activated
- [ ] Backup and recovery procedures verified
- [ ] Test environment validation complete
- [ ] Performance benchmarks established
- [ ] Security review completed
- [ ] Compliance requirements verified
### During Migration
- [ ] Execute migration phases in planned order
- [ ] Monitor key performance indicators continuously
- [ ] Validate data consistency at each checkpoint
- [ ] Communicate progress to stakeholders
- [ ] Document any deviations from plan
- [ ] Execute rollback if success criteria not met
- [ ] Coordinate with dependent teams
- [ ] Maintain detailed execution logs
### Post-Migration
- [ ] Validate all success criteria met
- [ ] Perform comprehensive system health checks
- [ ] Execute data reconciliation procedures
- [ ] Monitor system performance over 72 hours
- [ ] Update documentation and runbooks
- [ ] Decommission legacy systems (if applicable)
- [ ] Conduct post-migration retrospective
- [ ] Archive migration artifacts
- [ ] Update disaster recovery procedures
## Communication Templates
### Executive Summary Template
```
Migration Status: [IN_PROGRESS | COMPLETED | ROLLED_BACK]
Start Time: [YYYY-MM-DD HH:MM UTC]
Current Phase: [X of Y]
Overall Progress: [X%]
Key Metrics:
- System Availability: [X.XX%]
- Data Migration Progress: [X.XX%]
- Performance Impact: [+/-X%]
- Issues Encountered: [X]
Next Steps:
1. [Action item 1]
2. [Action item 2]
Risk Assessment: [LOW | MEDIUM | HIGH]
Rollback Status: [AVAILABLE | NOT_AVAILABLE]
```
### Technical Team Update Template
```
Phase: [Phase Name] - [Status]
Duration: [Started] - [Expected End]
Completed Tasks:
✓ [Task 1]
✓ [Task 2]
In Progress:
🔄 [Task 3] - [X% complete]
Upcoming:
⏳ [Task 4] - [Expected start time]
Issues:
⚠️ [Issue description] - [Severity] - [ETA resolution]
Metrics:
- Migration Rate: [X records/minute]
- Error Rate: [X.XX%]
- System Load: [CPU/Memory/Disk]
```
## Success Metrics
### Technical Metrics
- **Migration Completion Rate:** Percentage of data/services successfully migrated
- **Downtime Duration:** Total system unavailability during migration
- **Data Consistency Score:** Percentage of data validation checks passing
- **Performance Delta:** Performance change compared to baseline
- **Error Rate:** Percentage of failed operations during migration
### Business Metrics
- **Customer Impact Score:** Measure of customer experience degradation
- **Revenue Protection:** Percentage of revenue maintained during migration
- **Time to Value:** Duration from migration start to business value realization
- **Stakeholder Satisfaction:** Post-migration stakeholder feedback scores
### Operational Metrics
- **Plan Adherence:** Percentage of migration executed according to plan
- **Issue Resolution Time:** Average time to resolve migration issues
- **Team Efficiency:** Resource utilization and productivity metrics
- **Knowledge Transfer Score:** Team readiness for post-migration operations
## Tools and Technologies
### Migration Planning Tools
- **migration_planner.py:** Automated migration plan generation
- **compatibility_checker.py:** Schema and API compatibility analysis
- **rollback_generator.py:** Comprehensive rollback procedure generation
### Validation Tools
- Database comparison utilities (schema and data)
- API contract testing frameworks
- Performance benchmarking tools
- Data quality validation pipelines
### Monitoring and Alerting
- Real-time migration progress dashboards
- Automated rollback trigger systems
- Business metric monitoring
- Stakeholder notification systems
## Best Practices
### Planning Phase
1. **Start with Risk Assessment:** Identify all potential failure modes before planning
2. **Design for Rollback:** Every migration step should have a tested rollback procedure
3. **Validate in Staging:** Execute full migration process in production-like environment
4. **Plan for Gradual Rollout:** Use feature flags and traffic routing for controlled migration
### Execution Phase
1. **Monitor Continuously:** Track both technical and business metrics throughout
2. **Communicate Proactively:** Keep all stakeholders informed of progress and issues
3. **Document Everything:** Maintain detailed logs for post-migration analysis
4. **Stay Flexible:** Be prepared to adjust timeline based on real-world performance
### Validation Phase
1. **Automate Validation:** Use automated tools for data consistency and performance checks
2. **Business Logic Testing:** Validate critical business processes end-to-end
3. **Load Testing:** Verify system performance under expected production load
4. **Security Validation:** Ensure security controls function properly in new environment
## Integration with Development Lifecycle
### CI/CD Integration
```yaml
# Example migration pipeline stage
migration_validation:
stage: test
script:
- python scripts/compatibility_checker.py --before=old_schema.json --after=new_schema.json
- python scripts/migration_planner.py --config=migration_config.json --validate
artifacts:
reports:
- compatibility_report.json
- migration_plan.json
```
### Infrastructure as Code
```terraform
# Example Terraform for blue-green infrastructure
resource "aws_instance" "blue_environment" {
count = var.migration_phase == "preparation" ? var.instance_count : 0
# Blue environment configuration
}
resource "aws_instance" "green_environment" {
count = var.migration_phase == "execution" ? var.instance_count : 0
# Green environment configuration
}
```
This Migration Architect skill provides a comprehensive framework for planning, executing, and validating complex system migrations while minimizing business impact and technical risk. The combination of automated tools, proven patterns, and detailed procedures enables organizations to confidently undertake even the most complex migration projects.
FILE:assets/database_schema_after.json
{
"schema_version": "2.0",
"database": "user_management_v2",
"tables": {
"users": {
"columns": {
"id": {
"type": "bigint",
"nullable": false,
"primary_key": true,
"auto_increment": true
},
"username": {
"type": "varchar",
"length": 50,
"nullable": false,
"unique": true
},
"email": {
"type": "varchar",
"length": 320,
"nullable": false,
"unique": true
},
"password_hash": {
"type": "varchar",
"length": 255,
"nullable": false
},
"first_name": {
"type": "varchar",
"length": 100,
"nullable": true
},
"last_name": {
"type": "varchar",
"length": 100,
"nullable": true
},
"created_at": {
"type": "timestamp",
"nullable": false,
"default": "CURRENT_TIMESTAMP"
},
"updated_at": {
"type": "timestamp",
"nullable": false,
"default": "CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"
},
"is_active": {
"type": "boolean",
"nullable": false,
"default": true
},
"phone": {
"type": "varchar",
"length": 20,
"nullable": true
},
"email_verified_at": {
"type": "timestamp",
"nullable": true,
"comment": "When email was verified"
},
"phone_verified_at": {
"type": "timestamp",
"nullable": true,
"comment": "When phone was verified"
},
"two_factor_enabled": {
"type": "boolean",
"nullable": false,
"default": false
},
"last_login_at": {
"type": "timestamp",
"nullable": true
}
},
"constraints": {
"primary_key": ["id"],
"unique": [
"username",
"email"
],
"foreign_key": [],
"check": [
"email LIKE '%@%'",
"LENGTH(password_hash) >= 60",
"phone IS NULL OR LENGTH(phone) >= 10"
]
},
"indexes": [
{
"name": "idx_users_email",
"columns": ["email"],
"unique": true
},
{
"name": "idx_users_username",
"columns": ["username"],
"unique": true
},
{
"name": "idx_users_created_at",
"columns": ["created_at"]
},
{
"name": "idx_users_email_verified",
"columns": ["email_verified_at"]
},
{
"name": "idx_users_last_login",
"columns": ["last_login_at"]
}
]
},
"user_profiles": {
"columns": {
"id": {
"type": "bigint",
"nullable": false,
"primary_key": true,
"auto_increment": true
},
"user_id": {
"type": "bigint",
"nullable": false
},
"bio": {
"type": "text",
"nullable": true
},
"avatar_url": {
"type": "varchar",
"length": 500,
"nullable": true
},
"birth_date": {
"type": "date",
"nullable": true
},
"location": {
"type": "varchar",
"length": 100,
"nullable": true
},
"website": {
"type": "varchar",
"length": 255,
"nullable": true
},
"privacy_level": {
"type": "varchar",
"length": 20,
"nullable": false,
"default": "public"
},
"timezone": {
"type": "varchar",
"length": 50,
"nullable": true,
"default": "UTC"
},
"language": {
"type": "varchar",
"length": 10,
"nullable": false,
"default": "en"
},
"created_at": {
"type": "timestamp",
"nullable": false,
"default": "CURRENT_TIMESTAMP"
},
"updated_at": {
"type": "timestamp",
"nullable": false,
"default": "CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"
}
},
"constraints": {
"primary_key": ["id"],
"unique": [],
"foreign_key": [
{
"columns": ["user_id"],
"references": "users(id)",
"on_delete": "CASCADE"
}
],
"check": [
"privacy_level IN ('public', 'private', 'friends_only')",
"bio IS NULL OR LENGTH(bio) <= 2000",
"language IN ('en', 'es', 'fr', 'de', 'it', 'pt', 'ru', 'ja', 'ko', 'zh')"
]
},
"indexes": [
{
"name": "idx_user_profiles_user_id",
"columns": ["user_id"],
"unique": true
},
{
"name": "idx_user_profiles_privacy",
"columns": ["privacy_level"]
},
{
"name": "idx_user_profiles_language",
"columns": ["language"]
}
]
},
"user_sessions": {
"columns": {
"id": {
"type": "varchar",
"length": 128,
"nullable": false,
"primary_key": true
},
"user_id": {
"type": "bigint",
"nullable": false
},
"ip_address": {
"type": "varchar",
"length": 45,
"nullable": true
},
"user_agent": {
"type": "text",
"nullable": true
},
"expires_at": {
"type": "timestamp",
"nullable": false
},
"created_at": {
"type": "timestamp",
"nullable": false,
"default": "CURRENT_TIMESTAMP"
},
"last_activity": {
"type": "timestamp",
"nullable": false,
"default": "CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"
},
"session_type": {
"type": "varchar",
"length": 20,
"nullable": false,
"default": "web"
},
"is_mobile": {
"type": "boolean",
"nullable": false,
"default": false
}
},
"constraints": {
"primary_key": ["id"],
"unique": [],
"foreign_key": [
{
"columns": ["user_id"],
"references": "users(id)",
"on_delete": "CASCADE"
}
],
"check": [
"session_type IN ('web', 'mobile', 'api', 'admin')"
]
},
"indexes": [
{
"name": "idx_user_sessions_user_id",
"columns": ["user_id"]
},
{
"name": "idx_user_sessions_expires",
"columns": ["expires_at"]
},
{
"name": "idx_user_sessions_type",
"columns": ["session_type"]
}
]
},
"user_preferences": {
"columns": {
"id": {
"type": "bigint",
"nullable": false,
"primary_key": true,
"auto_increment": true
},
"user_id": {
"type": "bigint",
"nullable": false
},
"preference_key": {
"type": "varchar",
"length": 100,
"nullable": false
},
"preference_value": {
"type": "json",
"nullable": true
},
"created_at": {
"type": "timestamp",
"nullable": false,
"default": "CURRENT_TIMESTAMP"
},
"updated_at": {
"type": "timestamp",
"nullable": false,
"default": "CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"
}
},
"constraints": {
"primary_key": ["id"],
"unique": [
["user_id", "preference_key"]
],
"foreign_key": [
{
"columns": ["user_id"],
"references": "users(id)",
"on_delete": "CASCADE"
}
],
"check": []
},
"indexes": [
{
"name": "idx_user_preferences_user_key",
"columns": ["user_id", "preference_key"],
"unique": true
}
]
}
},
"views": {
"active_users": {
"definition": "SELECT u.id, u.username, u.email, u.first_name, u.last_name, u.email_verified_at, u.last_login_at FROM users u WHERE u.is_active = true",
"columns": ["id", "username", "email", "first_name", "last_name", "email_verified_at", "last_login_at"]
},
"verified_users": {
"definition": "SELECT u.id, u.username, u.email FROM users u WHERE u.is_active = true AND u.email_verified_at IS NOT NULL",
"columns": ["id", "username", "email"]
}
},
"procedures": [
{
"name": "cleanup_expired_sessions",
"parameters": [],
"definition": "DELETE FROM user_sessions WHERE expires_at < NOW()"
},
{
"name": "get_user_with_profile",
"parameters": ["user_id BIGINT"],
"definition": "SELECT u.*, p.bio, p.avatar_url, p.privacy_level FROM users u LEFT JOIN user_profiles p ON u.id = p.user_id WHERE u.id = user_id"
}
]
}
FILE:assets/database_schema_before.json
{
"schema_version": "1.0",
"database": "user_management",
"tables": {
"users": {
"columns": {
"id": {
"type": "bigint",
"nullable": false,
"primary_key": true,
"auto_increment": true
},
"username": {
"type": "varchar",
"length": 50,
"nullable": false,
"unique": true
},
"email": {
"type": "varchar",
"length": 255,
"nullable": false,
"unique": true
},
"password_hash": {
"type": "varchar",
"length": 255,
"nullable": false
},
"first_name": {
"type": "varchar",
"length": 100,
"nullable": true
},
"last_name": {
"type": "varchar",
"length": 100,
"nullable": true
},
"created_at": {
"type": "timestamp",
"nullable": false,
"default": "CURRENT_TIMESTAMP"
},
"updated_at": {
"type": "timestamp",
"nullable": false,
"default": "CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"
},
"is_active": {
"type": "boolean",
"nullable": false,
"default": true
},
"phone": {
"type": "varchar",
"length": 20,
"nullable": true
}
},
"constraints": {
"primary_key": ["id"],
"unique": [
"username",
"email"
],
"foreign_key": [],
"check": [
"email LIKE '%@%'",
"LENGTH(password_hash) >= 60"
]
},
"indexes": [
{
"name": "idx_users_email",
"columns": ["email"],
"unique": true
},
{
"name": "idx_users_username",
"columns": ["username"],
"unique": true
},
{
"name": "idx_users_created_at",
"columns": ["created_at"]
}
]
},
"user_profiles": {
"columns": {
"id": {
"type": "bigint",
"nullable": false,
"primary_key": true,
"auto_increment": true
},
"user_id": {
"type": "bigint",
"nullable": false
},
"bio": {
"type": "varchar",
"length": 255,
"nullable": true
},
"avatar_url": {
"type": "varchar",
"length": 500,
"nullable": true
},
"birth_date": {
"type": "date",
"nullable": true
},
"location": {
"type": "varchar",
"length": 100,
"nullable": true
},
"website": {
"type": "varchar",
"length": 255,
"nullable": true
},
"privacy_level": {
"type": "varchar",
"length": 20,
"nullable": false,
"default": "public"
},
"created_at": {
"type": "timestamp",
"nullable": false,
"default": "CURRENT_TIMESTAMP"
},
"updated_at": {
"type": "timestamp",
"nullable": false,
"default": "CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"
}
},
"constraints": {
"primary_key": ["id"],
"unique": [],
"foreign_key": [
{
"columns": ["user_id"],
"references": "users(id)",
"on_delete": "CASCADE"
}
],
"check": [
"privacy_level IN ('public', 'private', 'friends_only')"
]
},
"indexes": [
{
"name": "idx_user_profiles_user_id",
"columns": ["user_id"],
"unique": true
},
{
"name": "idx_user_profiles_privacy",
"columns": ["privacy_level"]
}
]
},
"user_sessions": {
"columns": {
"id": {
"type": "varchar",
"length": 128,
"nullable": false,
"primary_key": true
},
"user_id": {
"type": "bigint",
"nullable": false
},
"ip_address": {
"type": "varchar",
"length": 45,
"nullable": true
},
"user_agent": {
"type": "varchar",
"length": 500,
"nullable": true
},
"expires_at": {
"type": "timestamp",
"nullable": false
},
"created_at": {
"type": "timestamp",
"nullable": false,
"default": "CURRENT_TIMESTAMP"
},
"last_activity": {
"type": "timestamp",
"nullable": false,
"default": "CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"
}
},
"constraints": {
"primary_key": ["id"],
"unique": [],
"foreign_key": [
{
"columns": ["user_id"],
"references": "users(id)",
"on_delete": "CASCADE"
}
],
"check": []
},
"indexes": [
{
"name": "idx_user_sessions_user_id",
"columns": ["user_id"]
},
{
"name": "idx_user_sessions_expires",
"columns": ["expires_at"]
}
]
}
},
"views": {
"active_users": {
"definition": "SELECT u.id, u.username, u.email, u.first_name, u.last_name FROM users u WHERE u.is_active = true",
"columns": ["id", "username", "email", "first_name", "last_name"]
}
},
"procedures": [
{
"name": "cleanup_expired_sessions",
"parameters": [],
"definition": "DELETE FROM user_sessions WHERE expires_at < NOW()"
}
]
}
FILE:assets/sample_database_migration.json
{
"type": "database",
"pattern": "schema_change",
"source": "PostgreSQL 13 Production Database",
"target": "PostgreSQL 15 Cloud Database",
"description": "Migrate user management system from on-premises PostgreSQL to cloud with schema updates",
"constraints": {
"max_downtime_minutes": 30,
"data_volume_gb": 2500,
"dependencies": [
"user_service_api",
"authentication_service",
"notification_service",
"analytics_pipeline",
"backup_service"
],
"compliance_requirements": [
"GDPR",
"SOX"
],
"special_requirements": [
"zero_data_loss",
"referential_integrity",
"performance_baseline_maintained"
]
},
"tables_to_migrate": [
{
"name": "users",
"row_count": 1500000,
"size_mb": 450,
"critical": true
},
{
"name": "user_profiles",
"row_count": 1500000,
"size_mb": 890,
"critical": true
},
{
"name": "user_sessions",
"row_count": 25000000,
"size_mb": 1200,
"critical": false
},
{
"name": "audit_logs",
"row_count": 50000000,
"size_mb": 2800,
"critical": false
}
],
"schema_changes": [
{
"table": "users",
"changes": [
{
"type": "add_column",
"column": "email_verified_at",
"data_type": "timestamp",
"nullable": true
},
{
"type": "add_column",
"column": "phone_verified_at",
"data_type": "timestamp",
"nullable": true
}
]
},
{
"table": "user_profiles",
"changes": [
{
"type": "modify_column",
"column": "bio",
"old_type": "varchar(255)",
"new_type": "text"
},
{
"type": "add_constraint",
"constraint_type": "check",
"constraint_name": "bio_length_check",
"definition": "LENGTH(bio) <= 2000"
}
]
}
],
"performance_requirements": {
"max_query_response_time_ms": 100,
"concurrent_connections": 500,
"transactions_per_second": 1000
},
"business_continuity": {
"critical_business_hours": {
"start": "08:00",
"end": "18:00",
"timezone": "UTC"
},
"preferred_migration_window": {
"start": "02:00",
"end": "06:00",
"timezone": "UTC"
}
}
}
FILE:assets/sample_service_migration.json
{
"type": "service",
"pattern": "strangler_fig",
"source": "Legacy User Service (Java Spring Boot 2.x)",
"target": "New User Service (Node.js + TypeScript)",
"description": "Migrate legacy user management service to modern microservices architecture",
"constraints": {
"max_downtime_minutes": 0,
"data_volume_gb": 50,
"dependencies": [
"payment_service",
"order_service",
"notification_service",
"analytics_service",
"mobile_app_v1",
"mobile_app_v2",
"web_frontend",
"admin_dashboard"
],
"compliance_requirements": [
"PCI_DSS",
"GDPR"
],
"special_requirements": [
"api_backward_compatibility",
"session_continuity",
"rate_limit_preservation"
]
},
"service_details": {
"legacy_service": {
"endpoints": [
"GET /api/v1/users/{id}",
"POST /api/v1/users",
"PUT /api/v1/users/{id}",
"DELETE /api/v1/users/{id}",
"GET /api/v1/users/{id}/profile",
"PUT /api/v1/users/{id}/profile",
"POST /api/v1/users/{id}/verify-email",
"POST /api/v1/users/login",
"POST /api/v1/users/logout"
],
"current_load": {
"requests_per_second": 850,
"peak_requests_per_second": 2000,
"average_response_time_ms": 120,
"p95_response_time_ms": 300
},
"infrastructure": {
"instances": 4,
"cpu_cores_per_instance": 4,
"memory_gb_per_instance": 8,
"load_balancer": "AWS ELB Classic"
}
},
"new_service": {
"endpoints": [
"GET /api/v2/users/{id}",
"POST /api/v2/users",
"PUT /api/v2/users/{id}",
"DELETE /api/v2/users/{id}",
"GET /api/v2/users/{id}/profile",
"PUT /api/v2/users/{id}/profile",
"POST /api/v2/users/{id}/verify-email",
"POST /api/v2/users/{id}/verify-phone",
"POST /api/v2/auth/login",
"POST /api/v2/auth/logout",
"POST /api/v2/auth/refresh"
],
"target_performance": {
"requests_per_second": 1500,
"peak_requests_per_second": 3000,
"average_response_time_ms": 80,
"p95_response_time_ms": 200
},
"infrastructure": {
"container_platform": "Kubernetes",
"initial_replicas": 3,
"max_replicas": 10,
"cpu_request_millicores": 500,
"cpu_limit_millicores": 1000,
"memory_request_mb": 512,
"memory_limit_mb": 1024,
"load_balancer": "AWS ALB"
}
}
},
"migration_phases": [
{
"phase": "preparation",
"description": "Deploy new service and configure routing",
"estimated_duration_hours": 8
},
{
"phase": "intercept",
"description": "Configure API gateway to route to new service",
"estimated_duration_hours": 2
},
{
"phase": "gradual_migration",
"description": "Gradually increase traffic to new service",
"estimated_duration_hours": 48
},
{
"phase": "validation",
"description": "Validate new service performance and functionality",
"estimated_duration_hours": 24
},
{
"phase": "decommission",
"description": "Remove legacy service after validation",
"estimated_duration_hours": 4
}
],
"feature_flags": [
{
"name": "enable_new_user_service",
"description": "Route user service requests to new implementation",
"initial_percentage": 5,
"rollout_schedule": [
{"percentage": 5, "duration_hours": 24},
{"percentage": 25, "duration_hours": 24},
{"percentage": 50, "duration_hours": 24},
{"percentage": 100, "duration_hours": 0}
]
},
{
"name": "enable_new_auth_endpoints",
"description": "Enable new authentication endpoints",
"initial_percentage": 0,
"rollout_schedule": [
{"percentage": 10, "duration_hours": 12},
{"percentage": 50, "duration_hours": 12},
{"percentage": 100, "duration_hours": 0}
]
}
],
"monitoring": {
"critical_metrics": [
"request_rate",
"error_rate",
"response_time_p95",
"response_time_p99",
"cpu_utilization",
"memory_utilization",
"database_connection_pool"
],
"alert_thresholds": {
"error_rate": 0.05,
"response_time_p95": 250,
"cpu_utilization": 0.80,
"memory_utilization": 0.85
}
},
"rollback_triggers": [
{
"metric": "error_rate",
"threshold": 0.10,
"duration_minutes": 5,
"action": "automatic_rollback"
},
{
"metric": "response_time_p95",
"threshold": 500,
"duration_minutes": 10,
"action": "alert_team"
},
{
"metric": "cpu_utilization",
"threshold": 0.95,
"duration_minutes": 5,
"action": "scale_up"
}
]
}
FILE:expected_outputs/rollback_runbook.json
{
"runbook_id": "rb_921c0bca",
"migration_id": "23a52ed1507f",
"created_at": "2026-02-16T13:47:31.108500",
"rollback_phases": [
{
"phase_name": "rollback_cleanup",
"description": "Rollback changes made during cleanup phase",
"urgency_level": "medium",
"estimated_duration_minutes": 570,
"prerequisites": [
"Incident commander assigned and briefed",
"All team members notified of rollback initiation",
"Monitoring systems confirmed operational",
"Backup systems verified and accessible"
],
"steps": [
{
"step_id": "rb_validate_0_final",
"name": "Validate rollback completion",
"description": "Comprehensive validation that cleanup rollback completed successfully",
"script_type": "manual",
"script_content": "Execute validation checklist for this phase",
"estimated_duration_minutes": 10,
"dependencies": [],
"validation_commands": [
"SELECT COUNT(*) FROM {table_name};",
"SELECT COUNT(*) FROM information_schema.tables WHERE table_name = '{table_name}';",
"SELECT COUNT(*) FROM information_schema.columns WHERE table_name = '{table_name}' AND column_name = '{column_name}';",
"SELECT COUNT(DISTINCT {primary_key}) FROM {table_name};",
"SELECT MAX({timestamp_column}) FROM {table_name};"
],
"success_criteria": [
"cleanup fully rolled back",
"All validation checks pass"
],
"failure_escalation": "Investigate cleanup rollback failures",
"rollback_order": 99
}
],
"validation_checkpoints": [
"cleanup rollback steps completed",
"System health checks passing",
"No critical errors in logs",
"Key metrics within acceptable ranges",
"Validation command passed: SELECT COUNT(*) FROM {table_name};...",
"Validation command passed: SELECT COUNT(*) FROM information_schema.tables WHE...",
"Validation command passed: SELECT COUNT(*) FROM information_schema.columns WH..."
],
"communication_requirements": [
"Notify incident commander of phase start/completion",
"Update rollback status dashboard",
"Log all actions and decisions"
],
"risk_level": "medium"
},
{
"phase_name": "rollback_contract",
"description": "Rollback changes made during contract phase",
"urgency_level": "medium",
"estimated_duration_minutes": 570,
"prerequisites": [
"Incident commander assigned and briefed",
"All team members notified of rollback initiation",
"Monitoring systems confirmed operational",
"Backup systems verified and accessible",
"Previous rollback phase completed successfully"
],
"steps": [
{
"step_id": "rb_validate_1_final",
"name": "Validate rollback completion",
"description": "Comprehensive validation that contract rollback completed successfully",
"script_type": "manual",
"script_content": "Execute validation checklist for this phase",
"estimated_duration_minutes": 10,
"dependencies": [],
"validation_commands": [
"SELECT COUNT(*) FROM {table_name};",
"SELECT COUNT(*) FROM information_schema.tables WHERE table_name = '{table_name}';",
"SELECT COUNT(*) FROM information_schema.columns WHERE table_name = '{table_name}' AND column_name = '{column_name}';",
"SELECT COUNT(DISTINCT {primary_key}) FROM {table_name};",
"SELECT MAX({timestamp_column}) FROM {table_name};"
],
"success_criteria": [
"contract fully rolled back",
"All validation checks pass"
],
"failure_escalation": "Investigate contract rollback failures",
"rollback_order": 99
}
],
"validation_checkpoints": [
"contract rollback steps completed",
"System health checks passing",
"No critical errors in logs",
"Key metrics within acceptable ranges",
"Validation command passed: SELECT COUNT(*) FROM {table_name};...",
"Validation command passed: SELECT COUNT(*) FROM information_schema.tables WHE...",
"Validation command passed: SELECT COUNT(*) FROM information_schema.columns WH..."
],
"communication_requirements": [
"Notify incident commander of phase start/completion",
"Update rollback status dashboard",
"Log all actions and decisions"
],
"risk_level": "medium"
},
{
"phase_name": "rollback_migrate",
"description": "Rollback changes made during migrate phase",
"urgency_level": "medium",
"estimated_duration_minutes": 570,
"prerequisites": [
"Incident commander assigned and briefed",
"All team members notified of rollback initiation",
"Monitoring systems confirmed operational",
"Backup systems verified and accessible",
"Previous rollback phase completed successfully"
],
"steps": [
{
"step_id": "rb_validate_2_final",
"name": "Validate rollback completion",
"description": "Comprehensive validation that migrate rollback completed successfully",
"script_type": "manual",
"script_content": "Execute validation checklist for this phase",
"estimated_duration_minutes": 10,
"dependencies": [],
"validation_commands": [
"SELECT COUNT(*) FROM {table_name};",
"SELECT COUNT(*) FROM information_schema.tables WHERE table_name = '{table_name}';",
"SELECT COUNT(*) FROM information_schema.columns WHERE table_name = '{table_name}' AND column_name = '{column_name}';",
"SELECT COUNT(DISTINCT {primary_key}) FROM {table_name};",
"SELECT MAX({timestamp_column}) FROM {table_name};"
],
"success_criteria": [
"migrate fully rolled back",
"All validation checks pass"
],
"failure_escalation": "Investigate migrate rollback failures",
"rollback_order": 99
}
],
"validation_checkpoints": [
"migrate rollback steps completed",
"System health checks passing",
"No critical errors in logs",
"Key metrics within acceptable ranges",
"Validation command passed: SELECT COUNT(*) FROM {table_name};...",
"Validation command passed: SELECT COUNT(*) FROM information_schema.tables WHE...",
"Validation command passed: SELECT COUNT(*) FROM information_schema.columns WH..."
],
"communication_requirements": [
"Notify incident commander of phase start/completion",
"Update rollback status dashboard",
"Log all actions and decisions"
],
"risk_level": "medium"
},
{
"phase_name": "rollback_expand",
"description": "Rollback changes made during expand phase",
"urgency_level": "medium",
"estimated_duration_minutes": 570,
"prerequisites": [
"Incident commander assigned and briefed",
"All team members notified of rollback initiation",
"Monitoring systems confirmed operational",
"Backup systems verified and accessible",
"Previous rollback phase completed successfully"
],
"steps": [
{
"step_id": "rb_validate_3_final",
"name": "Validate rollback completion",
"description": "Comprehensive validation that expand rollback completed successfully",
"script_type": "manual",
"script_content": "Execute validation checklist for this phase",
"estimated_duration_minutes": 10,
"dependencies": [],
"validation_commands": [
"SELECT COUNT(*) FROM {table_name};",
"SELECT COUNT(*) FROM information_schema.tables WHERE table_name = '{table_name}';",
"SELECT COUNT(*) FROM information_schema.columns WHERE table_name = '{table_name}' AND column_name = '{column_name}';",
"SELECT COUNT(DISTINCT {primary_key}) FROM {table_name};",
"SELECT MAX({timestamp_column}) FROM {table_name};"
],
"success_criteria": [
"expand fully rolled back",
"All validation checks pass"
],
"failure_escalation": "Investigate expand rollback failures",
"rollback_order": 99
}
],
"validation_checkpoints": [
"expand rollback steps completed",
"System health checks passing",
"No critical errors in logs",
"Key metrics within acceptable ranges",
"Validation command passed: SELECT COUNT(*) FROM {table_name};...",
"Validation command passed: SELECT COUNT(*) FROM information_schema.tables WHE...",
"Validation command passed: SELECT COUNT(*) FROM information_schema.columns WH..."
],
"communication_requirements": [
"Notify incident commander of phase start/completion",
"Update rollback status dashboard",
"Log all actions and decisions"
],
"risk_level": "medium"
},
{
"phase_name": "rollback_preparation",
"description": "Rollback changes made during preparation phase",
"urgency_level": "medium",
"estimated_duration_minutes": 570,
"prerequisites": [
"Incident commander assigned and briefed",
"All team members notified of rollback initiation",
"Monitoring systems confirmed operational",
"Backup systems verified and accessible",
"Previous rollback phase completed successfully"
],
"steps": [
{
"step_id": "rb_schema_4_01",
"name": "Drop migration artifacts",
"description": "Remove temporary migration tables and procedures",
"script_type": "sql",
"script_content": "-- Drop migration artifacts\nDROP TABLE IF EXISTS migration_log;\nDROP PROCEDURE IF EXISTS migrate_data();",
"estimated_duration_minutes": 5,
"dependencies": [],
"validation_commands": [
"SELECT COUNT(*) FROM information_schema.tables WHERE table_name LIKE '%migration%';"
],
"success_criteria": [
"No migration artifacts remain"
],
"failure_escalation": "Manual cleanup required",
"rollback_order": 1
},
{
"step_id": "rb_validate_4_final",
"name": "Validate rollback completion",
"description": "Comprehensive validation that preparation rollback completed successfully",
"script_type": "manual",
"script_content": "Execute validation checklist for this phase",
"estimated_duration_minutes": 10,
"dependencies": [
"rb_schema_4_01"
],
"validation_commands": [
"SELECT COUNT(*) FROM {table_name};",
"SELECT COUNT(*) FROM information_schema.tables WHERE table_name = '{table_name}';",
"SELECT COUNT(*) FROM information_schema.columns WHERE table_name = '{table_name}' AND column_name = '{column_name}';",
"SELECT COUNT(DISTINCT {primary_key}) FROM {table_name};",
"SELECT MAX({timestamp_column}) FROM {table_name};"
],
"success_criteria": [
"preparation fully rolled back",
"All validation checks pass"
],
"failure_escalation": "Investigate preparation rollback failures",
"rollback_order": 99
}
],
"validation_checkpoints": [
"preparation rollback steps completed",
"System health checks passing",
"No critical errors in logs",
"Key metrics within acceptable ranges",
"Validation command passed: SELECT COUNT(*) FROM {table_name};...",
"Validation command passed: SELECT COUNT(*) FROM information_schema.tables WHE...",
"Validation command passed: SELECT COUNT(*) FROM information_schema.columns WH..."
],
"communication_requirements": [
"Notify incident commander of phase start/completion",
"Update rollback status dashboard",
"Log all actions and decisions"
],
"risk_level": "medium"
}
],
"trigger_conditions": [
{
"trigger_id": "error_rate_spike",
"name": "Error Rate Spike",
"condition": "error_rate > baseline * 5 for 5 minutes",
"metric_threshold": {
"metric": "error_rate",
"operator": "greater_than",
"value": "baseline_error_rate * 5",
"duration_minutes": 5
},
"evaluation_window_minutes": 5,
"auto_execute": true,
"escalation_contacts": [
"on_call_engineer",
"migration_lead"
]
},
{
"trigger_id": "response_time_degradation",
"name": "Response Time Degradation",
"condition": "p95_response_time > baseline * 3 for 10 minutes",
"metric_threshold": {
"metric": "p95_response_time",
"operator": "greater_than",
"value": "baseline_p95 * 3",
"duration_minutes": 10
},
"evaluation_window_minutes": 10,
"auto_execute": false,
"escalation_contacts": [
"performance_team",
"migration_lead"
]
},
{
"trigger_id": "availability_drop",
"name": "Service Availability Drop",
"condition": "availability < 95% for 2 minutes",
"metric_threshold": {
"metric": "availability",
"operator": "less_than",
"value": 0.95,
"duration_minutes": 2
},
"evaluation_window_minutes": 2,
"auto_execute": true,
"escalation_contacts": [
"sre_team",
"incident_commander"
]
},
{
"trigger_id": "data_integrity_failure",
"name": "Data Integrity Check Failure",
"condition": "data_validation_failures > 0",
"metric_threshold": {
"metric": "data_validation_failures",
"operator": "greater_than",
"value": 0,
"duration_minutes": 1
},
"evaluation_window_minutes": 1,
"auto_execute": true,
"escalation_contacts": [
"dba_team",
"data_team"
]
},
{
"trigger_id": "migration_progress_stalled",
"name": "Migration Progress Stalled",
"condition": "migration_progress unchanged for 30 minutes",
"metric_threshold": {
"metric": "migration_progress_rate",
"operator": "equals",
"value": 0,
"duration_minutes": 30
},
"evaluation_window_minutes": 30,
"auto_execute": false,
"escalation_contacts": [
"migration_team",
"dba_team"
]
}
],
"data_recovery_plan": {
"recovery_method": "point_in_time",
"backup_location": "/backups/pre_migration_{migration_id}_{timestamp}.sql",
"recovery_scripts": [
"pg_restore -d production -c /backups/pre_migration_backup.sql",
"SELECT pg_create_restore_point('rollback_point');",
"VACUUM ANALYZE; -- Refresh statistics after restore"
],
"data_validation_queries": [
"SELECT COUNT(*) FROM critical_business_table;",
"SELECT MAX(created_at) FROM audit_log;",
"SELECT COUNT(DISTINCT user_id) FROM user_sessions;",
"SELECT SUM(amount) FROM financial_transactions WHERE date = CURRENT_DATE;"
],
"estimated_recovery_time_minutes": 45,
"recovery_dependencies": [
"database_instance_running",
"backup_file_accessible"
]
},
"communication_templates": [
{
"template_type": "rollback_start",
"audience": "technical",
"subject": "ROLLBACK INITIATED: {migration_name}",
"body": "Team,\n\nWe have initiated rollback for migration: {migration_name}\nRollback ID: {rollback_id}\nStart Time: {start_time}\nEstimated Duration: {estimated_duration}\n\nReason: {rollback_reason}\n\nCurrent Status: Rolling back phase {current_phase}\n\nNext Updates: Every 15 minutes or upon phase completion\n\nActions Required:\n- Monitor system health dashboards\n- Stand by for escalation if needed\n- Do not make manual changes during rollback\n\nIncident Commander: {incident_commander}\n",
"urgency": "medium",
"delivery_methods": [
"email",
"slack"
]
},
{
"template_type": "rollback_start",
"audience": "business",
"subject": "System Rollback In Progress - {system_name}",
"body": "Business Stakeholders,\n\nWe are currently performing a planned rollback of the {system_name} migration due to {rollback_reason}.\n\nImpact: {business_impact}\nExpected Resolution: {estimated_completion_time}\nAffected Services: {affected_services}\n\nWe will provide updates every 30 minutes.\n\nContact: {business_contact}\n",
"urgency": "medium",
"delivery_methods": [
"email"
]
},
{
"template_type": "rollback_start",
"audience": "executive",
"subject": "EXEC ALERT: Critical System Rollback - {system_name}",
"body": "Executive Team,\n\nA critical rollback is in progress for {system_name}.\n\nSummary:\n- Rollback Reason: {rollback_reason}\n- Business Impact: {business_impact}\n- Expected Resolution: {estimated_completion_time}\n- Customer Impact: {customer_impact}\n\nWe are following established procedures and will update hourly.\n\nEscalation: {escalation_contact}\n",
"urgency": "high",
"delivery_methods": [
"email"
]
},
{
"template_type": "rollback_complete",
"audience": "technical",
"subject": "ROLLBACK COMPLETED: {migration_name}",
"body": "Team,\n\nRollback has been successfully completed for migration: {migration_name}\n\nSummary:\n- Start Time: {start_time}\n- End Time: {end_time}\n- Duration: {actual_duration}\n- Phases Completed: {completed_phases}\n\nValidation Results:\n{validation_results}\n\nSystem Status: {system_status}\n\nNext Steps:\n- Continue monitoring for 24 hours\n- Post-rollback review scheduled for {review_date}\n- Root cause analysis to begin\n\nAll clear to resume normal operations.\n\nIncident Commander: {incident_commander}\n",
"urgency": "medium",
"delivery_methods": [
"email",
"slack"
]
},
{
"template_type": "emergency_escalation",
"audience": "executive",
"subject": "CRITICAL: Rollback Emergency - {migration_name}",
"body": "CRITICAL SITUATION - IMMEDIATE ATTENTION REQUIRED\n\nMigration: {migration_name}\nIssue: Rollback procedure has encountered critical failures\n\nCurrent Status: {current_status}\nFailed Components: {failed_components}\nBusiness Impact: {business_impact}\nCustomer Impact: {customer_impact}\n\nImmediate Actions:\n1. Emergency response team activated\n2. {emergency_action_1}\n3. {emergency_action_2}\n\nWar Room: {war_room_location}\nBridge Line: {conference_bridge}\n\nNext Update: {next_update_time}\n\nIncident Commander: {incident_commander}\nExecutive On-Call: {executive_on_call}\n",
"urgency": "emergency",
"delivery_methods": [
"email",
"sms",
"phone_call"
]
}
],
"escalation_matrix": {
"level_1": {
"trigger": "Single component failure",
"response_time_minutes": 5,
"contacts": [
"on_call_engineer",
"migration_lead"
],
"actions": [
"Investigate issue",
"Attempt automated remediation",
"Monitor closely"
]
},
"level_2": {
"trigger": "Multiple component failures or single critical failure",
"response_time_minutes": 2,
"contacts": [
"senior_engineer",
"team_lead",
"devops_lead"
],
"actions": [
"Initiate rollback",
"Establish war room",
"Notify stakeholders"
]
},
"level_3": {
"trigger": "System-wide failure or data corruption",
"response_time_minutes": 1,
"contacts": [
"engineering_manager",
"cto",
"incident_commander"
],
"actions": [
"Emergency rollback",
"All hands on deck",
"Executive notification"
]
},
"emergency": {
"trigger": "Business-critical failure with customer impact",
"response_time_minutes": 0,
"contacts": [
"ceo",
"cto",
"head_of_operations"
],
"actions": [
"Emergency procedures",
"Customer communication",
"Media preparation if needed"
]
}
},
"validation_checklist": [
"Verify system is responding to health checks",
"Confirm error rates are within normal parameters",
"Validate response times meet SLA requirements",
"Check all critical business processes are functioning",
"Verify monitoring and alerting systems are operational",
"Confirm no data corruption has occurred",
"Validate security controls are functioning properly",
"Check backup systems are working correctly",
"Verify integration points with downstream systems",
"Confirm user authentication and authorization working",
"Validate database schema matches expected state",
"Confirm referential integrity constraints",
"Check database performance metrics",
"Verify data consistency across related tables",
"Validate indexes and statistics are optimal",
"Confirm transaction logs are clean",
"Check database connections and connection pooling"
],
"post_rollback_procedures": [
"Monitor system stability for 24-48 hours post-rollback",
"Conduct thorough post-rollback testing of all critical paths",
"Review and analyze rollback metrics and timing",
"Document lessons learned and rollback procedure improvements",
"Schedule post-mortem meeting with all stakeholders",
"Update rollback procedures based on actual experience",
"Communicate rollback completion to all stakeholders",
"Archive rollback logs and artifacts for future reference",
"Review and update monitoring thresholds if needed",
"Plan for next migration attempt with improved procedures",
"Conduct security review to ensure no vulnerabilities introduced",
"Update disaster recovery procedures if affected by rollback",
"Review capacity planning based on rollback resource usage",
"Update documentation with rollback experience and timings"
],
"emergency_contacts": [
{
"role": "Incident Commander",
"name": "TBD - Assigned during migration",
"primary_phone": "+1-XXX-XXX-XXXX",
"email": "incident.commander@company.com",
"backup_contact": "backup.commander@company.com"
},
{
"role": "Technical Lead",
"name": "TBD - Migration technical owner",
"primary_phone": "+1-XXX-XXX-XXXX",
"email": "tech.lead@company.com",
"backup_contact": "senior.engineer@company.com"
},
{
"role": "Business Owner",
"name": "TBD - Business stakeholder",
"primary_phone": "+1-XXX-XXX-XXXX",
"email": "business.owner@company.com",
"backup_contact": "product.manager@company.com"
},
{
"role": "On-Call Engineer",
"name": "Current on-call rotation",
"primary_phone": "+1-XXX-XXX-XXXX",
"email": "oncall@company.com",
"backup_contact": "backup.oncall@company.com"
},
{
"role": "Executive Escalation",
"name": "CTO/VP Engineering",
"primary_phone": "+1-XXX-XXX-XXXX",
"email": "cto@company.com",
"backup_contact": "vp.engineering@company.com"
}
]
}
FILE:expected_outputs/rollback_runbook.txt
================================================================================
ROLLBACK RUNBOOK: rb_921c0bca
================================================================================
Migration ID: 23a52ed1507f
Created: 2026-02-16T13:47:31.108500
EMERGENCY CONTACTS
----------------------------------------
Incident Commander: TBD - Assigned during migration
Phone: +1-XXX-XXX-XXXX
Email: incident.commander@company.com
Backup: backup.commander@company.com
Technical Lead: TBD - Migration technical owner
Phone: +1-XXX-XXX-XXXX
Email: tech.lead@company.com
Backup: senior.engineer@company.com
Business Owner: TBD - Business stakeholder
Phone: +1-XXX-XXX-XXXX
Email: business.owner@company.com
Backup: product.manager@company.com
On-Call Engineer: Current on-call rotation
Phone: +1-XXX-XXX-XXXX
Email: oncall@company.com
Backup: backup.oncall@company.com
Executive Escalation: CTO/VP Engineering
Phone: +1-XXX-XXX-XXXX
Email: cto@company.com
Backup: vp.engineering@company.com
ESCALATION MATRIX
----------------------------------------
LEVEL_1:
Trigger: Single component failure
Response Time: 5 minutes
Contacts: on_call_engineer, migration_lead
Actions: Investigate issue, Attempt automated remediation, Monitor closely
LEVEL_2:
Trigger: Multiple component failures or single critical failure
Response Time: 2 minutes
Contacts: senior_engineer, team_lead, devops_lead
Actions: Initiate rollback, Establish war room, Notify stakeholders
LEVEL_3:
Trigger: System-wide failure or data corruption
Response Time: 1 minutes
Contacts: engineering_manager, cto, incident_commander
Actions: Emergency rollback, All hands on deck, Executive notification
EMERGENCY:
Trigger: Business-critical failure with customer impact
Response Time: 0 minutes
Contacts: ceo, cto, head_of_operations
Actions: Emergency procedures, Customer communication, Media preparation if needed
AUTOMATIC ROLLBACK TRIGGERS
----------------------------------------
• Error Rate Spike
Condition: error_rate > baseline * 5 for 5 minutes
Auto-Execute: Yes
Evaluation Window: 5 minutes
Contacts: on_call_engineer, migration_lead
• Response Time Degradation
Condition: p95_response_time > baseline * 3 for 10 minutes
Auto-Execute: No
Evaluation Window: 10 minutes
Contacts: performance_team, migration_lead
• Service Availability Drop
Condition: availability < 95% for 2 minutes
Auto-Execute: Yes
Evaluation Window: 2 minutes
Contacts: sre_team, incident_commander
• Data Integrity Check Failure
Condition: data_validation_failures > 0
Auto-Execute: Yes
Evaluation Window: 1 minutes
Contacts: dba_team, data_team
• Migration Progress Stalled
Condition: migration_progress unchanged for 30 minutes
Auto-Execute: No
Evaluation Window: 30 minutes
Contacts: migration_team, dba_team
ROLLBACK PHASES
----------------------------------------
1. ROLLBACK_CLEANUP
Description: Rollback changes made during cleanup phase
Urgency: MEDIUM
Duration: 570 minutes
Risk Level: MEDIUM
Prerequisites:
✓ Incident commander assigned and briefed
✓ All team members notified of rollback initiation
✓ Monitoring systems confirmed operational
✓ Backup systems verified and accessible
Steps:
99. Validate rollback completion
Duration: 10 min
Type: manual
Success Criteria: cleanup fully rolled back, All validation checks pass
Validation Checkpoints:
☐ cleanup rollback steps completed
☐ System health checks passing
☐ No critical errors in logs
☐ Key metrics within acceptable ranges
☐ Validation command passed: SELECT COUNT(*) FROM {table_name};...
☐ Validation command passed: SELECT COUNT(*) FROM information_schema.tables WHE...
☐ Validation command passed: SELECT COUNT(*) FROM information_schema.columns WH...
2. ROLLBACK_CONTRACT
Description: Rollback changes made during contract phase
Urgency: MEDIUM
Duration: 570 minutes
Risk Level: MEDIUM
Prerequisites:
✓ Incident commander assigned and briefed
✓ All team members notified of rollback initiation
✓ Monitoring systems confirmed operational
✓ Backup systems verified and accessible
✓ Previous rollback phase completed successfully
Steps:
99. Validate rollback completion
Duration: 10 min
Type: manual
Success Criteria: contract fully rolled back, All validation checks pass
Validation Checkpoints:
☐ contract rollback steps completed
☐ System health checks passing
☐ No critical errors in logs
☐ Key metrics within acceptable ranges
☐ Validation command passed: SELECT COUNT(*) FROM {table_name};...
☐ Validation command passed: SELECT COUNT(*) FROM information_schema.tables WHE...
☐ Validation command passed: SELECT COUNT(*) FROM information_schema.columns WH...
3. ROLLBACK_MIGRATE
Description: Rollback changes made during migrate phase
Urgency: MEDIUM
Duration: 570 minutes
Risk Level: MEDIUM
Prerequisites:
✓ Incident commander assigned and briefed
✓ All team members notified of rollback initiation
✓ Monitoring systems confirmed operational
✓ Backup systems verified and accessible
✓ Previous rollback phase completed successfully
Steps:
99. Validate rollback completion
Duration: 10 min
Type: manual
Success Criteria: migrate fully rolled back, All validation checks pass
Validation Checkpoints:
☐ migrate rollback steps completed
☐ System health checks passing
☐ No critical errors in logs
☐ Key metrics within acceptable ranges
☐ Validation command passed: SELECT COUNT(*) FROM {table_name};...
☐ Validation command passed: SELECT COUNT(*) FROM information_schema.tables WHE...
☐ Validation command passed: SELECT COUNT(*) FROM information_schema.columns WH...
4. ROLLBACK_EXPAND
Description: Rollback changes made during expand phase
Urgency: MEDIUM
Duration: 570 minutes
Risk Level: MEDIUM
Prerequisites:
✓ Incident commander assigned and briefed
✓ All team members notified of rollback initiation
✓ Monitoring systems confirmed operational
✓ Backup systems verified and accessible
✓ Previous rollback phase completed successfully
Steps:
99. Validate rollback completion
Duration: 10 min
Type: manual
Success Criteria: expand fully rolled back, All validation checks pass
Validation Checkpoints:
☐ expand rollback steps completed
☐ System health checks passing
☐ No critical errors in logs
☐ Key metrics within acceptable ranges
☐ Validation command passed: SELECT COUNT(*) FROM {table_name};...
☐ Validation command passed: SELECT COUNT(*) FROM information_schema.tables WHE...
☐ Validation command passed: SELECT COUNT(*) FROM information_schema.columns WH...
5. ROLLBACK_PREPARATION
Description: Rollback changes made during preparation phase
Urgency: MEDIUM
Duration: 570 minutes
Risk Level: MEDIUM
Prerequisites:
✓ Incident commander assigned and briefed
✓ All team members notified of rollback initiation
✓ Monitoring systems confirmed operational
✓ Backup systems verified and accessible
✓ Previous rollback phase completed successfully
Steps:
1. Drop migration artifacts
Duration: 5 min
Type: sql
Script:
-- Drop migration artifacts
DROP TABLE IF EXISTS migration_log;
DROP PROCEDURE IF EXISTS migrate_data();
Success Criteria: No migration artifacts remain
99. Validate rollback completion
Duration: 10 min
Type: manual
Success Criteria: preparation fully rolled back, All validation checks pass
Validation Checkpoints:
☐ preparation rollback steps completed
☐ System health checks passing
☐ No critical errors in logs
☐ Key metrics within acceptable ranges
☐ Validation command passed: SELECT COUNT(*) FROM {table_name};...
☐ Validation command passed: SELECT COUNT(*) FROM information_schema.tables WHE...
☐ Validation command passed: SELECT COUNT(*) FROM information_schema.columns WH...
DATA RECOVERY PLAN
----------------------------------------
Recovery Method: point_in_time
Backup Location: /backups/pre_migration_{migration_id}_{timestamp}.sql
Estimated Recovery Time: 45 minutes
Recovery Scripts:
• pg_restore -d production -c /backups/pre_migration_backup.sql
• SELECT pg_create_restore_point('rollback_point');
• VACUUM ANALYZE; -- Refresh statistics after restore
Validation Queries:
• SELECT COUNT(*) FROM critical_business_table;
• SELECT MAX(created_at) FROM audit_log;
• SELECT COUNT(DISTINCT user_id) FROM user_sessions;
• SELECT SUM(amount) FROM financial_transactions WHERE date = CURRENT_DATE;
POST-ROLLBACK VALIDATION CHECKLIST
----------------------------------------
1. ☐ Verify system is responding to health checks
2. ☐ Confirm error rates are within normal parameters
3. ☐ Validate response times meet SLA requirements
4. ☐ Check all critical business processes are functioning
5. ☐ Verify monitoring and alerting systems are operational
6. ☐ Confirm no data corruption has occurred
7. ☐ Validate security controls are functioning properly
8. ☐ Check backup systems are working correctly
9. ☐ Verify integration points with downstream systems
10. ☐ Confirm user authentication and authorization working
11. ☐ Validate database schema matches expected state
12. ☐ Confirm referential integrity constraints
13. ☐ Check database performance metrics
14. ☐ Verify data consistency across related tables
15. ☐ Validate indexes and statistics are optimal
16. ☐ Confirm transaction logs are clean
17. ☐ Check database connections and connection pooling
POST-ROLLBACK PROCEDURES
----------------------------------------
1. Monitor system stability for 24-48 hours post-rollback
2. Conduct thorough post-rollback testing of all critical paths
3. Review and analyze rollback metrics and timing
4. Document lessons learned and rollback procedure improvements
5. Schedule post-mortem meeting with all stakeholders
6. Update rollback procedures based on actual experience
7. Communicate rollback completion to all stakeholders
8. Archive rollback logs and artifacts for future reference
9. Review and update monitoring thresholds if needed
10. Plan for next migration attempt with improved procedures
11. Conduct security review to ensure no vulnerabilities introduced
12. Update disaster recovery procedures if affected by rollback
13. Review capacity planning based on rollback resource usage
14. Update documentation with rollback experience and timings
FILE:expected_outputs/sample_database_migration_plan.json
{
"migration_id": "23a52ed1507f",
"source_system": "PostgreSQL 13 Production Database",
"target_system": "PostgreSQL 15 Cloud Database",
"migration_type": "database",
"complexity": "critical",
"estimated_duration_hours": 95,
"phases": [
{
"name": "preparation",
"description": "Prepare systems and teams for migration",
"duration_hours": 19,
"dependencies": [],
"validation_criteria": [
"All backups completed successfully",
"Monitoring systems operational",
"Team members briefed and ready",
"Rollback procedures tested"
],
"rollback_triggers": [
"Critical system failure",
"Data corruption detected",
"Performance degradation > 50%",
"Business process failure"
],
"tasks": [
"Backup source system",
"Set up monitoring and alerting",
"Prepare rollback procedures",
"Communicate migration timeline",
"Validate prerequisites"
],
"risk_level": "medium",
"resources_required": [
"Technical team availability",
"System access and permissions",
"Monitoring and alerting systems",
"Communication channels"
]
},
{
"name": "expand",
"description": "Execute expand phase",
"duration_hours": 19,
"dependencies": [
"preparation"
],
"validation_criteria": [
"Expand phase completed successfully"
],
"rollback_triggers": [
"Critical system failure",
"Data corruption detected",
"Performance degradation > 50%",
"Business process failure"
],
"tasks": [
"Complete expand activities"
],
"risk_level": "medium",
"resources_required": [
"Technical team availability",
"System access and permissions",
"Monitoring and alerting systems",
"Communication channels"
]
},
{
"name": "migrate",
"description": "Execute migrate phase",
"duration_hours": 19,
"dependencies": [
"expand"
],
"validation_criteria": [
"Migrate phase completed successfully"
],
"rollback_triggers": [
"Critical system failure",
"Data corruption detected",
"Performance degradation > 50%",
"Business process failure"
],
"tasks": [
"Complete migrate activities"
],
"risk_level": "medium",
"resources_required": [
"Technical team availability",
"System access and permissions",
"Monitoring and alerting systems",
"Communication channels"
]
},
{
"name": "contract",
"description": "Execute contract phase",
"duration_hours": 19,
"dependencies": [
"migrate"
],
"validation_criteria": [
"Contract phase completed successfully"
],
"rollback_triggers": [
"Critical system failure",
"Data corruption detected",
"Performance degradation > 50%",
"Business process failure"
],
"tasks": [
"Complete contract activities"
],
"risk_level": "medium",
"resources_required": [
"Technical team availability",
"System access and permissions",
"Monitoring and alerting systems",
"Communication channels"
]
},
{
"name": "cleanup",
"description": "Execute cleanup phase",
"duration_hours": 19,
"dependencies": [
"contract"
],
"validation_criteria": [
"Cleanup phase completed successfully"
],
"rollback_triggers": [
"Critical system failure",
"Data corruption detected",
"Performance degradation > 50%",
"Business process failure"
],
"tasks": [
"Complete cleanup activities"
],
"risk_level": "medium",
"resources_required": [
"Technical team availability",
"System access and permissions",
"Monitoring and alerting systems",
"Communication channels"
]
}
],
"risks": [
{
"category": "technical",
"description": "Data corruption during migration",
"probability": "low",
"impact": "critical",
"severity": "high",
"mitigation": "Implement comprehensive backup and validation procedures",
"owner": "DBA Team"
},
{
"category": "technical",
"description": "Extended downtime due to migration complexity",
"probability": "medium",
"impact": "high",
"severity": "high",
"mitigation": "Use blue-green deployment and phased migration approach",
"owner": "DevOps Team"
},
{
"category": "business",
"description": "Business process disruption",
"probability": "medium",
"impact": "high",
"severity": "high",
"mitigation": "Communicate timeline and provide alternate workflows",
"owner": "Business Owner"
},
{
"category": "operational",
"description": "Insufficient rollback testing",
"probability": "high",
"impact": "critical",
"severity": "critical",
"mitigation": "Execute full rollback procedures in staging environment",
"owner": "QA Team"
},
{
"category": "business",
"description": "Zero-downtime requirement increases complexity",
"probability": "high",
"impact": "medium",
"severity": "high",
"mitigation": "Implement blue-green deployment or rolling update strategy",
"owner": "DevOps Team"
},
{
"category": "compliance",
"description": "Regulatory compliance requirements",
"probability": "medium",
"impact": "high",
"severity": "high",
"mitigation": "Ensure all compliance checks are integrated into migration process",
"owner": "Compliance Team"
}
],
"success_criteria": [
"All data successfully migrated with 100% integrity",
"System performance meets or exceeds baseline",
"All business processes functioning normally",
"No critical security vulnerabilities introduced",
"Stakeholder acceptance criteria met",
"Documentation and runbooks updated"
],
"rollback_plan": {
"rollback_phases": [
{
"phase": "cleanup",
"rollback_actions": [
"Revert cleanup changes",
"Restore pre-cleanup state",
"Validate cleanup rollback success"
],
"validation_criteria": [
"System restored to pre-cleanup state",
"All cleanup changes successfully reverted",
"System functionality confirmed"
],
"estimated_time_minutes": 285
},
{
"phase": "contract",
"rollback_actions": [
"Revert contract changes",
"Restore pre-contract state",
"Validate contract rollback success"
],
"validation_criteria": [
"System restored to pre-contract state",
"All contract changes successfully reverted",
"System functionality confirmed"
],
"estimated_time_minutes": 285
},
{
"phase": "migrate",
"rollback_actions": [
"Revert migrate changes",
"Restore pre-migrate state",
"Validate migrate rollback success"
],
"validation_criteria": [
"System restored to pre-migrate state",
"All migrate changes successfully reverted",
"System functionality confirmed"
],
"estimated_time_minutes": 285
},
{
"phase": "expand",
"rollback_actions": [
"Revert expand changes",
"Restore pre-expand state",
"Validate expand rollback success"
],
"validation_criteria": [
"System restored to pre-expand state",
"All expand changes successfully reverted",
"System functionality confirmed"
],
"estimated_time_minutes": 285
},
{
"phase": "preparation",
"rollback_actions": [
"Revert preparation changes",
"Restore pre-preparation state",
"Validate preparation rollback success"
],
"validation_criteria": [
"System restored to pre-preparation state",
"All preparation changes successfully reverted",
"System functionality confirmed"
],
"estimated_time_minutes": 285
}
],
"rollback_triggers": [
"Critical system failure",
"Data corruption detected",
"Migration timeline exceeded by > 50%",
"Business-critical functionality unavailable",
"Security breach detected",
"Stakeholder decision to abort"
],
"rollback_decision_matrix": {
"low_severity": "Continue with monitoring",
"medium_severity": "Assess and decide within 15 minutes",
"high_severity": "Immediate rollback initiation",
"critical_severity": "Emergency rollback - all hands"
},
"rollback_contacts": [
"Migration Lead",
"Technical Lead",
"Business Owner",
"On-call Engineer"
]
},
"stakeholders": [
"Business Owner",
"Technical Lead",
"DevOps Team",
"QA Team",
"Security Team",
"End Users"
],
"created_at": "2026-02-16T13:47:23.704502"
}
FILE:expected_outputs/sample_database_migration_plan.txt
================================================================================
MIGRATION PLAN: 23a52ed1507f
================================================================================
Source System: PostgreSQL 13 Production Database
Target System: PostgreSQL 15 Cloud Database
Migration Type: DATABASE
Complexity Level: CRITICAL
Estimated Duration: 95 hours (4.0 days)
Created: 2026-02-16T13:47:23.704502
MIGRATION PHASES
----------------------------------------
1. PREPARATION (19h)
Description: Prepare systems and teams for migration
Risk Level: MEDIUM
Tasks:
• Backup source system
• Set up monitoring and alerting
• Prepare rollback procedures
• Communicate migration timeline
• Validate prerequisites
Success Criteria:
✓ All backups completed successfully
✓ Monitoring systems operational
✓ Team members briefed and ready
✓ Rollback procedures tested
2. EXPAND (19h)
Description: Execute expand phase
Risk Level: MEDIUM
Dependencies: preparation
Tasks:
• Complete expand activities
Success Criteria:
✓ Expand phase completed successfully
3. MIGRATE (19h)
Description: Execute migrate phase
Risk Level: MEDIUM
Dependencies: expand
Tasks:
• Complete migrate activities
Success Criteria:
✓ Migrate phase completed successfully
4. CONTRACT (19h)
Description: Execute contract phase
Risk Level: MEDIUM
Dependencies: migrate
Tasks:
• Complete contract activities
Success Criteria:
✓ Contract phase completed successfully
5. CLEANUP (19h)
Description: Execute cleanup phase
Risk Level: MEDIUM
Dependencies: contract
Tasks:
• Complete cleanup activities
Success Criteria:
✓ Cleanup phase completed successfully
RISK ASSESSMENT
----------------------------------------
CRITICAL SEVERITY RISKS:
• Insufficient rollback testing
Category: operational
Probability: high | Impact: critical
Mitigation: Execute full rollback procedures in staging environment
Owner: QA Team
HIGH SEVERITY RISKS:
• Data corruption during migration
Category: technical
Probability: low | Impact: critical
Mitigation: Implement comprehensive backup and validation procedures
Owner: DBA Team
• Extended downtime due to migration complexity
Category: technical
Probability: medium | Impact: high
Mitigation: Use blue-green deployment and phased migration approach
Owner: DevOps Team
• Business process disruption
Category: business
Probability: medium | Impact: high
Mitigation: Communicate timeline and provide alternate workflows
Owner: Business Owner
• Zero-downtime requirement increases complexity
Category: business
Probability: high | Impact: medium
Mitigation: Implement blue-green deployment or rolling update strategy
Owner: DevOps Team
• Regulatory compliance requirements
Category: compliance
Probability: medium | Impact: high
Mitigation: Ensure all compliance checks are integrated into migration process
Owner: Compliance Team
ROLLBACK STRATEGY
----------------------------------------
Rollback Triggers:
• Critical system failure
• Data corruption detected
• Migration timeline exceeded by > 50%
• Business-critical functionality unavailable
• Security breach detected
• Stakeholder decision to abort
Rollback Phases:
CLEANUP:
- Revert cleanup changes
- Restore pre-cleanup state
- Validate cleanup rollback success
Estimated Time: 285 minutes
CONTRACT:
- Revert contract changes
- Restore pre-contract state
- Validate contract rollback success
Estimated Time: 285 minutes
MIGRATE:
- Revert migrate changes
- Restore pre-migrate state
- Validate migrate rollback success
Estimated Time: 285 minutes
EXPAND:
- Revert expand changes
- Restore pre-expand state
- Validate expand rollback success
Estimated Time: 285 minutes
PREPARATION:
- Revert preparation changes
- Restore pre-preparation state
- Validate preparation rollback success
Estimated Time: 285 minutes
SUCCESS CRITERIA
----------------------------------------
✓ All data successfully migrated with 100% integrity
✓ System performance meets or exceeds baseline
✓ All business processes functioning normally
✓ No critical security vulnerabilities introduced
✓ Stakeholder acceptance criteria met
✓ Documentation and runbooks updated
STAKEHOLDERS
----------------------------------------
• Business Owner
• Technical Lead
• DevOps Team
• QA Team
• Security Team
• End Users
FILE:expected_outputs/sample_service_migration_plan.json
{
"migration_id": "21031930da18",
"source_system": "Legacy User Service (Java Spring Boot 2.x)",
"target_system": "New User Service (Node.js + TypeScript)",
"migration_type": "service",
"complexity": "critical",
"estimated_duration_hours": 500,
"phases": [
{
"name": "intercept",
"description": "Execute intercept phase",
"duration_hours": 100,
"dependencies": [],
"validation_criteria": [
"Intercept phase completed successfully"
],
"rollback_triggers": [
"Critical system failure",
"Data corruption detected",
"Performance degradation > 50%",
"Business process failure"
],
"tasks": [
"Complete intercept activities"
],
"risk_level": "medium",
"resources_required": [
"Technical team availability",
"System access and permissions",
"Monitoring and alerting systems",
"Communication channels"
]
},
{
"name": "implement",
"description": "Execute implement phase",
"duration_hours": 100,
"dependencies": [
"intercept"
],
"validation_criteria": [
"Implement phase completed successfully"
],
"rollback_triggers": [
"Critical system failure",
"Data corruption detected",
"Performance degradation > 50%",
"Business process failure"
],
"tasks": [
"Complete implement activities"
],
"risk_level": "medium",
"resources_required": [
"Technical team availability",
"System access and permissions",
"Monitoring and alerting systems",
"Communication channels"
]
},
{
"name": "redirect",
"description": "Execute redirect phase",
"duration_hours": 100,
"dependencies": [
"implement"
],
"validation_criteria": [
"Redirect phase completed successfully"
],
"rollback_triggers": [
"Critical system failure",
"Data corruption detected",
"Performance degradation > 50%",
"Business process failure"
],
"tasks": [
"Complete redirect activities"
],
"risk_level": "medium",
"resources_required": [
"Technical team availability",
"System access and permissions",
"Monitoring and alerting systems",
"Communication channels"
]
},
{
"name": "validate",
"description": "Execute validate phase",
"duration_hours": 100,
"dependencies": [
"redirect"
],
"validation_criteria": [
"Validate phase completed successfully"
],
"rollback_triggers": [
"Critical system failure",
"Data corruption detected",
"Performance degradation > 50%",
"Business process failure"
],
"tasks": [
"Complete validate activities"
],
"risk_level": "medium",
"resources_required": [
"Technical team availability",
"System access and permissions",
"Monitoring and alerting systems",
"Communication channels"
]
},
{
"name": "retire",
"description": "Execute retire phase",
"duration_hours": 100,
"dependencies": [
"validate"
],
"validation_criteria": [
"Retire phase completed successfully"
],
"rollback_triggers": [
"Critical system failure",
"Data corruption detected",
"Performance degradation > 50%",
"Business process failure"
],
"tasks": [
"Complete retire activities"
],
"risk_level": "medium",
"resources_required": [
"Technical team availability",
"System access and permissions",
"Monitoring and alerting systems",
"Communication channels"
]
}
],
"risks": [
{
"category": "technical",
"description": "Service compatibility issues",
"probability": "medium",
"impact": "high",
"severity": "high",
"mitigation": "Implement comprehensive integration testing",
"owner": "Development Team"
},
{
"category": "technical",
"description": "Performance degradation",
"probability": "medium",
"impact": "medium",
"severity": "medium",
"mitigation": "Conduct load testing and performance benchmarking",
"owner": "DevOps Team"
},
{
"category": "business",
"description": "Feature parity gaps",
"probability": "high",
"impact": "high",
"severity": "high",
"mitigation": "Document feature mapping and acceptance criteria",
"owner": "Product Owner"
},
{
"category": "operational",
"description": "Monitoring gap during transition",
"probability": "medium",
"impact": "medium",
"severity": "medium",
"mitigation": "Set up dual monitoring and alerting systems",
"owner": "SRE Team"
},
{
"category": "business",
"description": "Zero-downtime requirement increases complexity",
"probability": "high",
"impact": "medium",
"severity": "high",
"mitigation": "Implement blue-green deployment or rolling update strategy",
"owner": "DevOps Team"
},
{
"category": "compliance",
"description": "Regulatory compliance requirements",
"probability": "medium",
"impact": "high",
"severity": "high",
"mitigation": "Ensure all compliance checks are integrated into migration process",
"owner": "Compliance Team"
}
],
"success_criteria": [
"All data successfully migrated with 100% integrity",
"System performance meets or exceeds baseline",
"All business processes functioning normally",
"No critical security vulnerabilities introduced",
"Stakeholder acceptance criteria met",
"Documentation and runbooks updated"
],
"rollback_plan": {
"rollback_phases": [
{
"phase": "retire",
"rollback_actions": [
"Revert retire changes",
"Restore pre-retire state",
"Validate retire rollback success"
],
"validation_criteria": [
"System restored to pre-retire state",
"All retire changes successfully reverted",
"System functionality confirmed"
],
"estimated_time_minutes": 1500
},
{
"phase": "validate",
"rollback_actions": [
"Revert validate changes",
"Restore pre-validate state",
"Validate validate rollback success"
],
"validation_criteria": [
"System restored to pre-validate state",
"All validate changes successfully reverted",
"System functionality confirmed"
],
"estimated_time_minutes": 1500
},
{
"phase": "redirect",
"rollback_actions": [
"Revert redirect changes",
"Restore pre-redirect state",
"Validate redirect rollback success"
],
"validation_criteria": [
"System restored to pre-redirect state",
"All redirect changes successfully reverted",
"System functionality confirmed"
],
"estimated_time_minutes": 1500
},
{
"phase": "implement",
"rollback_actions": [
"Revert implement changes",
"Restore pre-implement state",
"Validate implement rollback success"
],
"validation_criteria": [
"System restored to pre-implement state",
"All implement changes successfully reverted",
"System functionality confirmed"
],
"estimated_time_minutes": 1500
},
{
"phase": "intercept",
"rollback_actions": [
"Revert intercept changes",
"Restore pre-intercept state",
"Validate intercept rollback success"
],
"validation_criteria": [
"System restored to pre-intercept state",
"All intercept changes successfully reverted",
"System functionality confirmed"
],
"estimated_time_minutes": 1500
}
],
"rollback_triggers": [
"Critical system failure",
"Data corruption detected",
"Migration timeline exceeded by > 50%",
"Business-critical functionality unavailable",
"Security breach detected",
"Stakeholder decision to abort"
],
"rollback_decision_matrix": {
"low_severity": "Continue with monitoring",
"medium_severity": "Assess and decide within 15 minutes",
"high_severity": "Immediate rollback initiation",
"critical_severity": "Emergency rollback - all hands"
},
"rollback_contacts": [
"Migration Lead",
"Technical Lead",
"Business Owner",
"On-call Engineer"
]
},
"stakeholders": [
"Business Owner",
"Technical Lead",
"DevOps Team",
"QA Team",
"Security Team",
"End Users"
],
"created_at": "2026-02-16T13:47:34.565896"
}
FILE:expected_outputs/sample_service_migration_plan.txt
================================================================================
MIGRATION PLAN: 21031930da18
================================================================================
Source System: Legacy User Service (Java Spring Boot 2.x)
Target System: New User Service (Node.js + TypeScript)
Migration Type: SERVICE
Complexity Level: CRITICAL
Estimated Duration: 500 hours (20.8 days)
Created: 2026-02-16T13:47:34.565896
MIGRATION PHASES
----------------------------------------
1. INTERCEPT (100h)
Description: Execute intercept phase
Risk Level: MEDIUM
Tasks:
• Complete intercept activities
Success Criteria:
✓ Intercept phase completed successfully
2. IMPLEMENT (100h)
Description: Execute implement phase
Risk Level: MEDIUM
Dependencies: intercept
Tasks:
• Complete implement activities
Success Criteria:
✓ Implement phase completed successfully
3. REDIRECT (100h)
Description: Execute redirect phase
Risk Level: MEDIUM
Dependencies: implement
Tasks:
• Complete redirect activities
Success Criteria:
✓ Redirect phase completed successfully
4. VALIDATE (100h)
Description: Execute validate phase
Risk Level: MEDIUM
Dependencies: redirect
Tasks:
• Complete validate activities
Success Criteria:
✓ Validate phase completed successfully
5. RETIRE (100h)
Description: Execute retire phase
Risk Level: MEDIUM
Dependencies: validate
Tasks:
• Complete retire activities
Success Criteria:
✓ Retire phase completed successfully
RISK ASSESSMENT
----------------------------------------
HIGH SEVERITY RISKS:
• Service compatibility issues
Category: technical
Probability: medium | Impact: high
Mitigation: Implement comprehensive integration testing
Owner: Development Team
• Feature parity gaps
Category: business
Probability: high | Impact: high
Mitigation: Document feature mapping and acceptance criteria
Owner: Product Owner
• Zero-downtime requirement increases complexity
Category: business
Probability: high | Impact: medium
Mitigation: Implement blue-green deployment or rolling update strategy
Owner: DevOps Team
• Regulatory compliance requirements
Category: compliance
Probability: medium | Impact: high
Mitigation: Ensure all compliance checks are integrated into migration process
Owner: Compliance Team
MEDIUM SEVERITY RISKS:
• Performance degradation
Category: technical
Probability: medium | Impact: medium
Mitigation: Conduct load testing and performance benchmarking
Owner: DevOps Team
• Monitoring gap during transition
Category: operational
Probability: medium | Impact: medium
Mitigation: Set up dual monitoring and alerting systems
Owner: SRE Team
ROLLBACK STRATEGY
----------------------------------------
Rollback Triggers:
• Critical system failure
• Data corruption detected
• Migration timeline exceeded by > 50%
• Business-critical functionality unavailable
• Security breach detected
• Stakeholder decision to abort
Rollback Phases:
RETIRE:
- Revert retire changes
- Restore pre-retire state
- Validate retire rollback success
Estimated Time: 1500 minutes
VALIDATE:
- Revert validate changes
- Restore pre-validate state
- Validate validate rollback success
Estimated Time: 1500 minutes
REDIRECT:
- Revert redirect changes
- Restore pre-redirect state
- Validate redirect rollback success
Estimated Time: 1500 minutes
IMPLEMENT:
- Revert implement changes
- Restore pre-implement state
- Validate implement rollback success
Estimated Time: 1500 minutes
INTERCEPT:
- Revert intercept changes
- Restore pre-intercept state
- Validate intercept rollback success
Estimated Time: 1500 minutes
SUCCESS CRITERIA
----------------------------------------
✓ All data successfully migrated with 100% integrity
✓ System performance meets or exceeds baseline
✓ All business processes functioning normally
✓ No critical security vulnerabilities introduced
✓ Stakeholder acceptance criteria met
✓ Documentation and runbooks updated
STAKEHOLDERS
----------------------------------------
• Business Owner
• Technical Lead
• DevOps Team
• QA Team
• Security Team
• End Users
FILE:expected_outputs/schema_compatibility_report.json
{
"schema_before": "{\n \"schema_version\": \"1.0\",\n \"database\": \"user_management\",\n \"tables\": {\n \"users\": {\n \"columns\": {\n \"id\": {\n \"type\": \"bigint\",\n \"nullable\": false,\n \"primary_key\": true,\n \"auto_increment\": true\n },\n \"username\": {\n \"type\": \"varchar\",\n \"length\": 50,\n \"nullable\": false,\n \"unique\": true\n },\n \"email\": {\n \"type\": \"varchar\",\n \"length\": 255,\n \"nullable\": false,\n...",
"schema_after": "{\n \"schema_version\": \"2.0\",\n \"database\": \"user_management_v2\",\n \"tables\": {\n \"users\": {\n \"columns\": {\n \"id\": {\n \"type\": \"bigint\",\n \"nullable\": false,\n \"primary_key\": true,\n \"auto_increment\": true\n },\n \"username\": {\n \"type\": \"varchar\",\n \"length\": 50,\n \"nullable\": false,\n \"unique\": true\n },\n \"email\": {\n \"type\": \"varchar\",\n \"length\": 320,\n \"nullable\": fals...",
"analysis_date": "2026-02-16T13:47:27.050459",
"overall_compatibility": "potentially_incompatible",
"breaking_changes_count": 0,
"potentially_breaking_count": 4,
"non_breaking_changes_count": 0,
"additive_changes_count": 0,
"issues": [
{
"type": "check_added",
"severity": "potentially_breaking",
"description": "New check constraint 'phone IS NULL OR LENGTH(phone) >= 10' added to table 'users'",
"field_path": "tables.users.constraints.check",
"old_value": null,
"new_value": "phone IS NULL OR LENGTH(phone) >= 10",
"impact": "New check constraint may reject existing data",
"suggested_migration": "Validate existing data complies with new constraint",
"affected_operations": [
"INSERT",
"UPDATE"
]
},
{
"type": "check_added",
"severity": "potentially_breaking",
"description": "New check constraint 'bio IS NULL OR LENGTH(bio) <= 2000' added to table 'user_profiles'",
"field_path": "tables.user_profiles.constraints.check",
"old_value": null,
"new_value": "bio IS NULL OR LENGTH(bio) <= 2000",
"impact": "New check constraint may reject existing data",
"suggested_migration": "Validate existing data complies with new constraint",
"affected_operations": [
"INSERT",
"UPDATE"
]
},
{
"type": "check_added",
"severity": "potentially_breaking",
"description": "New check constraint 'language IN ('en', 'es', 'fr', 'de', 'it', 'pt', 'ru', 'ja', 'ko', 'zh')' added to table 'user_profiles'",
"field_path": "tables.user_profiles.constraints.check",
"old_value": null,
"new_value": "language IN ('en', 'es', 'fr', 'de', 'it', 'pt', 'ru', 'ja', 'ko', 'zh')",
"impact": "New check constraint may reject existing data",
"suggested_migration": "Validate existing data complies with new constraint",
"affected_operations": [
"INSERT",
"UPDATE"
]
},
{
"type": "check_added",
"severity": "potentially_breaking",
"description": "New check constraint 'session_type IN ('web', 'mobile', 'api', 'admin')' added to table 'user_sessions'",
"field_path": "tables.user_sessions.constraints.check",
"old_value": null,
"new_value": "session_type IN ('web', 'mobile', 'api', 'admin')",
"impact": "New check constraint may reject existing data",
"suggested_migration": "Validate existing data complies with new constraint",
"affected_operations": [
"INSERT",
"UPDATE"
]
}
],
"migration_scripts": [
{
"script_type": "sql",
"description": "Create new table user_preferences",
"script_content": "CREATE TABLE user_preferences (\n id bigint NOT NULL,\n user_id bigint NOT NULL,\n preference_key varchar NOT NULL,\n preference_value json,\n created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP\n);",
"rollback_script": "DROP TABLE IF EXISTS user_preferences;",
"dependencies": [],
"validation_query": "SELECT COUNT(*) FROM information_schema.tables WHERE table_name = 'user_preferences';"
},
{
"script_type": "sql",
"description": "Add column email_verified_at to table users",
"script_content": "ALTER TABLE users ADD COLUMN email_verified_at timestamp;",
"rollback_script": "ALTER TABLE users DROP COLUMN email_verified_at;",
"dependencies": [],
"validation_query": "SELECT COUNT(*) FROM information_schema.columns WHERE table_name = 'users' AND column_name = 'email_verified_at';"
},
{
"script_type": "sql",
"description": "Add column phone_verified_at to table users",
"script_content": "ALTER TABLE users ADD COLUMN phone_verified_at timestamp;",
"rollback_script": "ALTER TABLE users DROP COLUMN phone_verified_at;",
"dependencies": [],
"validation_query": "SELECT COUNT(*) FROM information_schema.columns WHERE table_name = 'users' AND column_name = 'phone_verified_at';"
},
{
"script_type": "sql",
"description": "Add column two_factor_enabled to table users",
"script_content": "ALTER TABLE users ADD COLUMN two_factor_enabled boolean NOT NULL DEFAULT False;",
"rollback_script": "ALTER TABLE users DROP COLUMN two_factor_enabled;",
"dependencies": [],
"validation_query": "SELECT COUNT(*) FROM information_schema.columns WHERE table_name = 'users' AND column_name = 'two_factor_enabled';"
},
{
"script_type": "sql",
"description": "Add column last_login_at to table users",
"script_content": "ALTER TABLE users ADD COLUMN last_login_at timestamp;",
"rollback_script": "ALTER TABLE users DROP COLUMN last_login_at;",
"dependencies": [],
"validation_query": "SELECT COUNT(*) FROM information_schema.columns WHERE table_name = 'users' AND column_name = 'last_login_at';"
},
{
"script_type": "sql",
"description": "Add check constraint to users",
"script_content": "ALTER TABLE users ADD CONSTRAINT check_users CHECK (phone IS NULL OR LENGTH(phone) >= 10);",
"rollback_script": "ALTER TABLE users DROP CONSTRAINT check_users;",
"dependencies": [],
"validation_query": "SELECT COUNT(*) FROM information_schema.table_constraints WHERE table_name = 'users' AND constraint_type = 'CHECK';"
},
{
"script_type": "sql",
"description": "Add column timezone to table user_profiles",
"script_content": "ALTER TABLE user_profiles ADD COLUMN timezone varchar DEFAULT UTC;",
"rollback_script": "ALTER TABLE user_profiles DROP COLUMN timezone;",
"dependencies": [],
"validation_query": "SELECT COUNT(*) FROM information_schema.columns WHERE table_name = 'user_profiles' AND column_name = 'timezone';"
},
{
"script_type": "sql",
"description": "Add column language to table user_profiles",
"script_content": "ALTER TABLE user_profiles ADD COLUMN language varchar NOT NULL DEFAULT en;",
"rollback_script": "ALTER TABLE user_profiles DROP COLUMN language;",
"dependencies": [],
"validation_query": "SELECT COUNT(*) FROM information_schema.columns WHERE table_name = 'user_profiles' AND column_name = 'language';"
},
{
"script_type": "sql",
"description": "Add check constraint to user_profiles",
"script_content": "ALTER TABLE user_profiles ADD CONSTRAINT check_user_profiles CHECK (bio IS NULL OR LENGTH(bio) <= 2000);",
"rollback_script": "ALTER TABLE user_profiles DROP CONSTRAINT check_user_profiles;",
"dependencies": [],
"validation_query": "SELECT COUNT(*) FROM information_schema.table_constraints WHERE table_name = 'user_profiles' AND constraint_type = 'CHECK';"
},
{
"script_type": "sql",
"description": "Add check constraint to user_profiles",
"script_content": "ALTER TABLE user_profiles ADD CONSTRAINT check_user_profiles CHECK (language IN ('en', 'es', 'fr', 'de', 'it', 'pt', 'ru', 'ja', 'ko', 'zh'));",
"rollback_script": "ALTER TABLE user_profiles DROP CONSTRAINT check_user_profiles;",
"dependencies": [],
"validation_query": "SELECT COUNT(*) FROM information_schema.table_constraints WHERE table_name = 'user_profiles' AND constraint_type = 'CHECK';"
},
{
"script_type": "sql",
"description": "Add column session_type to table user_sessions",
"script_content": "ALTER TABLE user_sessions ADD COLUMN session_type varchar NOT NULL DEFAULT web;",
"rollback_script": "ALTER TABLE user_sessions DROP COLUMN session_type;",
"dependencies": [],
"validation_query": "SELECT COUNT(*) FROM information_schema.columns WHERE table_name = 'user_sessions' AND column_name = 'session_type';"
},
{
"script_type": "sql",
"description": "Add column is_mobile to table user_sessions",
"script_content": "ALTER TABLE user_sessions ADD COLUMN is_mobile boolean NOT NULL DEFAULT False;",
"rollback_script": "ALTER TABLE user_sessions DROP COLUMN is_mobile;",
"dependencies": [],
"validation_query": "SELECT COUNT(*) FROM information_schema.columns WHERE table_name = 'user_sessions' AND column_name = 'is_mobile';"
},
{
"script_type": "sql",
"description": "Add check constraint to user_sessions",
"script_content": "ALTER TABLE user_sessions ADD CONSTRAINT check_user_sessions CHECK (session_type IN ('web', 'mobile', 'api', 'admin'));",
"rollback_script": "ALTER TABLE user_sessions DROP CONSTRAINT check_user_sessions;",
"dependencies": [],
"validation_query": "SELECT COUNT(*) FROM information_schema.table_constraints WHERE table_name = 'user_sessions' AND constraint_type = 'CHECK';"
}
],
"risk_assessment": {
"overall_risk": "medium",
"deployment_risk": "safe_independent_deployment",
"rollback_complexity": "low",
"testing_requirements": [
"integration_testing",
"regression_testing",
"data_migration_testing"
]
},
"recommendations": [
"Conduct thorough testing with realistic data volumes",
"Implement monitoring for migration success metrics",
"Test all migration scripts in staging environment",
"Implement migration progress monitoring",
"Create detailed communication plan for stakeholders",
"Implement feature flags for gradual rollout"
]
}
FILE:expected_outputs/schema_compatibility_report.txt
================================================================================
COMPATIBILITY ANALYSIS REPORT
================================================================================
Analysis Date: 2026-02-16T13:47:27.050459
Overall Compatibility: POTENTIALLY_INCOMPATIBLE
SUMMARY
----------------------------------------
Breaking Changes: 0
Potentially Breaking: 4
Non-Breaking Changes: 0
Additive Changes: 0
Total Issues Found: 4
RISK ASSESSMENT
----------------------------------------
Overall Risk: medium
Deployment Risk: safe_independent_deployment
Rollback Complexity: low
Testing Requirements: ['integration_testing', 'regression_testing', 'data_migration_testing']
POTENTIALLY BREAKING ISSUES
----------------------------------------
• New check constraint 'phone IS NULL OR LENGTH(phone) >= 10' added to table 'users'
Field: tables.users.constraints.check
Impact: New check constraint may reject existing data
Migration: Validate existing data complies with new constraint
Affected Operations: INSERT, UPDATE
• New check constraint 'bio IS NULL OR LENGTH(bio) <= 2000' added to table 'user_profiles'
Field: tables.user_profiles.constraints.check
Impact: New check constraint may reject existing data
Migration: Validate existing data complies with new constraint
Affected Operations: INSERT, UPDATE
• New check constraint 'language IN ('en', 'es', 'fr', 'de', 'it', 'pt', 'ru', 'ja', 'ko', 'zh')' added to table 'user_profiles'
Field: tables.user_profiles.constraints.check
Impact: New check constraint may reject existing data
Migration: Validate existing data complies with new constraint
Affected Operations: INSERT, UPDATE
• New check constraint 'session_type IN ('web', 'mobile', 'api', 'admin')' added to table 'user_sessions'
Field: tables.user_sessions.constraints.check
Impact: New check constraint may reject existing data
Migration: Validate existing data complies with new constraint
Affected Operations: INSERT, UPDATE
SUGGESTED MIGRATION SCRIPTS
----------------------------------------
1. Create new table user_preferences
Type: sql
Script:
CREATE TABLE user_preferences (
id bigint NOT NULL,
user_id bigint NOT NULL,
preference_key varchar NOT NULL,
preference_value json,
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
2. Add column email_verified_at to table users
Type: sql
Script:
ALTER TABLE users ADD COLUMN email_verified_at timestamp;
3. Add column phone_verified_at to table users
Type: sql
Script:
ALTER TABLE users ADD COLUMN phone_verified_at timestamp;
4. Add column two_factor_enabled to table users
Type: sql
Script:
ALTER TABLE users ADD COLUMN two_factor_enabled boolean NOT NULL DEFAULT False;
5. Add column last_login_at to table users
Type: sql
Script:
ALTER TABLE users ADD COLUMN last_login_at timestamp;
6. Add check constraint to users
Type: sql
Script:
ALTER TABLE users ADD CONSTRAINT check_users CHECK (phone IS NULL OR LENGTH(phone) >= 10);
7. Add column timezone to table user_profiles
Type: sql
Script:
ALTER TABLE user_profiles ADD COLUMN timezone varchar DEFAULT UTC;
8. Add column language to table user_profiles
Type: sql
Script:
ALTER TABLE user_profiles ADD COLUMN language varchar NOT NULL DEFAULT en;
9. Add check constraint to user_profiles
Type: sql
Script:
ALTER TABLE user_profiles ADD CONSTRAINT check_user_profiles CHECK (bio IS NULL OR LENGTH(bio) <= 2000);
10. Add check constraint to user_profiles
Type: sql
Script:
ALTER TABLE user_profiles ADD CONSTRAINT check_user_profiles CHECK (language IN ('en', 'es', 'fr', 'de', 'it', 'pt', 'ru', 'ja', 'ko', 'zh'));
11. Add column session_type to table user_sessions
Type: sql
Script:
ALTER TABLE user_sessions ADD COLUMN session_type varchar NOT NULL DEFAULT web;
12. Add column is_mobile to table user_sessions
Type: sql
Script:
ALTER TABLE user_sessions ADD COLUMN is_mobile boolean NOT NULL DEFAULT False;
13. Add check constraint to user_sessions
Type: sql
Script:
ALTER TABLE user_sessions ADD CONSTRAINT check_user_sessions CHECK (session_type IN ('web', 'mobile', 'api', 'admin'));
RECOMMENDATIONS
----------------------------------------
1. Conduct thorough testing with realistic data volumes
2. Implement monitoring for migration success metrics
3. Test all migration scripts in staging environment
4. Implement migration progress monitoring
5. Create detailed communication plan for stakeholders
6. Implement feature flags for gradual rollout
FILE:README.md
# Migration Architect
**Tier:** POWERFUL
**Category:** Engineering - Migration Strategy
**Purpose:** Zero-downtime migration planning, compatibility validation, and rollback strategy generation
## Overview
The Migration Architect skill provides comprehensive tools and methodologies for planning, executing, and validating complex system migrations with minimal business impact. This skill combines proven migration patterns with automated planning tools to ensure successful transitions between systems, databases, and infrastructure.
## Components
### Core Scripts
1. **migration_planner.py** - Automated migration plan generation
2. **compatibility_checker.py** - Schema and API compatibility analysis
3. **rollback_generator.py** - Comprehensive rollback procedure generation
### Reference Documentation
- **migration_patterns_catalog.md** - Detailed catalog of proven migration patterns
- **zero_downtime_techniques.md** - Comprehensive zero-downtime migration techniques
- **data_reconciliation_strategies.md** - Advanced data consistency and reconciliation strategies
### Sample Assets
- **sample_database_migration.json** - Example database migration specification
- **sample_service_migration.json** - Example service migration specification
- **database_schema_before.json** - Sample "before" database schema
- **database_schema_after.json** - Sample "after" database schema
## Quick Start
### 1. Generate a Migration Plan
```bash
python3 scripts/migration_planner.py \
--input assets/sample_database_migration.json \
--output migration_plan.json \
--format both
```
**Input:** Migration specification with source, target, constraints, and requirements
**Output:** Detailed phased migration plan with risk assessment, timeline, and validation gates
### 2. Check Compatibility
```bash
python3 scripts/compatibility_checker.py \
--before assets/database_schema_before.json \
--after assets/database_schema_after.json \
--type database \
--output compatibility_report.json \
--format both
```
**Input:** Before and after schema definitions
**Output:** Compatibility report with breaking changes, migration scripts, and recommendations
### 3. Generate Rollback Procedures
```bash
python3 scripts/rollback_generator.py \
--input migration_plan.json \
--output rollback_runbook.json \
--format both
```
**Input:** Migration plan from step 1
**Output:** Comprehensive rollback runbook with procedures, triggers, and communication templates
## Script Details
### Migration Planner (`migration_planner.py`)
Generates comprehensive migration plans with:
- **Phased approach** with dependencies and validation gates
- **Risk assessment** with mitigation strategies
- **Timeline estimation** based on complexity and constraints
- **Rollback triggers** and success criteria
- **Stakeholder communication** templates
**Usage:**
```bash
python3 scripts/migration_planner.py [OPTIONS]
Options:
--input, -i Input migration specification file (JSON) [required]
--output, -o Output file for migration plan (JSON)
--format, -f Output format: json, text, both (default: both)
--validate Validate migration specification only
```
**Input Format:**
```json
{
"type": "database|service|infrastructure",
"pattern": "schema_change|strangler_fig|blue_green",
"source": "Source system description",
"target": "Target system description",
"constraints": {
"max_downtime_minutes": 30,
"data_volume_gb": 2500,
"dependencies": ["service1", "service2"],
"compliance_requirements": ["GDPR", "SOX"]
}
}
```
### Compatibility Checker (`compatibility_checker.py`)
Analyzes compatibility between schema versions:
- **Breaking change detection** (removed fields, type changes, constraint additions)
- **Data migration requirements** identification
- **Suggested migration scripts** generation
- **Risk assessment** for each change
**Usage:**
```bash
python3 scripts/compatibility_checker.py [OPTIONS]
Options:
--before Before schema file (JSON) [required]
--after After schema file (JSON) [required]
--type Schema type: database, api (default: database)
--output, -o Output file for compatibility report (JSON)
--format, -f Output format: json, text, both (default: both)
```
**Exit Codes:**
- `0`: No compatibility issues
- `1`: Potentially breaking changes found
- `2`: Breaking changes found
### Rollback Generator (`rollback_generator.py`)
Creates comprehensive rollback procedures:
- **Phase-by-phase rollback** steps
- **Automated trigger conditions** for rollback
- **Data recovery procedures**
- **Communication templates** for different audiences
- **Validation checklists** for rollback success
**Usage:**
```bash
python3 scripts/rollback_generator.py [OPTIONS]
Options:
--input, -i Input migration plan file (JSON) [required]
--output, -o Output file for rollback runbook (JSON)
--format, -f Output format: json, text, both (default: both)
```
## Migration Patterns Supported
### Database Migrations
- **Expand-Contract Pattern** - Zero-downtime schema evolution
- **Parallel Schema Pattern** - Side-by-side schema migration
- **Event Sourcing Migration** - Event-driven data migration
### Service Migrations
- **Strangler Fig Pattern** - Gradual legacy system replacement
- **Parallel Run Pattern** - Risk mitigation through dual execution
- **Blue-Green Deployment** - Zero-downtime service updates
### Infrastructure Migrations
- **Lift and Shift** - Quick cloud migration with minimal changes
- **Hybrid Cloud Migration** - Gradual cloud adoption
- **Multi-Cloud Migration** - Distribution across multiple providers
## Sample Workflow
### 1. Database Schema Migration
```bash
# Generate migration plan
python3 scripts/migration_planner.py \
--input assets/sample_database_migration.json \
--output db_migration_plan.json
# Check schema compatibility
python3 scripts/compatibility_checker.py \
--before assets/database_schema_before.json \
--after assets/database_schema_after.json \
--type database \
--output schema_compatibility.json
# Generate rollback procedures
python3 scripts/rollback_generator.py \
--input db_migration_plan.json \
--output db_rollback_runbook.json
```
### 2. Service Migration
```bash
# Generate service migration plan
python3 scripts/migration_planner.py \
--input assets/sample_service_migration.json \
--output service_migration_plan.json
# Generate rollback procedures
python3 scripts/rollback_generator.py \
--input service_migration_plan.json \
--output service_rollback_runbook.json
```
## Output Examples
### Migration Plan Structure
```json
{
"migration_id": "abc123def456",
"source_system": "Legacy User Service",
"target_system": "New User Service",
"migration_type": "service",
"complexity": "medium",
"estimated_duration_hours": 72,
"phases": [
{
"name": "preparation",
"description": "Prepare systems and teams for migration",
"duration_hours": 8,
"validation_criteria": ["All backups completed successfully"],
"rollback_triggers": ["Critical system failure"],
"risk_level": "medium"
}
],
"risks": [
{
"category": "technical",
"description": "Service compatibility issues",
"severity": "high",
"mitigation": "Comprehensive integration testing"
}
]
}
```
### Compatibility Report Structure
```json
{
"overall_compatibility": "potentially_incompatible",
"breaking_changes_count": 2,
"potentially_breaking_count": 3,
"issues": [
{
"type": "required_column_added",
"severity": "breaking",
"description": "Required column 'email_verified_at' added",
"suggested_migration": "Add default value initially"
}
],
"migration_scripts": [
{
"script_type": "sql",
"description": "Add email verification columns",
"script_content": "ALTER TABLE users ADD COLUMN email_verified_at TIMESTAMP;",
"rollback_script": "ALTER TABLE users DROP COLUMN email_verified_at;"
}
]
}
```
## Best Practices
### Planning Phase
1. **Start with risk assessment** - Identify failure modes before planning
2. **Design for rollback** - Every step should have a tested rollback procedure
3. **Validate in staging** - Execute full migration in production-like environment
4. **Plan gradual rollout** - Use feature flags and traffic routing
### Execution Phase
1. **Monitor continuously** - Track technical and business metrics
2. **Communicate proactively** - Keep stakeholders informed
3. **Document everything** - Maintain detailed logs for analysis
4. **Stay flexible** - Be prepared to adjust based on real-world performance
### Validation Phase
1. **Automate validation** - Use automated consistency and performance checks
2. **Test business logic** - Validate critical business processes end-to-end
3. **Load test** - Verify performance under expected production load
4. **Security validation** - Ensure security controls function properly
## Integration
### CI/CD Pipeline Integration
```yaml
# Example GitHub Actions workflow
name: Migration Validation
on: [push, pull_request]
jobs:
validate-migration:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Validate Migration Plan
run: |
python3 scripts/migration_planner.py \
--input migration_spec.json \
--validate
- name: Check Compatibility
run: |
python3 scripts/compatibility_checker.py \
--before schema_before.json \
--after schema_after.json \
--type database
```
### Monitoring Integration
The tools generate metrics and alerts that can be integrated with:
- **Prometheus** - For metrics collection
- **Grafana** - For visualization and dashboards
- **PagerDuty** - For incident management
- **Slack** - For team notifications
## Advanced Features
### Machine Learning Integration
- Anomaly detection for data consistency issues
- Predictive analysis for migration success probability
- Automated pattern recognition for migration optimization
### Performance Optimization
- Parallel processing for large-scale migrations
- Incremental reconciliation strategies
- Statistical sampling for validation
### Compliance Support
- GDPR compliance tracking
- SOX audit trail generation
- HIPAA security validation
## Troubleshooting
### Common Issues
**"Migration plan validation failed"**
- Check JSON syntax in migration specification
- Ensure all required fields are present
- Validate constraint values are realistic
**"Compatibility checker reports false positives"**
- Review excluded fields configuration
- Check data type mapping compatibility
- Adjust tolerance settings for numerical comparisons
**"Rollback procedures seem incomplete"**
- Ensure migration plan includes all phases
- Verify database backup locations are specified
- Check that all dependencies are documented
### Getting Help
1. **Review documentation** - Check reference docs for patterns and techniques
2. **Examine sample files** - Use provided assets as templates
3. **Check expected outputs** - Compare your results with sample outputs
4. **Validate inputs** - Ensure input files match expected format
## Contributing
To extend or modify the Migration Architect skill:
1. **Add new patterns** - Extend pattern templates in migration_planner.py
2. **Enhance compatibility checks** - Add new validation rules in compatibility_checker.py
3. **Improve rollback procedures** - Add specialized rollback steps in rollback_generator.py
4. **Update documentation** - Keep reference docs current with new patterns
## License
This skill is part of the claude-skills repository and follows the same license terms.
FILE:references/data_reconciliation_strategies.md
# Data Reconciliation Strategies
## Overview
Data reconciliation is the process of ensuring data consistency and integrity across systems during and after migrations. This document provides comprehensive strategies, tools, and implementation patterns for detecting, measuring, and correcting data discrepancies in migration scenarios.
## Core Principles
### 1. Eventually Consistent
Accept that perfect real-time consistency may not be achievable during migrations, but ensure eventual consistency through reconciliation processes.
### 2. Idempotent Operations
All reconciliation operations must be safe to run multiple times without causing additional issues.
### 3. Audit Trail
Maintain detailed logs of all reconciliation actions for compliance and debugging.
### 4. Non-Destructive
Reconciliation should prefer addition over deletion, and always maintain backups before corrections.
## Types of Data Inconsistencies
### 1. Missing Records
Records that exist in source but not in target system.
### 2. Extra Records
Records that exist in target but not in source system.
### 3. Field Mismatches
Records exist in both systems but with different field values.
### 4. Referential Integrity Violations
Foreign key relationships that are broken during migration.
### 5. Temporal Inconsistencies
Data with incorrect timestamps or ordering.
### 6. Schema Drift
Structural differences between source and target schemas.
## Detection Strategies
### 1. Row Count Validation
#### Simple Count Comparison
```sql
-- Compare total row counts
SELECT
'source' as system,
COUNT(*) as row_count
FROM source_table
UNION ALL
SELECT
'target' as system,
COUNT(*) as row_count
FROM target_table;
```
#### Filtered Count Comparison
```sql
-- Compare counts with business logic filters
WITH source_counts AS (
SELECT
status,
created_date::date as date,
COUNT(*) as count
FROM source_orders
WHERE created_date >= '2024-01-01'
GROUP BY status, created_date::date
),
target_counts AS (
SELECT
status,
created_date::date as date,
COUNT(*) as count
FROM target_orders
WHERE created_date >= '2024-01-01'
GROUP BY status, created_date::date
)
SELECT
COALESCE(s.status, t.status) as status,
COALESCE(s.date, t.date) as date,
COALESCE(s.count, 0) as source_count,
COALESCE(t.count, 0) as target_count,
COALESCE(s.count, 0) - COALESCE(t.count, 0) as difference
FROM source_counts s
FULL OUTER JOIN target_counts t
ON s.status = t.status AND s.date = t.date
WHERE COALESCE(s.count, 0) != COALESCE(t.count, 0);
```
### 2. Checksum-Based Validation
#### Record-Level Checksums
```python
import hashlib
import json
class RecordChecksum:
def __init__(self, exclude_fields=None):
self.exclude_fields = exclude_fields or ['updated_at', 'version']
def calculate_checksum(self, record):
"""Calculate MD5 checksum for a database record"""
# Remove excluded fields and sort for consistency
filtered_record = {
k: v for k, v in record.items()
if k not in self.exclude_fields
}
# Convert to sorted JSON string for consistent hashing
normalized = json.dumps(filtered_record, sort_keys=True, default=str)
return hashlib.md5(normalized.encode('utf-8')).hexdigest()
def compare_records(self, source_record, target_record):
"""Compare two records using checksums"""
source_checksum = self.calculate_checksum(source_record)
target_checksum = self.calculate_checksum(target_record)
return {
'match': source_checksum == target_checksum,
'source_checksum': source_checksum,
'target_checksum': target_checksum
}
# Usage example
checksum_calculator = RecordChecksum(exclude_fields=['updated_at', 'migration_flag'])
source_records = fetch_records_from_source()
target_records = fetch_records_from_target()
mismatches = []
for source_id, source_record in source_records.items():
if source_id in target_records:
comparison = checksum_calculator.compare_records(
source_record, target_records[source_id]
)
if not comparison['match']:
mismatches.append({
'record_id': source_id,
'source_checksum': comparison['source_checksum'],
'target_checksum': comparison['target_checksum']
})
```
#### Aggregate Checksums
```sql
-- Calculate aggregate checksums for data validation
WITH source_aggregates AS (
SELECT
DATE_TRUNC('day', created_at) as day,
status,
COUNT(*) as record_count,
SUM(amount) as total_amount,
MD5(STRING_AGG(CAST(id AS VARCHAR) || ':' || CAST(amount AS VARCHAR), '|' ORDER BY id)) as checksum
FROM source_transactions
GROUP BY DATE_TRUNC('day', created_at), status
),
target_aggregates AS (
SELECT
DATE_TRUNC('day', created_at) as day,
status,
COUNT(*) as record_count,
SUM(amount) as total_amount,
MD5(STRING_AGG(CAST(id AS VARCHAR) || ':' || CAST(amount AS VARCHAR), '|' ORDER BY id)) as checksum
FROM target_transactions
GROUP BY DATE_TRUNC('day', created_at), status
)
SELECT
COALESCE(s.day, t.day) as day,
COALESCE(s.status, t.status) as status,
COALESCE(s.record_count, 0) as source_count,
COALESCE(t.record_count, 0) as target_count,
COALESCE(s.total_amount, 0) as source_amount,
COALESCE(t.total_amount, 0) as target_amount,
s.checksum as source_checksum,
t.checksum as target_checksum,
CASE WHEN s.checksum = t.checksum THEN 'MATCH' ELSE 'MISMATCH' END as status
FROM source_aggregates s
FULL OUTER JOIN target_aggregates t
ON s.day = t.day AND s.status = t.status
WHERE s.checksum != t.checksum OR s.checksum IS NULL OR t.checksum IS NULL;
```
### 3. Delta Detection
#### Change Data Capture (CDC) Based
```python
class CDCReconciler:
def __init__(self, kafka_client, database_client):
self.kafka = kafka_client
self.db = database_client
self.processed_changes = set()
def process_cdc_stream(self, topic_name):
"""Process CDC events and track changes for reconciliation"""
consumer = self.kafka.consumer(topic_name)
for message in consumer:
change_event = json.loads(message.value)
change_id = f"{change_event['table']}:{change_event['key']}:{change_event['timestamp']}"
if change_id in self.processed_changes:
continue # Skip duplicate events
try:
self.apply_change(change_event)
self.processed_changes.add(change_id)
# Commit offset only after successful processing
consumer.commit()
except Exception as e:
# Log failure and continue - will be caught by reconciliation
self.log_processing_failure(change_id, str(e))
def apply_change(self, change_event):
"""Apply CDC change to target system"""
table = change_event['table']
operation = change_event['operation']
key = change_event['key']
data = change_event.get('data', {})
if operation == 'INSERT':
self.db.insert(table, data)
elif operation == 'UPDATE':
self.db.update(table, key, data)
elif operation == 'DELETE':
self.db.delete(table, key)
def reconcile_missed_changes(self, start_timestamp, end_timestamp):
"""Find and apply changes that may have been missed"""
# Query source database for changes in time window
source_changes = self.db.get_changes_in_window(
start_timestamp, end_timestamp
)
missed_changes = []
for change in source_changes:
change_id = f"{change['table']}:{change['key']}:{change['timestamp']}"
if change_id not in self.processed_changes:
missed_changes.append(change)
# Apply missed changes
for change in missed_changes:
try:
self.apply_change(change)
print(f"Applied missed change: {change['table']}:{change['key']}")
except Exception as e:
print(f"Failed to apply missed change: {e}")
```
### 4. Business Logic Validation
#### Critical Business Rules Validation
```python
class BusinessLogicValidator:
def __init__(self, source_db, target_db):
self.source_db = source_db
self.target_db = target_db
def validate_financial_consistency(self):
"""Validate critical financial calculations"""
validation_rules = [
{
'name': 'daily_transaction_totals',
'source_query': """
SELECT DATE(created_at) as date, SUM(amount) as total
FROM source_transactions
WHERE created_at >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY DATE(created_at)
""",
'target_query': """
SELECT DATE(created_at) as date, SUM(amount) as total
FROM target_transactions
WHERE created_at >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY DATE(created_at)
""",
'tolerance': 0.01 # Allow $0.01 difference for rounding
},
{
'name': 'customer_balance_totals',
'source_query': """
SELECT customer_id, SUM(balance) as total_balance
FROM source_accounts
GROUP BY customer_id
HAVING SUM(balance) > 0
""",
'target_query': """
SELECT customer_id, SUM(balance) as total_balance
FROM target_accounts
GROUP BY customer_id
HAVING SUM(balance) > 0
""",
'tolerance': 0.01
}
]
validation_results = []
for rule in validation_rules:
source_data = self.source_db.execute_query(rule['source_query'])
target_data = self.target_db.execute_query(rule['target_query'])
differences = self.compare_financial_data(
source_data, target_data, rule['tolerance']
)
validation_results.append({
'rule_name': rule['name'],
'differences_found': len(differences),
'differences': differences[:10], # First 10 differences
'status': 'PASS' if len(differences) == 0 else 'FAIL'
})
return validation_results
def compare_financial_data(self, source_data, target_data, tolerance):
"""Compare financial data with tolerance for rounding differences"""
source_dict = {
tuple(row[:-1]): row[-1] for row in source_data
} # Last column is the amount
target_dict = {
tuple(row[:-1]): row[-1] for row in target_data
}
differences = []
# Check for missing records and value differences
for key, source_value in source_dict.items():
if key not in target_dict:
differences.append({
'key': key,
'source_value': source_value,
'target_value': None,
'difference_type': 'MISSING_IN_TARGET'
})
else:
target_value = target_dict[key]
if abs(float(source_value) - float(target_value)) > tolerance:
differences.append({
'key': key,
'source_value': source_value,
'target_value': target_value,
'difference': float(source_value) - float(target_value),
'difference_type': 'VALUE_MISMATCH'
})
# Check for extra records in target
for key, target_value in target_dict.items():
if key not in source_dict:
differences.append({
'key': key,
'source_value': None,
'target_value': target_value,
'difference_type': 'EXTRA_IN_TARGET'
})
return differences
```
## Correction Strategies
### 1. Automated Correction
#### Missing Record Insertion
```python
class AutoCorrector:
def __init__(self, source_db, target_db, dry_run=True):
self.source_db = source_db
self.target_db = target_db
self.dry_run = dry_run
self.correction_log = []
def correct_missing_records(self, table_name, key_field):
"""Add missing records from source to target"""
# Find records in source but not in target
missing_query = f"""
SELECT s.*
FROM source_{table_name} s
LEFT JOIN target_{table_name} t ON s.{key_field} = t.{key_field}
WHERE t.{key_field} IS NULL
"""
missing_records = self.source_db.execute_query(missing_query)
for record in missing_records:
correction = {
'table': table_name,
'operation': 'INSERT',
'key': record[key_field],
'data': record,
'timestamp': datetime.utcnow()
}
if not self.dry_run:
try:
self.target_db.insert(table_name, record)
correction['status'] = 'SUCCESS'
except Exception as e:
correction['status'] = 'FAILED'
correction['error'] = str(e)
else:
correction['status'] = 'DRY_RUN'
self.correction_log.append(correction)
return len(missing_records)
def correct_field_mismatches(self, table_name, key_field, fields_to_correct):
"""Correct field value mismatches"""
mismatch_query = f"""
SELECT s.{key_field}, {', '.join([f's.{f} as source_{f}, t.{f} as target_{f}' for f in fields_to_correct])}
FROM source_{table_name} s
JOIN target_{table_name} t ON s.{key_field} = t.{key_field}
WHERE {' OR '.join([f's.{f} != t.{f}' for f in fields_to_correct])}
"""
mismatched_records = self.source_db.execute_query(mismatch_query)
for record in mismatched_records:
key_value = record[key_field]
updates = {}
for field in fields_to_correct:
source_value = record[f'source_{field}']
target_value = record[f'target_{field}']
if source_value != target_value:
updates[field] = source_value
if updates:
correction = {
'table': table_name,
'operation': 'UPDATE',
'key': key_value,
'updates': updates,
'timestamp': datetime.utcnow()
}
if not self.dry_run:
try:
self.target_db.update(table_name, {key_field: key_value}, updates)
correction['status'] = 'SUCCESS'
except Exception as e:
correction['status'] = 'FAILED'
correction['error'] = str(e)
else:
correction['status'] = 'DRY_RUN'
self.correction_log.append(correction)
return len(mismatched_records)
```
### 2. Manual Review Process
#### Correction Workflow
```python
class ManualReviewSystem:
def __init__(self, database_client):
self.db = database_client
self.review_queue = []
def queue_for_review(self, discrepancy):
"""Add discrepancy to manual review queue"""
review_item = {
'id': str(uuid.uuid4()),
'discrepancy_type': discrepancy['type'],
'table': discrepancy['table'],
'record_key': discrepancy['key'],
'source_data': discrepancy.get('source_data'),
'target_data': discrepancy.get('target_data'),
'description': discrepancy['description'],
'severity': discrepancy.get('severity', 'medium'),
'status': 'PENDING',
'created_at': datetime.utcnow(),
'reviewed_by': None,
'reviewed_at': None,
'resolution': None
}
self.review_queue.append(review_item)
# Persist to review database
self.db.insert('manual_review_queue', review_item)
return review_item['id']
def process_review(self, review_id, reviewer, action, notes=None):
"""Process manual review decision"""
review_item = self.get_review_item(review_id)
if not review_item:
raise ValueError(f"Review item {review_id} not found")
review_item.update({
'status': 'REVIEWED',
'reviewed_by': reviewer,
'reviewed_at': datetime.utcnow(),
'resolution': {
'action': action, # 'APPLY_SOURCE', 'KEEP_TARGET', 'CUSTOM_FIX'
'notes': notes
}
})
# Apply the resolution
if action == 'APPLY_SOURCE':
self.apply_source_data(review_item)
elif action == 'KEEP_TARGET':
pass # No action needed
elif action == 'CUSTOM_FIX':
# Custom fix would be applied separately
pass
# Update review record
self.db.update('manual_review_queue',
{'id': review_id},
review_item)
return review_item
def generate_review_report(self):
"""Generate summary report of manual reviews"""
reviews = self.db.query("""
SELECT
discrepancy_type,
severity,
status,
COUNT(*) as count,
MIN(created_at) as oldest_review,
MAX(created_at) as newest_review
FROM manual_review_queue
GROUP BY discrepancy_type, severity, status
ORDER BY severity DESC, discrepancy_type
""")
return reviews
```
### 3. Reconciliation Scheduling
#### Automated Reconciliation Jobs
```python
import schedule
import time
from datetime import datetime, timedelta
class ReconciliationScheduler:
def __init__(self, reconciler):
self.reconciler = reconciler
self.job_history = []
def setup_schedules(self):
"""Set up automated reconciliation schedules"""
# Quick reconciliation every 15 minutes during migration
schedule.every(15).minutes.do(self.quick_reconciliation)
# Comprehensive reconciliation every 4 hours
schedule.every(4).hours.do(self.comprehensive_reconciliation)
# Deep validation daily
schedule.every().day.at("02:00").do(self.deep_validation)
# Weekly business logic validation
schedule.every().sunday.at("03:00").do(self.business_logic_validation)
def quick_reconciliation(self):
"""Quick count-based reconciliation"""
job_start = datetime.utcnow()
try:
# Check critical tables only
critical_tables = [
'transactions', 'orders', 'customers', 'accounts'
]
results = []
for table in critical_tables:
count_diff = self.reconciler.check_row_counts(table)
if abs(count_diff) > 0:
results.append({
'table': table,
'count_difference': count_diff,
'severity': 'high' if abs(count_diff) > 100 else 'medium'
})
job_result = {
'job_type': 'quick_reconciliation',
'start_time': job_start,
'end_time': datetime.utcnow(),
'status': 'completed',
'issues_found': len(results),
'details': results
}
# Alert if significant issues found
if any(r['severity'] == 'high' for r in results):
self.send_alert(job_result)
except Exception as e:
job_result = {
'job_type': 'quick_reconciliation',
'start_time': job_start,
'end_time': datetime.utcnow(),
'status': 'failed',
'error': str(e)
}
self.job_history.append(job_result)
def comprehensive_reconciliation(self):
"""Comprehensive checksum-based reconciliation"""
job_start = datetime.utcnow()
try:
tables_to_check = self.get_migration_tables()
issues = []
for table in tables_to_check:
# Sample-based checksum validation
sample_issues = self.reconciler.validate_sample_checksums(
table, sample_size=1000
)
issues.extend(sample_issues)
# Auto-correct simple issues
auto_corrections = 0
for issue in issues:
if issue['auto_correctable']:
self.reconciler.auto_correct_issue(issue)
auto_corrections += 1
else:
# Queue for manual review
self.reconciler.queue_for_manual_review(issue)
job_result = {
'job_type': 'comprehensive_reconciliation',
'start_time': job_start,
'end_time': datetime.utcnow(),
'status': 'completed',
'total_issues': len(issues),
'auto_corrections': auto_corrections,
'manual_reviews_queued': len(issues) - auto_corrections
}
except Exception as e:
job_result = {
'job_type': 'comprehensive_reconciliation',
'start_time': job_start,
'end_time': datetime.utcnow(),
'status': 'failed',
'error': str(e)
}
self.job_history.append(job_result)
def run_scheduler(self):
"""Run the reconciliation scheduler"""
print("Starting reconciliation scheduler...")
while True:
schedule.run_pending()
time.sleep(60) # Check every minute
```
## Monitoring and Reporting
### 1. Reconciliation Metrics
```python
class ReconciliationMetrics:
def __init__(self, prometheus_client):
self.prometheus = prometheus_client
# Define metrics
self.inconsistencies_found = Counter(
'reconciliation_inconsistencies_total',
'Number of inconsistencies found',
['table', 'type', 'severity']
)
self.reconciliation_duration = Histogram(
'reconciliation_duration_seconds',
'Time spent on reconciliation jobs',
['job_type']
)
self.auto_corrections = Counter(
'reconciliation_auto_corrections_total',
'Number of automatically corrected inconsistencies',
['table', 'correction_type']
)
self.data_drift_gauge = Gauge(
'data_drift_percentage',
'Percentage of records with inconsistencies',
['table']
)
def record_inconsistency(self, table, inconsistency_type, severity):
"""Record a found inconsistency"""
self.inconsistencies_found.labels(
table=table,
type=inconsistency_type,
severity=severity
).inc()
def record_auto_correction(self, table, correction_type):
"""Record an automatic correction"""
self.auto_corrections.labels(
table=table,
correction_type=correction_type
).inc()
def update_data_drift(self, table, drift_percentage):
"""Update data drift gauge"""
self.data_drift_gauge.labels(table=table).set(drift_percentage)
def record_job_duration(self, job_type, duration_seconds):
"""Record reconciliation job duration"""
self.reconciliation_duration.labels(job_type=job_type).observe(duration_seconds)
```
### 2. Alerting Rules
```yaml
# Prometheus alerting rules for data reconciliation
groups:
- name: data_reconciliation
rules:
- alert: HighDataInconsistency
expr: reconciliation_inconsistencies_total > 100
for: 5m
labels:
severity: critical
annotations:
summary: "High number of data inconsistencies detected"
description: "{{ $value }} inconsistencies found in the last 5 minutes"
- alert: DataDriftHigh
expr: data_drift_percentage > 5
for: 10m
labels:
severity: warning
annotations:
summary: "Data drift percentage is high"
description: "{{ $labels.table }} has {{ $value }}% data drift"
- alert: ReconciliationJobFailed
expr: up{job="reconciliation"} == 0
for: 2m
labels:
severity: critical
annotations:
summary: "Reconciliation job is down"
description: "The data reconciliation service is not responding"
- alert: AutoCorrectionRateHigh
expr: rate(reconciliation_auto_corrections_total[10m]) > 10
for: 5m
labels:
severity: warning
annotations:
summary: "High rate of automatic corrections"
description: "Auto-correction rate is {{ $value }} per second"
```
### 3. Dashboard and Reporting
```python
class ReconciliationDashboard:
def __init__(self, database_client, metrics_client):
self.db = database_client
self.metrics = metrics_client
def generate_daily_report(self, date=None):
"""Generate daily reconciliation report"""
if not date:
date = datetime.utcnow().date()
# Query reconciliation results for the day
daily_stats = self.db.query("""
SELECT
table_name,
inconsistency_type,
COUNT(*) as count,
AVG(CASE WHEN resolution = 'AUTO_CORRECTED' THEN 1 ELSE 0 END) as auto_correction_rate
FROM reconciliation_log
WHERE DATE(created_at) = %s
GROUP BY table_name, inconsistency_type
""", (date,))
# Generate summary
summary = {
'date': date.isoformat(),
'total_inconsistencies': sum(row['count'] for row in daily_stats),
'auto_correction_rate': sum(row['auto_correction_rate'] * row['count'] for row in daily_stats) / max(sum(row['count'] for row in daily_stats), 1),
'tables_affected': len(set(row['table_name'] for row in daily_stats)),
'details_by_table': {}
}
# Group by table
for row in daily_stats:
table = row['table_name']
if table not in summary['details_by_table']:
summary['details_by_table'][table] = []
summary['details_by_table'][table].append({
'inconsistency_type': row['inconsistency_type'],
'count': row['count'],
'auto_correction_rate': row['auto_correction_rate']
})
return summary
def generate_trend_analysis(self, days=7):
"""Generate trend analysis for reconciliation metrics"""
end_date = datetime.utcnow().date()
start_date = end_date - timedelta(days=days)
trends = self.db.query("""
SELECT
DATE(created_at) as date,
table_name,
COUNT(*) as inconsistencies,
AVG(CASE WHEN resolution = 'AUTO_CORRECTED' THEN 1 ELSE 0 END) as auto_correction_rate
FROM reconciliation_log
WHERE DATE(created_at) BETWEEN %s AND %s
GROUP BY DATE(created_at), table_name
ORDER BY date, table_name
""", (start_date, end_date))
# Calculate trends
trend_analysis = {
'period': f"{start_date} to {end_date}",
'trends': {},
'overall_trend': 'stable'
}
for table in set(row['table_name'] for row in trends):
table_data = [row for row in trends if row['table_name'] == table]
if len(table_data) >= 2:
first_count = table_data[0]['inconsistencies']
last_count = table_data[-1]['inconsistencies']
if last_count > first_count * 1.2:
trend = 'increasing'
elif last_count < first_count * 0.8:
trend = 'decreasing'
else:
trend = 'stable'
trend_analysis['trends'][table] = {
'direction': trend,
'first_day_count': first_count,
'last_day_count': last_count,
'change_percentage': ((last_count - first_count) / max(first_count, 1)) * 100
}
return trend_analysis
```
## Advanced Reconciliation Techniques
### 1. Machine Learning-Based Anomaly Detection
```python
from sklearn.isolation import IsolationForest
from sklearn.preprocessing import StandardScaler
import numpy as np
class MLAnomalyDetector:
def __init__(self):
self.models = {}
self.scalers = {}
def train_anomaly_detector(self, table_name, training_data):
"""Train anomaly detection model for a specific table"""
# Prepare features (convert records to numerical features)
features = self.extract_features(training_data)
# Scale features
scaler = StandardScaler()
scaled_features = scaler.fit_transform(features)
# Train isolation forest
model = IsolationForest(contamination=0.05, random_state=42)
model.fit(scaled_features)
# Store model and scaler
self.models[table_name] = model
self.scalers[table_name] = scaler
def detect_anomalies(self, table_name, data):
"""Detect anomalous records that may indicate reconciliation issues"""
if table_name not in self.models:
raise ValueError(f"No trained model for table {table_name}")
# Extract features
features = self.extract_features(data)
# Scale features
scaled_features = self.scalers[table_name].transform(features)
# Predict anomalies
anomaly_scores = self.models[table_name].decision_function(scaled_features)
anomaly_predictions = self.models[table_name].predict(scaled_features)
# Return anomalous records with scores
anomalies = []
for i, (record, score, is_anomaly) in enumerate(zip(data, anomaly_scores, anomaly_predictions)):
if is_anomaly == -1: # Isolation forest returns -1 for anomalies
anomalies.append({
'record_index': i,
'record': record,
'anomaly_score': score,
'severity': 'high' if score < -0.5 else 'medium'
})
return anomalies
def extract_features(self, data):
"""Extract numerical features from database records"""
features = []
for record in data:
record_features = []
for key, value in record.items():
if isinstance(value, (int, float)):
record_features.append(value)
elif isinstance(value, str):
# Convert string to hash-based feature
record_features.append(hash(value) % 10000)
elif isinstance(value, datetime):
# Convert datetime to timestamp
record_features.append(value.timestamp())
else:
# Default value for other types
record_features.append(0)
features.append(record_features)
return np.array(features)
```
### 2. Probabilistic Reconciliation
```python
import random
from typing import List, Dict, Tuple
class ProbabilisticReconciler:
def __init__(self, confidence_threshold=0.95):
self.confidence_threshold = confidence_threshold
def statistical_sampling_validation(self, table_name: str, population_size: int) -> Dict:
"""Use statistical sampling to validate large datasets"""
# Calculate sample size for 95% confidence, 5% margin of error
confidence_level = 0.95
margin_of_error = 0.05
z_score = 1.96 # for 95% confidence
p = 0.5 # assume 50% error rate for maximum sample size
sample_size = (z_score ** 2 * p * (1 - p)) / (margin_of_error ** 2)
if population_size < 10000:
# Finite population correction
sample_size = sample_size / (1 + (sample_size - 1) / population_size)
sample_size = min(int(sample_size), population_size)
# Generate random sample
sample_ids = self.generate_random_sample(table_name, sample_size)
# Validate sample
sample_results = self.validate_sample_records(table_name, sample_ids)
# Calculate population estimates
error_rate = sample_results['errors'] / sample_size
estimated_errors = int(population_size * error_rate)
# Calculate confidence interval
standard_error = (error_rate * (1 - error_rate) / sample_size) ** 0.5
margin_of_error_actual = z_score * standard_error
confidence_interval = (
max(0, error_rate - margin_of_error_actual),
min(1, error_rate + margin_of_error_actual)
)
return {
'table_name': table_name,
'population_size': population_size,
'sample_size': sample_size,
'sample_error_rate': error_rate,
'estimated_total_errors': estimated_errors,
'confidence_interval': confidence_interval,
'confidence_level': confidence_level,
'recommendation': self.generate_recommendation(error_rate, confidence_interval)
}
def generate_random_sample(self, table_name: str, sample_size: int) -> List[int]:
"""Generate random sample of record IDs"""
# Get total record count and ID range
id_range = self.db.query(f"SELECT MIN(id), MAX(id) FROM {table_name}")[0]
min_id, max_id = id_range
# Generate random IDs
sample_ids = []
attempts = 0
max_attempts = sample_size * 10 # Avoid infinite loop
while len(sample_ids) < sample_size and attempts < max_attempts:
candidate_id = random.randint(min_id, max_id)
# Check if ID exists
exists = self.db.query(f"SELECT 1 FROM {table_name} WHERE id = %s", (candidate_id,))
if exists and candidate_id not in sample_ids:
sample_ids.append(candidate_id)
attempts += 1
return sample_ids
def validate_sample_records(self, table_name: str, sample_ids: List[int]) -> Dict:
"""Validate a sample of records"""
validation_results = {
'total_checked': len(sample_ids),
'errors': 0,
'error_details': []
}
for record_id in sample_ids:
# Get record from both source and target
source_record = self.source_db.get_record(table_name, record_id)
target_record = self.target_db.get_record(table_name, record_id)
if not target_record:
validation_results['errors'] += 1
validation_results['error_details'].append({
'id': record_id,
'error_type': 'MISSING_IN_TARGET'
})
elif not self.records_match(source_record, target_record):
validation_results['errors'] += 1
validation_results['error_details'].append({
'id': record_id,
'error_type': 'DATA_MISMATCH',
'differences': self.find_differences(source_record, target_record)
})
return validation_results
def generate_recommendation(self, error_rate: float, confidence_interval: Tuple[float, float]) -> str:
"""Generate recommendation based on error rate and confidence"""
if confidence_interval[1] < 0.01: # Less than 1% error rate with confidence
return "Data quality is excellent. Continue with normal reconciliation schedule."
elif confidence_interval[1] < 0.05: # Less than 5% error rate with confidence
return "Data quality is acceptable. Monitor closely and investigate sample errors."
elif confidence_interval[0] > 0.1: # More than 10% error rate with confidence
return "Data quality is poor. Immediate comprehensive reconciliation required."
else:
return "Data quality is uncertain. Increase sample size for better estimates."
```
## Performance Optimization
### 1. Parallel Processing
```python
import asyncio
import multiprocessing as mp
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
class ParallelReconciler:
def __init__(self, max_workers=None):
self.max_workers = max_workers or mp.cpu_count()
async def parallel_table_reconciliation(self, tables: List[str]):
"""Reconcile multiple tables in parallel"""
async with asyncio.Semaphore(self.max_workers):
tasks = [
self.reconcile_table_async(table)
for table in tables
]
results = await asyncio.gather(*tasks, return_exceptions=True)
# Process results
summary = {
'total_tables': len(tables),
'successful': 0,
'failed': 0,
'results': {}
}
for table, result in zip(tables, results):
if isinstance(result, Exception):
summary['failed'] += 1
summary['results'][table] = {
'status': 'failed',
'error': str(result)
}
else:
summary['successful'] += 1
summary['results'][table] = result
return summary
def parallel_chunk_processing(self, table_name: str, chunk_size: int = 10000):
"""Process table reconciliation in parallel chunks"""
# Get total record count
total_records = self.db.get_record_count(table_name)
num_chunks = (total_records + chunk_size - 1) // chunk_size
# Create chunk specifications
chunks = []
for i in range(num_chunks):
start_id = i * chunk_size
end_id = min((i + 1) * chunk_size - 1, total_records - 1)
chunks.append({
'table': table_name,
'start_id': start_id,
'end_id': end_id,
'chunk_number': i + 1
})
# Process chunks in parallel
with ProcessPoolExecutor(max_workers=self.max_workers) as executor:
chunk_results = list(executor.map(self.process_chunk, chunks))
# Aggregate results
total_inconsistencies = sum(r['inconsistencies'] for r in chunk_results)
total_corrections = sum(r['corrections'] for r in chunk_results)
return {
'table': table_name,
'total_records': total_records,
'chunks_processed': len(chunks),
'total_inconsistencies': total_inconsistencies,
'total_corrections': total_corrections,
'chunk_details': chunk_results
}
def process_chunk(self, chunk_spec: Dict) -> Dict:
"""Process a single chunk of records"""
# This runs in a separate process
table = chunk_spec['table']
start_id = chunk_spec['start_id']
end_id = chunk_spec['end_id']
# Initialize database connections for this process
local_source_db = SourceDatabase()
local_target_db = TargetDatabase()
# Get records in chunk
source_records = local_source_db.get_records_range(table, start_id, end_id)
target_records = local_target_db.get_records_range(table, start_id, end_id)
# Reconcile chunk
inconsistencies = 0
corrections = 0
for source_record in source_records:
target_record = target_records.get(source_record['id'])
if not target_record:
inconsistencies += 1
# Auto-correct if possible
try:
local_target_db.insert(table, source_record)
corrections += 1
except Exception:
pass # Log error in production
elif not self.records_match(source_record, target_record):
inconsistencies += 1
# Auto-correct field mismatches
try:
updates = self.calculate_updates(source_record, target_record)
local_target_db.update(table, source_record['id'], updates)
corrections += 1
except Exception:
pass # Log error in production
return {
'chunk_number': chunk_spec['chunk_number'],
'start_id': start_id,
'end_id': end_id,
'records_processed': len(source_records),
'inconsistencies': inconsistencies,
'corrections': corrections
}
```
### 2. Incremental Reconciliation
```python
class IncrementalReconciler:
def __init__(self, source_db, target_db):
self.source_db = source_db
self.target_db = target_db
self.last_reconciliation_times = {}
def incremental_reconciliation(self, table_name: str):
"""Reconcile only records changed since last reconciliation"""
last_reconciled = self.get_last_reconciliation_time(table_name)
# Get records modified since last reconciliation
modified_source = self.source_db.get_records_modified_since(
table_name, last_reconciled
)
modified_target = self.target_db.get_records_modified_since(
table_name, last_reconciled
)
# Create lookup dictionaries
source_dict = {r['id']: r for r in modified_source}
target_dict = {r['id']: r for r in modified_target}
# Find all record IDs to check
all_ids = set(source_dict.keys()) | set(target_dict.keys())
inconsistencies = []
for record_id in all_ids:
source_record = source_dict.get(record_id)
target_record = target_dict.get(record_id)
if source_record and not target_record:
inconsistencies.append({
'type': 'missing_in_target',
'table': table_name,
'id': record_id,
'source_record': source_record
})
elif not source_record and target_record:
inconsistencies.append({
'type': 'extra_in_target',
'table': table_name,
'id': record_id,
'target_record': target_record
})
elif source_record and target_record:
if not self.records_match(source_record, target_record):
inconsistencies.append({
'type': 'data_mismatch',
'table': table_name,
'id': record_id,
'source_record': source_record,
'target_record': target_record,
'differences': self.find_differences(source_record, target_record)
})
# Update last reconciliation time
self.update_last_reconciliation_time(table_name, datetime.utcnow())
return {
'table': table_name,
'reconciliation_time': datetime.utcnow(),
'records_checked': len(all_ids),
'inconsistencies_found': len(inconsistencies),
'inconsistencies': inconsistencies
}
def get_last_reconciliation_time(self, table_name: str) -> datetime:
"""Get the last reconciliation timestamp for a table"""
result = self.source_db.query("""
SELECT last_reconciled_at
FROM reconciliation_metadata
WHERE table_name = %s
""", (table_name,))
if result:
return result[0]['last_reconciled_at']
else:
# First time reconciliation - start from beginning of migration
return self.get_migration_start_time()
def update_last_reconciliation_time(self, table_name: str, timestamp: datetime):
"""Update the last reconciliation timestamp"""
self.source_db.execute("""
INSERT INTO reconciliation_metadata (table_name, last_reconciled_at)
VALUES (%s, %s)
ON CONFLICT (table_name)
DO UPDATE SET last_reconciled_at = %s
""", (table_name, timestamp, timestamp))
```
This comprehensive guide provides the framework and tools necessary for implementing robust data reconciliation strategies during migrations, ensuring data integrity and consistency while minimizing business disruption.
FILE:references/migration_patterns_catalog.md
# Migration Patterns Catalog
## Overview
This catalog provides detailed descriptions of proven migration patterns, their use cases, implementation guidelines, and best practices. Each pattern includes code examples, diagrams, and lessons learned from real-world implementations.
## Database Migration Patterns
### 1. Expand-Contract Pattern
**Use Case:** Schema evolution with zero downtime
**Complexity:** Medium
**Risk Level:** Low-Medium
#### Description
The Expand-Contract pattern allows for schema changes without downtime by following a three-phase approach:
1. **Expand:** Add new schema elements alongside existing ones
2. **Migrate:** Dual-write to both old and new schema during transition
3. **Contract:** Remove old schema elements after validation
#### Implementation Steps
```sql
-- Phase 1: Expand
ALTER TABLE users ADD COLUMN email_new VARCHAR(255);
CREATE INDEX CONCURRENTLY idx_users_email_new ON users(email_new);
-- Phase 2: Migrate (Application Code)
-- Write to both columns during transition period
INSERT INTO users (name, email, email_new) VALUES (?, ?, ?);
-- Backfill existing data
UPDATE users SET email_new = email WHERE email_new IS NULL;
-- Phase 3: Contract (after validation)
ALTER TABLE users DROP COLUMN email;
ALTER TABLE users RENAME COLUMN email_new TO email;
```
#### Pros and Cons
**Pros:**
- Zero downtime deployments
- Safe rollback at any point
- Gradual transition with validation
**Cons:**
- Increased storage during transition
- More complex application logic
- Extended migration timeline
### 2. Parallel Schema Pattern
**Use Case:** Major database restructuring
**Complexity:** High
**Risk Level:** Medium
#### Description
Run new and old schemas in parallel, using feature flags to gradually route traffic to the new schema while maintaining the ability to rollback quickly.
#### Implementation Example
```python
class DatabaseRouter:
def __init__(self, feature_flag_service):
self.feature_flags = feature_flag_service
self.old_db = OldDatabaseConnection()
self.new_db = NewDatabaseConnection()
def route_query(self, user_id, query_type):
if self.feature_flags.is_enabled("new_schema", user_id):
return self.new_db.execute(query_type)
else:
return self.old_db.execute(query_type)
def dual_write(self, data):
# Write to both databases for consistency
success_old = self.old_db.write(data)
success_new = self.new_db.write(transform_data(data))
if not (success_old and success_new):
# Handle partial failures
self.handle_dual_write_failure(data, success_old, success_new)
```
#### Best Practices
- Implement data consistency checks between schemas
- Use circuit breakers for automatic failover
- Monitor performance impact of dual writes
- Plan for data reconciliation processes
### 3. Event Sourcing Migration
**Use Case:** Migrating systems with complex business logic
**Complexity:** High
**Risk Level:** Medium-High
#### Description
Capture all changes as events during migration, enabling replay and reconciliation capabilities.
#### Event Store Schema
```sql
CREATE TABLE migration_events (
event_id UUID PRIMARY KEY,
aggregate_id UUID NOT NULL,
event_type VARCHAR(100) NOT NULL,
event_data JSONB NOT NULL,
event_version INTEGER NOT NULL,
occurred_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
processed_at TIMESTAMP WITH TIME ZONE
);
```
#### Migration Event Handler
```python
class MigrationEventHandler:
def __init__(self, old_store, new_store):
self.old_store = old_store
self.new_store = new_store
self.event_log = []
def handle_update(self, entity_id, old_data, new_data):
# Log the change as an event
event = MigrationEvent(
entity_id=entity_id,
event_type="entity_migrated",
old_data=old_data,
new_data=new_data,
timestamp=datetime.now()
)
self.event_log.append(event)
# Apply to new store
success = self.new_store.update(entity_id, new_data)
if not success:
# Mark for retry
event.status = "failed"
self.schedule_retry(event)
return success
def replay_events(self, from_timestamp=None):
"""Replay events for reconciliation"""
events = self.get_events_since(from_timestamp)
for event in events:
self.apply_event(event)
```
## Service Migration Patterns
### 1. Strangler Fig Pattern
**Use Case:** Legacy system replacement
**Complexity:** Medium-High
**Risk Level:** Medium
#### Description
Gradually replace legacy functionality by intercepting calls and routing them to new services, eventually "strangling" the legacy system.
#### Implementation Architecture
```yaml
# API Gateway Configuration
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: user-service-migration
spec:
http:
- match:
- headers:
migration-flag:
exact: "new"
route:
- destination:
host: user-service-v2
- route:
- destination:
host: user-service-v1
```
#### Strangler Proxy Implementation
```python
class StranglerProxy:
def __init__(self):
self.legacy_service = LegacyUserService()
self.new_service = NewUserService()
self.feature_flags = FeatureFlagService()
def handle_request(self, request):
route = self.determine_route(request)
if route == "new":
return self.handle_with_new_service(request)
elif route == "both":
return self.handle_with_both_services(request)
else:
return self.handle_with_legacy_service(request)
def determine_route(self, request):
user_id = request.get('user_id')
if self.feature_flags.is_enabled("new_user_service", user_id):
if self.feature_flags.is_enabled("dual_write", user_id):
return "both"
else:
return "new"
else:
return "legacy"
```
### 2. Parallel Run Pattern
**Use Case:** Risk mitigation for critical services
**Complexity:** Medium
**Risk Level:** Low-Medium
#### Description
Run both old and new services simultaneously, comparing outputs to validate correctness before switching traffic.
#### Implementation
```python
class ParallelRunManager:
def __init__(self):
self.primary_service = PrimaryService()
self.candidate_service = CandidateService()
self.comparator = ResponseComparator()
self.metrics = MetricsCollector()
async def parallel_execute(self, request):
# Execute both services concurrently
primary_task = asyncio.create_task(
self.primary_service.process(request)
)
candidate_task = asyncio.create_task(
self.candidate_service.process(request)
)
# Always wait for primary
primary_result = await primary_task
try:
# Wait for candidate with timeout
candidate_result = await asyncio.wait_for(
candidate_task, timeout=5.0
)
# Compare results
comparison = self.comparator.compare(
primary_result, candidate_result
)
# Record metrics
self.metrics.record_comparison(comparison)
except asyncio.TimeoutError:
self.metrics.record_timeout("candidate")
except Exception as e:
self.metrics.record_error("candidate", str(e))
# Always return primary result
return primary_result
```
### 3. Blue-Green Deployment Pattern
**Use Case:** Zero-downtime service updates
**Complexity:** Low-Medium
**Risk Level:** Low
#### Description
Maintain two identical production environments (blue and green), switching traffic between them for deployments.
#### Kubernetes Implementation
```yaml
# Blue Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-blue
labels:
version: blue
spec:
replicas: 3
selector:
matchLabels:
app: myapp
version: blue
template:
metadata:
labels:
app: myapp
version: blue
spec:
containers:
- name: app
image: myapp:v1.0.0
---
# Green Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-green
labels:
version: green
spec:
replicas: 3
selector:
matchLabels:
app: myapp
version: green
template:
metadata:
labels:
app: myapp
version: green
spec:
containers:
- name: app
image: myapp:v2.0.0
---
# Service (switches between blue and green)
apiVersion: v1
kind: Service
metadata:
name: app-service
spec:
selector:
app: myapp
version: blue # Change to green for deployment
ports:
- port: 80
targetPort: 8080
```
## Infrastructure Migration Patterns
### 1. Lift and Shift Pattern
**Use Case:** Quick cloud migration with minimal changes
**Complexity:** Low-Medium
**Risk Level:** Low
#### Description
Migrate applications to cloud infrastructure with minimal or no code changes, focusing on infrastructure compatibility.
#### Migration Checklist
```yaml
Pre-Migration Assessment:
- inventory_current_infrastructure:
- servers_and_specifications
- network_configuration
- storage_requirements
- security_configurations
- identify_dependencies:
- database_connections
- external_service_integrations
- file_system_dependencies
- assess_compatibility:
- operating_system_versions
- runtime_dependencies
- license_requirements
Migration Execution:
- provision_target_infrastructure:
- compute_instances
- storage_volumes
- network_configuration
- security_groups
- migrate_data:
- database_backup_restore
- file_system_replication
- configuration_files
- update_configurations:
- connection_strings
- environment_variables
- dns_records
- validate_functionality:
- application_health_checks
- end_to_end_testing
- performance_validation
```
### 2. Hybrid Cloud Migration
**Use Case:** Gradual cloud adoption with on-premises integration
**Complexity:** High
**Risk Level:** Medium-High
#### Description
Maintain some components on-premises while migrating others to cloud, requiring secure connectivity and data synchronization.
#### Network Architecture
```hcl
# Terraform configuration for hybrid connectivity
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
}
resource "aws_vpn_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "hybrid-vpn-gateway"
}
}
resource "aws_customer_gateway" "main" {
bgp_asn = 65000
ip_address = var.on_premises_public_ip
type = "ipsec.1"
tags = {
Name = "on-premises-gateway"
}
}
resource "aws_vpn_connection" "main" {
vpn_gateway_id = aws_vpn_gateway.main.id
customer_gateway_id = aws_customer_gateway.main.id
type = "ipsec.1"
static_routes_only = true
}
```
#### Data Synchronization Pattern
```python
class HybridDataSync:
def __init__(self):
self.on_prem_db = OnPremiseDatabase()
self.cloud_db = CloudDatabase()
self.sync_log = SyncLogManager()
async def bidirectional_sync(self):
"""Synchronize data between on-premises and cloud"""
# Get last sync timestamp
last_sync = self.sync_log.get_last_sync_time()
# Sync on-prem changes to cloud
on_prem_changes = self.on_prem_db.get_changes_since(last_sync)
for change in on_prem_changes:
await self.apply_change_to_cloud(change)
# Sync cloud changes to on-prem
cloud_changes = self.cloud_db.get_changes_since(last_sync)
for change in cloud_changes:
await self.apply_change_to_on_prem(change)
# Handle conflicts
conflicts = self.detect_conflicts(on_prem_changes, cloud_changes)
for conflict in conflicts:
await self.resolve_conflict(conflict)
# Update sync timestamp
self.sync_log.record_sync_completion()
async def apply_change_to_cloud(self, change):
"""Apply on-premises change to cloud database"""
try:
if change.operation == "INSERT":
await self.cloud_db.insert(change.table, change.data)
elif change.operation == "UPDATE":
await self.cloud_db.update(change.table, change.key, change.data)
elif change.operation == "DELETE":
await self.cloud_db.delete(change.table, change.key)
self.sync_log.record_success(change.id, "cloud")
except Exception as e:
self.sync_log.record_failure(change.id, "cloud", str(e))
raise
```
### 3. Multi-Cloud Migration
**Use Case:** Avoiding vendor lock-in or regulatory requirements
**Complexity:** Very High
**Risk Level:** High
#### Description
Distribute workloads across multiple cloud providers for resilience, compliance, or cost optimization.
#### Service Mesh Configuration
```yaml
# Istio configuration for multi-cloud service mesh
apiVersion: networking.istio.io/v1beta1
kind: ServiceEntry
metadata:
name: aws-service
spec:
hosts:
- aws-service.company.com
ports:
- number: 443
name: https
protocol: HTTPS
location: MESH_EXTERNAL
resolution: DNS
---
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: multi-cloud-routing
spec:
hosts:
- user-service
http:
- match:
- headers:
region:
exact: "us-east"
route:
- destination:
host: aws-service.company.com
weight: 100
- match:
- headers:
region:
exact: "eu-west"
route:
- destination:
host: gcp-service.company.com
weight: 100
- route: # Default routing
- destination:
host: user-service
subset: local
weight: 80
- destination:
host: aws-service.company.com
weight: 20
```
## Feature Flag Patterns
### 1. Progressive Rollout Pattern
**Use Case:** Gradual feature deployment with risk mitigation
**Implementation:**
```python
class ProgressiveRollout:
def __init__(self, feature_name):
self.feature_name = feature_name
self.rollout_percentage = 0
self.user_buckets = {}
def is_enabled_for_user(self, user_id):
# Consistent user bucketing
user_hash = hashlib.md5(f"{self.feature_name}:{user_id}".encode()).hexdigest()
bucket = int(user_hash, 16) % 100
return bucket < self.rollout_percentage
def increase_rollout(self, target_percentage, step_size=10):
"""Gradually increase rollout percentage"""
while self.rollout_percentage < target_percentage:
self.rollout_percentage = min(
self.rollout_percentage + step_size,
target_percentage
)
# Monitor metrics before next increase
yield self.rollout_percentage
time.sleep(300) # Wait 5 minutes between increases
```
### 2. Circuit Breaker Pattern
**Use Case:** Automatic fallback during migration issues
```python
class MigrationCircuitBreaker:
def __init__(self, failure_threshold=5, timeout=60):
self.failure_count = 0
self.failure_threshold = failure_threshold
self.timeout = timeout
self.last_failure_time = None
self.state = 'CLOSED' # CLOSED, OPEN, HALF_OPEN
def call_new_service(self, request):
if self.state == 'OPEN':
if self.should_attempt_reset():
self.state = 'HALF_OPEN'
else:
return self.fallback_to_legacy(request)
try:
response = self.new_service.process(request)
self.on_success()
return response
except Exception as e:
self.on_failure()
return self.fallback_to_legacy(request)
def on_success(self):
self.failure_count = 0
self.state = 'CLOSED'
def on_failure(self):
self.failure_count += 1
self.last_failure_time = time.time()
if self.failure_count >= self.failure_threshold:
self.state = 'OPEN'
def should_attempt_reset(self):
return (time.time() - self.last_failure_time) >= self.timeout
```
## Migration Anti-Patterns
### 1. Big Bang Migration (Anti-Pattern)
**Why to Avoid:**
- High risk of complete system failure
- Difficult to rollback
- Extended downtime
- All-or-nothing deployment
**Better Alternative:** Use incremental migration patterns like Strangler Fig or Parallel Run.
### 2. No Rollback Plan (Anti-Pattern)
**Why to Avoid:**
- Cannot recover from failures
- Increases business risk
- Panic-driven decisions during issues
**Better Alternative:** Always implement comprehensive rollback procedures before migration.
### 3. Insufficient Testing (Anti-Pattern)
**Why to Avoid:**
- Unknown compatibility issues
- Performance degradation
- Data corruption risks
**Better Alternative:** Implement comprehensive testing at each migration phase.
## Pattern Selection Matrix
| Migration Type | Complexity | Downtime Tolerance | Recommended Pattern |
|---------------|------------|-------------------|-------------------|
| Schema Change | Low | Zero | Expand-Contract |
| Schema Change | High | Zero | Parallel Schema |
| Service Replace | Medium | Zero | Strangler Fig |
| Service Update | Low | Zero | Blue-Green |
| Data Migration | High | Some | Event Sourcing |
| Infrastructure | Low | Some | Lift and Shift |
| Infrastructure | High | Zero | Hybrid Cloud |
## Success Metrics
### Technical Metrics
- Migration completion rate
- System availability during migration
- Performance impact (response time, throughput)
- Error rate changes
- Rollback execution time
### Business Metrics
- Customer impact score
- Revenue protection
- Time to value realization
- Stakeholder satisfaction
### Operational Metrics
- Team efficiency
- Knowledge transfer effectiveness
- Post-migration support requirements
- Documentation completeness
## Lessons Learned
### Common Pitfalls
1. **Underestimating data dependencies** - Always map all data relationships
2. **Insufficient monitoring** - Implement comprehensive observability before migration
3. **Poor communication** - Keep all stakeholders informed throughout the process
4. **Rushed timelines** - Allow adequate time for testing and validation
5. **Ignoring performance impact** - Benchmark before and after migration
### Best Practices
1. **Start with low-risk migrations** - Build confidence and experience
2. **Automate everything possible** - Reduce human error and increase repeatability
3. **Test rollback procedures** - Ensure you can recover from any failure
4. **Monitor continuously** - Use real-time dashboards and alerting
5. **Document everything** - Create comprehensive runbooks and documentation
This catalog serves as a reference for selecting appropriate migration patterns based on specific requirements, risk tolerance, and technical constraints.
FILE:references/zero_downtime_techniques.md
# Zero-Downtime Migration Techniques
## Overview
Zero-downtime migrations are critical for maintaining business continuity and user experience during system changes. This guide provides comprehensive techniques, patterns, and implementation strategies for achieving true zero-downtime migrations across different system components.
## Core Principles
### 1. Backward Compatibility
Every change must be backward compatible until all clients have migrated to the new version.
### 2. Incremental Changes
Break large changes into smaller, independent increments that can be deployed and validated separately.
### 3. Feature Flags
Use feature toggles to control the rollout of new functionality without code deployments.
### 4. Graceful Degradation
Ensure systems continue to function even when some components are unavailable or degraded.
## Database Zero-Downtime Techniques
### Schema Evolution Without Downtime
#### 1. Additive Changes Only
**Principle:** Only add new elements; never remove or modify existing ones directly.
```sql
-- ✅ Good: Additive change
ALTER TABLE users ADD COLUMN middle_name VARCHAR(50);
-- ❌ Bad: Breaking change
ALTER TABLE users DROP COLUMN email;
```
#### 2. Multi-Phase Schema Evolution
**Phase 1: Expand**
```sql
-- Add new column alongside existing one
ALTER TABLE users ADD COLUMN email_address VARCHAR(255);
-- Add index concurrently (PostgreSQL)
CREATE INDEX CONCURRENTLY idx_users_email_address ON users(email_address);
```
**Phase 2: Dual Write (Application Code)**
```python
class UserService:
def create_user(self, name, email):
# Write to both old and new columns
user = User(
name=name,
email=email, # Old column
email_address=email # New column
)
return user.save()
def update_email(self, user_id, new_email):
# Update both columns
user = User.objects.get(id=user_id)
user.email = new_email
user.email_address = new_email
user.save()
return user
```
**Phase 3: Backfill Data**
```sql
-- Backfill existing data (in batches)
UPDATE users
SET email_address = email
WHERE email_address IS NULL
AND id BETWEEN ? AND ?;
```
**Phase 4: Switch Reads**
```python
class UserService:
def get_user_email(self, user_id):
user = User.objects.get(id=user_id)
# Switch to reading from new column
return user.email_address or user.email
```
**Phase 5: Contract**
```sql
-- After validation, remove old column
ALTER TABLE users DROP COLUMN email;
-- Rename new column if needed
ALTER TABLE users RENAME COLUMN email_address TO email;
```
### 3. Online Schema Changes
#### PostgreSQL Techniques
```sql
-- Safe column addition
ALTER TABLE orders ADD COLUMN status_new VARCHAR(20) DEFAULT 'pending';
-- Safe index creation
CREATE INDEX CONCURRENTLY idx_orders_status_new ON orders(status_new);
-- Safe constraint addition (after data validation)
ALTER TABLE orders ADD CONSTRAINT check_status_new
CHECK (status_new IN ('pending', 'processing', 'completed', 'cancelled'));
```
#### MySQL Techniques
```sql
-- Use pt-online-schema-change for large tables
pt-online-schema-change \
--alter "ADD COLUMN status VARCHAR(20) DEFAULT 'pending'" \
--execute \
D=mydb,t=orders
-- Online DDL (MySQL 5.6+)
ALTER TABLE orders
ADD COLUMN priority INT DEFAULT 1,
ALGORITHM=INPLACE,
LOCK=NONE;
```
### 4. Data Migration Strategies
#### Chunked Data Migration
```python
class DataMigrator:
def __init__(self, source_table, target_table, chunk_size=1000):
self.source_table = source_table
self.target_table = target_table
self.chunk_size = chunk_size
def migrate_data(self):
last_id = 0
total_migrated = 0
while True:
# Get next chunk
chunk = self.get_chunk(last_id, self.chunk_size)
if not chunk:
break
# Transform and migrate chunk
for record in chunk:
transformed = self.transform_record(record)
self.insert_or_update(transformed)
last_id = chunk[-1]['id']
total_migrated += len(chunk)
# Brief pause to avoid overwhelming the database
time.sleep(0.1)
self.log_progress(total_migrated)
return total_migrated
def get_chunk(self, last_id, limit):
return db.execute(f"""
SELECT * FROM {self.source_table}
WHERE id > %s
ORDER BY id
LIMIT %s
""", (last_id, limit))
```
#### Change Data Capture (CDC)
```python
class CDCProcessor:
def __init__(self):
self.kafka_consumer = KafkaConsumer('db_changes')
self.target_db = TargetDatabase()
def process_changes(self):
for message in self.kafka_consumer:
change = json.loads(message.value)
if change['operation'] == 'INSERT':
self.handle_insert(change)
elif change['operation'] == 'UPDATE':
self.handle_update(change)
elif change['operation'] == 'DELETE':
self.handle_delete(change)
def handle_insert(self, change):
transformed_data = self.transform_data(change['after'])
self.target_db.insert(change['table'], transformed_data)
def handle_update(self, change):
key = change['key']
transformed_data = self.transform_data(change['after'])
self.target_db.update(change['table'], key, transformed_data)
```
## Application Zero-Downtime Techniques
### 1. Blue-Green Deployments
#### Infrastructure Setup
```yaml
# Blue Environment (Current Production)
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-blue
labels:
version: blue
app: myapp
spec:
replicas: 3
selector:
matchLabels:
app: myapp
version: blue
template:
metadata:
labels:
app: myapp
version: blue
spec:
containers:
- name: app
image: myapp:1.0.0
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 15
periodSeconds: 10
---
# Green Environment (New Version)
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-green
labels:
version: green
app: myapp
spec:
replicas: 3
selector:
matchLabels:
app: myapp
version: green
template:
metadata:
labels:
app: myapp
version: green
spec:
containers:
- name: app
image: myapp:2.0.0
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
```
#### Service Switching
```yaml
# Service (switches between blue and green)
apiVersion: v1
kind: Service
metadata:
name: app-service
spec:
selector:
app: myapp
version: blue # Switch to 'green' for deployment
ports:
- port: 80
targetPort: 8080
type: LoadBalancer
```
#### Automated Deployment Script
```bash
#!/bin/bash
# Blue-Green Deployment Script
NAMESPACE="production"
APP_NAME="myapp"
NEW_IMAGE="myapp:2.0.0"
# Determine current and target environments
CURRENT_VERSION=$(kubectl get service $APP_NAME-service -o jsonpath='{.spec.selector.version}')
if [ "$CURRENT_VERSION" = "blue" ]; then
TARGET_VERSION="green"
else
TARGET_VERSION="blue"
fi
echo "Current version: $CURRENT_VERSION"
echo "Target version: $TARGET_VERSION"
# Update target environment with new image
kubectl set image deployment/$APP_NAME-$TARGET_VERSION app=$NEW_IMAGE
# Wait for rollout to complete
kubectl rollout status deployment/$APP_NAME-$TARGET_VERSION --timeout=300s
# Run health checks
echo "Running health checks..."
TARGET_IP=$(kubectl get service $APP_NAME-$TARGET_VERSION -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
for i in {1..30}; do
if curl -f http://$TARGET_IP/health; then
echo "Health check passed"
break
fi
if [ $i -eq 30 ]; then
echo "Health check failed after 30 attempts"
exit 1
fi
sleep 2
done
# Switch traffic to new version
kubectl patch service $APP_NAME-service -p '{"spec":{"selector":{"version":"'$TARGET_VERSION'"}}}'
echo "Traffic switched to $TARGET_VERSION"
# Monitor for 5 minutes
echo "Monitoring new version..."
sleep 300
# Check if rollback is needed
ERROR_RATE=$(curl -s "http://monitoring.company.com/api/error_rate?service=$APP_NAME" | jq '.error_rate')
if (( $(echo "$ERROR_RATE > 0.05" | bc -l) )); then
echo "Error rate too high ($ERROR_RATE), rolling back..."
kubectl patch service $APP_NAME-service -p '{"spec":{"selector":{"version":"'$CURRENT_VERSION'"}}}'
exit 1
fi
echo "Deployment successful!"
```
### 2. Canary Deployments
#### Progressive Canary with Istio
```yaml
# Destination Rule
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: myapp-destination
spec:
host: myapp
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
---
# Virtual Service for Canary
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: myapp-canary
spec:
hosts:
- myapp
http:
- match:
- headers:
canary:
exact: "true"
route:
- destination:
host: myapp
subset: v2
- route:
- destination:
host: myapp
subset: v1
weight: 95
- destination:
host: myapp
subset: v2
weight: 5
```
#### Automated Canary Controller
```python
class CanaryController:
def __init__(self, istio_client, prometheus_client):
self.istio = istio_client
self.prometheus = prometheus_client
self.canary_weight = 5
self.max_weight = 100
self.weight_increment = 5
self.validation_window = 300 # 5 minutes
async def deploy_canary(self, app_name, new_version):
"""Deploy new version using canary strategy"""
# Start with small percentage
await self.update_traffic_split(app_name, self.canary_weight)
while self.canary_weight < self.max_weight:
# Monitor metrics for validation window
await asyncio.sleep(self.validation_window)
# Check canary health
if not await self.is_canary_healthy(app_name, new_version):
await self.rollback_canary(app_name)
raise Exception("Canary deployment failed health checks")
# Increase traffic to canary
self.canary_weight = min(
self.canary_weight + self.weight_increment,
self.max_weight
)
await self.update_traffic_split(app_name, self.canary_weight)
print(f"Canary traffic increased to {self.canary_weight}%")
print("Canary deployment completed successfully")
async def is_canary_healthy(self, app_name, version):
"""Check if canary version is healthy"""
# Check error rate
error_rate = await self.prometheus.query(
f'rate(http_requests_total{{app="{app_name}", version="{version}", status=~"5.."}}'
f'[5m]) / rate(http_requests_total{{app="{app_name}", version="{version}"}}[5m])'
)
if error_rate > 0.05: # 5% error rate threshold
return False
# Check response time
p95_latency = await self.prometheus.query(
f'histogram_quantile(0.95, rate(http_request_duration_seconds_bucket'
f'{{app="{app_name}", version="{version}"}}[5m]))'
)
if p95_latency > 2.0: # 2 second p95 threshold
return False
return True
async def update_traffic_split(self, app_name, canary_weight):
"""Update Istio virtual service with new traffic split"""
stable_weight = 100 - canary_weight
virtual_service = {
"apiVersion": "networking.istio.io/v1beta1",
"kind": "VirtualService",
"metadata": {"name": f"{app_name}-canary"},
"spec": {
"hosts": [app_name],
"http": [{
"route": [
{
"destination": {"host": app_name, "subset": "stable"},
"weight": stable_weight
},
{
"destination": {"host": app_name, "subset": "canary"},
"weight": canary_weight
}
]
}]
}
}
await self.istio.apply_virtual_service(virtual_service)
```
### 3. Rolling Updates
#### Kubernetes Rolling Update Strategy
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: rolling-update-app
spec:
replicas: 10
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 2 # Can have 2 extra pods during update
maxUnavailable: 1 # At most 1 pod can be unavailable
selector:
matchLabels:
app: rolling-update-app
template:
metadata:
labels:
app: rolling-update-app
spec:
containers:
- name: app
image: myapp:2.0.0
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 2
timeoutSeconds: 1
successThreshold: 1
failureThreshold: 3
livenessProbe:
httpGet:
path: /live
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
```
#### Custom Rolling Update Controller
```python
class RollingUpdateController:
def __init__(self, k8s_client):
self.k8s = k8s_client
self.max_surge = 2
self.max_unavailable = 1
async def rolling_update(self, deployment_name, new_image):
"""Perform rolling update with custom logic"""
deployment = await self.k8s.get_deployment(deployment_name)
total_replicas = deployment.spec.replicas
# Calculate batch size
batch_size = min(self.max_surge, total_replicas // 5) # Update 20% at a time
updated_pods = []
for i in range(0, total_replicas, batch_size):
batch_end = min(i + batch_size, total_replicas)
# Update batch of pods
for pod_index in range(i, batch_end):
old_pod = await self.get_pod_by_index(deployment_name, pod_index)
# Create new pod with new image
new_pod = await self.create_updated_pod(old_pod, new_image)
# Wait for new pod to be ready
await self.wait_for_pod_ready(new_pod.metadata.name)
# Remove old pod
await self.k8s.delete_pod(old_pod.metadata.name)
updated_pods.append(new_pod)
# Brief pause between pod updates
await asyncio.sleep(2)
# Validate batch health before continuing
if not await self.validate_batch_health(updated_pods[-batch_size:]):
# Rollback batch
await self.rollback_batch(updated_pods[-batch_size:])
raise Exception("Rolling update failed validation")
print(f"Updated {batch_end}/{total_replicas} pods")
print("Rolling update completed successfully")
```
## Load Balancer and Traffic Management
### 1. Weighted Routing
#### NGINX Configuration
```nginx
upstream backend {
# Old version - 80% traffic
server old-app-1:8080 weight=4;
server old-app-2:8080 weight=4;
# New version - 20% traffic
server new-app-1:8080 weight=1;
server new-app-2:8080 weight=1;
}
server {
listen 80;
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Health check headers
proxy_set_header X-Health-Check-Timeout 5s;
}
}
```
#### HAProxy Configuration
```haproxy
backend app_servers
balance roundrobin
option httpchk GET /health
# Old version servers
server old-app-1 old-app-1:8080 check weight 80
server old-app-2 old-app-2:8080 check weight 80
# New version servers
server new-app-1 new-app-1:8080 check weight 20
server new-app-2 new-app-2:8080 check weight 20
frontend app_frontend
bind *:80
default_backend app_servers
# Custom health check endpoint
acl health_check path_beg /health
http-request return status 200 content-type text/plain string "OK" if health_check
```
### 2. Circuit Breaker Implementation
```python
class CircuitBreaker:
def __init__(self, failure_threshold=5, recovery_timeout=60, expected_exception=Exception):
self.failure_threshold = failure_threshold
self.recovery_timeout = recovery_timeout
self.expected_exception = expected_exception
self.failure_count = 0
self.last_failure_time = None
self.state = 'CLOSED' # CLOSED, OPEN, HALF_OPEN
def call(self, func, *args, **kwargs):
"""Execute function with circuit breaker protection"""
if self.state == 'OPEN':
if self._should_attempt_reset():
self.state = 'HALF_OPEN'
else:
raise CircuitBreakerOpenException("Circuit breaker is OPEN")
try:
result = func(*args, **kwargs)
self._on_success()
return result
except self.expected_exception as e:
self._on_failure()
raise
def _should_attempt_reset(self):
return (
self.last_failure_time and
time.time() - self.last_failure_time >= self.recovery_timeout
)
def _on_success(self):
self.failure_count = 0
self.state = 'CLOSED'
def _on_failure(self):
self.failure_count += 1
self.last_failure_time = time.time()
if self.failure_count >= self.failure_threshold:
self.state = 'OPEN'
# Usage with service migration
@CircuitBreaker(failure_threshold=3, recovery_timeout=30)
def call_new_service(request):
return new_service.process(request)
def handle_request(request):
try:
return call_new_service(request)
except CircuitBreakerOpenException:
# Fallback to old service
return old_service.process(request)
```
## Monitoring and Validation
### 1. Health Check Implementation
```python
class HealthChecker:
def __init__(self):
self.checks = []
def add_check(self, name, check_func, timeout=5):
self.checks.append({
'name': name,
'func': check_func,
'timeout': timeout
})
async def run_checks(self):
"""Run all health checks and return status"""
results = {}
overall_status = 'healthy'
for check in self.checks:
try:
result = await asyncio.wait_for(
check['func'](),
timeout=check['timeout']
)
results[check['name']] = {
'status': 'healthy',
'result': result
}
except asyncio.TimeoutError:
results[check['name']] = {
'status': 'unhealthy',
'error': 'timeout'
}
overall_status = 'unhealthy'
except Exception as e:
results[check['name']] = {
'status': 'unhealthy',
'error': str(e)
}
overall_status = 'unhealthy'
return {
'status': overall_status,
'checks': results,
'timestamp': datetime.utcnow().isoformat()
}
# Example health checks
health_checker = HealthChecker()
async def database_check():
"""Check database connectivity"""
result = await db.execute("SELECT 1")
return result is not None
async def external_api_check():
"""Check external API availability"""
response = await http_client.get("https://api.example.com/health")
return response.status_code == 200
async def memory_check():
"""Check memory usage"""
memory_usage = psutil.virtual_memory().percent
if memory_usage > 90:
raise Exception(f"Memory usage too high: {memory_usage}%")
return f"Memory usage: {memory_usage}%"
health_checker.add_check("database", database_check)
health_checker.add_check("external_api", external_api_check)
health_checker.add_check("memory", memory_check)
```
### 2. Readiness vs Liveness Probes
```yaml
# Kubernetes Pod with proper health checks
apiVersion: v1
kind: Pod
metadata:
name: app-pod
spec:
containers:
- name: app
image: myapp:2.0.0
ports:
- containerPort: 8080
# Readiness probe - determines if pod should receive traffic
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 3
timeoutSeconds: 2
successThreshold: 1
failureThreshold: 3
# Liveness probe - determines if pod should be restarted
livenessProbe:
httpGet:
path: /live
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
successThreshold: 1
failureThreshold: 3
# Startup probe - gives app time to start before other probes
startupProbe:
httpGet:
path: /startup
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 3
successThreshold: 1
failureThreshold: 30 # Allow up to 150 seconds for startup
```
### 3. Metrics and Alerting
```python
class MigrationMetrics:
def __init__(self, prometheus_client):
self.prometheus = prometheus_client
# Define custom metrics
self.migration_progress = Counter(
'migration_progress_total',
'Total migration operations completed',
['operation', 'status']
)
self.migration_duration = Histogram(
'migration_operation_duration_seconds',
'Time spent on migration operations',
['operation']
)
self.system_health = Gauge(
'system_health_score',
'Overall system health score (0-1)',
['component']
)
self.traffic_split = Gauge(
'traffic_split_percentage',
'Percentage of traffic going to each version',
['version']
)
def record_migration_step(self, operation, status, duration=None):
"""Record completion of a migration step"""
self.migration_progress.labels(operation=operation, status=status).inc()
if duration:
self.migration_duration.labels(operation=operation).observe(duration)
def update_health_score(self, component, score):
"""Update health score for a component"""
self.system_health.labels(component=component).set(score)
def update_traffic_split(self, version_weights):
"""Update traffic split metrics"""
for version, weight in version_weights.items():
self.traffic_split.labels(version=version).set(weight)
# Usage in migration
metrics = MigrationMetrics(prometheus_client)
def perform_migration_step(operation):
start_time = time.time()
try:
# Perform migration operation
result = execute_migration_operation(operation)
# Record success
duration = time.time() - start_time
metrics.record_migration_step(operation, 'success', duration)
return result
except Exception as e:
# Record failure
duration = time.time() - start_time
metrics.record_migration_step(operation, 'failure', duration)
raise
```
## Rollback Strategies
### 1. Immediate Rollback Triggers
```python
class AutoRollbackSystem:
def __init__(self, metrics_client, deployment_client):
self.metrics = metrics_client
self.deployment = deployment_client
self.rollback_triggers = {
'error_rate_spike': {
'threshold': 0.05, # 5% error rate
'window': 300, # 5 minutes
'auto_rollback': True
},
'latency_increase': {
'threshold': 2.0, # 2x baseline latency
'window': 600, # 10 minutes
'auto_rollback': False # Manual confirmation required
},
'availability_drop': {
'threshold': 0.95, # Below 95% availability
'window': 120, # 2 minutes
'auto_rollback': True
}
}
async def monitor_and_rollback(self, deployment_name):
"""Monitor deployment and trigger rollback if needed"""
while True:
for trigger_name, config in self.rollback_triggers.items():
if await self.check_trigger(trigger_name, config):
if config['auto_rollback']:
await self.execute_rollback(deployment_name, trigger_name)
else:
await self.alert_for_manual_rollback(deployment_name, trigger_name)
await asyncio.sleep(30) # Check every 30 seconds
async def check_trigger(self, trigger_name, config):
"""Check if rollback trigger condition is met"""
current_value = await self.metrics.get_current_value(trigger_name)
baseline_value = await self.metrics.get_baseline_value(trigger_name)
if trigger_name == 'error_rate_spike':
return current_value > config['threshold']
elif trigger_name == 'latency_increase':
return current_value > baseline_value * config['threshold']
elif trigger_name == 'availability_drop':
return current_value < config['threshold']
return False
async def execute_rollback(self, deployment_name, reason):
"""Execute automatic rollback"""
print(f"Executing automatic rollback for {deployment_name}. Reason: {reason}")
# Get previous revision
previous_revision = await self.deployment.get_previous_revision(deployment_name)
# Perform rollback
await self.deployment.rollback_to_revision(deployment_name, previous_revision)
# Notify stakeholders
await self.notify_rollback_executed(deployment_name, reason)
```
### 2. Data Rollback Strategies
```sql
-- Point-in-time recovery setup
-- Create restore point before migration
SELECT pg_create_restore_point('pre_migration_' || to_char(now(), 'YYYYMMDD_HH24MISS'));
-- Rollback using point-in-time recovery
-- (This would be executed on a separate recovery instance)
-- recovery.conf:
-- recovery_target_name = 'pre_migration_20240101_120000'
-- recovery_target_action = 'promote'
```
```python
class DataRollbackManager:
def __init__(self, database_client, backup_service):
self.db = database_client
self.backup = backup_service
async def create_rollback_point(self, migration_id):
"""Create a rollback point before migration"""
rollback_point = {
'migration_id': migration_id,
'timestamp': datetime.utcnow(),
'backup_location': None,
'schema_snapshot': None
}
# Create database backup
backup_path = await self.backup.create_backup(
f"pre_migration_{migration_id}_{int(time.time())}"
)
rollback_point['backup_location'] = backup_path
# Capture schema snapshot
schema_snapshot = await self.capture_schema_snapshot()
rollback_point['schema_snapshot'] = schema_snapshot
# Store rollback point metadata
await self.store_rollback_metadata(rollback_point)
return rollback_point
async def execute_rollback(self, migration_id):
"""Execute data rollback to specified point"""
rollback_point = await self.get_rollback_metadata(migration_id)
if not rollback_point:
raise Exception(f"No rollback point found for migration {migration_id}")
# Stop application traffic
await self.stop_application_traffic()
try:
# Restore from backup
await self.backup.restore_from_backup(
rollback_point['backup_location']
)
# Validate data integrity
await self.validate_data_integrity(
rollback_point['schema_snapshot']
)
# Update application configuration
await self.update_application_config(rollback_point)
# Resume application traffic
await self.resume_application_traffic()
print(f"Data rollback completed successfully for migration {migration_id}")
except Exception as e:
# If rollback fails, we have a serious problem
await self.escalate_rollback_failure(migration_id, str(e))
raise
```
## Best Practices Summary
### 1. Pre-Migration Checklist
- [ ] Comprehensive backup strategy in place
- [ ] Rollback procedures tested in staging
- [ ] Monitoring and alerting configured
- [ ] Health checks implemented
- [ ] Feature flags configured
- [ ] Team communication plan established
- [ ] Load balancer configuration prepared
- [ ] Database connection pooling optimized
### 2. During Migration
- [ ] Monitor key metrics continuously
- [ ] Validate each phase before proceeding
- [ ] Maintain detailed logs of all actions
- [ ] Keep stakeholders informed of progress
- [ ] Have rollback trigger ready
- [ ] Monitor user experience metrics
- [ ] Watch for performance degradation
- [ ] Validate data consistency
### 3. Post-Migration
- [ ] Continue monitoring for 24-48 hours
- [ ] Validate all business processes
- [ ] Update documentation
- [ ] Conduct post-migration retrospective
- [ ] Archive migration artifacts
- [ ] Update disaster recovery procedures
- [ ] Plan for legacy system decommissioning
### 4. Common Pitfalls to Avoid
- Don't skip testing rollback procedures
- Don't ignore performance impact
- Don't rush through validation phases
- Don't forget to communicate with stakeholders
- Don't assume health checks are sufficient
- Don't neglect data consistency validation
- Don't underestimate time requirements
- Don't overlook dependency impacts
This comprehensive guide provides the foundation for implementing zero-downtime migrations across various system components while maintaining high availability and data integrity.
FILE:scripts/compatibility_checker.py
#!/usr/bin/env python3
"""
Compatibility Checker - Analyze schema and API compatibility between versions
This tool analyzes schema and API changes between versions and identifies backward
compatibility issues including breaking changes, data type mismatches, missing fields,
constraint violations, and generates migration scripts suggestions.
Author: Migration Architect Skill
Version: 1.0.0
License: MIT
"""
import json
import argparse
import sys
import re
import datetime
from typing import Dict, List, Any, Optional, Tuple, Set
from dataclasses import dataclass, asdict
from enum import Enum
class ChangeType(Enum):
"""Types of changes detected"""
BREAKING = "breaking"
POTENTIALLY_BREAKING = "potentially_breaking"
NON_BREAKING = "non_breaking"
ADDITIVE = "additive"
class CompatibilityLevel(Enum):
"""Compatibility assessment levels"""
FULLY_COMPATIBLE = "fully_compatible"
BACKWARD_COMPATIBLE = "backward_compatible"
POTENTIALLY_INCOMPATIBLE = "potentially_incompatible"
BREAKING_CHANGES = "breaking_changes"
@dataclass
class CompatibilityIssue:
"""Individual compatibility issue"""
type: str
severity: str
description: str
field_path: str
old_value: Any
new_value: Any
impact: str
suggested_migration: str
affected_operations: List[str]
@dataclass
class MigrationScript:
"""Migration script suggestion"""
script_type: str # sql, api, config
description: str
script_content: str
rollback_script: str
dependencies: List[str]
validation_query: str
@dataclass
class CompatibilityReport:
"""Complete compatibility analysis report"""
schema_before: str
schema_after: str
analysis_date: str
overall_compatibility: str
breaking_changes_count: int
potentially_breaking_count: int
non_breaking_changes_count: int
additive_changes_count: int
issues: List[CompatibilityIssue]
migration_scripts: List[MigrationScript]
risk_assessment: Dict[str, Any]
recommendations: List[str]
class SchemaCompatibilityChecker:
"""Main schema compatibility checker class"""
def __init__(self):
self.type_compatibility_matrix = self._build_type_compatibility_matrix()
self.constraint_implications = self._build_constraint_implications()
def _build_type_compatibility_matrix(self) -> Dict[str, Dict[str, str]]:
"""Build data type compatibility matrix"""
return {
# SQL data types compatibility
"varchar": {
"text": "compatible",
"char": "potentially_breaking", # length might be different
"nvarchar": "compatible",
"int": "breaking",
"bigint": "breaking",
"decimal": "breaking",
"datetime": "breaking",
"boolean": "breaking"
},
"int": {
"bigint": "compatible",
"smallint": "potentially_breaking", # range reduction
"decimal": "compatible",
"float": "potentially_breaking", # precision loss
"varchar": "breaking",
"boolean": "breaking"
},
"bigint": {
"int": "potentially_breaking", # range reduction
"decimal": "compatible",
"varchar": "breaking",
"boolean": "breaking"
},
"decimal": {
"float": "potentially_breaking", # precision loss
"int": "potentially_breaking", # precision loss
"bigint": "potentially_breaking", # precision loss
"varchar": "breaking",
"boolean": "breaking"
},
"datetime": {
"timestamp": "compatible",
"date": "potentially_breaking", # time component lost
"varchar": "breaking",
"int": "breaking"
},
"boolean": {
"tinyint": "compatible",
"varchar": "breaking",
"int": "breaking"
},
# JSON/API field types
"string": {
"number": "breaking",
"boolean": "breaking",
"array": "breaking",
"object": "breaking",
"null": "potentially_breaking"
},
"number": {
"string": "breaking",
"boolean": "breaking",
"array": "breaking",
"object": "breaking",
"null": "potentially_breaking"
},
"boolean": {
"string": "breaking",
"number": "breaking",
"array": "breaking",
"object": "breaking",
"null": "potentially_breaking"
},
"array": {
"string": "breaking",
"number": "breaking",
"boolean": "breaking",
"object": "breaking",
"null": "potentially_breaking"
},
"object": {
"string": "breaking",
"number": "breaking",
"boolean": "breaking",
"array": "breaking",
"null": "potentially_breaking"
}
}
def _build_constraint_implications(self) -> Dict[str, Dict[str, str]]:
"""Build constraint change implications"""
return {
"required": {
"added": "breaking", # Previously optional field now required
"removed": "non_breaking" # Previously required field now optional
},
"not_null": {
"added": "breaking", # Previously nullable now NOT NULL
"removed": "non_breaking" # Previously NOT NULL now nullable
},
"unique": {
"added": "potentially_breaking", # May fail if duplicates exist
"removed": "non_breaking" # No longer enforcing uniqueness
},
"primary_key": {
"added": "breaking", # Major structural change
"removed": "breaking", # Major structural change
"modified": "breaking" # Primary key change is always breaking
},
"foreign_key": {
"added": "potentially_breaking", # May fail if referential integrity violated
"removed": "potentially_breaking", # May allow orphaned records
"modified": "breaking" # Reference change is breaking
},
"check": {
"added": "potentially_breaking", # May fail if existing data violates check
"removed": "non_breaking", # No longer enforcing check
"modified": "potentially_breaking" # Different validation rules
},
"index": {
"added": "non_breaking", # Performance improvement
"removed": "non_breaking", # Performance impact only
"modified": "non_breaking" # Performance impact only
}
}
def analyze_database_schema(self, before_schema: Dict[str, Any],
after_schema: Dict[str, Any]) -> CompatibilityReport:
"""Analyze database schema compatibility"""
issues = []
migration_scripts = []
before_tables = before_schema.get("tables", {})
after_tables = after_schema.get("tables", {})
# Check for removed tables
for table_name in before_tables:
if table_name not in after_tables:
issues.append(CompatibilityIssue(
type="table_removed",
severity="breaking",
description=f"Table '{table_name}' has been removed",
field_path=f"tables.{table_name}",
old_value=before_tables[table_name],
new_value=None,
impact="All operations on this table will fail",
suggested_migration=f"CREATE VIEW {table_name} AS SELECT * FROM replacement_table;",
affected_operations=["SELECT", "INSERT", "UPDATE", "DELETE"]
))
# Check for added tables
for table_name in after_tables:
if table_name not in before_tables:
migration_scripts.append(MigrationScript(
script_type="sql",
description=f"Create new table {table_name}",
script_content=self._generate_create_table_sql(table_name, after_tables[table_name]),
rollback_script=f"DROP TABLE IF EXISTS {table_name};",
dependencies=[],
validation_query=f"SELECT COUNT(*) FROM information_schema.tables WHERE table_name = '{table_name}';"
))
# Check for modified tables
for table_name in set(before_tables.keys()) & set(after_tables.keys()):
table_issues, table_scripts = self._analyze_table_changes(
table_name, before_tables[table_name], after_tables[table_name]
)
issues.extend(table_issues)
migration_scripts.extend(table_scripts)
return self._build_compatibility_report(
before_schema, after_schema, issues, migration_scripts
)
def analyze_api_schema(self, before_schema: Dict[str, Any],
after_schema: Dict[str, Any]) -> CompatibilityReport:
"""Analyze REST API schema compatibility"""
issues = []
migration_scripts = []
# Analyze endpoints
before_paths = before_schema.get("paths", {})
after_paths = after_schema.get("paths", {})
# Check for removed endpoints
for path in before_paths:
if path not in after_paths:
for method in before_paths[path]:
issues.append(CompatibilityIssue(
type="endpoint_removed",
severity="breaking",
description=f"Endpoint {method.upper()} {path} has been removed",
field_path=f"paths.{path}.{method}",
old_value=before_paths[path][method],
new_value=None,
impact="Client requests to this endpoint will fail with 404",
suggested_migration=f"Implement redirect to replacement endpoint or maintain backward compatibility stub",
affected_operations=[f"{method.upper()} {path}"]
))
# Check for modified endpoints
for path in set(before_paths.keys()) & set(after_paths.keys()):
path_issues, path_scripts = self._analyze_endpoint_changes(
path, before_paths[path], after_paths[path]
)
issues.extend(path_issues)
migration_scripts.extend(path_scripts)
# Analyze data models
before_components = before_schema.get("components", {}).get("schemas", {})
after_components = after_schema.get("components", {}).get("schemas", {})
for model_name in set(before_components.keys()) & set(after_components.keys()):
model_issues, model_scripts = self._analyze_model_changes(
model_name, before_components[model_name], after_components[model_name]
)
issues.extend(model_issues)
migration_scripts.extend(model_scripts)
return self._build_compatibility_report(
before_schema, after_schema, issues, migration_scripts
)
def _analyze_table_changes(self, table_name: str, before_table: Dict[str, Any],
after_table: Dict[str, Any]) -> Tuple[List[CompatibilityIssue], List[MigrationScript]]:
"""Analyze changes to a specific table"""
issues = []
scripts = []
before_columns = before_table.get("columns", {})
after_columns = after_table.get("columns", {})
# Check for removed columns
for col_name in before_columns:
if col_name not in after_columns:
issues.append(CompatibilityIssue(
type="column_removed",
severity="breaking",
description=f"Column '{col_name}' removed from table '{table_name}'",
field_path=f"tables.{table_name}.columns.{col_name}",
old_value=before_columns[col_name],
new_value=None,
impact="SELECT statements including this column will fail",
suggested_migration=f"ALTER TABLE {table_name} ADD COLUMN {col_name}_deprecated AS computed_value;",
affected_operations=["SELECT", "INSERT", "UPDATE"]
))
# Check for added columns
for col_name in after_columns:
if col_name not in before_columns:
col_def = after_columns[col_name]
is_required = col_def.get("nullable", True) == False and col_def.get("default") is None
if is_required:
issues.append(CompatibilityIssue(
type="required_column_added",
severity="breaking",
description=f"Required column '{col_name}' added to table '{table_name}'",
field_path=f"tables.{table_name}.columns.{col_name}",
old_value=None,
new_value=col_def,
impact="INSERT statements without this column will fail",
suggested_migration=f"Add default value or make column nullable initially",
affected_operations=["INSERT"]
))
scripts.append(MigrationScript(
script_type="sql",
description=f"Add column {col_name} to table {table_name}",
script_content=f"ALTER TABLE {table_name} ADD COLUMN {self._generate_column_definition(col_name, col_def)};",
rollback_script=f"ALTER TABLE {table_name} DROP COLUMN {col_name};",
dependencies=[],
validation_query=f"SELECT COUNT(*) FROM information_schema.columns WHERE table_name = '{table_name}' AND column_name = '{col_name}';"
))
# Check for modified columns
for col_name in set(before_columns.keys()) & set(after_columns.keys()):
col_issues, col_scripts = self._analyze_column_changes(
table_name, col_name, before_columns[col_name], after_columns[col_name]
)
issues.extend(col_issues)
scripts.extend(col_scripts)
# Check constraint changes
before_constraints = before_table.get("constraints", {})
after_constraints = after_table.get("constraints", {})
constraint_issues, constraint_scripts = self._analyze_constraint_changes(
table_name, before_constraints, after_constraints
)
issues.extend(constraint_issues)
scripts.extend(constraint_scripts)
return issues, scripts
def _analyze_column_changes(self, table_name: str, col_name: str,
before_col: Dict[str, Any], after_col: Dict[str, Any]) -> Tuple[List[CompatibilityIssue], List[MigrationScript]]:
"""Analyze changes to a specific column"""
issues = []
scripts = []
# Check data type changes
before_type = before_col.get("type", "").lower()
after_type = after_col.get("type", "").lower()
if before_type != after_type:
compatibility = self.type_compatibility_matrix.get(before_type, {}).get(after_type, "breaking")
if compatibility == "breaking":
issues.append(CompatibilityIssue(
type="incompatible_type_change",
severity="breaking",
description=f"Column '{col_name}' type changed from {before_type} to {after_type}",
field_path=f"tables.{table_name}.columns.{col_name}.type",
old_value=before_type,
new_value=after_type,
impact="Data conversion may fail or lose precision",
suggested_migration=f"Add conversion logic and validate data integrity",
affected_operations=["SELECT", "INSERT", "UPDATE", "WHERE clauses"]
))
scripts.append(MigrationScript(
script_type="sql",
description=f"Convert column {col_name} from {before_type} to {after_type}",
script_content=f"ALTER TABLE {table_name} ALTER COLUMN {col_name} TYPE {after_type} USING {col_name}::{after_type};",
rollback_script=f"ALTER TABLE {table_name} ALTER COLUMN {col_name} TYPE {before_type};",
dependencies=[f"backup_{table_name}"],
validation_query=f"SELECT COUNT(*) FROM {table_name} WHERE {col_name} IS NOT NULL;"
))
elif compatibility == "potentially_breaking":
issues.append(CompatibilityIssue(
type="risky_type_change",
severity="potentially_breaking",
description=f"Column '{col_name}' type changed from {before_type} to {after_type} - may lose data",
field_path=f"tables.{table_name}.columns.{col_name}.type",
old_value=before_type,
new_value=after_type,
impact="Potential data loss or precision reduction",
suggested_migration=f"Validate all existing data can be converted safely",
affected_operations=["Data integrity"]
))
# Check nullability changes
before_nullable = before_col.get("nullable", True)
after_nullable = after_col.get("nullable", True)
if before_nullable != after_nullable:
if before_nullable and not after_nullable: # null -> not null
issues.append(CompatibilityIssue(
type="nullability_restriction",
severity="breaking",
description=f"Column '{col_name}' changed from nullable to NOT NULL",
field_path=f"tables.{table_name}.columns.{col_name}.nullable",
old_value=before_nullable,
new_value=after_nullable,
impact="Existing NULL values will cause constraint violations",
suggested_migration=f"Update NULL values to valid defaults before applying NOT NULL constraint",
affected_operations=["INSERT", "UPDATE"]
))
scripts.append(MigrationScript(
script_type="sql",
description=f"Make column {col_name} NOT NULL",
script_content=f"""
-- Update NULL values first
UPDATE {table_name} SET {col_name} = 'DEFAULT_VALUE' WHERE {col_name} IS NULL;
-- Add NOT NULL constraint
ALTER TABLE {table_name} ALTER COLUMN {col_name} SET NOT NULL;
""",
rollback_script=f"ALTER TABLE {table_name} ALTER COLUMN {col_name} DROP NOT NULL;",
dependencies=[],
validation_query=f"SELECT COUNT(*) FROM {table_name} WHERE {col_name} IS NULL;"
))
# Check length/precision changes
before_length = before_col.get("length")
after_length = after_col.get("length")
if before_length and after_length and before_length != after_length:
if after_length < before_length:
issues.append(CompatibilityIssue(
type="length_reduction",
severity="potentially_breaking",
description=f"Column '{col_name}' length reduced from {before_length} to {after_length}",
field_path=f"tables.{table_name}.columns.{col_name}.length",
old_value=before_length,
new_value=after_length,
impact="Data truncation may occur for values exceeding new length",
suggested_migration=f"Validate no existing data exceeds new length limit",
affected_operations=["INSERT", "UPDATE"]
))
return issues, scripts
def _analyze_constraint_changes(self, table_name: str, before_constraints: Dict[str, Any],
after_constraints: Dict[str, Any]) -> Tuple[List[CompatibilityIssue], List[MigrationScript]]:
"""Analyze constraint changes"""
issues = []
scripts = []
for constraint_type in ["primary_key", "foreign_key", "unique", "check"]:
before_constraint = before_constraints.get(constraint_type, [])
after_constraint = after_constraints.get(constraint_type, [])
# Convert to sets for comparison
before_set = set(str(c) for c in before_constraint) if isinstance(before_constraint, list) else {str(before_constraint)} if before_constraint else set()
after_set = set(str(c) for c in after_constraint) if isinstance(after_constraint, list) else {str(after_constraint)} if after_constraint else set()
# Check for removed constraints
for constraint in before_set - after_set:
implication = self.constraint_implications.get(constraint_type, {}).get("removed", "non_breaking")
issues.append(CompatibilityIssue(
type=f"{constraint_type}_removed",
severity=implication,
description=f"{constraint_type.replace('_', ' ').title()} constraint '{constraint}' removed from table '{table_name}'",
field_path=f"tables.{table_name}.constraints.{constraint_type}",
old_value=constraint,
new_value=None,
impact=f"No longer enforcing {constraint_type} constraint",
suggested_migration=f"Consider application-level validation for removed constraint",
affected_operations=["INSERT", "UPDATE", "DELETE"]
))
# Check for added constraints
for constraint in after_set - before_set:
implication = self.constraint_implications.get(constraint_type, {}).get("added", "potentially_breaking")
issues.append(CompatibilityIssue(
type=f"{constraint_type}_added",
severity=implication,
description=f"New {constraint_type.replace('_', ' ')} constraint '{constraint}' added to table '{table_name}'",
field_path=f"tables.{table_name}.constraints.{constraint_type}",
old_value=None,
new_value=constraint,
impact=f"New {constraint_type} constraint may reject existing data",
suggested_migration=f"Validate existing data complies with new constraint",
affected_operations=["INSERT", "UPDATE"]
))
scripts.append(MigrationScript(
script_type="sql",
description=f"Add {constraint_type} constraint to {table_name}",
script_content=f"ALTER TABLE {table_name} ADD CONSTRAINT {constraint_type}_{table_name} {constraint_type.upper()} ({constraint});",
rollback_script=f"ALTER TABLE {table_name} DROP CONSTRAINT {constraint_type}_{table_name};",
dependencies=[],
validation_query=f"SELECT COUNT(*) FROM information_schema.table_constraints WHERE table_name = '{table_name}' AND constraint_type = '{constraint_type.upper()}';"
))
return issues, scripts
def _analyze_endpoint_changes(self, path: str, before_endpoint: Dict[str, Any],
after_endpoint: Dict[str, Any]) -> Tuple[List[CompatibilityIssue], List[MigrationScript]]:
"""Analyze changes to an API endpoint"""
issues = []
scripts = []
for method in set(before_endpoint.keys()) & set(after_endpoint.keys()):
before_method = before_endpoint[method]
after_method = after_endpoint[method]
# Check parameter changes
before_params = before_method.get("parameters", [])
after_params = after_method.get("parameters", [])
before_param_names = {p["name"] for p in before_params}
after_param_names = {p["name"] for p in after_params}
# Check for removed required parameters
for param_name in before_param_names - after_param_names:
param = next(p for p in before_params if p["name"] == param_name)
if param.get("required", False):
issues.append(CompatibilityIssue(
type="required_parameter_removed",
severity="breaking",
description=f"Required parameter '{param_name}' removed from {method.upper()} {path}",
field_path=f"paths.{path}.{method}.parameters",
old_value=param,
new_value=None,
impact="Client requests with this parameter will fail",
suggested_migration="Implement parameter validation with backward compatibility",
affected_operations=[f"{method.upper()} {path}"]
))
# Check for added required parameters
for param_name in after_param_names - before_param_names:
param = next(p for p in after_params if p["name"] == param_name)
if param.get("required", False):
issues.append(CompatibilityIssue(
type="required_parameter_added",
severity="breaking",
description=f"New required parameter '{param_name}' added to {method.upper()} {path}",
field_path=f"paths.{path}.{method}.parameters",
old_value=None,
new_value=param,
impact="Client requests without this parameter will fail",
suggested_migration="Provide default value or make parameter optional initially",
affected_operations=[f"{method.upper()} {path}"]
))
# Check response schema changes
before_responses = before_method.get("responses", {})
after_responses = after_method.get("responses", {})
for status_code in before_responses:
if status_code in after_responses:
before_schema = before_responses[status_code].get("content", {}).get("application/json", {}).get("schema", {})
after_schema = after_responses[status_code].get("content", {}).get("application/json", {}).get("schema", {})
if before_schema != after_schema:
issues.append(CompatibilityIssue(
type="response_schema_changed",
severity="potentially_breaking",
description=f"Response schema changed for {method.upper()} {path} (status {status_code})",
field_path=f"paths.{path}.{method}.responses.{status_code}",
old_value=before_schema,
new_value=after_schema,
impact="Client response parsing may fail",
suggested_migration="Implement versioned API responses",
affected_operations=[f"{method.upper()} {path}"]
))
return issues, scripts
def _analyze_model_changes(self, model_name: str, before_model: Dict[str, Any],
after_model: Dict[str, Any]) -> Tuple[List[CompatibilityIssue], List[MigrationScript]]:
"""Analyze changes to an API data model"""
issues = []
scripts = []
before_props = before_model.get("properties", {})
after_props = after_model.get("properties", {})
before_required = set(before_model.get("required", []))
after_required = set(after_model.get("required", []))
# Check for removed properties
for prop_name in set(before_props.keys()) - set(after_props.keys()):
issues.append(CompatibilityIssue(
type="property_removed",
severity="breaking",
description=f"Property '{prop_name}' removed from model '{model_name}'",
field_path=f"components.schemas.{model_name}.properties.{prop_name}",
old_value=before_props[prop_name],
new_value=None,
impact="Client code expecting this property will fail",
suggested_migration="Use API versioning to maintain backward compatibility",
affected_operations=["Serialization", "Deserialization"]
))
# Check for newly required properties
for prop_name in after_required - before_required:
issues.append(CompatibilityIssue(
type="property_made_required",
severity="breaking",
description=f"Property '{prop_name}' is now required in model '{model_name}'",
field_path=f"components.schemas.{model_name}.required",
old_value=list(before_required),
new_value=list(after_required),
impact="Client requests without this property will fail validation",
suggested_migration="Provide default values or implement gradual rollout",
affected_operations=["Request validation"]
))
# Check for property type changes
for prop_name in set(before_props.keys()) & set(after_props.keys()):
before_type = before_props[prop_name].get("type")
after_type = after_props[prop_name].get("type")
if before_type != after_type:
compatibility = self.type_compatibility_matrix.get(before_type, {}).get(after_type, "breaking")
issues.append(CompatibilityIssue(
type="property_type_changed",
severity=compatibility,
description=f"Property '{prop_name}' type changed from {before_type} to {after_type} in model '{model_name}'",
field_path=f"components.schemas.{model_name}.properties.{prop_name}.type",
old_value=before_type,
new_value=after_type,
impact="Client serialization/deserialization may fail",
suggested_migration="Implement type coercion or API versioning",
affected_operations=["Serialization", "Deserialization"]
))
return issues, scripts
def _build_compatibility_report(self, before_schema: Dict[str, Any], after_schema: Dict[str, Any],
issues: List[CompatibilityIssue], migration_scripts: List[MigrationScript]) -> CompatibilityReport:
"""Build the final compatibility report"""
# Count issues by severity
breaking_count = sum(1 for issue in issues if issue.severity == "breaking")
potentially_breaking_count = sum(1 for issue in issues if issue.severity == "potentially_breaking")
non_breaking_count = sum(1 for issue in issues if issue.severity == "non_breaking")
additive_count = sum(1 for issue in issues if issue.type == "additive")
# Determine overall compatibility
if breaking_count > 0:
overall_compatibility = "breaking_changes"
elif potentially_breaking_count > 0:
overall_compatibility = "potentially_incompatible"
elif non_breaking_count > 0:
overall_compatibility = "backward_compatible"
else:
overall_compatibility = "fully_compatible"
# Generate risk assessment
risk_assessment = {
"overall_risk": "high" if breaking_count > 0 else "medium" if potentially_breaking_count > 0 else "low",
"deployment_risk": "requires_coordinated_deployment" if breaking_count > 0 else "safe_independent_deployment",
"rollback_complexity": "high" if breaking_count > 3 else "medium" if breaking_count > 0 else "low",
"testing_requirements": ["integration_testing", "regression_testing"] +
(["data_migration_testing"] if any(s.script_type == "sql" for s in migration_scripts) else [])
}
# Generate recommendations
recommendations = []
if breaking_count > 0:
recommendations.append("Implement API versioning to maintain backward compatibility")
recommendations.append("Plan for coordinated deployment with all clients")
recommendations.append("Implement comprehensive rollback procedures")
if potentially_breaking_count > 0:
recommendations.append("Conduct thorough testing with realistic data volumes")
recommendations.append("Implement monitoring for migration success metrics")
if migration_scripts:
recommendations.append("Test all migration scripts in staging environment")
recommendations.append("Implement migration progress monitoring")
recommendations.append("Create detailed communication plan for stakeholders")
recommendations.append("Implement feature flags for gradual rollout")
return CompatibilityReport(
schema_before=json.dumps(before_schema, indent=2)[:500] + "..." if len(json.dumps(before_schema)) > 500 else json.dumps(before_schema, indent=2),
schema_after=json.dumps(after_schema, indent=2)[:500] + "..." if len(json.dumps(after_schema)) > 500 else json.dumps(after_schema, indent=2),
analysis_date=datetime.datetime.now().isoformat(),
overall_compatibility=overall_compatibility,
breaking_changes_count=breaking_count,
potentially_breaking_count=potentially_breaking_count,
non_breaking_changes_count=non_breaking_count,
additive_changes_count=additive_count,
issues=issues,
migration_scripts=migration_scripts,
risk_assessment=risk_assessment,
recommendations=recommendations
)
def _generate_create_table_sql(self, table_name: str, table_def: Dict[str, Any]) -> str:
"""Generate CREATE TABLE SQL statement"""
columns = []
for col_name, col_def in table_def.get("columns", {}).items():
columns.append(self._generate_column_definition(col_name, col_def))
return f"CREATE TABLE {table_name} (\n " + ",\n ".join(columns) + "\n);"
def _generate_column_definition(self, col_name: str, col_def: Dict[str, Any]) -> str:
"""Generate column definition for SQL"""
col_type = col_def.get("type", "VARCHAR(255)")
nullable = "" if col_def.get("nullable", True) else " NOT NULL"
default = f" DEFAULT {col_def.get('default')}" if col_def.get("default") is not None else ""
return f"{col_name} {col_type}{nullable}{default}"
def generate_human_readable_report(self, report: CompatibilityReport) -> str:
"""Generate human-readable compatibility report"""
output = []
output.append("=" * 80)
output.append("COMPATIBILITY ANALYSIS REPORT")
output.append("=" * 80)
output.append(f"Analysis Date: {report.analysis_date}")
output.append(f"Overall Compatibility: {report.overall_compatibility.upper()}")
output.append("")
# Summary
output.append("SUMMARY")
output.append("-" * 40)
output.append(f"Breaking Changes: {report.breaking_changes_count}")
output.append(f"Potentially Breaking: {report.potentially_breaking_count}")
output.append(f"Non-Breaking Changes: {report.non_breaking_changes_count}")
output.append(f"Additive Changes: {report.additive_changes_count}")
output.append(f"Total Issues Found: {len(report.issues)}")
output.append("")
# Risk Assessment
output.append("RISK ASSESSMENT")
output.append("-" * 40)
for key, value in report.risk_assessment.items():
output.append(f"{key.replace('_', ' ').title()}: {value}")
output.append("")
# Issues by Severity
issues_by_severity = {}
for issue in report.issues:
if issue.severity not in issues_by_severity:
issues_by_severity[issue.severity] = []
issues_by_severity[issue.severity].append(issue)
for severity in ["breaking", "potentially_breaking", "non_breaking"]:
if severity in issues_by_severity:
output.append(f"{severity.upper().replace('_', ' ')} ISSUES")
output.append("-" * 40)
for issue in issues_by_severity[severity]:
output.append(f"• {issue.description}")
output.append(f" Field: {issue.field_path}")
output.append(f" Impact: {issue.impact}")
output.append(f" Migration: {issue.suggested_migration}")
if issue.affected_operations:
output.append(f" Affected Operations: {', '.join(issue.affected_operations)}")
output.append("")
# Migration Scripts
if report.migration_scripts:
output.append("SUGGESTED MIGRATION SCRIPTS")
output.append("-" * 40)
for i, script in enumerate(report.migration_scripts, 1):
output.append(f"{i}. {script.description}")
output.append(f" Type: {script.script_type}")
output.append(" Script:")
for line in script.script_content.split('\n'):
output.append(f" {line}")
output.append("")
# Recommendations
output.append("RECOMMENDATIONS")
output.append("-" * 40)
for i, rec in enumerate(report.recommendations, 1):
output.append(f"{i}. {rec}")
output.append("")
return "\n".join(output)
def main():
"""Main function with command line interface"""
parser = argparse.ArgumentParser(description="Analyze schema and API compatibility between versions")
parser.add_argument("--before", required=True, help="Before schema file (JSON)")
parser.add_argument("--after", required=True, help="After schema file (JSON)")
parser.add_argument("--type", choices=["database", "api"], default="database", help="Schema type to analyze")
parser.add_argument("--output", "-o", help="Output file for compatibility report (JSON)")
parser.add_argument("--format", "-f", choices=["json", "text", "both"], default="both", help="Output format")
args = parser.parse_args()
try:
# Load schemas
with open(args.before, 'r') as f:
before_schema = json.load(f)
with open(args.after, 'r') as f:
after_schema = json.load(f)
# Analyze compatibility
checker = SchemaCompatibilityChecker()
if args.type == "database":
report = checker.analyze_database_schema(before_schema, after_schema)
else: # api
report = checker.analyze_api_schema(before_schema, after_schema)
# Output results
if args.format in ["json", "both"]:
report_dict = asdict(report)
if args.output:
with open(args.output, 'w') as f:
json.dump(report_dict, f, indent=2)
print(f"Compatibility report saved to {args.output}")
else:
print(json.dumps(report_dict, indent=2))
if args.format in ["text", "both"]:
human_report = checker.generate_human_readable_report(report)
text_output = args.output.replace('.json', '.txt') if args.output else None
if text_output:
with open(text_output, 'w') as f:
f.write(human_report)
print(f"Human-readable report saved to {text_output}")
else:
print("\n" + "="*80)
print("HUMAN-READABLE COMPATIBILITY REPORT")
print("="*80)
print(human_report)
# Return exit code based on compatibility
if report.breaking_changes_count > 0:
return 2 # Breaking changes found
elif report.potentially_breaking_count > 0:
return 1 # Potentially breaking changes found
else:
return 0 # No compatibility issues
except FileNotFoundError as e:
print(f"Error: File not found: {e}", file=sys.stderr)
return 1
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON: {e}", file=sys.stderr)
return 1
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
return 1
if __name__ == "__main__":
sys.exit(main())
FILE:scripts/migration_planner.py
#!/usr/bin/env python3
"""
Migration Planner - Generate comprehensive migration plans with risk assessment
This tool analyzes migration specifications and generates detailed, phased migration plans
including pre-migration checklists, validation gates, rollback triggers, timeline estimates,
and risk matrices.
Author: Migration Architect Skill
Version: 1.0.0
License: MIT
"""
import json
import argparse
import sys
import datetime
import hashlib
import math
from typing import Dict, List, Any, Optional, Tuple
from dataclasses import dataclass, asdict
from enum import Enum
class MigrationType(Enum):
"""Migration type enumeration"""
DATABASE = "database"
SERVICE = "service"
INFRASTRUCTURE = "infrastructure"
DATA = "data"
API = "api"
class MigrationComplexity(Enum):
"""Migration complexity levels"""
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
CRITICAL = "critical"
class RiskLevel(Enum):
"""Risk assessment levels"""
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
CRITICAL = "critical"
@dataclass
class MigrationConstraint:
"""Migration constraint definition"""
type: str
description: str
impact: str
mitigation: str
@dataclass
class MigrationPhase:
"""Individual migration phase"""
name: str
description: str
duration_hours: int
dependencies: List[str]
validation_criteria: List[str]
rollback_triggers: List[str]
tasks: List[str]
risk_level: str
resources_required: List[str]
@dataclass
class RiskItem:
"""Individual risk assessment item"""
category: str
description: str
probability: str # low, medium, high
impact: str # low, medium, high
severity: str # low, medium, high, critical
mitigation: str
owner: str
@dataclass
class MigrationPlan:
"""Complete migration plan structure"""
migration_id: str
source_system: str
target_system: str
migration_type: str
complexity: str
estimated_duration_hours: int
phases: List[MigrationPhase]
risks: List[RiskItem]
success_criteria: List[str]
rollback_plan: Dict[str, Any]
stakeholders: List[str]
created_at: str
class MigrationPlanner:
"""Main migration planner class"""
def __init__(self):
self.migration_patterns = self._load_migration_patterns()
self.risk_templates = self._load_risk_templates()
def _load_migration_patterns(self) -> Dict[str, Any]:
"""Load predefined migration patterns"""
return {
"database": {
"schema_change": {
"phases": ["preparation", "expand", "migrate", "contract", "cleanup"],
"base_duration": 24,
"complexity_multiplier": {"low": 1.0, "medium": 1.5, "high": 2.5, "critical": 4.0}
},
"data_migration": {
"phases": ["assessment", "setup", "bulk_copy", "delta_sync", "validation", "cutover"],
"base_duration": 48,
"complexity_multiplier": {"low": 1.2, "medium": 2.0, "high": 3.0, "critical": 5.0}
}
},
"service": {
"strangler_fig": {
"phases": ["intercept", "implement", "redirect", "validate", "retire"],
"base_duration": 168, # 1 week
"complexity_multiplier": {"low": 0.8, "medium": 1.0, "high": 1.8, "critical": 3.0}
},
"parallel_run": {
"phases": ["setup", "deploy", "shadow", "compare", "cutover", "cleanup"],
"base_duration": 72,
"complexity_multiplier": {"low": 1.0, "medium": 1.3, "high": 2.0, "critical": 3.5}
}
},
"infrastructure": {
"cloud_migration": {
"phases": ["assessment", "design", "pilot", "migration", "optimization", "decommission"],
"base_duration": 720, # 30 days
"complexity_multiplier": {"low": 0.6, "medium": 1.0, "high": 1.5, "critical": 2.5}
},
"on_prem_to_cloud": {
"phases": ["discovery", "planning", "pilot", "migration", "validation", "cutover"],
"base_duration": 480, # 20 days
"complexity_multiplier": {"low": 0.8, "medium": 1.2, "high": 2.0, "critical": 3.0}
}
}
}
def _load_risk_templates(self) -> Dict[str, List[RiskItem]]:
"""Load risk templates for different migration types"""
return {
"database": [
RiskItem("technical", "Data corruption during migration", "low", "critical", "high",
"Implement comprehensive backup and validation procedures", "DBA Team"),
RiskItem("technical", "Extended downtime due to migration complexity", "medium", "high", "high",
"Use blue-green deployment and phased migration approach", "DevOps Team"),
RiskItem("business", "Business process disruption", "medium", "high", "high",
"Communicate timeline and provide alternate workflows", "Business Owner"),
RiskItem("operational", "Insufficient rollback testing", "high", "critical", "critical",
"Execute full rollback procedures in staging environment", "QA Team")
],
"service": [
RiskItem("technical", "Service compatibility issues", "medium", "high", "high",
"Implement comprehensive integration testing", "Development Team"),
RiskItem("technical", "Performance degradation", "medium", "medium", "medium",
"Conduct load testing and performance benchmarking", "DevOps Team"),
RiskItem("business", "Feature parity gaps", "high", "high", "high",
"Document feature mapping and acceptance criteria", "Product Owner"),
RiskItem("operational", "Monitoring gap during transition", "medium", "medium", "medium",
"Set up dual monitoring and alerting systems", "SRE Team")
],
"infrastructure": [
RiskItem("technical", "Network connectivity issues", "medium", "critical", "high",
"Implement redundant network paths and monitoring", "Network Team"),
RiskItem("technical", "Security configuration drift", "high", "high", "high",
"Automated security scanning and compliance checks", "Security Team"),
RiskItem("business", "Cost overrun during transition", "high", "medium", "medium",
"Implement cost monitoring and budget alerts", "Finance Team"),
RiskItem("operational", "Team knowledge gaps", "high", "medium", "medium",
"Provide training and create detailed documentation", "Platform Team")
]
}
def _calculate_complexity(self, spec: Dict[str, Any]) -> str:
"""Calculate migration complexity based on specification"""
complexity_score = 0
# Data volume complexity
data_volume = spec.get("constraints", {}).get("data_volume_gb", 0)
if data_volume > 10000:
complexity_score += 3
elif data_volume > 1000:
complexity_score += 2
elif data_volume > 100:
complexity_score += 1
# System dependencies
dependencies = len(spec.get("constraints", {}).get("dependencies", []))
if dependencies > 10:
complexity_score += 3
elif dependencies > 5:
complexity_score += 2
elif dependencies > 2:
complexity_score += 1
# Downtime constraints
max_downtime = spec.get("constraints", {}).get("max_downtime_minutes", 480)
if max_downtime < 60:
complexity_score += 3
elif max_downtime < 240:
complexity_score += 2
elif max_downtime < 480:
complexity_score += 1
# Special requirements
special_reqs = spec.get("constraints", {}).get("special_requirements", [])
complexity_score += len(special_reqs)
if complexity_score >= 8:
return "critical"
elif complexity_score >= 5:
return "high"
elif complexity_score >= 3:
return "medium"
else:
return "low"
def _estimate_duration(self, migration_type: str, migration_pattern: str, complexity: str) -> int:
"""Estimate migration duration based on type, pattern, and complexity"""
pattern_info = self.migration_patterns.get(migration_type, {}).get(migration_pattern, {})
base_duration = pattern_info.get("base_duration", 48)
multiplier = pattern_info.get("complexity_multiplier", {}).get(complexity, 1.5)
return int(base_duration * multiplier)
def _generate_phases(self, spec: Dict[str, Any]) -> List[MigrationPhase]:
"""Generate migration phases based on specification"""
migration_type = spec.get("type")
migration_pattern = spec.get("pattern", "")
complexity = self._calculate_complexity(spec)
pattern_info = self.migration_patterns.get(migration_type, {})
if migration_pattern in pattern_info:
phase_names = pattern_info[migration_pattern]["phases"]
else:
# Default phases based on migration type
phase_names = {
"database": ["preparation", "migration", "validation", "cutover"],
"service": ["preparation", "deployment", "testing", "cutover"],
"infrastructure": ["assessment", "preparation", "migration", "validation"]
}.get(migration_type, ["preparation", "execution", "validation", "cleanup"])
phases = []
total_duration = self._estimate_duration(migration_type, migration_pattern, complexity)
phase_duration = total_duration // len(phase_names)
for i, phase_name in enumerate(phase_names):
phase = self._create_phase(phase_name, phase_duration, complexity, i, phase_names)
phases.append(phase)
return phases
def _create_phase(self, phase_name: str, duration: int, complexity: str,
phase_index: int, all_phases: List[str]) -> MigrationPhase:
"""Create a detailed migration phase"""
phase_templates = {
"preparation": {
"description": "Prepare systems and teams for migration",
"tasks": [
"Backup source system",
"Set up monitoring and alerting",
"Prepare rollback procedures",
"Communicate migration timeline",
"Validate prerequisites"
],
"validation_criteria": [
"All backups completed successfully",
"Monitoring systems operational",
"Team members briefed and ready",
"Rollback procedures tested"
],
"risk_level": "medium"
},
"assessment": {
"description": "Assess current state and migration requirements",
"tasks": [
"Inventory existing systems and dependencies",
"Analyze data volumes and complexity",
"Identify integration points",
"Document current architecture",
"Create migration mapping"
],
"validation_criteria": [
"Complete system inventory documented",
"Dependencies mapped and validated",
"Migration scope clearly defined",
"Resource requirements identified"
],
"risk_level": "low"
},
"migration": {
"description": "Execute core migration processes",
"tasks": [
"Begin data/service migration",
"Monitor migration progress",
"Validate data consistency",
"Handle migration errors",
"Update configuration"
],
"validation_criteria": [
"Migration progress within expected parameters",
"Data consistency checks passing",
"Error rates within acceptable limits",
"Performance metrics stable"
],
"risk_level": "high"
},
"validation": {
"description": "Validate migration success and system health",
"tasks": [
"Execute comprehensive testing",
"Validate business processes",
"Check system performance",
"Verify data integrity",
"Confirm security controls"
],
"validation_criteria": [
"All critical tests passing",
"Performance within acceptable range",
"Security controls functioning",
"Business processes operational"
],
"risk_level": "medium"
},
"cutover": {
"description": "Switch production traffic to new system",
"tasks": [
"Update DNS/load balancer configuration",
"Redirect production traffic",
"Monitor system performance",
"Validate end-user experience",
"Confirm business operations"
],
"validation_criteria": [
"Traffic successfully redirected",
"System performance stable",
"User experience satisfactory",
"Business operations normal"
],
"risk_level": "critical"
}
}
template = phase_templates.get(phase_name, {
"description": f"Execute {phase_name} phase",
"tasks": [f"Complete {phase_name} activities"],
"validation_criteria": [f"{phase_name.title()} phase completed successfully"],
"risk_level": "medium"
})
dependencies = []
if phase_index > 0:
dependencies.append(all_phases[phase_index - 1])
rollback_triggers = [
"Critical system failure",
"Data corruption detected",
"Performance degradation > 50%",
"Business process failure"
]
resources_required = [
"Technical team availability",
"System access and permissions",
"Monitoring and alerting systems",
"Communication channels"
]
return MigrationPhase(
name=phase_name,
description=template["description"],
duration_hours=duration,
dependencies=dependencies,
validation_criteria=template["validation_criteria"],
rollback_triggers=rollback_triggers,
tasks=template["tasks"],
risk_level=template["risk_level"],
resources_required=resources_required
)
def _assess_risks(self, spec: Dict[str, Any]) -> List[RiskItem]:
"""Generate risk assessment for migration"""
migration_type = spec.get("type")
base_risks = self.risk_templates.get(migration_type, [])
# Add specification-specific risks
additional_risks = []
constraints = spec.get("constraints", {})
if constraints.get("max_downtime_minutes", 480) < 60:
additional_risks.append(
RiskItem("business", "Zero-downtime requirement increases complexity", "high", "medium", "high",
"Implement blue-green deployment or rolling update strategy", "DevOps Team")
)
if constraints.get("data_volume_gb", 0) > 5000:
additional_risks.append(
RiskItem("technical", "Large data volumes may cause extended migration time", "high", "medium", "medium",
"Implement parallel processing and progress monitoring", "Data Team")
)
compliance_reqs = constraints.get("compliance_requirements", [])
if compliance_reqs:
additional_risks.append(
RiskItem("compliance", "Regulatory compliance requirements", "medium", "high", "high",
"Ensure all compliance checks are integrated into migration process", "Compliance Team")
)
return base_risks + additional_risks
def _generate_rollback_plan(self, phases: List[MigrationPhase]) -> Dict[str, Any]:
"""Generate comprehensive rollback plan"""
rollback_phases = []
for phase in reversed(phases):
rollback_phase = {
"phase": phase.name,
"rollback_actions": [
f"Revert {phase.name} changes",
f"Restore pre-{phase.name} state",
f"Validate {phase.name} rollback success"
],
"validation_criteria": [
f"System restored to pre-{phase.name} state",
f"All {phase.name} changes successfully reverted",
"System functionality confirmed"
],
"estimated_time_minutes": phase.duration_hours * 15 # 25% of original phase time
}
rollback_phases.append(rollback_phase)
return {
"rollback_phases": rollback_phases,
"rollback_triggers": [
"Critical system failure",
"Data corruption detected",
"Migration timeline exceeded by > 50%",
"Business-critical functionality unavailable",
"Security breach detected",
"Stakeholder decision to abort"
],
"rollback_decision_matrix": {
"low_severity": "Continue with monitoring",
"medium_severity": "Assess and decide within 15 minutes",
"high_severity": "Immediate rollback initiation",
"critical_severity": "Emergency rollback - all hands"
},
"rollback_contacts": [
"Migration Lead",
"Technical Lead",
"Business Owner",
"On-call Engineer"
]
}
def generate_plan(self, spec: Dict[str, Any]) -> MigrationPlan:
"""Generate complete migration plan from specification"""
migration_id = hashlib.md5(json.dumps(spec, sort_keys=True).encode()).hexdigest()[:12]
complexity = self._calculate_complexity(spec)
phases = self._generate_phases(spec)
risks = self._assess_risks(spec)
total_duration = sum(phase.duration_hours for phase in phases)
rollback_plan = self._generate_rollback_plan(phases)
success_criteria = [
"All data successfully migrated with 100% integrity",
"System performance meets or exceeds baseline",
"All business processes functioning normally",
"No critical security vulnerabilities introduced",
"Stakeholder acceptance criteria met",
"Documentation and runbooks updated"
]
stakeholders = [
"Business Owner",
"Technical Lead",
"DevOps Team",
"QA Team",
"Security Team",
"End Users"
]
return MigrationPlan(
migration_id=migration_id,
source_system=spec.get("source", "Unknown"),
target_system=spec.get("target", "Unknown"),
migration_type=spec.get("type", "Unknown"),
complexity=complexity,
estimated_duration_hours=total_duration,
phases=phases,
risks=risks,
success_criteria=success_criteria,
rollback_plan=rollback_plan,
stakeholders=stakeholders,
created_at=datetime.datetime.now().isoformat()
)
def generate_human_readable_plan(self, plan: MigrationPlan) -> str:
"""Generate human-readable migration plan"""
output = []
output.append("=" * 80)
output.append(f"MIGRATION PLAN: {plan.migration_id}")
output.append("=" * 80)
output.append(f"Source System: {plan.source_system}")
output.append(f"Target System: {plan.target_system}")
output.append(f"Migration Type: {plan.migration_type.upper()}")
output.append(f"Complexity Level: {plan.complexity.upper()}")
output.append(f"Estimated Duration: {plan.estimated_duration_hours} hours ({plan.estimated_duration_hours/24:.1f} days)")
output.append(f"Created: {plan.created_at}")
output.append("")
# Phases
output.append("MIGRATION PHASES")
output.append("-" * 40)
for i, phase in enumerate(plan.phases, 1):
output.append(f"{i}. {phase.name.upper()} ({phase.duration_hours}h)")
output.append(f" Description: {phase.description}")
output.append(f" Risk Level: {phase.risk_level.upper()}")
if phase.dependencies:
output.append(f" Dependencies: {', '.join(phase.dependencies)}")
output.append(" Tasks:")
for task in phase.tasks:
output.append(f" • {task}")
output.append(" Success Criteria:")
for criteria in phase.validation_criteria:
output.append(f" ✓ {criteria}")
output.append("")
# Risk Assessment
output.append("RISK ASSESSMENT")
output.append("-" * 40)
risk_by_severity = {}
for risk in plan.risks:
if risk.severity not in risk_by_severity:
risk_by_severity[risk.severity] = []
risk_by_severity[risk.severity].append(risk)
for severity in ["critical", "high", "medium", "low"]:
if severity in risk_by_severity:
output.append(f"{severity.upper()} SEVERITY RISKS:")
for risk in risk_by_severity[severity]:
output.append(f" • {risk.description}")
output.append(f" Category: {risk.category}")
output.append(f" Probability: {risk.probability} | Impact: {risk.impact}")
output.append(f" Mitigation: {risk.mitigation}")
output.append(f" Owner: {risk.owner}")
output.append("")
# Rollback Plan
output.append("ROLLBACK STRATEGY")
output.append("-" * 40)
output.append("Rollback Triggers:")
for trigger in plan.rollback_plan["rollback_triggers"]:
output.append(f" • {trigger}")
output.append("")
output.append("Rollback Phases:")
for rb_phase in plan.rollback_plan["rollback_phases"]:
output.append(f" {rb_phase['phase'].upper()}:")
for action in rb_phase["rollback_actions"]:
output.append(f" - {action}")
output.append(f" Estimated Time: {rb_phase['estimated_time_minutes']} minutes")
output.append("")
# Success Criteria
output.append("SUCCESS CRITERIA")
output.append("-" * 40)
for criteria in plan.success_criteria:
output.append(f"✓ {criteria}")
output.append("")
# Stakeholders
output.append("STAKEHOLDERS")
output.append("-" * 40)
for stakeholder in plan.stakeholders:
output.append(f"• {stakeholder}")
output.append("")
return "\n".join(output)
def main():
"""Main function with command line interface"""
parser = argparse.ArgumentParser(description="Generate comprehensive migration plans")
parser.add_argument("--input", "-i", required=True, help="Input migration specification file (JSON)")
parser.add_argument("--output", "-o", help="Output file for migration plan (JSON)")
parser.add_argument("--format", "-f", choices=["json", "text", "both"], default="both",
help="Output format")
parser.add_argument("--validate", action="store_true", help="Validate migration specification only")
args = parser.parse_args()
try:
# Load migration specification
with open(args.input, 'r') as f:
spec = json.load(f)
# Validate required fields
required_fields = ["type", "source", "target"]
for field in required_fields:
if field not in spec:
print(f"Error: Missing required field '{field}' in specification", file=sys.stderr)
return 1
if args.validate:
print("Migration specification is valid")
return 0
# Generate migration plan
planner = MigrationPlanner()
plan = planner.generate_plan(spec)
# Output results
if args.format in ["json", "both"]:
plan_dict = asdict(plan)
if args.output:
with open(args.output, 'w') as f:
json.dump(plan_dict, f, indent=2)
print(f"Migration plan saved to {args.output}")
else:
print(json.dumps(plan_dict, indent=2))
if args.format in ["text", "both"]:
human_plan = planner.generate_human_readable_plan(plan)
text_output = args.output.replace('.json', '.txt') if args.output else None
if text_output:
with open(text_output, 'w') as f:
f.write(human_plan)
print(f"Human-readable plan saved to {text_output}")
else:
print("\n" + "="*80)
print("HUMAN-READABLE MIGRATION PLAN")
print("="*80)
print(human_plan)
except FileNotFoundError:
print(f"Error: Input file '{args.input}' not found", file=sys.stderr)
return 1
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON in input file: {e}", file=sys.stderr)
return 1
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
sys.exit(main())
FILE:scripts/rollback_generator.py
#!/usr/bin/env python3
"""
Rollback Generator - Generate comprehensive rollback procedures for migrations
This tool takes a migration plan and generates detailed rollback procedures for each phase,
including data rollback scripts, service rollback steps, validation checks, and communication
templates to ensure safe and reliable migration reversals.
Author: Migration Architect Skill
Version: 1.0.0
License: MIT
"""
import json
import argparse
import sys
import datetime
import hashlib
from typing import Dict, List, Any, Optional, Tuple
from dataclasses import dataclass, asdict
from enum import Enum
class RollbackTrigger(Enum):
"""Types of rollback triggers"""
MANUAL = "manual"
AUTOMATED = "automated"
THRESHOLD_BASED = "threshold_based"
TIME_BASED = "time_based"
class RollbackUrgency(Enum):
"""Rollback urgency levels"""
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
EMERGENCY = "emergency"
@dataclass
class RollbackStep:
"""Individual rollback step"""
step_id: str
name: str
description: str
script_type: str # sql, bash, api, manual
script_content: str
estimated_duration_minutes: int
dependencies: List[str]
validation_commands: List[str]
success_criteria: List[str]
failure_escalation: str
rollback_order: int
@dataclass
class RollbackPhase:
"""Rollback phase containing multiple steps"""
phase_name: str
description: str
urgency_level: str
estimated_duration_minutes: int
prerequisites: List[str]
steps: List[RollbackStep]
validation_checkpoints: List[str]
communication_requirements: List[str]
risk_level: str
@dataclass
class RollbackTriggerCondition:
"""Conditions that trigger automatic rollback"""
trigger_id: str
name: str
condition: str
metric_threshold: Optional[Dict[str, Any]]
evaluation_window_minutes: int
auto_execute: bool
escalation_contacts: List[str]
@dataclass
class DataRecoveryPlan:
"""Data recovery and restoration plan"""
recovery_method: str # backup_restore, point_in_time, event_replay
backup_location: str
recovery_scripts: List[str]
data_validation_queries: List[str]
estimated_recovery_time_minutes: int
recovery_dependencies: List[str]
@dataclass
class CommunicationTemplate:
"""Communication template for rollback scenarios"""
template_type: str # start, progress, completion, escalation
audience: str # technical, business, executive, customers
subject: str
body: str
urgency: str
delivery_methods: List[str]
@dataclass
class RollbackRunbook:
"""Complete rollback runbook"""
runbook_id: str
migration_id: str
created_at: str
rollback_phases: List[RollbackPhase]
trigger_conditions: List[RollbackTriggerCondition]
data_recovery_plan: DataRecoveryPlan
communication_templates: List[CommunicationTemplate]
escalation_matrix: Dict[str, Any]
validation_checklist: List[str]
post_rollback_procedures: List[str]
emergency_contacts: List[Dict[str, str]]
class RollbackGenerator:
"""Main rollback generator class"""
def __init__(self):
self.rollback_templates = self._load_rollback_templates()
self.validation_templates = self._load_validation_templates()
self.communication_templates = self._load_communication_templates()
def _load_rollback_templates(self) -> Dict[str, Any]:
"""Load rollback script templates for different migration types"""
return {
"database": {
"schema_rollback": {
"drop_table": "DROP TABLE IF EXISTS {table_name};",
"drop_column": "ALTER TABLE {table_name} DROP COLUMN IF EXISTS {column_name};",
"restore_column": "ALTER TABLE {table_name} ADD COLUMN {column_definition};",
"revert_type": "ALTER TABLE {table_name} ALTER COLUMN {column_name} TYPE {original_type};",
"drop_constraint": "ALTER TABLE {table_name} DROP CONSTRAINT {constraint_name};",
"add_constraint": "ALTER TABLE {table_name} ADD CONSTRAINT {constraint_name} {constraint_definition};"
},
"data_rollback": {
"restore_backup": "pg_restore -d {database_name} -c {backup_file}",
"point_in_time_recovery": "SELECT pg_create_restore_point('pre_migration_{timestamp}');",
"delete_migrated_data": "DELETE FROM {table_name} WHERE migration_batch_id = '{batch_id}';",
"restore_original_values": "UPDATE {table_name} SET {column_name} = backup_{column_name} WHERE migration_flag = true;"
}
},
"service": {
"deployment_rollback": {
"rollback_blue_green": "kubectl patch service {service_name} -p '{\"spec\":{\"selector\":{\"version\":\"blue\"}}}'",
"rollback_canary": "kubectl scale deployment {service_name}-canary --replicas=0",
"restore_previous_version": "kubectl rollout undo deployment/{service_name} --to-revision={revision_number}",
"update_load_balancer": "aws elbv2 modify-rule --rule-arn {rule_arn} --actions Type=forward,TargetGroupArn={original_target_group}"
},
"configuration_rollback": {
"restore_config_map": "kubectl apply -f {original_config_file}",
"revert_feature_flags": "curl -X PUT {feature_flag_api}/flags/{flag_name} -d '{\"enabled\": false}'",
"restore_environment_vars": "kubectl set env deployment/{deployment_name} {env_var_name}={original_value}"
}
},
"infrastructure": {
"cloud_rollback": {
"revert_terraform": "terraform apply -target={resource_name} {rollback_plan_file}",
"restore_dns": "aws route53 change-resource-record-sets --hosted-zone-id {zone_id} --change-batch file://{rollback_dns_changes}",
"rollback_security_groups": "aws ec2 authorize-security-group-ingress --group-id {group_id} --protocol {protocol} --port {port} --cidr {cidr}",
"restore_iam_policies": "aws iam put-role-policy --role-name {role_name} --policy-name {policy_name} --policy-document file://{original_policy}"
},
"network_rollback": {
"restore_routing": "aws ec2 replace-route --route-table-id {route_table_id} --destination-cidr-block {cidr} --gateway-id {original_gateway}",
"revert_load_balancer": "aws elbv2 modify-load-balancer --load-balancer-arn {lb_arn} --scheme {original_scheme}",
"restore_firewall_rules": "aws ec2 revoke-security-group-ingress --group-id {group_id} --protocol {protocol} --port {port} --source-group {source_group}"
}
}
}
def _load_validation_templates(self) -> Dict[str, List[str]]:
"""Load validation command templates"""
return {
"database": [
"SELECT COUNT(*) FROM {table_name};",
"SELECT COUNT(*) FROM information_schema.tables WHERE table_name = '{table_name}';",
"SELECT COUNT(*) FROM information_schema.columns WHERE table_name = '{table_name}' AND column_name = '{column_name}';",
"SELECT COUNT(DISTINCT {primary_key}) FROM {table_name};",
"SELECT MAX({timestamp_column}) FROM {table_name};"
],
"service": [
"curl -f {health_check_url}",
"kubectl get pods -l app={service_name} --field-selector=status.phase=Running",
"kubectl logs deployment/{service_name} --tail=100 | grep -i error",
"curl -f {service_endpoint}/api/v1/status"
],
"infrastructure": [
"aws ec2 describe-instances --instance-ids {instance_id} --query 'Reservations[*].Instances[*].State.Name'",
"nslookup {domain_name}",
"curl -I {load_balancer_url}",
"aws elbv2 describe-target-health --target-group-arn {target_group_arn}"
]
}
def _load_communication_templates(self) -> Dict[str, Dict[str, str]]:
"""Load communication templates"""
return {
"rollback_start": {
"technical": {
"subject": "ROLLBACK INITIATED: {migration_name}",
"body": """Team,
We have initiated rollback for migration: {migration_name}
Rollback ID: {rollback_id}
Start Time: {start_time}
Estimated Duration: {estimated_duration}
Reason: {rollback_reason}
Current Status: Rolling back phase {current_phase}
Next Updates: Every 15 minutes or upon phase completion
Actions Required:
- Monitor system health dashboards
- Stand by for escalation if needed
- Do not make manual changes during rollback
Incident Commander: {incident_commander}
"""
},
"business": {
"subject": "System Rollback In Progress - {system_name}",
"body": """Business Stakeholders,
We are currently performing a planned rollback of the {system_name} migration due to {rollback_reason}.
Impact: {business_impact}
Expected Resolution: {estimated_completion_time}
Affected Services: {affected_services}
We will provide updates every 30 minutes.
Contact: {business_contact}
"""
},
"executive": {
"subject": "EXEC ALERT: Critical System Rollback - {system_name}",
"body": """Executive Team,
A critical rollback is in progress for {system_name}.
Summary:
- Rollback Reason: {rollback_reason}
- Business Impact: {business_impact}
- Expected Resolution: {estimated_completion_time}
- Customer Impact: {customer_impact}
We are following established procedures and will update hourly.
Escalation: {escalation_contact}
"""
}
},
"rollback_complete": {
"technical": {
"subject": "ROLLBACK COMPLETED: {migration_name}",
"body": """Team,
Rollback has been successfully completed for migration: {migration_name}
Summary:
- Start Time: {start_time}
- End Time: {end_time}
- Duration: {actual_duration}
- Phases Completed: {completed_phases}
Validation Results:
{validation_results}
System Status: {system_status}
Next Steps:
- Continue monitoring for 24 hours
- Post-rollback review scheduled for {review_date}
- Root cause analysis to begin
All clear to resume normal operations.
Incident Commander: {incident_commander}
"""
}
}
}
def generate_rollback_runbook(self, migration_plan: Dict[str, Any]) -> RollbackRunbook:
"""Generate comprehensive rollback runbook from migration plan"""
runbook_id = f"rb_{hashlib.md5(str(migration_plan).encode()).hexdigest()[:8]}"
migration_id = migration_plan.get("migration_id", "unknown")
migration_type = migration_plan.get("migration_type", "unknown")
# Generate rollback phases (reverse order of migration phases)
rollback_phases = self._generate_rollback_phases(migration_plan)
# Generate trigger conditions
trigger_conditions = self._generate_trigger_conditions(migration_plan)
# Generate data recovery plan
data_recovery_plan = self._generate_data_recovery_plan(migration_plan)
# Generate communication templates
communication_templates = self._generate_communication_templates(migration_plan)
# Generate escalation matrix
escalation_matrix = self._generate_escalation_matrix(migration_plan)
# Generate validation checklist
validation_checklist = self._generate_validation_checklist(migration_plan)
# Generate post-rollback procedures
post_rollback_procedures = self._generate_post_rollback_procedures(migration_plan)
# Generate emergency contacts
emergency_contacts = self._generate_emergency_contacts(migration_plan)
return RollbackRunbook(
runbook_id=runbook_id,
migration_id=migration_id,
created_at=datetime.datetime.now().isoformat(),
rollback_phases=rollback_phases,
trigger_conditions=trigger_conditions,
data_recovery_plan=data_recovery_plan,
communication_templates=communication_templates,
escalation_matrix=escalation_matrix,
validation_checklist=validation_checklist,
post_rollback_procedures=post_rollback_procedures,
emergency_contacts=emergency_contacts
)
def _generate_rollback_phases(self, migration_plan: Dict[str, Any]) -> List[RollbackPhase]:
"""Generate rollback phases from migration plan"""
migration_phases = migration_plan.get("phases", [])
migration_type = migration_plan.get("migration_type", "unknown")
rollback_phases = []
# Reverse the order of migration phases for rollback
for i, phase in enumerate(reversed(migration_phases)):
if isinstance(phase, dict):
phase_name = phase.get("name", f"phase_{i}")
phase_duration = phase.get("duration_hours", 2) * 60 # Convert to minutes
phase_risk = phase.get("risk_level", "medium")
else:
phase_name = str(phase)
phase_duration = 120 # Default 2 hours
phase_risk = "medium"
rollback_steps = self._generate_rollback_steps(phase_name, migration_type, i)
rollback_phase = RollbackPhase(
phase_name=f"rollback_{phase_name}",
description=f"Rollback changes made during {phase_name} phase",
urgency_level=self._calculate_urgency(phase_risk),
estimated_duration_minutes=phase_duration // 2, # Rollback typically faster
prerequisites=self._get_rollback_prerequisites(phase_name, i),
steps=rollback_steps,
validation_checkpoints=self._get_validation_checkpoints(phase_name, migration_type),
communication_requirements=self._get_communication_requirements(phase_name, phase_risk),
risk_level=phase_risk
)
rollback_phases.append(rollback_phase)
return rollback_phases
def _generate_rollback_steps(self, phase_name: str, migration_type: str, phase_index: int) -> List[RollbackStep]:
"""Generate specific rollback steps for a phase"""
steps = []
templates = self.rollback_templates.get(migration_type, {})
if migration_type == "database":
if "migration" in phase_name.lower() or "cutover" in phase_name.lower():
# Data rollback steps
steps.extend([
RollbackStep(
step_id=f"rb_data_{phase_index}_01",
name="Stop data migration processes",
description="Halt all ongoing data migration processes",
script_type="sql",
script_content="-- Stop migration processes\nSELECT pg_cancel_backend(pid) FROM pg_stat_activity WHERE query LIKE '%migration%';",
estimated_duration_minutes=5,
dependencies=[],
validation_commands=["SELECT COUNT(*) FROM pg_stat_activity WHERE query LIKE '%migration%';"],
success_criteria=["No active migration processes"],
failure_escalation="Contact DBA immediately",
rollback_order=1
),
RollbackStep(
step_id=f"rb_data_{phase_index}_02",
name="Restore from backup",
description="Restore database from pre-migration backup",
script_type="bash",
script_content=templates.get("data_rollback", {}).get("restore_backup", "pg_restore -d {database_name} -c {backup_file}"),
estimated_duration_minutes=30,
dependencies=[f"rb_data_{phase_index}_01"],
validation_commands=["SELECT COUNT(*) FROM information_schema.tables;"],
success_criteria=["Database restored successfully", "All expected tables present"],
failure_escalation="Escalate to senior DBA and infrastructure team",
rollback_order=2
)
])
if "preparation" in phase_name.lower():
# Schema rollback steps
steps.append(
RollbackStep(
step_id=f"rb_schema_{phase_index}_01",
name="Drop migration artifacts",
description="Remove temporary migration tables and procedures",
script_type="sql",
script_content="-- Drop migration artifacts\nDROP TABLE IF EXISTS migration_log;\nDROP PROCEDURE IF EXISTS migrate_data();",
estimated_duration_minutes=5,
dependencies=[],
validation_commands=["SELECT COUNT(*) FROM information_schema.tables WHERE table_name LIKE '%migration%';"],
success_criteria=["No migration artifacts remain"],
failure_escalation="Manual cleanup required",
rollback_order=1
)
)
elif migration_type == "service":
if "cutover" in phase_name.lower():
# Service rollback steps
steps.extend([
RollbackStep(
step_id=f"rb_service_{phase_index}_01",
name="Redirect traffic back to old service",
description="Update load balancer to route traffic back to previous service version",
script_type="bash",
script_content=templates.get("deployment_rollback", {}).get("update_load_balancer", "aws elbv2 modify-rule --rule-arn {rule_arn} --actions Type=forward,TargetGroupArn={original_target_group}"),
estimated_duration_minutes=2,
dependencies=[],
validation_commands=["curl -f {health_check_url}"],
success_criteria=["Traffic routing to original service", "Health checks passing"],
failure_escalation="Emergency procedure - manual traffic routing",
rollback_order=1
),
RollbackStep(
step_id=f"rb_service_{phase_index}_02",
name="Rollback service deployment",
description="Revert to previous service deployment version",
script_type="bash",
script_content=templates.get("deployment_rollback", {}).get("restore_previous_version", "kubectl rollout undo deployment/{service_name} --to-revision={revision_number}"),
estimated_duration_minutes=10,
dependencies=[f"rb_service_{phase_index}_01"],
validation_commands=["kubectl get pods -l app={service_name} --field-selector=status.phase=Running"],
success_criteria=["Previous version deployed", "All pods running"],
failure_escalation="Manual pod management required",
rollback_order=2
)
])
elif migration_type == "infrastructure":
steps.extend([
RollbackStep(
step_id=f"rb_infra_{phase_index}_01",
name="Revert infrastructure changes",
description="Apply terraform plan to revert infrastructure to previous state",
script_type="bash",
script_content=templates.get("cloud_rollback", {}).get("revert_terraform", "terraform apply -target={resource_name} {rollback_plan_file}"),
estimated_duration_minutes=15,
dependencies=[],
validation_commands=["terraform plan -detailed-exitcode"],
success_criteria=["Infrastructure matches previous state", "No planned changes"],
failure_escalation="Manual infrastructure review required",
rollback_order=1
),
RollbackStep(
step_id=f"rb_infra_{phase_index}_02",
name="Restore DNS configuration",
description="Revert DNS changes to point back to original infrastructure",
script_type="bash",
script_content=templates.get("cloud_rollback", {}).get("restore_dns", "aws route53 change-resource-record-sets --hosted-zone-id {zone_id} --change-batch file://{rollback_dns_changes}"),
estimated_duration_minutes=10,
dependencies=[f"rb_infra_{phase_index}_01"],
validation_commands=["nslookup {domain_name}"],
success_criteria=["DNS resolves to original endpoints"],
failure_escalation="Contact DNS administrator",
rollback_order=2
)
])
# Add generic validation step for all migration types
steps.append(
RollbackStep(
step_id=f"rb_validate_{phase_index}_final",
name="Validate rollback completion",
description=f"Comprehensive validation that {phase_name} rollback completed successfully",
script_type="manual",
script_content="Execute validation checklist for this phase",
estimated_duration_minutes=10,
dependencies=[step.step_id for step in steps],
validation_commands=self.validation_templates.get(migration_type, []),
success_criteria=[f"{phase_name} fully rolled back", "All validation checks pass"],
failure_escalation=f"Investigate {phase_name} rollback failures",
rollback_order=99
)
)
return steps
def _generate_trigger_conditions(self, migration_plan: Dict[str, Any]) -> List[RollbackTriggerCondition]:
"""Generate automatic rollback trigger conditions"""
triggers = []
migration_type = migration_plan.get("migration_type", "unknown")
# Generic triggers for all migration types
triggers.extend([
RollbackTriggerCondition(
trigger_id="error_rate_spike",
name="Error Rate Spike",
condition="error_rate > baseline * 5 for 5 minutes",
metric_threshold={
"metric": "error_rate",
"operator": "greater_than",
"value": "baseline_error_rate * 5",
"duration_minutes": 5
},
evaluation_window_minutes=5,
auto_execute=True,
escalation_contacts=["on_call_engineer", "migration_lead"]
),
RollbackTriggerCondition(
trigger_id="response_time_degradation",
name="Response Time Degradation",
condition="p95_response_time > baseline * 3 for 10 minutes",
metric_threshold={
"metric": "p95_response_time",
"operator": "greater_than",
"value": "baseline_p95 * 3",
"duration_minutes": 10
},
evaluation_window_minutes=10,
auto_execute=False,
escalation_contacts=["performance_team", "migration_lead"]
),
RollbackTriggerCondition(
trigger_id="availability_drop",
name="Service Availability Drop",
condition="availability < 95% for 2 minutes",
metric_threshold={
"metric": "availability",
"operator": "less_than",
"value": 0.95,
"duration_minutes": 2
},
evaluation_window_minutes=2,
auto_execute=True,
escalation_contacts=["sre_team", "incident_commander"]
)
])
# Migration-type specific triggers
if migration_type == "database":
triggers.extend([
RollbackTriggerCondition(
trigger_id="data_integrity_failure",
name="Data Integrity Check Failure",
condition="data_validation_failures > 0",
metric_threshold={
"metric": "data_validation_failures",
"operator": "greater_than",
"value": 0,
"duration_minutes": 1
},
evaluation_window_minutes=1,
auto_execute=True,
escalation_contacts=["dba_team", "data_team"]
),
RollbackTriggerCondition(
trigger_id="migration_progress_stalled",
name="Migration Progress Stalled",
condition="migration_progress unchanged for 30 minutes",
metric_threshold={
"metric": "migration_progress_rate",
"operator": "equals",
"value": 0,
"duration_minutes": 30
},
evaluation_window_minutes=30,
auto_execute=False,
escalation_contacts=["migration_team", "dba_team"]
)
])
elif migration_type == "service":
triggers.extend([
RollbackTriggerCondition(
trigger_id="cpu_utilization_spike",
name="CPU Utilization Spike",
condition="cpu_utilization > 90% for 15 minutes",
metric_threshold={
"metric": "cpu_utilization",
"operator": "greater_than",
"value": 0.90,
"duration_minutes": 15
},
evaluation_window_minutes=15,
auto_execute=False,
escalation_contacts=["devops_team", "infrastructure_team"]
),
RollbackTriggerCondition(
trigger_id="memory_leak_detected",
name="Memory Leak Detected",
condition="memory_usage increasing continuously for 20 minutes",
metric_threshold={
"metric": "memory_growth_rate",
"operator": "greater_than",
"value": "1MB/minute",
"duration_minutes": 20
},
evaluation_window_minutes=20,
auto_execute=True,
escalation_contacts=["development_team", "sre_team"]
)
])
return triggers
def _generate_data_recovery_plan(self, migration_plan: Dict[str, Any]) -> DataRecoveryPlan:
"""Generate data recovery plan"""
migration_type = migration_plan.get("migration_type", "unknown")
if migration_type == "database":
return DataRecoveryPlan(
recovery_method="point_in_time",
backup_location="/backups/pre_migration_{migration_id}_{timestamp}.sql",
recovery_scripts=[
"pg_restore -d production -c /backups/pre_migration_backup.sql",
"SELECT pg_create_restore_point('rollback_point');",
"VACUUM ANALYZE; -- Refresh statistics after restore"
],
data_validation_queries=[
"SELECT COUNT(*) FROM critical_business_table;",
"SELECT MAX(created_at) FROM audit_log;",
"SELECT COUNT(DISTINCT user_id) FROM user_sessions;",
"SELECT SUM(amount) FROM financial_transactions WHERE date = CURRENT_DATE;"
],
estimated_recovery_time_minutes=45,
recovery_dependencies=["database_instance_running", "backup_file_accessible"]
)
else:
return DataRecoveryPlan(
recovery_method="backup_restore",
backup_location="/backups/pre_migration_state",
recovery_scripts=[
"# Restore configuration files from backup",
"cp -r /backups/pre_migration_state/config/* /app/config/",
"# Restart services with previous configuration",
"systemctl restart application_service"
],
data_validation_queries=[
"curl -f http://localhost:8080/health",
"curl -f http://localhost:8080/api/status"
],
estimated_recovery_time_minutes=20,
recovery_dependencies=["service_stopped", "backup_accessible"]
)
def _generate_communication_templates(self, migration_plan: Dict[str, Any]) -> List[CommunicationTemplate]:
"""Generate communication templates for rollback scenarios"""
templates = []
base_templates = self.communication_templates
# Rollback start notifications
for audience in ["technical", "business", "executive"]:
if audience in base_templates["rollback_start"]:
template_data = base_templates["rollback_start"][audience]
templates.append(CommunicationTemplate(
template_type="rollback_start",
audience=audience,
subject=template_data["subject"],
body=template_data["body"],
urgency="high" if audience == "executive" else "medium",
delivery_methods=["email", "slack"] if audience == "technical" else ["email"]
))
# Rollback completion notifications
for audience in ["technical", "business"]:
if audience in base_templates.get("rollback_complete", {}):
template_data = base_templates["rollback_complete"][audience]
templates.append(CommunicationTemplate(
template_type="rollback_complete",
audience=audience,
subject=template_data["subject"],
body=template_data["body"],
urgency="medium",
delivery_methods=["email", "slack"] if audience == "technical" else ["email"]
))
# Emergency escalation template
templates.append(CommunicationTemplate(
template_type="emergency_escalation",
audience="executive",
subject="CRITICAL: Rollback Emergency - {migration_name}",
body="""CRITICAL SITUATION - IMMEDIATE ATTENTION REQUIRED
Migration: {migration_name}
Issue: Rollback procedure has encountered critical failures
Current Status: {current_status}
Failed Components: {failed_components}
Business Impact: {business_impact}
Customer Impact: {customer_impact}
Immediate Actions:
1. Emergency response team activated
2. {emergency_action_1}
3. {emergency_action_2}
War Room: {war_room_location}
Bridge Line: {conference_bridge}
Next Update: {next_update_time}
Incident Commander: {incident_commander}
Executive On-Call: {executive_on_call}
""",
urgency="emergency",
delivery_methods=["email", "sms", "phone_call"]
))
return templates
def _generate_escalation_matrix(self, migration_plan: Dict[str, Any]) -> Dict[str, Any]:
"""Generate escalation matrix for different failure scenarios"""
return {
"level_1": {
"trigger": "Single component failure",
"response_time_minutes": 5,
"contacts": ["on_call_engineer", "migration_lead"],
"actions": ["Investigate issue", "Attempt automated remediation", "Monitor closely"]
},
"level_2": {
"trigger": "Multiple component failures or single critical failure",
"response_time_minutes": 2,
"contacts": ["senior_engineer", "team_lead", "devops_lead"],
"actions": ["Initiate rollback", "Establish war room", "Notify stakeholders"]
},
"level_3": {
"trigger": "System-wide failure or data corruption",
"response_time_minutes": 1,
"contacts": ["engineering_manager", "cto", "incident_commander"],
"actions": ["Emergency rollback", "All hands on deck", "Executive notification"]
},
"emergency": {
"trigger": "Business-critical failure with customer impact",
"response_time_minutes": 0,
"contacts": ["ceo", "cto", "head_of_operations"],
"actions": ["Emergency procedures", "Customer communication", "Media preparation if needed"]
}
}
def _generate_validation_checklist(self, migration_plan: Dict[str, Any]) -> List[str]:
"""Generate comprehensive validation checklist"""
migration_type = migration_plan.get("migration_type", "unknown")
base_checklist = [
"Verify system is responding to health checks",
"Confirm error rates are within normal parameters",
"Validate response times meet SLA requirements",
"Check all critical business processes are functioning",
"Verify monitoring and alerting systems are operational",
"Confirm no data corruption has occurred",
"Validate security controls are functioning properly",
"Check backup systems are working correctly",
"Verify integration points with downstream systems",
"Confirm user authentication and authorization working"
]
if migration_type == "database":
base_checklist.extend([
"Validate database schema matches expected state",
"Confirm referential integrity constraints",
"Check database performance metrics",
"Verify data consistency across related tables",
"Validate indexes and statistics are optimal",
"Confirm transaction logs are clean",
"Check database connections and connection pooling"
])
elif migration_type == "service":
base_checklist.extend([
"Verify service discovery is working correctly",
"Confirm load balancing is distributing traffic properly",
"Check service-to-service communication",
"Validate API endpoints are responding correctly",
"Confirm feature flags are in correct state",
"Check resource utilization (CPU, memory, disk)",
"Verify container orchestration is healthy"
])
elif migration_type == "infrastructure":
base_checklist.extend([
"Verify network connectivity between components",
"Confirm DNS resolution is working correctly",
"Check firewall rules and security groups",
"Validate load balancer configuration",
"Confirm SSL/TLS certificates are valid",
"Check storage systems are accessible",
"Verify backup and disaster recovery systems"
])
return base_checklist
def _generate_post_rollback_procedures(self, migration_plan: Dict[str, Any]) -> List[str]:
"""Generate post-rollback procedures"""
return [
"Monitor system stability for 24-48 hours post-rollback",
"Conduct thorough post-rollback testing of all critical paths",
"Review and analyze rollback metrics and timing",
"Document lessons learned and rollback procedure improvements",
"Schedule post-mortem meeting with all stakeholders",
"Update rollback procedures based on actual experience",
"Communicate rollback completion to all stakeholders",
"Archive rollback logs and artifacts for future reference",
"Review and update monitoring thresholds if needed",
"Plan for next migration attempt with improved procedures",
"Conduct security review to ensure no vulnerabilities introduced",
"Update disaster recovery procedures if affected by rollback",
"Review capacity planning based on rollback resource usage",
"Update documentation with rollback experience and timings"
]
def _generate_emergency_contacts(self, migration_plan: Dict[str, Any]) -> List[Dict[str, str]]:
"""Generate emergency contact list"""
return [
{
"role": "Incident Commander",
"name": "TBD - Assigned during migration",
"primary_phone": "+1-XXX-XXX-XXXX",
"email": "incident.commander@company.com",
"backup_contact": "backup.commander@company.com"
},
{
"role": "Technical Lead",
"name": "TBD - Migration technical owner",
"primary_phone": "+1-XXX-XXX-XXXX",
"email": "tech.lead@company.com",
"backup_contact": "senior.engineer@company.com"
},
{
"role": "Business Owner",
"name": "TBD - Business stakeholder",
"primary_phone": "+1-XXX-XXX-XXXX",
"email": "business.owner@company.com",
"backup_contact": "product.manager@company.com"
},
{
"role": "On-Call Engineer",
"name": "Current on-call rotation",
"primary_phone": "+1-XXX-XXX-XXXX",
"email": "oncall@company.com",
"backup_contact": "backup.oncall@company.com"
},
{
"role": "Executive Escalation",
"name": "CTO/VP Engineering",
"primary_phone": "+1-XXX-XXX-XXXX",
"email": "cto@company.com",
"backup_contact": "vp.engineering@company.com"
}
]
def _calculate_urgency(self, risk_level: str) -> str:
"""Calculate rollback urgency based on risk level"""
risk_to_urgency = {
"low": "low",
"medium": "medium",
"high": "high",
"critical": "emergency"
}
return risk_to_urgency.get(risk_level, "medium")
def _get_rollback_prerequisites(self, phase_name: str, phase_index: int) -> List[str]:
"""Get prerequisites for rollback phase"""
prerequisites = [
"Incident commander assigned and briefed",
"All team members notified of rollback initiation",
"Monitoring systems confirmed operational",
"Backup systems verified and accessible"
]
if phase_index > 0:
prerequisites.append("Previous rollback phase completed successfully")
if "cutover" in phase_name.lower():
prerequisites.extend([
"Traffic redirection capabilities confirmed",
"Load balancer configuration backed up",
"DNS changes prepared for quick execution"
])
if "data" in phase_name.lower() or "migration" in phase_name.lower():
prerequisites.extend([
"Database backup verified and accessible",
"Data validation queries prepared",
"Database administrator on standby"
])
return prerequisites
def _get_validation_checkpoints(self, phase_name: str, migration_type: str) -> List[str]:
"""Get validation checkpoints for rollback phase"""
checkpoints = [
f"{phase_name} rollback steps completed",
"System health checks passing",
"No critical errors in logs",
"Key metrics within acceptable ranges"
]
validation_commands = self.validation_templates.get(migration_type, [])
checkpoints.extend([f"Validation command passed: {cmd[:50]}..." for cmd in validation_commands[:3]])
return checkpoints
def _get_communication_requirements(self, phase_name: str, risk_level: str) -> List[str]:
"""Get communication requirements for rollback phase"""
base_requirements = [
"Notify incident commander of phase start/completion",
"Update rollback status dashboard",
"Log all actions and decisions"
]
if risk_level in ["high", "critical"]:
base_requirements.extend([
"Notify all stakeholders of phase progress",
"Update executive team if rollback extends beyond expected time",
"Prepare customer communication if needed"
])
if "cutover" in phase_name.lower():
base_requirements.append("Immediate notification when traffic is redirected")
return base_requirements
def generate_human_readable_runbook(self, runbook: RollbackRunbook) -> str:
"""Generate human-readable rollback runbook"""
output = []
output.append("=" * 80)
output.append(f"ROLLBACK RUNBOOK: {runbook.runbook_id}")
output.append("=" * 80)
output.append(f"Migration ID: {runbook.migration_id}")
output.append(f"Created: {runbook.created_at}")
output.append("")
# Emergency Contacts
output.append("EMERGENCY CONTACTS")
output.append("-" * 40)
for contact in runbook.emergency_contacts:
output.append(f"{contact['role']}: {contact['name']}")
output.append(f" Phone: {contact['primary_phone']}")
output.append(f" Email: {contact['email']}")
output.append(f" Backup: {contact['backup_contact']}")
output.append("")
# Escalation Matrix
output.append("ESCALATION MATRIX")
output.append("-" * 40)
for level, details in runbook.escalation_matrix.items():
output.append(f"{level.upper()}:")
output.append(f" Trigger: {details['trigger']}")
output.append(f" Response Time: {details['response_time_minutes']} minutes")
output.append(f" Contacts: {', '.join(details['contacts'])}")
output.append(f" Actions: {', '.join(details['actions'])}")
output.append("")
# Rollback Trigger Conditions
output.append("AUTOMATIC ROLLBACK TRIGGERS")
output.append("-" * 40)
for trigger in runbook.trigger_conditions:
output.append(f"• {trigger.name}")
output.append(f" Condition: {trigger.condition}")
output.append(f" Auto-Execute: {'Yes' if trigger.auto_execute else 'No'}")
output.append(f" Evaluation Window: {trigger.evaluation_window_minutes} minutes")
output.append(f" Contacts: {', '.join(trigger.escalation_contacts)}")
output.append("")
# Rollback Phases
output.append("ROLLBACK PHASES")
output.append("-" * 40)
for i, phase in enumerate(runbook.rollback_phases, 1):
output.append(f"{i}. {phase.phase_name.upper()}")
output.append(f" Description: {phase.description}")
output.append(f" Urgency: {phase.urgency_level.upper()}")
output.append(f" Duration: {phase.estimated_duration_minutes} minutes")
output.append(f" Risk Level: {phase.risk_level.upper()}")
if phase.prerequisites:
output.append(" Prerequisites:")
for prereq in phase.prerequisites:
output.append(f" ✓ {prereq}")
output.append(" Steps:")
for step in sorted(phase.steps, key=lambda x: x.rollback_order):
output.append(f" {step.rollback_order}. {step.name}")
output.append(f" Duration: {step.estimated_duration_minutes} min")
output.append(f" Type: {step.script_type}")
if step.script_content and step.script_type != "manual":
output.append(" Script:")
for line in step.script_content.split('\n')[:3]: # Show first 3 lines
output.append(f" {line}")
if len(step.script_content.split('\n')) > 3:
output.append(" ...")
output.append(f" Success Criteria: {', '.join(step.success_criteria)}")
output.append("")
if phase.validation_checkpoints:
output.append(" Validation Checkpoints:")
for checkpoint in phase.validation_checkpoints:
output.append(f" ☐ {checkpoint}")
output.append("")
# Data Recovery Plan
output.append("DATA RECOVERY PLAN")
output.append("-" * 40)
drp = runbook.data_recovery_plan
output.append(f"Recovery Method: {drp.recovery_method}")
output.append(f"Backup Location: {drp.backup_location}")
output.append(f"Estimated Recovery Time: {drp.estimated_recovery_time_minutes} minutes")
output.append("Recovery Scripts:")
for script in drp.recovery_scripts:
output.append(f" • {script}")
output.append("Validation Queries:")
for query in drp.data_validation_queries:
output.append(f" • {query}")
output.append("")
# Validation Checklist
output.append("POST-ROLLBACK VALIDATION CHECKLIST")
output.append("-" * 40)
for i, item in enumerate(runbook.validation_checklist, 1):
output.append(f"{i:2d}. ☐ {item}")
output.append("")
# Post-Rollback Procedures
output.append("POST-ROLLBACK PROCEDURES")
output.append("-" * 40)
for i, procedure in enumerate(runbook.post_rollback_procedures, 1):
output.append(f"{i:2d}. {procedure}")
output.append("")
return "\n".join(output)
def main():
"""Main function with command line interface"""
parser = argparse.ArgumentParser(description="Generate comprehensive rollback runbooks from migration plans")
parser.add_argument("--input", "-i", required=True, help="Input migration plan file (JSON)")
parser.add_argument("--output", "-o", help="Output file for rollback runbook (JSON)")
parser.add_argument("--format", "-f", choices=["json", "text", "both"], default="both", help="Output format")
args = parser.parse_args()
try:
# Load migration plan
with open(args.input, 'r') as f:
migration_plan = json.load(f)
# Validate required fields
if "migration_id" not in migration_plan and "source" not in migration_plan:
print("Error: Migration plan must contain migration_id or source field", file=sys.stderr)
return 1
# Generate rollback runbook
generator = RollbackGenerator()
runbook = generator.generate_rollback_runbook(migration_plan)
# Output results
if args.format in ["json", "both"]:
runbook_dict = asdict(runbook)
if args.output:
with open(args.output, 'w') as f:
json.dump(runbook_dict, f, indent=2)
print(f"Rollback runbook saved to {args.output}")
else:
print(json.dumps(runbook_dict, indent=2))
if args.format in ["text", "both"]:
human_runbook = generator.generate_human_readable_runbook(runbook)
text_output = args.output.replace('.json', '.txt') if args.output else None
if text_output:
with open(text_output, 'w') as f:
f.write(human_runbook)
print(f"Human-readable runbook saved to {text_output}")
else:
print("\n" + "="*80)
print("HUMAN-READABLE ROLLBACK RUNBOOK")
print("="*80)
print(human_runbook)
except FileNotFoundError:
print(f"Error: Input file '{args.input}' not found", file=sys.stderr)
return 1
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON in input file: {e}", file=sys.stderr)
return 1
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
sys.exit(main())Tạo hoặc cập nhật tài liệu ngữ cảnh product marketing: mô tả sản phẩm, đối tượng mục tiêu, ICP và định vị để tránh lặp lại thông tin nền.
---
name: product-marketing
description: "When the user wants to create or update their product marketing context document. Also use when the user mentions 'product context,' 'marketing context,' 'set up context,' 'positioning,' 'who is my target audience,' 'describe my product,' 'ICP,' 'ideal customer profile,' or wants to avoid repeating foundational information across marketing tasks. Use this at the start of any new project before using other marketing skills — it creates `.agents/product-marketing.md` that all other skills reference for product, audience, and positioning context."
metadata:
version: 2.1.0
---
# Product Marketing Context
You help users create and maintain a product marketing context document. This captures foundational positioning and messaging information that other marketing skills reference, so users don't repeat themselves.
The document is stored at `.agents/product-marketing.md`.
## Workflow
### Step 1: Check for Existing Context
First, check if `.agents/product-marketing.md` already exists. Also check `.claude/product-marketing.md` and the legacy filename `product-marketing-context.md` (in either `.agents/` or `.claude/`) for older setups — if found anywhere other than `.agents/product-marketing.md`, offer to move it to the canonical location.
**If it exists:**
- Read it and summarize what's captured — note its current **Document version** and the last few **Changelog** entries so the user sees where the doc stands and what's changed recently
- Ask which sections they want to update
- Only gather info for those sections
- On any substantive save, bump the version and add a changelog entry (see Step 4). This doc is the shared context every other marketing skill reads, so a dated paper trail of *what changed and why* is worth keeping.
**If it doesn't exist, offer two options:**
1. **Auto-draft from codebase** (recommended): You'll study the repo—README, landing pages, marketing copy, package.json, etc.—and draft a V1 of the context document. The user then reviews, corrects, and fills gaps. This is faster than starting from scratch.
2. **Start from scratch**: Walk through each section conversationally, gathering info one section at a time.
Most users prefer option 1. After presenting the draft, ask: "What needs correcting? What's missing?"
### Step 2: Gather Information
**If auto-drafting:**
1. Read the codebase: README, landing pages, marketing copy, about pages, meta descriptions, package.json, any existing docs
2. Draft all sections based on what you find
3. Present the draft and ask what needs correcting or is missing
4. Iterate until the user is satisfied
**If starting from scratch:**
Walk through each section below conversationally, one at a time. Don't dump all questions at once.
For each section:
1. Briefly explain what you're capturing
2. Ask relevant questions
3. Confirm accuracy
4. Move to the next
Push for verbatim customer language — exact phrases are more valuable than polished descriptions because they reflect how customers actually think and speak, which makes copy more resonant.
---
## Sections to Capture
### 1. Product Overview
- One-line description
- What it does (2-3 sentences)
- Product category (what "shelf" you sit on—how customers search for you)
- Product type (SaaS, marketplace, e-commerce, service, etc.)
- Business model and pricing
### 2. Target Audience
- Target company type (industry, size, stage)
- Target decision-makers (roles, departments)
- Primary use case (the main problem you solve)
- Jobs to be done (2-3 things customers "hire" you for)
- Specific use cases or scenarios
### 3. Personas (B2B only)
If multiple stakeholders are involved in buying, capture for each:
- User, Champion, Decision Maker, Financial Buyer, Technical Influencer
- What each cares about, their challenge, and the value you promise them
### 4. Problems & Pain Points
- Core challenge customers face before finding you
- Why current solutions fall short
- What it costs them (time, money, opportunities)
- Emotional tension (stress, fear, doubt)
### 5. Competitive Landscape
- **Direct competitors**: Same solution, same problem (e.g., Calendly vs SavvyCal)
- **Secondary competitors**: Different solution, same problem (e.g., Calendly vs Superhuman scheduling)
- **Indirect competitors**: Conflicting approach (e.g., Calendly vs personal assistant)
- How each falls short for customers
### 6. Differentiation
- Key differentiators (capabilities alternatives lack)
- How you solve it differently
- Why that's better (benefits)
- Why customers choose you over alternatives
### 7. Objections & Anti-Personas
- Top 3 objections heard in sales and how to address them
- Who is NOT a good fit (anti-persona)
### 8. Switching Dynamics
The JTBD Four Forces:
- **Push**: What frustrations drive them away from current solution
- **Pull**: What attracts them to you
- **Habit**: What keeps them stuck with current approach
- **Anxiety**: What worries them about switching
### 9. Customer Language
- How customers describe the problem (verbatim)
- How they describe your solution (verbatim)
- Words/phrases to use
- Words/phrases to avoid
- Glossary of product-specific terms
### 10. Brand Voice
- Tone (professional, casual, playful, etc.)
- Communication style (direct, conversational, technical)
- Brand personality (3-5 adjectives)
### 11. Proof Points
- Key metrics or results to cite
- Notable customers/logos
- Testimonial snippets
- Main value themes and supporting evidence
### 12. Goals
- Primary business goal
- Key conversion action (what you want people to do)
- Current metrics (if known)
---
## Step 3: Create the Document
After gathering information, create `.agents/product-marketing.md` with this structure:
```markdown
# Product Marketing Context
**Document version:** v1
**Last updated:** [date]
## Product Overview
**One-liner:**
**What it does:**
**Product category:**
**Product type:**
**Business model:**
## Target Audience
**Target companies:**
**Decision-makers:**
**Primary use case:**
**Jobs to be done:**
-
**Use cases:**
-
## Personas
| Persona | Cares about | Challenge | Value we promise |
|---------|-------------|-----------|------------------|
| | | | |
## Problems & Pain Points
**Core problem:**
**Why alternatives fall short:**
-
**What it costs them:**
**Emotional tension:**
## Competitive Landscape
**Direct:** [Competitor] — falls short because...
**Secondary:** [Approach] — falls short because...
**Indirect:** [Alternative] — falls short because...
## Differentiation
**Key differentiators:**
-
**How we do it differently:**
**Why that's better:**
**Why customers choose us:**
## Objections
| Objection | Response |
|-----------|----------|
| | |
**Anti-persona:**
## Switching Dynamics
**Push:**
**Pull:**
**Habit:**
**Anxiety:**
## Customer Language
**How they describe the problem:**
- "[verbatim]"
**How they describe us:**
- "[verbatim]"
**Words to use:**
**Words to avoid:**
**Glossary:**
| Term | Meaning |
|------|---------|
| | |
## Brand Voice
**Tone:**
**Style:**
**Personality:**
## Proof Points
**Metrics:**
**Customers:**
**Testimonials:**
> "[quote]" — [who]
**Value themes:**
| Theme | Proof |
|-------|-------|
| | |
## Goals
**Business goal:**
**Conversion action:**
**Current metrics:**
## Changelog
*Newest first. One line per revision: what changed and why.*
- v1 ([date]) — Initial context.
```
---
## Step 4: Confirm, Version, and Save
- Show the completed document
- Ask if anything needs adjustment
- **Set the version and changelog** — this is the paper trail for a doc every other skill reads:
- **New document:** set `Document version: v1` and a single Changelog entry — `- v1 ([today]) — Initial context.`
- **Updating an existing document:** increment the version (v2 → v3 …), update `Last updated` to today, and **prepend a new Changelog entry** at the top of the list (newest first) summarizing *what changed and why* in one line. Never rewrite or reorder past entries.
- A good entry names the sections touched and the reason, not "updated the doc." Examples:
- `- v3 (2026-07-16) — Repositioned from "email tool" to "deliverability platform"; added RevOps to the ICP.`
- `- v2 (2026-06-02) — Rewrote value prop and objections after 5 customer interviews; added competitor Acme.`
- Use today's date in ISO form (YYYY-MM-DD) for the entry and `Last updated`.
- **Pure typo-only fix:** don't bump the version or add a changelog entry — just save the correction. Every other change bumps the version and gets an entry. When the change is a real repositioning, say so plainly — downstream skills will now generate against the new context.
- Save to `.agents/product-marketing.md`
- Tell them: "Other marketing skills will now use this context automatically. The Changelog at the bottom tracks every revision — check it to see how your positioning has evolved. Run `/product-marketing` anytime to update it."
---
## Tips
- **Be specific**: Ask "What's the #1 frustration that brings them to you?" not "What problem do they solve?"
- **Capture exact words**: Customer language beats polished descriptions
- **Ask for examples**: "Can you give me an example?" unlocks better answers
- **Validate as you go**: Summarize each section and confirm before moving on
- **Skip what doesn't apply**: Not every product needs all sections (e.g., Personas for B2C)
FILE:evals/evals.json
{
"skill_name": "product-marketing",
"evals": [
{
"id": 1,
"prompt": "I want to set up my product marketing context. We're a B2B SaaS company that sells a customer feedback platform to product teams.",
"expected_output": "Should check if .agents/product-marketing.md already exists. If not, should offer two options: (1) Auto-draft from codebase (recommended) or (2) Start from scratch. If user chooses start from scratch, should walk through sections conversationally one at a time. Should cover all applicable sections: Product Overview, Target Audience, Personas, Problems You Solve, Competitive Landscape, Differentiation, Objections, Switching Dynamics, Customer Language, Brand Voice, Proof Points, and Goals. Should create the file at .agents/product-marketing.md when complete.",
"assertions": [
"Checks for existing product-marketing.md",
"Offers two options: auto-draft or start from scratch",
"Covers applicable sections",
"Walks through sections conversationally one at a time",
"Creates file at .agents/product-marketing.md"
],
"files": []
},
{
"id": 2,
"prompt": "Update our product marketing context. We just added a new enterprise tier and our target audience has expanded to include VP of Engineering, not just Product Managers.",
"expected_output": "Should check for existing .agents/product-marketing.md and read it. Should identify which sections need updating based on the changes: Target Audience (add VP of Engineering), Personas (add new persona), Product Overview (new enterprise tier, including pricing updates within that section), Objections (enterprise-specific), and Competitive Landscape (enterprise competitors). Should update only the relevant sections, preserving existing content that hasn't changed.",
"assertions": [
"Reads existing product-marketing.md",
"Identifies sections that need updating",
"Updates Target Audience with VP of Engineering",
"Adds new persona for the expanded audience",
"Updates Product Overview for enterprise tier",
"Preserves unchanged sections"
],
"files": []
},
{
"id": 3,
"prompt": "create a product context doc for my app. it's a mobile app that helps people find hiking trails. we're just getting started.",
"expected_output": "Should trigger on casual phrasing. Should check for existing context doc. Should offer auto-draft or start-from-scratch options. Should adapt questions for an early-stage B2C mobile app (outdoor/fitness niche). Should note that some sections may be sparse for an early-stage product and that's okay — they can be filled in as the business matures. Should skip non-applicable sections (e.g., Personas section is B2B-focused) rather than forcing all 12. Should accept lighter answers for sections like Proof Points or Competitive Landscape if the company is new.",
"assertions": [
"Triggers on casual phrasing",
"Checks for existing context doc",
"Offers auto-draft or start-from-scratch options",
"Adapts questions for early-stage B2C mobile app",
"Notes some sections may be sparse early on",
"Skips non-applicable sections rather than forcing all 12",
"Creates file at .agents/product-marketing.md"
],
"files": []
},
{
"id": 4,
"prompt": "Can you auto-draft our product marketing context from our existing codebase and marketing materials?",
"expected_output": "Should activate the auto-draft workflow mode. Should scan the codebase for existing marketing context: README, landing page copy, pricing page, about page, meta descriptions, any existing documentation. Should draft the product-marketing.md from what it finds, filling in sections where information is available and flagging sections that need manual input. Should present the draft for review before saving.",
"assertions": [
"Activates auto-draft workflow mode",
"Scans codebase for existing marketing materials",
"Drafts context from found information",
"Flags sections needing manual input",
"Presents draft for review before saving"
],
"files": []
},
{
"id": 5,
"prompt": "Do we have a product marketing context set up? I want to make sure the other marketing skills have context about our product.",
"expected_output": "Should check for .agents/product-marketing.md (and the older .claude/product-marketing.md location). Should report whether it exists and summarize its contents if found. If it doesn't exist, should offer to create one and explain why it's valuable (other skills like copywriting, cro, seo-audit check for it first). Should explain how other skills use this context document.",
"assertions": [
"Checks both file locations",
"Reports whether context doc exists",
"Summarizes contents if found",
"Offers to create if missing",
"Explains how other skills use it"
],
"files": []
},
{
"id": 6,
"prompt": "Write homepage copy for our SaaS product.",
"expected_output": "Should recognize this is a copywriting task, not a product marketing context task. Should check for product-marketing.md (as other skills do), and if it doesn't exist, may suggest creating one first. But should defer to the copywriting skill for actually writing the homepage copy.",
"assertions": [
"Recognizes this as a copywriting task",
"May check for or suggest creating product-marketing.md",
"References or defers to copywriting skill for the actual copy",
"Does not attempt to write homepage copy using context creation patterns"
],
"files": []
},
{
"id": 7,
"prompt": "We just repositioned — we're no longer an 'email tool,' we're a 'deliverability platform,' and our ICP now includes RevOps teams. Update our product marketing context.",
"expected_output": "Should recognize an existing .agents/product-marketing.md, read it, note its current Document version and recent Changelog entries, and update only the affected sections (product overview/positioning, target audience/ICP). On save, should bump the Document version (e.g. v2 → v3), update the Last updated date, and PREPEND a new newest-first Changelog entry summarizing what changed and why in one line — e.g. 'Repositioned from email tool to deliverability platform; added RevOps to the ICP' — naming the sections touched and the reason, not just 'updated the doc.' Should not rewrite or reorder past changelog entries. Should tell the user the changelog tracks revisions and that downstream skills will now use the new context.",
"assertions": [
"Reads the existing doc and surfaces its current version + recent changelog",
"Updates only the affected sections (positioning + ICP)",
"Bumps the Document version and updates Last updated",
"Prepends a newest-first changelog entry naming what changed and why",
"Preserves prior changelog entries unchanged"
],
"files": []
}
]
}
Bộ công cụ chiến lược cho Head of Product: chuỗi OKR, kế hoạch quý, phân tích cảnh quan cạnh tranh, tầm nhìn sản phẩm và đề xuất mở rộng đội ngũ.
---
name: "product-strategist"
description: Strategic product leadership toolkit for Head of Product covering OKR cascade generation, quarterly planning, competitive landscape analysis, product vision documents, and team scaling proposals. Use when creating quarterly OKR documents, defining product goals or KPIs, building product roadmaps, running competitive analysis, drafting team structure or hiring plans, aligning product strategy across engineering and design, or generating cascaded goal hierarchies from company to team level.
---
# Product Strategist
Strategic toolkit for Head of Product to drive vision, alignment, and organizational excellence.
---
## Core Capabilities
| Capability | Description | Tool |
|------------|-------------|------|
| **OKR Cascade** | Generate aligned OKRs from company to team level | `okr_cascade_generator.py` |
| **Alignment Scoring** | Measure vertical and horizontal alignment | Built into generator |
| **Strategy Templates** | 5 pre-built strategy types | Growth, Retention, Revenue, Innovation, Operational |
| **Team Configuration** | Customize for your org structure | `--teams` flag |
---
## Quick Start
```bash
# Growth strategy with default teams
python scripts/okr_cascade_generator.py growth
# Retention strategy with custom teams
python scripts/okr_cascade_generator.py retention --teams "Engineering,Design,Data"
# Revenue strategy with 40% product contribution
python scripts/okr_cascade_generator.py revenue --contribution 0.4
# Export as JSON for integration
python scripts/okr_cascade_generator.py growth --json > okrs.json
```
---
## Workflow: Quarterly Strategic Planning
### Step 1: Define Strategic Focus
| Strategy | When to Use |
|----------|-------------|
| **Growth** | Scaling user base, market expansion |
| **Retention** | Reducing churn, improving LTV |
| **Revenue** | Increasing ARPU, new monetization |
| **Innovation** | Market differentiation, new capabilities |
| **Operational** | Improving efficiency, scaling operations |
See `references/strategy_types.md` for detailed guidance.
### Step 2: Gather Input Metrics
```json
{
"current": 100000, // Current MAU
"target": 150000, // Target MAU
"current_nps": 40, // Current NPS
"target_nps": 60 // Target NPS
}
```
### Step 3: Configure Teams & Run Generator
```bash
# Default teams
python scripts/okr_cascade_generator.py growth
# Custom org structure with contribution percentage
python scripts/okr_cascade_generator.py growth \
--teams "Core,Platform,Mobile,AI" \
--contribution 0.3
```
### Step 4: Review Alignment Scores
| Score | Target | Action if Below |
|-------|--------|-----------------|
| Vertical Alignment | >90% | Ensure all objectives link to parent |
| Horizontal Alignment | >75% | Check for team coordination gaps |
| Coverage | >80% | Validate all company OKRs are addressed |
| Balance | >80% | Redistribute if one team is overloaded |
| **Overall** | **>80%** | <60% needs restructuring |
### Step 5: Refine, Validate, and Export
Before finalizing:
- [ ] Review generated objectives with stakeholders
- [ ] Adjust team assignments based on capacity
- [ ] Validate contribution percentages are realistic
- [ ] Ensure no conflicting objectives across teams
- [ ] Set up tracking cadence (bi-weekly check-ins)
```bash
# Export JSON for tools like Lattice, Ally, Workboard
python scripts/okr_cascade_generator.py growth --json > q1_okrs.json
```
---
## OKR Cascade Generator
### Usage
```bash
python scripts/okr_cascade_generator.py [strategy] [options]
```
**Strategies:** `growth` | `retention` | `revenue` | `innovation` | `operational`
### Configuration Options
| Option | Description | Default |
|--------|-------------|---------|
| `--teams`, `-t` | Comma-separated team names | Growth,Platform,Mobile,Data |
| `--contribution`, `-c` | Product contribution to company OKRs (0-1) | 0.3 (30%) |
| `--json`, `-j` | Output as JSON instead of dashboard | False |
| `--metrics`, `-m` | Metrics as JSON string | Sample metrics |
### Output Examples
#### Dashboard Output (`growth` strategy)
```
============================================================
OKR CASCADE DASHBOARD
Quarter: Q1 2025 | Strategy: GROWTH
Teams: Growth, Platform, Mobile, Data | Product Contribution: 30%
============================================================
🏢 COMPANY OKRS
📌 CO-1: Accelerate user acquisition and market expansion
└─ CO-1-KR1: Increase MAU from 100,000 to 150,000
└─ CO-1-KR2: Achieve 50% MoM growth rate
└─ CO-1-KR3: Expand to 3 new markets
📌 CO-2: Achieve product-market fit in new segments
📌 CO-3: Build sustainable growth engine
🚀 PRODUCT OKRS
📌 PO-1: Build viral product features and market expansion
↳ Supports: CO-1
└─ PO-1-KR1: Increase product MAU to 45,000
└─ PO-1-KR2: Achieve 45% feature adoption rate
👥 TEAM OKRS
Growth Team:
📌 GRO-1: Build viral product features through acquisition and activation
└─ GRO-1-KR1: Increase product MAU to 11,250
└─ GRO-1-KR2: Achieve 11.25% feature adoption rate
🎯 ALIGNMENT SCORES
✓ Vertical Alignment: 100.0%
! Horizontal Alignment: 75.0%
✓ Coverage: 100.0% | ✓ Balance: 97.5% | ✓ Overall: 94.0%
✅ Overall alignment is GOOD (≥80%)
```
#### JSON Output (`retention --json`, truncated)
```json
{
"quarter": "Q1 2025",
"strategy": "retention",
"company": {
"objectives": [
{
"id": "CO-1",
"title": "Create lasting customer value and loyalty",
"key_results": [
{ "id": "CO-1-KR1", "title": "Improve retention from 70% to 85%", "current": 70, "target": 85 }
]
}
]
},
"product": { "contribution": 0.3, "objectives": ["..."] },
"teams": ["..."],
"alignment_scores": {
"vertical_alignment": 100.0, "horizontal_alignment": 75.0,
"coverage": 100.0, "balance": 97.5, "overall": 94.0
}
}
```
See `references/examples/sample_growth_okrs.json` for a complete example.
---
## Reference Documents
| Document | Description |
|----------|-------------|
| `references/okr_framework.md` | OKR methodology, writing guidelines, alignment scoring |
| `references/strategy_types.md` | Detailed breakdown of all 5 strategy types with examples |
| `references/examples/sample_growth_okrs.json` | Complete sample output for growth strategy |
---
## Best Practices
### OKR Cascade
- Limit to 3-5 objectives per level, each with 3-5 key results
- Key results must be measurable with current and target values
- Validate parent-child relationships before finalizing
### Alignment Scoring
- Target >80% overall alignment; investigate any score below 60%
- Balance scores ensure no team is overloaded
- Horizontal alignment prevents conflicting goals across teams
### Team Configuration
- Configure teams to match your actual org structure
- Adjust contribution percentages based on team size
- Platform/Infrastructure teams often support all objectives
- Specialized teams (ML, Data) may only support relevant objectives
## Related Skills
- **Senior PM** (`project-management/senior-pm/`) — Portfolio management and risk analysis inform strategic planning
- **Competitive Teardown** (`product-team/competitive-teardown/`) — Competitive intelligence feeds product strategy
FILE:assets/okr_template.md
# OKR Planning Template
## Planning Info
| Field | Value |
|-------|-------|
| **Quarter** | [Q1/Q2/Q3/Q4 YYYY] |
| **Team** | [Team Name] |
| **Owner** | [Name] |
| **Status** | Planning / Active / Complete |
| **Tracking Cadence** | Weekly check-in, Monthly review |
---
## Company Objective
**[Company-level objective this product work supports]**
_Example: "Become the market leader in our category by delivering exceptional customer value"_
---
## Product Objective 1: [Objective Title]
_[Qualitative, inspirational statement. What does success look like?]_
### Key Results
| # | Key Result | Baseline | Target | Current | Status |
|---|-----------|----------|--------|---------|--------|
| 1.1 | [Measurable outcome] | [Current value] | [Target value] | [Progress] | On Track / At Risk / Off Track |
| 1.2 | [Measurable outcome] | [Current value] | [Target value] | [Progress] | On Track / At Risk / Off Track |
| 1.3 | [Measurable outcome] | [Current value] | [Target value] | [Progress] | On Track / At Risk / Off Track |
### Initiatives
| Initiative | Key Result | Owner | Status | Effort |
|-----------|-----------|-------|--------|--------|
| [Feature/project name] | KR 1.1 | [Name] | Not Started / In Progress / Complete | [T-shirt size] |
| [Feature/project name] | KR 1.2 | [Name] | Not Started / In Progress / Complete | [T-shirt size] |
---
## Product Objective 2: [Objective Title]
_[Qualitative, inspirational statement]_
### Key Results
| # | Key Result | Baseline | Target | Current | Status |
|---|-----------|----------|--------|---------|--------|
| 2.1 | [Measurable outcome] | [Current value] | [Target value] | [Progress] | On Track / At Risk / Off Track |
| 2.2 | [Measurable outcome] | [Current value] | [Target value] | [Progress] | On Track / At Risk / Off Track |
| 2.3 | [Measurable outcome] | [Current value] | [Target value] | [Progress] | On Track / At Risk / Off Track |
| 2.4 | [Measurable outcome] | [Current value] | [Target value] | [Progress] | On Track / At Risk / Off Track |
### Initiatives
| Initiative | Key Result | Owner | Status | Effort |
|-----------|-----------|-------|--------|--------|
| [Feature/project name] | KR 2.1 | [Name] | Not Started / In Progress / Complete | [T-shirt size] |
| [Feature/project name] | KR 2.2 | [Name] | Not Started / In Progress / Complete | [T-shirt size] |
---
## Product Objective 3: [Objective Title]
_[Qualitative, inspirational statement]_
### Key Results
| # | Key Result | Baseline | Target | Current | Status |
|---|-----------|----------|--------|---------|--------|
| 3.1 | [Measurable outcome] | [Current value] | [Target value] | [Progress] | On Track / At Risk / Off Track |
| 3.2 | [Measurable outcome] | [Current value] | [Target value] | [Progress] | On Track / At Risk / Off Track |
| 3.3 | [Measurable outcome] | [Current value] | [Target value] | [Progress] | On Track / At Risk / Off Track |
### Initiatives
| Initiative | Key Result | Owner | Status | Effort |
|-----------|-----------|-------|--------|--------|
| [Feature/project name] | KR 3.1 | [Name] | Not Started / In Progress / Complete | [T-shirt size] |
---
## Tracking
### Weekly Check-In Format
- **Confidence level** (1-10) for each key result
- **Blockers** identified and escalated
- **Adjustments** to initiatives if needed
### Monthly Review Format
- **Progress update** on all key results with data
- **Initiative status** review
- **Risk assessment** and mitigation updates
- **Stakeholder communication** summary
### End-of-Quarter Scoring
Score each key result 0.0 - 1.0:
- **1.0:** Fully achieved
- **0.7:** Strong progress, nearly there (ideal target)
- **0.4:** Meaningful progress but missed target
- **0.0:** No progress
_Note: Consistently scoring 1.0 means OKRs are not ambitious enough. Target 0.6-0.7 average._
FILE:references/examples/sample_growth_okrs.json
{
"metadata": {
"strategy": "growth",
"quarter": "Q1 2025",
"generated_at": "2025-01-15T10:30:00Z",
"teams": ["Growth", "Platform", "Mobile", "Data"],
"product_contribution": 0.3
},
"company": {
"level": "Company",
"quarter": "Q1 2025",
"strategy": "growth",
"objectives": [
{
"id": "CO-1",
"title": "Accelerate user acquisition and market expansion",
"owner": "CEO",
"status": "active",
"key_results": [
{
"id": "CO-1-KR1",
"title": "Increase MAU from 100,000 to 150,000",
"current": 100000,
"target": 150000,
"unit": "users",
"status": "in_progress",
"progress": 0.2
},
{
"id": "CO-1-KR2",
"title": "Achieve 15% MoM growth rate",
"current": 8,
"target": 15,
"unit": "%",
"status": "in_progress",
"progress": 0.53
},
{
"id": "CO-1-KR3",
"title": "Expand to 3 new markets",
"current": 0,
"target": 3,
"unit": "markets",
"status": "not_started",
"progress": 0
}
]
},
{
"id": "CO-2",
"title": "Achieve product-market fit in enterprise segment",
"owner": "CEO",
"status": "active",
"key_results": [
{
"id": "CO-2-KR1",
"title": "Reduce CAC by 25%",
"current": 150,
"target": 112.5,
"unit": "$",
"status": "in_progress",
"progress": 0.4
},
{
"id": "CO-2-KR2",
"title": "Improve activation rate to 60%",
"current": 42,
"target": 60,
"unit": "%",
"status": "in_progress",
"progress": 0.3
}
]
},
{
"id": "CO-3",
"title": "Build sustainable growth engine",
"owner": "CEO",
"status": "active",
"key_results": [
{
"id": "CO-3-KR1",
"title": "Increase viral coefficient to 1.2",
"current": 0.8,
"target": 1.2,
"unit": "coefficient",
"status": "not_started",
"progress": 0
},
{
"id": "CO-3-KR2",
"title": "Grow organic acquisition to 40% of total",
"current": 25,
"target": 40,
"unit": "%",
"status": "in_progress",
"progress": 0.2
}
]
}
]
},
"product": {
"level": "Product",
"quarter": "Q1 2025",
"parent": "Company",
"objectives": [
{
"id": "PO-1",
"title": "Build viral product features to drive acquisition",
"parent_objective": "CO-1",
"owner": "Head of Product",
"status": "active",
"key_results": [
{
"id": "PO-1-KR1",
"title": "Increase product MAU from 100,000 to 115,000 (30% contribution)",
"contributes_to": "CO-1-KR1",
"current": 100000,
"target": 115000,
"unit": "users",
"status": "in_progress"
},
{
"id": "PO-1-KR2",
"title": "Achieve 12% feature adoption rate for sharing features",
"contributes_to": "CO-1-KR2",
"current": 5,
"target": 12,
"unit": "%",
"status": "in_progress"
}
]
},
{
"id": "PO-2",
"title": "Validate product hypotheses for enterprise segment",
"parent_objective": "CO-2",
"owner": "Head of Product",
"status": "active",
"key_results": [
{
"id": "PO-2-KR1",
"title": "Improve product onboarding efficiency by 30%",
"contributes_to": "CO-2-KR1",
"current": 0,
"target": 30,
"unit": "%",
"status": "not_started"
},
{
"id": "PO-2-KR2",
"title": "Increase product activation rate to 55%",
"contributes_to": "CO-2-KR2",
"current": 42,
"target": 55,
"unit": "%",
"status": "in_progress"
}
]
},
{
"id": "PO-3",
"title": "Create product-led growth loops",
"parent_objective": "CO-3",
"owner": "Head of Product",
"status": "active",
"key_results": [
{
"id": "PO-3-KR1",
"title": "Launch referral program with 0.3 viral coefficient contribution",
"contributes_to": "CO-3-KR1",
"current": 0,
"target": 0.3,
"unit": "coefficient",
"status": "not_started"
},
{
"id": "PO-3-KR2",
"title": "Increase product-driven organic signups to 35%",
"contributes_to": "CO-3-KR2",
"current": 20,
"target": 35,
"unit": "%",
"status": "in_progress"
}
]
}
]
},
"teams": [
{
"level": "Team",
"team": "Growth",
"quarter": "Q1 2025",
"parent": "Product",
"objectives": [
{
"id": "GRO-1",
"title": "Build viral product features through acquisition and activation",
"parent_objective": "PO-1",
"owner": "Growth PM",
"status": "active",
"key_results": [
{
"id": "GRO-1-KR1",
"title": "[Growth] Increase product MAU contribution by 5,000 users",
"contributes_to": "PO-1-KR1",
"current": 0,
"target": 5000,
"unit": "users",
"status": "in_progress"
},
{
"id": "GRO-1-KR2",
"title": "[Growth] Launch 3 viral feature experiments",
"contributes_to": "PO-1-KR2",
"current": 0,
"target": 3,
"unit": "experiments",
"status": "not_started"
}
]
}
]
},
{
"level": "Team",
"team": "Platform",
"quarter": "Q1 2025",
"parent": "Product",
"objectives": [
{
"id": "PLA-1",
"title": "Support growth through infrastructure and reliability",
"parent_objective": "PO-1",
"owner": "Platform PM",
"status": "active",
"key_results": [
{
"id": "PLA-1-KR1",
"title": "[Platform] Scale infrastructure to support 200K MAU",
"contributes_to": "PO-1-KR1",
"current": 100000,
"target": 200000,
"unit": "users",
"status": "in_progress"
},
{
"id": "PLA-1-KR2",
"title": "[Platform] Maintain 99.9% uptime during growth",
"contributes_to": "PO-1-KR2",
"current": 99.5,
"target": 99.9,
"unit": "%",
"status": "in_progress"
}
]
},
{
"id": "PLA-2",
"title": "Improve onboarding infrastructure efficiency",
"parent_objective": "PO-2",
"owner": "Platform PM",
"status": "active",
"key_results": [
{
"id": "PLA-2-KR1",
"title": "[Platform] Reduce onboarding API latency by 40%",
"contributes_to": "PO-2-KR1",
"current": 0,
"target": 40,
"unit": "%",
"status": "not_started"
}
]
}
]
},
{
"level": "Team",
"team": "Mobile",
"quarter": "Q1 2025",
"parent": "Product",
"objectives": [
{
"id": "MOB-1",
"title": "Build viral features through mobile experience",
"parent_objective": "PO-1",
"owner": "Mobile PM",
"status": "active",
"key_results": [
{
"id": "MOB-1-KR1",
"title": "[Mobile] Increase mobile MAU by 3,000 users",
"contributes_to": "PO-1-KR1",
"current": 0,
"target": 3000,
"unit": "users",
"status": "not_started"
},
{
"id": "MOB-1-KR2",
"title": "[Mobile] Launch native share feature with 15% adoption",
"contributes_to": "PO-1-KR2",
"current": 0,
"target": 15,
"unit": "%",
"status": "not_started"
}
]
}
]
},
{
"level": "Team",
"team": "Data",
"quarter": "Q1 2025",
"parent": "Product",
"objectives": [
{
"id": "DAT-1",
"title": "Enable growth through analytics and insights",
"parent_objective": "PO-1",
"owner": "Data PM",
"status": "active",
"key_results": [
{
"id": "DAT-1-KR1",
"title": "[Data] Build growth dashboard tracking all acquisition metrics",
"contributes_to": "PO-1-KR1",
"current": 0,
"target": 1,
"unit": "dashboard",
"status": "not_started"
},
{
"id": "DAT-1-KR2",
"title": "[Data] Implement experimentation platform for A/B testing",
"contributes_to": "PO-1-KR2",
"current": 0,
"target": 1,
"unit": "platform",
"status": "not_started"
}
]
}
]
}
],
"alignment_scores": {
"vertical_alignment": 100.0,
"horizontal_alignment": 75.0,
"coverage": 100.0,
"balance": 85.0,
"overall": 92.0
},
"summary": {
"total_objectives": 11,
"total_key_results": 22,
"company_objectives": 3,
"product_objectives": 3,
"team_objectives": 5,
"teams_involved": 4
}
}
FILE:references/okr_framework.md
# OKR Cascade Framework
A practical guide to Objectives and Key Results (OKRs) and how to cascade them across organizational levels.
---
## Table of Contents
- [What Are OKRs](#what-are-okrs)
- [The Cascade Model](#the-cascade-model)
- [Writing Effective Objectives](#writing-effective-objectives)
- [Defining Key Results](#defining-key-results)
- [Alignment Scoring](#alignment-scoring)
- [Common Pitfalls](#common-pitfalls)
- [OKR Cadence](#okr-cadence)
---
## What Are OKRs
**Objectives and Key Results (OKRs)** are a goal-setting framework that connects organizational strategy to measurable outcomes.
### Components
| Component | Definition | Characteristics |
|-----------|------------|-----------------|
| **Objective** | What you want to achieve | Qualitative, inspirational, time-bound |
| **Key Result** | How you measure progress | Quantitative, specific, measurable |
### OKR Formula
```
Objective: [Inspirational goal statement]
├── KR1: [Metric] from [current] to [target] by [date]
├── KR2: [Metric] from [current] to [target] by [date]
└── KR3: [Metric] from [current] to [target] by [date]
```
### Example
```
Objective: Become the go-to solution for enterprise customers
KR1: Increase enterprise ARR from $5M to $8M
KR2: Improve enterprise NPS from 35 to 50
KR3: Reduce enterprise onboarding time from 30 days to 14 days
```
---
## The Cascade Model
OKRs cascade from company strategy down to individual teams, ensuring alignment at every level.
### Cascade Structure
```
┌─────────────────────────────────────────┐
│ COMPANY LEVEL │
│ Strategic objectives set by leadership │
│ Owned by: CEO, Executive Team │
└───────────────┬─────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ PRODUCT LEVEL │
│ How product org contributes to company │
│ Owned by: Head of Product, CPO │
└───────────────┬─────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ TEAM LEVEL │
│ Specific initiatives and deliverables │
│ Owned by: Product Managers, Tech Leads │
└─────────────────────────────────────────┘
```
### Contribution Model
Each level contributes a percentage to the level above:
| Level | Typical Contribution | Range |
|-------|---------------------|-------|
| Product → Company | 30% | 20-50% |
| Team → Product | 25% per team | 15-35% |
**Note:** Contribution percentages should be calibrated based on:
- Number of teams
- Relative team size
- Strategic importance of initiatives
### Alignment Types
| Alignment | Description | Goal |
|-----------|-------------|------|
| **Vertical** | Each level supports the level above | >90% of objectives linked |
| **Horizontal** | Teams coordinate on shared objectives | No conflicting goals |
| **Temporal** | Quarterly OKRs support annual goals | Clear progression |
---
## Writing Effective Objectives
### The 3 Cs of Objectives
| Criterion | Description | Example |
|-----------|-------------|---------|
| **Clear** | Unambiguous intent | "Improve customer onboarding" not "Make things better" |
| **Compelling** | Inspires action | "Delight enterprise customers" not "Serve enterprise" |
| **Challenging** | Stretches capabilities | Achievable but requires effort |
### Objective Templates by Strategy
**Growth Strategy:**
```
- Accelerate user acquisition in [segment]
- Expand market presence in [region/vertical]
- Build sustainable acquisition channels
```
**Retention Strategy:**
```
- Create lasting value for [user segment]
- Improve product experience for [use case]
- Maximize customer lifetime value
```
**Revenue Strategy:**
```
- Drive revenue growth through [mechanism]
- Optimize monetization for [segment]
- Expand revenue per customer
```
**Innovation Strategy:**
```
- Pioneer [capability] in the market
- Establish leadership through [innovation area]
- Build competitive differentiation
```
**Operational Strategy:**
```
- Improve delivery efficiency by [mechanism]
- Scale operations to support [target]
- Reduce operational friction in [area]
```
### Objective Anti-Patterns
| Anti-Pattern | Problem | Better Alternative |
|--------------|---------|-------------------|
| "Increase revenue" | Too vague | "Grow enterprise ARR to $10M" |
| "Be the best" | Not measurable | "Achieve #1 NPS in category" |
| "Fix bugs" | Too tactical | "Improve platform reliability" |
| "Launch feature X" | Output, not outcome | "Improve [metric] through [capability]" |
---
## Defining Key Results
### Key Result Anatomy
```
[Verb] [metric] from [current baseline] to [target] by [deadline]
```
### Key Result Types
| Type | Characteristics | When to Use |
|------|-----------------|-------------|
| **Metric-based** | Track a number | Most common, highly measurable |
| **Milestone-based** | Track completion | For binary deliverables |
| **Health-based** | Track stability | For maintenance objectives |
### Metric Categories
| Category | Examples |
|----------|----------|
| **Acquisition** | Signups, trials started, leads generated |
| **Activation** | Onboarding completion, first value moment |
| **Retention** | D7/D30 retention, churn rate, repeat usage |
| **Revenue** | ARR, ARPU, conversion rate, LTV |
| **Engagement** | DAU/MAU, session duration, actions per session |
| **Satisfaction** | NPS, CSAT, support tickets |
| **Efficiency** | Cycle time, automation rate, cost per unit |
### Key Result Scoring
| Score | Status | Description |
|-------|--------|-------------|
| 0.0-0.3 | Red | Significant gap, needs intervention |
| 0.4-0.6 | Yellow | Partial progress, on watch |
| 0.7-0.9 | Green | Strong progress, on track |
| 1.0 | Complete | Target achieved |
**Note:** Hitting 0.7 is considered success for stretch goals. Consistently hitting 1.0 suggests targets aren't ambitious enough.
---
## Alignment Scoring
The OKR cascade generator calculates alignment scores across four dimensions:
### Scoring Dimensions
| Dimension | Weight | What It Measures |
|-----------|--------|------------------|
| **Vertical Alignment** | 40% | % of objectives with parent links |
| **Horizontal Alignment** | 20% | Cross-team coordination on shared goals |
| **Coverage** | 20% | % of company KRs addressed by product |
| **Balance** | 20% | Even distribution of work across teams |
### Alignment Score Interpretation
| Score | Grade | Interpretation |
|-------|-------|----------------|
| 90-100% | A | Excellent alignment, well-cascaded |
| 80-89% | B | Good alignment, minor gaps |
| 70-79% | C | Adequate, needs attention |
| 60-69% | D | Poor alignment, significant gaps |
| <60% | F | Misaligned, requires restructuring |
### Target Benchmarks
| Metric | Target | Red Flag |
|--------|--------|----------|
| Vertical alignment | >90% | <70% |
| Horizontal alignment | >75% | <50% |
| Coverage | >80% | <60% |
| Balance | >80% | <60% |
| Overall | >80% | <65% |
---
## Common Pitfalls
### OKR Anti-Patterns
| Pitfall | Symptom | Fix |
|---------|---------|-----|
| **Too many OKRs** | 10+ objectives per level | Limit to 3-5 objectives |
| **Sandbagging** | Always hit 100% | Set stretch targets (0.7 = success) |
| **Task lists** | KRs are tasks, not outcomes | Focus on measurable impact |
| **Set and forget** | No mid-quarter reviews | Check-ins every 2 weeks |
| **Cascade disconnect** | Team OKRs don't link up | Validate parent relationships |
| **Metric gaming** | Optimizing for KR, not intent | Balance with health metrics |
### Warning Signs
- All teams have identical objectives (lack of specialization)
- No team owns a critical company objective (gap in coverage)
- One team owns everything (unrealistic load)
- Objectives change weekly (lack of commitment)
- KRs are activities, not outcomes (wrong focus)
---
## OKR Cadence
### Quarterly Rhythm
| Week | Activity |
|------|----------|
| **Week -2** | Leadership sets company OKRs draft |
| **Week -1** | Product and team OKR drafting |
| **Week 0** | OKR finalization and alignment review |
| **Week 2** | First check-in, adjust if needed |
| **Week 6** | Mid-quarter review |
| **Week 10** | Pre-quarter reflection |
| **Week 12** | Quarter close, scoring, learnings |
### Check-in Format
```
Weekly/Bi-weekly Status Update:
1. Confidence level: [Red/Yellow/Green]
2. Progress since last check-in: [specific updates]
3. Blockers: [what's in the way]
4. Asks: [what help is needed]
5. Forecast: [expected end-of-quarter score]
```
### Annual Alignment
Quarterly OKRs should ladder up to annual goals:
```
Annual Goal: Become a $100M ARR business
Q1: Build enterprise sales motion (ARR: $25M → $32M)
Q2: Expand into APAC region (ARR: $32M → $45M)
Q3: Launch self-serve enterprise tier (ARR: $45M → $65M)
Q4: Scale and optimize (ARR: $65M → $100M)
```
---
## Quick Reference
### OKR Checklist
**Before finalizing OKRs:**
- [ ] 3-5 objectives per level (not more)
- [ ] 3-5 key results per objective
- [ ] Each KR has a current baseline and target
- [ ] Vertical alignment validated (parent links)
- [ ] No conflicting objectives across teams
- [ ] Owners assigned to every objective
- [ ] Check-in cadence defined
**During the quarter:**
- [ ] Bi-weekly progress updates
- [ ] Mid-quarter formal review
- [ ] Adjust forecasts based on learnings
- [ ] Escalate blockers early
**End of quarter:**
- [ ] Score all key results (0.0-1.0)
- [ ] Document learnings
- [ ] Celebrate wins
- [ ] Carry forward or close incomplete items
---
*See also: `strategy_types.md` for strategy-specific OKR templates*
FILE:references/strategy_types.md
# Strategy Types for OKR Generation
Comprehensive breakdown of the five core strategy types with objectives, key results, and when to use each.
---
## Table of Contents
- [Strategy Selection Guide](#strategy-selection-guide)
- [Growth Strategy](#growth-strategy)
- [Retention Strategy](#retention-strategy)
- [Revenue Strategy](#revenue-strategy)
- [Innovation Strategy](#innovation-strategy)
- [Operational Strategy](#operational-strategy)
- [Multi-Strategy Combinations](#multi-strategy-combinations)
---
## Strategy Selection Guide
### Decision Matrix
| If your priority is... | Primary Strategy | Secondary Strategy |
|------------------------|------------------|-------------------|
| Scaling user base | Growth | Retention |
| Reducing churn | Retention | Revenue |
| Increasing ARPU | Revenue | Retention |
| Market differentiation | Innovation | Growth |
| Improving efficiency | Operational | Revenue |
| New market entry | Growth | Innovation |
### Strategy by Company Stage
| Stage | Typical Priority | Rationale |
|-------|------------------|-----------|
| **Pre-PMF** | Innovation | Finding product-market fit |
| **Early Growth** | Growth | Scaling acquisition |
| **Growth** | Growth + Retention | Balancing acquisition with value |
| **Scale** | Revenue + Retention | Optimizing unit economics |
| **Mature** | Operational + Revenue | Efficiency and margins |
---
## Growth Strategy
**Focus:** Accelerating user acquisition and market expansion
### When to Use
- User growth is primary company objective
- Product-market fit is validated
- Acquisition channels are scaling
- Ready to invest in growth loops
### Company-Level Objectives
| Objective | Key Results Template |
|-----------|---------------------|
| Accelerate user acquisition and market expansion | - Increase MAU from X to Y<br>- Achieve Z% MoM growth rate<br>- Expand to N new markets |
| Achieve product-market fit in new segments | - Reach X users in [segment]<br>- Achieve Y% activation rate<br>- Validate Z use cases |
| Build sustainable growth engine | - Reduce CAC by X%<br>- Improve viral coefficient to Y<br>- Increase organic share to Z% |
### Product-Level Cascade
| Product Objective | Supports | Key Results |
|-------------------|----------|-------------|
| Build viral product features | User acquisition | - Launch referral program (target: X referrals/user)<br>- Increase shareability by Y% |
| Optimize onboarding experience | Activation | - Improve activation rate from X% to Y%<br>- Reduce time-to-value by Z% |
| Create product-led growth loops | Sustainable growth | - Increase product-qualified leads by X%<br>- Improve trial-to-paid by Y% |
### Team-Level Examples
| Team | Focus Area | Sample KRs |
|------|------------|------------|
| Growth Team | Acquisition & activation | - Improve signup conversion by X%<br>- Launch Y experiments/week |
| Platform Team | Scale & reliability | - Support X concurrent users<br>- Maintain Y% uptime |
| Mobile Team | Mobile acquisition | - Increase mobile signups by X%<br>- Improve mobile activation by Y% |
### Key Metrics to Track
- Monthly Active Users (MAU)
- Growth rate (MoM, YoY)
- Customer Acquisition Cost (CAC)
- Activation rate
- Viral coefficient
- Channel efficiency
---
## Retention Strategy
**Focus:** Creating lasting customer value and reducing churn
### When to Use
- Churn is above industry benchmark
- LTV/CAC needs improvement
- Product stickiness is low
- Expansion revenue is a priority
### Company-Level Objectives
| Objective | Key Results Template |
|-----------|---------------------|
| Create lasting customer value and loyalty | - Improve retention from X% to Y%<br>- Increase NPS from X to Y<br>- Reduce churn to below Z% |
| Deliver a superior user experience | - Achieve X% product stickiness<br>- Improve satisfaction to Y/10<br>- Reduce support tickets by Z% |
| Maximize customer lifetime value | - Increase LTV by X%<br>- Improve LTV/CAC ratio to Y<br>- Grow expansion revenue by Z% |
### Product-Level Cascade
| Product Objective | Supports | Key Results |
|-------------------|----------|-------------|
| Design sticky user experiences | Customer retention | - Increase DAU/MAU ratio from X to Y<br>- Improve weekly return rate by Z% |
| Build habit-forming features | Product stickiness | - Achieve X% feature adoption<br>- Increase sessions/user by Y |
| Create expansion opportunities | Lifetime value | - Launch N upsell touchpoints<br>- Improve upgrade rate by X% |
### Team-Level Examples
| Team | Focus Area | Sample KRs |
|------|------------|------------|
| Growth Team | Retention loops | - Improve D7 retention by X%<br>- Reduce first-week churn by Y% |
| Data Team | Churn prediction | - Build churn model (accuracy >X%)<br>- Identify Y at-risk signals |
| Platform Team | Reliability | - Reduce error rates by X%<br>- Improve load times by Y% |
### Key Metrics to Track
- Retention rates (D1, D7, D30, D90)
- Churn rate
- Net Promoter Score (NPS)
- Customer Satisfaction (CSAT)
- Feature stickiness
- Session frequency
---
## Revenue Strategy
**Focus:** Driving sustainable revenue growth and monetization
### When to Use
- Company is focused on profitability
- Monetization needs optimization
- Pricing strategy is being revised
- Expansion revenue is priority
### Company-Level Objectives
| Objective | Key Results Template |
|-----------|---------------------|
| Drive sustainable revenue growth | - Grow ARR from $X to $Y<br>- Achieve Z% revenue growth rate<br>- Maintain X% gross margin |
| Optimize monetization strategy | - Increase ARPU by X%<br>- Improve pricing efficiency by Y%<br>- Launch Z new pricing tiers |
| Expand revenue per customer | - Grow expansion revenue by X%<br>- Reduce revenue churn to Y%<br>- Increase upsell rate by Z% |
### Product-Level Cascade
| Product Objective | Supports | Key Results |
|-------------------|----------|-------------|
| Optimize product monetization | Revenue growth | - Improve conversion to paid by X%<br>- Reduce free tier abuse by Y% |
| Build premium features | ARPU growth | - Launch N premium features<br>- Achieve X% premium adoption |
| Create value-based pricing alignment | Pricing efficiency | - Implement usage-based pricing<br>- Improve price-to-value ratio by X% |
### Team-Level Examples
| Team | Focus Area | Sample KRs |
|------|------------|------------|
| Growth Team | Conversion | - Improve trial-to-paid by X%<br>- Reduce time-to-upgrade by Y days |
| Platform Team | Usage metering | - Implement accurate usage tracking<br>- Support X billing scenarios |
| Data Team | Revenue analytics | - Build revenue forecasting model<br>- Identify Y expansion signals |
### Key Metrics to Track
- Annual Recurring Revenue (ARR)
- Average Revenue Per User (ARPU)
- Gross margin
- Revenue churn (net and gross)
- Expansion revenue
- LTV/CAC ratio
---
## Innovation Strategy
**Focus:** Building competitive advantage through product innovation
### When to Use
- Market is commoditizing
- Competitors are catching up
- New technology opportunity exists
- Company needs differentiation
### Company-Level Objectives
| Objective | Key Results Template |
|-----------|---------------------|
| Lead the market through product innovation | - Launch X breakthrough features<br>- Achieve Y% revenue from new products<br>- File Z patents/IP |
| Establish market leadership in [area] | - Become #1 in category for X<br>- Win Y analyst recognitions<br>- Achieve Z% awareness |
| Build sustainable competitive moat | - Reduce feature parity gap by X%<br>- Create Y unique capabilities<br>- Build Z switching barriers |
### Product-Level Cascade
| Product Objective | Supports | Key Results |
|-------------------|----------|-------------|
| Ship innovative features faster | Breakthrough innovation | - Reduce time-to-market by X%<br>- Launch Y experiments/quarter |
| Build unique technical capabilities | Competitive moat | - Develop X proprietary algorithms<br>- Achieve Y performance advantage |
| Create platform extensibility | Ecosystem advantage | - Launch N API endpoints<br>- Enable X third-party integrations |
### Team-Level Examples
| Team | Focus Area | Sample KRs |
|------|------------|------------|
| Platform Team | Core technology | - Build X new infrastructure capabilities<br>- Improve performance by Y% |
| Data Team | ML/AI innovation | - Deploy X ML models<br>- Improve prediction accuracy by Y% |
| Mobile Team | Mobile innovation | - Launch X mobile-first features<br>- Achieve Y% mobile parity |
### Key Metrics to Track
- Time-to-market
- Revenue from new products
- Feature uniqueness score
- Patent/IP filings
- Technology differentiation
- Innovation velocity
---
## Operational Strategy
**Focus:** Improving efficiency and organizational excellence
### When to Use
- Scaling challenges are emerging
- Operational costs are high
- Team productivity needs improvement
- Quality issues are increasing
### Company-Level Objectives
| Objective | Key Results Template |
|-----------|---------------------|
| Improve organizational efficiency | - Improve velocity by X%<br>- Reduce cycle time to Y days<br>- Achieve Z% automation |
| Scale operations sustainably | - Support X users per engineer<br>- Reduce cost per transaction by Y%<br>- Improve operational leverage by Z% |
| Achieve operational excellence | - Reduce incidents by X%<br>- Improve team NPS to Y<br>- Achieve Z% on-time delivery |
### Product-Level Cascade
| Product Objective | Supports | Key Results |
|-------------------|----------|-------------|
| Improve product delivery efficiency | Velocity | - Reduce PR cycle time by X%<br>- Increase deployment frequency by Y% |
| Reduce operational toil | Automation | - Automate X% of manual processes<br>- Reduce on-call burden by Y% |
| Improve product quality | Excellence | - Reduce bugs by X%<br>- Improve test coverage to Y% |
### Team-Level Examples
| Team | Focus Area | Sample KRs |
|------|------------|------------|
| Platform Team | Infrastructure efficiency | - Reduce infrastructure costs by X%<br>- Improve deployment reliability to Y% |
| Data Team | Data operations | - Improve data pipeline reliability to X%<br>- Reduce data latency by Y% |
| All Teams | Process improvement | - Reduce meeting overhead by X%<br>- Improve sprint predictability to Y% |
### Key Metrics to Track
- Velocity (story points, throughput)
- Cycle time
- Deployment frequency
- Change failure rate
- Incident count and MTTR
- Team satisfaction (eNPS)
---
## Multi-Strategy Combinations
### Common Pairings
| Primary | Secondary | Balanced Objectives |
|---------|-----------|---------------------|
| Growth + Retention | 60/40 | Grow while keeping users |
| Revenue + Retention | 50/50 | Monetize without churning |
| Innovation + Growth | 40/60 | Differentiate to acquire |
| Operational + Revenue | 50/50 | Efficiency for margins |
### Balanced OKR Set Example
**Mixed Growth + Retention Strategy:**
```
Company Objective 1: Accelerate user growth (Growth)
├── KR1: Increase MAU from 100K to 200K
├── KR2: Achieve 15% MoM growth rate
└── KR3: Reduce CAC by 20%
Company Objective 2: Improve user retention (Retention)
├── KR1: Improve D30 retention from 20% to 35%
├── KR2: Increase NPS from 40 to 55
└── KR3: Reduce churn to below 5%
Company Objective 3: Improve delivery efficiency (Operational)
├── KR1: Reduce cycle time by 30%
├── KR2: Achieve 95% on-time delivery
└── KR3: Improve team eNPS to 50
```
---
## Strategy Selection Checklist
Before choosing a strategy:
- [ ] What is the company's #1 priority this quarter?
- [ ] What metrics is leadership being evaluated on?
- [ ] Where are the biggest gaps vs. competitors?
- [ ] What does customer feedback emphasize?
- [ ] What can we realistically move in 90 days?
---
*See also: `okr_framework.md` for OKR writing best practices*
FILE:scripts/okr_cascade_generator.py
#!/usr/bin/env python3
"""
OKR Cascade Generator
Creates aligned OKRs from company strategy down to team level.
Features:
- Generates company → product → team OKR cascade
- Configurable team structure and contribution percentages
- Alignment scoring across vertical and horizontal dimensions
- Multiple output formats (dashboard, JSON)
Usage:
python okr_cascade_generator.py growth
python okr_cascade_generator.py retention --teams "Engineering,Design,Data"
python okr_cascade_generator.py revenue --contribution 0.4 --json
"""
import json
import argparse
from typing import Dict, List
from datetime import datetime
class OKRGenerator:
"""Generate and cascade OKRs across the organization"""
def __init__(self, teams: List[str] = None, product_contribution: float = 0.3):
"""
Initialize OKR generator.
Args:
teams: List of team names (default: Growth, Platform, Mobile, Data)
product_contribution: Fraction of company KRs that product owns (default: 0.3)
"""
self.teams = teams or ['Growth', 'Platform', 'Mobile', 'Data']
self.product_contribution = product_contribution
self.okr_templates = {
'growth': {
'objectives': [
'Accelerate user acquisition and market expansion',
'Achieve product-market fit in new segments',
'Build sustainable growth engine'
],
'key_results': [
'Increase MAU from {current} to {target}',
'Achieve {target}% MoM growth rate',
'Expand to {target} new markets',
'Reduce CAC by {target}%',
'Improve activation rate to {target}%'
]
},
'retention': {
'objectives': [
'Create lasting customer value and loyalty',
'Deliver a superior user experience',
'Maximize customer lifetime value'
],
'key_results': [
'Improve retention from {current}% to {target}%',
'Increase NPS from {current} to {target}',
'Reduce churn to below {target}%',
'Achieve {target}% product stickiness',
'Increase LTV/CAC ratio to {target}'
]
},
'revenue': {
'objectives': [
'Drive sustainable revenue growth',
'Optimize monetization strategy',
'Expand revenue per customer'
],
'key_results': [
'Grow ARR from currentM to targetM',
'Increase ARPU by {target}%',
'Launch {target} new revenue streams',
'Achieve {target}% gross margin',
'Reduce revenue churn to {target}%'
]
},
'innovation': {
'objectives': [
'Lead the market through product innovation',
'Establish leadership in key capability areas',
'Build sustainable competitive differentiation'
],
'key_results': [
'Launch {target} breakthrough features',
'Achieve {target}% of revenue from new products',
'File {target} patents/IP',
'Reduce time-to-market by {target}%',
'Achieve {target} innovation score'
]
},
'operational': {
'objectives': [
'Improve organizational efficiency',
'Achieve operational excellence',
'Scale operations sustainably'
],
'key_results': [
'Improve velocity by {target}%',
'Reduce cycle time to {target} days',
'Achieve {target}% automation',
'Improve team satisfaction to {target}',
'Reduce incidents by {target}%'
]
}
}
# Team focus areas for objective relevance matching
self.team_relevance = {
'Growth': ['acquisition', 'growth', 'activation', 'viral', 'onboarding', 'conversion'],
'Platform': ['infrastructure', 'reliability', 'scale', 'performance', 'efficiency', 'automation'],
'Mobile': ['mobile', 'app', 'ios', 'android', 'native'],
'Data': ['analytics', 'metrics', 'insights', 'data', 'measurement', 'experimentation'],
'Engineering': ['delivery', 'velocity', 'quality', 'automation', 'infrastructure'],
'Design': ['experience', 'usability', 'interface', 'user', 'accessibility'],
'Product': ['features', 'roadmap', 'prioritization', 'strategy'],
}
def generate_company_okrs(self, strategy: str, metrics: Dict) -> Dict:
"""Generate company-level OKRs based on strategy"""
if strategy not in self.okr_templates:
strategy = 'growth'
template = self.okr_templates[strategy]
company_okrs = {
'level': 'Company',
'quarter': self._get_current_quarter(),
'strategy': strategy,
'objectives': []
}
for i in range(min(3, len(template['objectives']))):
obj = {
'id': f'CO-{i+1}',
'title': template['objectives'][i],
'key_results': [],
'owner': 'CEO',
'status': 'draft'
}
for j in range(3):
if j < len(template['key_results']):
kr_template = template['key_results'][j]
kr = {
'id': f'CO-{i+1}-KR{j+1}',
'title': self._fill_metrics(kr_template, metrics),
'current': metrics.get('current', 0),
'target': metrics.get('target', 100),
'unit': self._extract_unit(kr_template),
'status': 'not_started'
}
obj['key_results'].append(kr)
company_okrs['objectives'].append(obj)
return company_okrs
def cascade_to_product(self, company_okrs: Dict) -> Dict:
"""Cascade company OKRs to product organization"""
product_okrs = {
'level': 'Product',
'quarter': company_okrs['quarter'],
'parent': 'Company',
'contribution': self.product_contribution,
'objectives': []
}
for company_obj in company_okrs['objectives']:
product_obj = {
'id': f'PO-{company_obj["id"].split("-")[1]}',
'title': self._translate_to_product(company_obj['title']),
'parent_objective': company_obj['id'],
'key_results': [],
'owner': 'Head of Product',
'status': 'draft'
}
for kr in company_obj['key_results']:
product_kr = {
'id': f'PO-{product_obj["id"].split("-")[1]}-KR{kr["id"].split("KR")[1]}',
'title': self._translate_kr_to_product(kr['title']),
'contributes_to': kr['id'],
'current': kr['current'],
'target': kr['target'] * self.product_contribution,
'unit': kr['unit'],
'contribution_pct': self.product_contribution * 100,
'status': 'not_started'
}
product_obj['key_results'].append(product_kr)
product_okrs['objectives'].append(product_obj)
return product_okrs
def cascade_to_teams(self, product_okrs: Dict) -> List[Dict]:
"""Cascade product OKRs to individual teams"""
team_okrs = []
team_contribution = 1.0 / len(self.teams) if self.teams else 0.25
for team in self.teams:
team_okr = {
'level': 'Team',
'team': team,
'quarter': product_okrs['quarter'],
'parent': 'Product',
'contribution': team_contribution,
'objectives': []
}
for product_obj in product_okrs['objectives']:
if self._is_relevant_for_team(product_obj['title'], team):
team_obj = {
'id': f'{team[:3].upper()}-{product_obj["id"].split("-")[1]}',
'title': self._translate_to_team(product_obj['title'], team),
'parent_objective': product_obj['id'],
'key_results': [],
'owner': f'{team} PM',
'status': 'draft'
}
for kr in product_obj['key_results'][:2]:
team_kr = {
'id': f'{team[:3].upper()}-{team_obj["id"].split("-")[1]}-KR{kr["id"].split("KR")[1]}',
'title': self._translate_kr_to_team(kr['title'], team),
'contributes_to': kr['id'],
'current': kr['current'],
'target': kr['target'] * team_contribution,
'unit': kr['unit'],
'status': 'not_started'
}
team_obj['key_results'].append(team_kr)
team_okr['objectives'].append(team_obj)
if team_okr['objectives']:
team_okrs.append(team_okr)
return team_okrs
def generate_okr_dashboard(self, all_okrs: Dict) -> str:
"""Generate OKR dashboard view"""
dashboard = ["=" * 60]
dashboard.append("OKR CASCADE DASHBOARD")
dashboard.append(f"Quarter: {all_okrs.get('quarter', 'Q1 2025')}")
dashboard.append(f"Strategy: {all_okrs.get('strategy', 'growth').upper()}")
dashboard.append(f"Teams: {', '.join(self.teams)}")
dashboard.append(f"Product Contribution: {self.product_contribution * 100:.0f}%")
dashboard.append("=" * 60)
# Company OKRs
if 'company' in all_okrs:
dashboard.append("\n🏢 COMPANY OKRS\n")
for obj in all_okrs['company']['objectives']:
dashboard.append(f"📌 {obj['id']}: {obj['title']}")
for kr in obj['key_results']:
dashboard.append(f" └─ {kr['id']}: {kr['title']}")
# Product OKRs
if 'product' in all_okrs:
dashboard.append("\n🚀 PRODUCT OKRS\n")
for obj in all_okrs['product']['objectives']:
dashboard.append(f"📌 {obj['id']}: {obj['title']}")
dashboard.append(f" ↳ Supports: {obj.get('parent_objective', 'N/A')}")
for kr in obj['key_results']:
dashboard.append(f" └─ {kr['id']}: {kr['title']}")
# Team OKRs
if 'teams' in all_okrs:
dashboard.append("\n👥 TEAM OKRS\n")
for team_okr in all_okrs['teams']:
dashboard.append(f"\n{team_okr['team']} Team:")
for obj in team_okr['objectives']:
dashboard.append(f" 📌 {obj['id']}: {obj['title']}")
for kr in obj['key_results']:
dashboard.append(f" └─ {kr['id']}: {kr['title']}")
# Alignment Matrix
dashboard.append("\n\n📊 ALIGNMENT MATRIX\n")
dashboard.append("Company → Product → Teams")
dashboard.append("-" * 40)
if 'company' in all_okrs and 'product' in all_okrs:
for c_obj in all_okrs['company']['objectives']:
dashboard.append(f"\n{c_obj['id']}")
for p_obj in all_okrs['product']['objectives']:
if p_obj.get('parent_objective') == c_obj['id']:
dashboard.append(f" ├─ {p_obj['id']}")
if 'teams' in all_okrs:
for team_okr in all_okrs['teams']:
for t_obj in team_okr['objectives']:
if t_obj.get('parent_objective') == p_obj['id']:
dashboard.append(f" └─ {t_obj['id']} ({team_okr['team']})")
return "\n".join(dashboard)
def calculate_alignment_score(self, all_okrs: Dict) -> Dict:
"""Calculate alignment score across OKR cascade"""
scores = {
'vertical_alignment': 0,
'horizontal_alignment': 0,
'coverage': 0,
'balance': 0,
'overall': 0
}
# Vertical alignment: How well each level supports the above
total_objectives = 0
aligned_objectives = 0
if 'product' in all_okrs:
for obj in all_okrs['product']['objectives']:
total_objectives += 1
if 'parent_objective' in obj:
aligned_objectives += 1
if 'teams' in all_okrs:
for team in all_okrs['teams']:
for obj in team['objectives']:
total_objectives += 1
if 'parent_objective' in obj:
aligned_objectives += 1
if total_objectives > 0:
scores['vertical_alignment'] = round((aligned_objectives / total_objectives) * 100, 1)
# Horizontal alignment: How well teams coordinate
if 'teams' in all_okrs and len(all_okrs['teams']) > 1:
shared_objectives = set()
for team in all_okrs['teams']:
for obj in team['objectives']:
parent = obj.get('parent_objective')
if parent:
shared_objectives.add(parent)
scores['horizontal_alignment'] = min(100, len(shared_objectives) * 25)
# Coverage: How much of company OKRs are covered
if 'company' in all_okrs and 'product' in all_okrs:
company_krs = sum(len(obj['key_results']) for obj in all_okrs['company']['objectives'])
covered_krs = sum(len(obj['key_results']) for obj in all_okrs['product']['objectives'])
if company_krs > 0:
scores['coverage'] = round((covered_krs / company_krs) * 100, 1)
# Balance: Distribution across teams
if 'teams' in all_okrs:
objectives_per_team = [len(team['objectives']) for team in all_okrs['teams']]
if objectives_per_team:
avg_objectives = sum(objectives_per_team) / len(objectives_per_team)
variance = sum((x - avg_objectives) ** 2 for x in objectives_per_team) / len(objectives_per_team)
scores['balance'] = round(max(0, 100 - variance * 10), 1)
# Overall score
scores['overall'] = round(sum([
scores['vertical_alignment'] * 0.4,
scores['horizontal_alignment'] * 0.2,
scores['coverage'] * 0.2,
scores['balance'] * 0.2
]), 1)
return scores
def _get_current_quarter(self) -> str:
"""Get current quarter"""
now = datetime.now()
quarter = (now.month - 1) // 3 + 1
return f"Q{quarter} {now.year}"
def _fill_metrics(self, template: str, metrics: Dict) -> str:
"""Fill template with actual metrics"""
result = template
for key, value in metrics.items():
result = result.replace(f'{{{key}}}', str(value))
return result
def _extract_unit(self, kr_template: str) -> str:
"""Extract measurement unit from KR template"""
if '%' in kr_template:
return '%'
elif '$' in kr_template:
return '$'
elif 'days' in kr_template.lower():
return 'days'
elif 'score' in kr_template.lower():
return 'points'
return 'count'
def _translate_to_product(self, company_objective: str) -> str:
"""Translate company objective to product objective"""
translations = {
'Accelerate user acquisition': 'Build viral product features',
'Achieve product-market fit': 'Validate product hypotheses',
'Build sustainable growth': 'Create product-led growth loops',
'Create lasting customer value': 'Design sticky user experiences',
'Drive sustainable revenue': 'Optimize product monetization',
'Lead the market through': 'Ship innovative features to',
'Improve organizational': 'Improve product delivery'
}
for key, value in translations.items():
if key in company_objective:
return company_objective.replace(key, value)
return f"Product: {company_objective}"
def _translate_kr_to_product(self, kr: str) -> str:
"""Translate KR to product context"""
product_terms = {
'MAU': 'product MAU',
'growth rate': 'feature adoption rate',
'CAC': 'product onboarding efficiency',
'retention': 'product retention',
'NPS': 'product NPS',
'ARR': 'product-driven revenue',
'churn': 'product churn'
}
result = kr
for term, replacement in product_terms.items():
if term in result:
result = result.replace(term, replacement)
break
return result
def _translate_to_team(self, objective: str, team: str) -> str:
"""Translate objective to team context"""
team_focus = {
'Growth': 'acquisition and activation',
'Platform': 'infrastructure and reliability',
'Mobile': 'mobile experience',
'Data': 'analytics and insights',
'Engineering': 'technical delivery',
'Design': 'user experience',
'Product': 'product strategy'
}
focus = team_focus.get(team, 'delivery')
return f"{objective} through {focus}"
def _translate_kr_to_team(self, kr: str, team: str) -> str:
"""Translate KR to team context"""
return f"[{team}] {kr}"
def _is_relevant_for_team(self, objective: str, team: str) -> bool:
"""Check if objective is relevant for team"""
keywords = self.team_relevance.get(team, [])
objective_lower = objective.lower()
# Platform is always relevant (infrastructure supports everything)
if team == 'Platform':
return True
return any(keyword in objective_lower for keyword in keywords)
def parse_teams(teams_str: str) -> List[str]:
"""Parse comma-separated team string into list"""
if not teams_str:
return None
return [t.strip() for t in teams_str.split(',') if t.strip()]
def main():
parser = argparse.ArgumentParser(
description='Generate OKR cascade from company strategy to team level',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Generate growth strategy OKRs with default teams
python okr_cascade_generator.py growth
# Custom teams
python okr_cascade_generator.py retention --teams "Engineering,Design,Data,Growth"
# Custom product contribution percentage
python okr_cascade_generator.py revenue --contribution 0.4
# JSON output
python okr_cascade_generator.py innovation --json
# All options combined
python okr_cascade_generator.py operational --teams "Core,Platform" --contribution 0.5 --json
"""
)
parser.add_argument(
'strategy',
nargs='?',
choices=['growth', 'retention', 'revenue', 'innovation', 'operational'],
default='growth',
help='Strategy type (default: growth)'
)
parser.add_argument(
'--teams', '-t',
type=str,
help='Comma-separated list of team names (default: Growth,Platform,Mobile,Data)'
)
parser.add_argument(
'--contribution', '-c',
type=float,
default=0.3,
help='Product contribution to company OKRs as decimal (default: 0.3 = 30%%)'
)
parser.add_argument(
'--json', '-j',
action='store_true',
help='Output as JSON instead of dashboard'
)
parser.add_argument(
'--metrics', '-m',
type=str,
help='Metrics as JSON string (default: sample metrics)'
)
args = parser.parse_args()
# Parse teams
teams = parse_teams(args.teams)
# Parse metrics
if args.metrics:
metrics = json.loads(args.metrics)
else:
metrics = {
'current': 100000,
'target': 150000,
'current_revenue': 10,
'target_revenue': 15,
'current_nps': 40,
'target_nps': 60
}
# Validate contribution
if not 0 < args.contribution <= 1:
print("Error: Contribution must be between 0 and 1")
return 1
# Generate OKRs
generator = OKRGenerator(teams=teams, product_contribution=args.contribution)
company_okrs = generator.generate_company_okrs(args.strategy, metrics)
product_okrs = generator.cascade_to_product(company_okrs)
team_okrs = generator.cascade_to_teams(product_okrs)
all_okrs = {
'quarter': company_okrs['quarter'],
'strategy': args.strategy,
'company': company_okrs,
'product': product_okrs,
'teams': team_okrs
}
alignment = generator.calculate_alignment_score(all_okrs)
if args.json:
all_okrs['alignment_scores'] = alignment
all_okrs['config'] = {
'teams': generator.teams,
'product_contribution': generator.product_contribution
}
print(json.dumps(all_okrs, indent=2))
else:
dashboard = generator.generate_okr_dashboard(all_okrs)
print(dashboard)
print("\n\n🎯 ALIGNMENT SCORES")
print("-" * 40)
for metric, score in alignment.items():
status = "✓" if score >= 80 else "!" if score >= 60 else "✗"
print(f"{status} {metric.replace('_', ' ').title()}: {score}%")
if alignment['overall'] >= 80:
print("\n✅ Overall alignment is GOOD (≥80%)")
elif alignment['overall'] >= 60:
print("\n⚠️ Overall alignment NEEDS ATTENTION (60-80%)")
else:
print("\n❌ Overall alignment is POOR (<60%)")
if __name__ == "__main__":
main()
Tạo hàng loạt trang SEO bằng template và dữ liệu: trang vị trí, so sánh, tích hợp, thư mục và các trang theo mẫu từ khóa + thành phố.
---
name: programmatic-seo
description: When the user wants to create SEO-driven pages at scale using templates and data. Also use when the user mentions "programmatic SEO," "template pages," "pages at scale," "directory pages," "location pages," "[keyword] + [city] pages," "comparison pages," "integration pages," "building many pages for SEO," "pSEO," "generate 100 pages," "data-driven pages," or "templated landing pages." Use this whenever someone wants to create many similar pages targeting different keywords or locations. For auditing existing SEO issues, see seo-audit. For content strategy planning, see content-strategy.
metadata:
version: 2.0.0
---
# Programmatic SEO
You are an expert in programmatic SEO—building SEO-optimized pages at scale using templates and data. Your goal is to create pages that rank, provide value, and avoid thin content penalties.
## Initial Assessment
**Check for product marketing context first:**
If `.agents/product-marketing.md` exists (or `.claude/product-marketing.md`, or the legacy `product-marketing-context.md` filename, in older setups), read it before asking questions. Use that context and only ask for information not already covered or specific to this task.
Before designing a programmatic SEO strategy, understand:
1. **Business Context**
- What's the product/service?
- Who is the target audience?
- What's the conversion goal for these pages?
2. **Opportunity Assessment**
- What search patterns exist?
- How many potential pages?
- What's the search volume distribution?
3. **Competitive Landscape**
- Who ranks for these terms now?
- What do their pages look like?
- Can you realistically compete?
---
## Core Principles
### 1. Unique Value Per Page
- Every page must provide value specific to that page
- Not just swapped variables in a template
- Maximize unique content—the more differentiated, the better
### 2. Proprietary Data Wins
Hierarchy of data defensibility:
1. Proprietary (you created it)
2. Product-derived (from your users)
3. User-generated (your community)
4. Licensed (exclusive access)
5. Public (anyone can use—weakest)
### 3. Clean URL Structure
**Use subfolders, not subdomains** — subfolders consolidate domain authority while subdomains split it:
- Good: `yoursite.com/templates/resume/`
- Bad: `templates.yoursite.com/resume/`
### 4. Genuine Search Intent Match
Pages must actually answer what people are searching for.
### 5. Quality Over Quantity
Better to have 100 great pages than 10,000 thin ones.
### 6. Avoid Google Penalties
- No doorway pages
- No keyword stuffing
- No duplicate content
- Genuine utility for users
---
## The 12 Playbooks (Overview)
| Playbook | Pattern | Example |
|----------|---------|---------|
| Templates | "[Type] template" | "resume template" |
| Curation | "best [category]" | "best website builders" |
| Conversions | "[X] to [Y]" | "$10 USD to GBP" |
| Comparisons | "[X] vs [Y]" | "webflow vs wordpress" |
| Examples | "[type] examples" | "landing page examples" |
| Locations | "[service] in [location]" | "dentists in austin" |
| Personas | "[product] for [audience]" | "crm for real estate" |
| Integrations | "[product A] [product B] integration" | "slack asana integration" |
| Glossary | "what is [term]" | "what is pSEO" |
| Translations | Content in multiple languages | Localized content |
| Directory | "[category] tools" | "ai copywriting tools" |
| Profiles | "[entity name]" | "stripe ceo" |
**For detailed playbook implementation**: See [references/playbooks.md](references/playbooks.md)
---
## Choosing Your Playbook
| If you have... | Consider... |
|----------------|-------------|
| Proprietary data | Directories, Profiles |
| Product with integrations | Integrations |
| Design/creative product | Templates, Examples |
| Multi-segment audience | Personas |
| Local presence | Locations |
| Tool or utility product | Conversions |
| Content/expertise | Glossary, Curation |
| Competitor landscape | Comparisons |
You can layer multiple playbooks (e.g., "Best coworking spaces in San Diego").
---
## Implementation Framework
### 1. Keyword Pattern Research
**Identify the pattern:**
- What's the repeating structure?
- What are the variables?
- How many unique combinations exist?
**Validate demand:**
- Aggregate search volume
- Volume distribution (head vs. long tail)
- Trend direction
### 2. Data Requirements
**Identify data sources:**
- What data populates each page?
- Is it first-party, scraped, licensed, public?
- How is it updated?
### 3. Template Design
**Page structure:**
- Header with target keyword
- Unique intro (not just variables swapped)
- Data-driven sections
- Related pages / internal links
- CTAs appropriate to intent
**Ensuring uniqueness:**
- Each page needs unique value
- Conditional content based on data
- Original insights/analysis per page
### 4. Internal Linking Architecture
**Hub and spoke model:**
- Hub: Main category page
- Spokes: Individual programmatic pages
- Cross-links between related spokes
**Avoid orphan pages:**
- Every page reachable from main site
- XML sitemap for all pages
- Breadcrumbs with structured data
### 5. Indexation Strategy
- Prioritize high-volume patterns
- Noindex very thin variations
- Manage crawl budget thoughtfully
- Separate sitemaps by page type
---
## Quality Checks
### Pre-Launch Checklist
**Content quality:**
- [ ] Each page provides unique value
- [ ] Answers search intent
- [ ] Readable and useful
**Technical SEO:**
- [ ] Unique titles and meta descriptions
- [ ] Proper heading structure
- [ ] Schema markup implemented
- [ ] Page speed acceptable
**Internal linking:**
- [ ] Connected to site architecture
- [ ] Related pages linked
- [ ] No orphan pages
**Indexation:**
- [ ] In XML sitemap
- [ ] Crawlable
- [ ] No conflicting noindex
### Post-Launch Monitoring
Track: Indexation rate, Rankings, Traffic, Engagement, Conversion
Watch for: Thin content warnings, Ranking drops, Manual actions, Crawl errors
---
## Common Mistakes
- **Thin content**: Just swapping city names in identical content
- **Keyword cannibalization**: Multiple pages targeting same keyword
- **Over-generation**: Creating pages with no search demand
- **Poor data quality**: Outdated or incorrect information
- **Ignoring UX**: Pages exist for Google, not users
---
## Output Format
### Strategy Document
- Opportunity analysis
- Implementation plan
- Content guidelines
### Page Template
- URL structure
- Title/meta templates
- Content outline
- Schema markup
---
## Task-Specific Questions
1. What keyword patterns are you targeting?
2. What data do you have (or can acquire)?
3. How many pages are you planning?
4. What does your site authority look like?
5. Who currently ranks for these terms?
6. What's your technical stack?
---
## Related Skills
- **seo-audit**: For auditing programmatic pages after launch
- **schema**: For adding structured data
- **site-architecture**: For page hierarchy, URL structure, and internal linking
- **competitors**: For comparison page frameworks
FILE:evals/evals.json
{
"skill_name": "programmatic-seo",
"evals": [
{
"id": 1,
"prompt": "We want to create programmatic SEO pages for our CRM. We're thinking of 'CRM for [industry]' pages — like 'CRM for Real Estate,' 'CRM for Healthcare,' etc. How should we approach this?",
"expected_output": "Should check for product-marketing.md first. Should identify this as the Personas playbook (industry-specific pages). Should apply the core principles: unique value per page (not just swapping the industry name), proprietary data or insights per industry, clean URL structure. Should recommend the implementation framework: keyword research for each industry variation, data requirements (what industry-specific content makes each page unique), template design, internal linking strategy between industry pages and main pages, and indexation strategy. Should warn against thin content (just template + keyword swap).",
"assertions": [
"Checks for product-marketing.md",
"Identifies as Personas playbook",
"Applies core principles (unique value, proprietary data, clean URLs)",
"Recommends keyword research per variation",
"Addresses data requirements for unique content",
"Provides template design guidance",
"Includes internal linking strategy",
"Warns against thin content"
],
"files": []
},
{
"id": 2,
"prompt": "Create a comparison page strategy. We want pages like 'Notion vs Asana', 'Notion vs Monday', etc. for all our competitors. We have 15 competitors.",
"expected_output": "Should identify this as the Comparisons playbook. Should apply the programmatic approach for competitor comparison pages at scale. Should recommend: template structure for comparison pages, unique data per comparison (not just the same template with names swapped), keyword research for each '[competitor A] vs [competitor B]' variation, URL structure (/compare/notion-vs-asana), internal linking between comparison pages, and quality checks. Should cross-reference the competitors skill for page content structure.",
"assertions": [
"Identifies as Comparisons playbook",
"Recommends template structure for scale",
"Addresses unique data per comparison",
"Includes keyword research for variations",
"Provides URL structure recommendation",
"Includes internal linking strategy",
"Cross-references competitors skill",
"Applies quality checks"
],
"files": []
},
{
"id": 3,
"prompt": "we want to rank for '[tool name] integration' keywords. we integrate with 50+ tools and want a page for each. like 'Slack integration', 'Salesforce integration' etc.",
"expected_output": "Should trigger on casual phrasing. Should identify this as the Integrations playbook. Should recommend: template design for integration pages (what it does, how to set up, use cases), unique content per integration (specific workflows, screenshots, setup steps), keyword research for '[tool] + [your product] integration', URL structure (/integrations/slack), hub page linking to all integration pages, and schema markup considerations. Should emphasize that each page needs genuine unique value, not just 'we integrate with [tool].'",
"assertions": [
"Triggers on casual phrasing",
"Identifies as Integrations playbook",
"Recommends template with unique content per integration",
"Includes setup steps and use cases per page",
"Provides URL structure recommendation",
"Recommends hub page for all integrations",
"Emphasizes genuine unique value per page"
],
"files": []
},
{
"id": 4,
"prompt": "We built 500 programmatic pages but Google isn't indexing most of them. Only 80 are in the index. What's going wrong?",
"expected_output": "Should diagnose the indexation problem. Should apply the quality checks and indexation strategy guidance. Should investigate: thin content (are pages providing unique value or just template + keyword?), crawl budget (500 pages may be fine but depends on site authority), internal linking (are the pages discoverable?), XML sitemap inclusion, duplicate/near-duplicate content issues. Should recommend specific fixes: improve content uniqueness, strengthen internal linking, submit sitemap, check robots.txt, use Search Console for indexation requests. Should warn that Google may choose not to index thin pages regardless.",
"assertions": [
"Diagnoses indexation problem",
"Investigates thin content as likely cause",
"Checks crawl budget considerations",
"Checks internal linking to programmatic pages",
"Checks XML sitemap and robots.txt",
"Recommends specific fixes for indexation",
"Warns about Google's thin content policies"
],
"files": []
},
{
"id": 5,
"prompt": "Help me create a glossary section for our marketing automation platform. We want to define 200+ marketing terms and rank for '[term] definition' keywords.",
"expected_output": "Should identify this as the Glossary playbook. Should apply the template design: term definition page template (definition, examples, related terms, how it applies to the user's product), hub/index page linking to all terms, URL structure (/glossary/[term]), alphabetical and categorical navigation. Should address quality: each definition should provide genuine value beyond a dictionary definition. Should include internal linking strategy and schema markup (DefinedTerm schema). Should recommend starting with highest-volume terms.",
"assertions": [
"Identifies as Glossary playbook",
"Provides template design for term pages",
"Recommends hub/index page",
"Provides URL structure",
"Addresses content quality beyond dictionary definitions",
"Includes internal linking strategy",
"Recommends starting with highest-volume terms"
],
"files": []
},
{
"id": 6,
"prompt": "Can you audit our existing programmatic SEO pages for technical issues? We have crawl errors and some pages return 404s.",
"expected_output": "Should recognize this is a technical SEO audit task, not a programmatic SEO strategy task. Should defer to or cross-reference the seo-audit skill, which handles crawlability, indexation, and technical SEO issues. Programmatic-seo focuses on strategy, template design, and content planning for scaled pages.",
"assertions": [
"Recognizes this as technical SEO audit task",
"References or defers to seo-audit skill",
"Explains that programmatic-seo is for strategy and template design",
"Does not attempt full technical SEO audit"
],
"files": []
}
]
}
FILE:references/playbooks.md
# The 12 Programmatic SEO Playbooks
Beyond mixing and matching data point permutations, these are the proven playbooks for programmatic SEO.
## Contents
- 1. Templates
- 2. Curation
- 3. Conversions
- 4. Comparisons
- 5. Examples
- 6. Locations
- 7. Personas
- 8. Integrations
- 9. Glossary
- 10. Translations
- 11. Directory
- 12. Profiles
- Choosing Your Playbook (Match to Your Assets, Combine Playbooks)
## 1. Templates
**Pattern**: "[Type] template" or "free [type] template"
**Example searches**: "resume template", "invoice template", "pitch deck template"
**What it is**: Downloadable or interactive templates users can use directly.
**Why it works**:
- High intent—people need it now
- Shareable/linkable assets
- Natural for product-led companies
**Value requirements**:
- Actually usable templates (not just previews)
- Multiple variations per type
- Quality comparable to paid options
- Easy download/use flow
**URL structure**: `/templates/[type]/` or `/templates/[category]/[type]/`
---
## 2. Curation
**Pattern**: "best [category]" or "top [number] [things]"
**Example searches**: "best website builders", "top 10 crm software", "best free design tools"
**What it is**: Curated lists ranking or recommending options in a category.
**Why it works**:
- Comparison shoppers searching for guidance
- High commercial intent
- Evergreen with updates
**Value requirements**:
- Genuine evaluation criteria
- Real testing or expertise
- Regular updates (date visible)
- Not just affiliate-driven rankings
**URL structure**: `/best/[category]/` or `/[category]/best/`
---
## 3. Conversions
**Pattern**: "[X] to [Y]" or "[amount] [unit] in [unit]"
**Example searches**: "$10 USD to GBP", "100 kg to lbs", "pdf to word"
**What it is**: Tools or pages that convert between formats, units, or currencies.
**Why it works**:
- Instant utility
- Extremely high search volume
- Repeat usage potential
**Value requirements**:
- Accurate, real-time data
- Fast, functional tool
- Related conversions suggested
- Mobile-friendly interface
**URL structure**: `/convert/[from]-to-[to]/` or `/[from]-to-[to]-converter/`
---
## 4. Comparisons
**Pattern**: "[X] vs [Y]" or "[X] alternative"
**Example searches**: "webflow vs wordpress", "notion vs coda", "figma alternatives"
**What it is**: Head-to-head comparisons between products, tools, or options.
**Why it works**:
- High purchase intent
- Clear search pattern
- Scales with number of competitors
**Value requirements**:
- Honest, balanced analysis
- Actual feature comparison data
- Clear recommendation by use case
- Updated when products change
**URL structure**: `/compare/[x]-vs-[y]/` or `/[x]-vs-[y]/`
*See also: competitors skill for detailed frameworks*
---
## 5. Examples
**Pattern**: "[type] examples" or "[category] inspiration"
**Example searches**: "saas landing page examples", "email subject line examples", "portfolio website examples"
**What it is**: Galleries or collections of real-world examples for inspiration.
**Why it works**:
- Research phase traffic
- Highly shareable
- Natural for design/creative tools
**Value requirements**:
- Real, high-quality examples
- Screenshots or embeds
- Categorization/filtering
- Analysis of why they work
**URL structure**: `/examples/[type]/` or `/[type]-examples/`
---
## 6. Locations
**Pattern**: "[service/thing] in [location]"
**Example searches**: "coworking spaces in san diego", "dentists in austin", "best restaurants in brooklyn"
**What it is**: Location-specific pages for services, businesses, or information.
**Why it works**:
- Local intent is massive
- Scales with geography
- Natural for marketplaces/directories
**Value requirements**:
- Actual local data (not just city name swapped)
- Local providers/options listed
- Location-specific insights (pricing, regulations)
- Map integration helpful
**URL structure**: `/[service]/[city]/` or `/locations/[city]/[service]/`
---
## 7. Personas
**Pattern**: "[product] for [audience]" or "[solution] for [role/industry]"
**Example searches**: "payroll software for agencies", "crm for real estate", "project management for freelancers"
**What it is**: Tailored landing pages addressing specific audience segments.
**Why it works**:
- Speaks directly to searcher's context
- Higher conversion than generic pages
- Scales with personas
**Value requirements**:
- Genuine persona-specific content
- Relevant features highlighted
- Testimonials from that segment
- Use cases specific to audience
**URL structure**: `/for/[persona]/` or `/solutions/[industry]/`
---
## 8. Integrations
**Pattern**: "[your product] [other product] integration" or "[product] + [product]"
**Example searches**: "slack asana integration", "zapier airtable", "hubspot salesforce sync"
**What it is**: Pages explaining how your product works with other tools.
**Why it works**:
- Captures users of other products
- High intent (they want the solution)
- Scales with integration ecosystem
**Value requirements**:
- Real integration details
- Setup instructions
- Use cases for the combination
- Working integration (not vaporware)
**URL structure**: `/integrations/[product]/` or `/connect/[product]/`
---
## 9. Glossary
**Pattern**: "what is [term]" or "[term] definition" or "[term] meaning"
**Example searches**: "what is pSEO", "api definition", "what does crm stand for"
**What it is**: Educational definitions of industry terms and concepts.
**Why it works**:
- Top-of-funnel awareness
- Establishes expertise
- Natural internal linking opportunities
**Value requirements**:
- Clear, accurate definitions
- Examples and context
- Related terms linked
- More depth than a dictionary
**URL structure**: `/glossary/[term]/` or `/learn/[term]/`
---
## 10. Translations
**Pattern**: Same content in multiple languages
**Example searches**: "qué es pSEO", "was ist SEO", "マーケティングとは"
**What it is**: Your content translated and localized for other language markets.
**Why it works**:
- Opens entirely new markets
- Lower competition in many languages
- Multiplies your content reach
**Value requirements**:
- Quality translation (not just Google Translate)
- Cultural localization
- hreflang tags properly implemented
- Native speaker review
**URL structure**: `/[lang]/[page]/` or `yoursite.com/es/`, `/de/`, etc.
---
## 11. Directory
**Pattern**: "[category] tools" or "[type] software" or "[category] companies"
**Example searches**: "ai copywriting tools", "email marketing software", "crm companies"
**What it is**: Comprehensive directories listing options in a category.
**Why it works**:
- Research phase capture
- Link building magnet
- Natural for aggregators/reviewers
**Value requirements**:
- Comprehensive coverage
- Useful filtering/sorting
- Details per listing (not just names)
- Regular updates
**URL structure**: `/directory/[category]/` or `/[category]-directory/`
---
## 12. Profiles
**Pattern**: "[person/company name]" or "[entity] + [attribute]"
**Example searches**: "stripe ceo", "airbnb founding story", "elon musk companies"
**What it is**: Profile pages about notable people, companies, or entities.
**Why it works**:
- Informational intent traffic
- Builds topical authority
- Natural for B2B, news, research
**Value requirements**:
- Accurate, sourced information
- Regularly updated
- Unique insights or aggregation
- Not just Wikipedia rehash
**URL structure**: `/people/[name]/` or `/companies/[name]/`
---
## Choosing Your Playbook
### Match to Your Assets
| If you have... | Consider... |
|----------------|-------------|
| Proprietary data | Stats, Directories, Profiles |
| Product with integrations | Integrations |
| Design/creative product | Templates, Examples |
| Multi-segment audience | Personas |
| Local presence | Locations |
| Tool or utility product | Conversions |
| Content/expertise | Glossary, Curation |
| International potential | Translations |
| Competitor landscape | Comparisons |
### Combine Playbooks
You can layer multiple playbooks:
- **Locations + Personas**: "Marketing agencies for startups in Austin"
- **Curation + Locations**: "Best coworking spaces in San Diego"
- **Integrations + Personas**: "Slack for sales teams"
- **Glossary + Translations**: Multi-language educational content
Quản lý prompt ở quy mô production: phiên bản, A/B test, registry, chống hồi quy và pipeline đánh giá cho tính năng AI.
---
name: prompt-governance
description: "Use when managing prompts in production at scale: versioning prompts, running A/B tests on prompts, building prompt registries, preventing prompt regressions, or creating eval pipelines for production AI features. Triggers: 'manage prompts in production', 'prompt versioning', 'prompt regression', 'prompt A/B test', 'prompt registry', 'eval pipeline'. NOT for writing or improving individual prompts (use senior-prompt-engineer). NOT for RAG pipeline design (use rag-architect). NOT for LLM cost reduction (use llm-cost-optimizer)."
---
# Prompt Governance
> Originally contributed by [chad848](https://github.com/chad848) — enhanced and integrated by the claude-skills team.
You are an expert in production prompt engineering and AI feature governance. Your goal is to treat prompts as first-class infrastructure -- versioned, tested, evaluated, and deployed with the same rigor as application code. You prevent quality regressions, enable safe iteration, and give teams confidence that prompt changes will not break production.
Prompts are code. They change behavior in production. Ship them like code.
## Before Starting
**Check for context first:** If project-context.md exists, read it before asking questions. Pull the AI tech stack, deployment patterns, and any existing prompt management approach.
Gather this context (ask in one shot):
### 1. Current State
- How are prompts currently stored? (hardcoded in code, config files, database, prompt management tool?)
- How many distinct prompts are in production?
- Has a prompt change ever caused a quality regression you did not catch before users reported it?
### 2. Goals
- What is the primary pain? (versioning chaos, no evals, blind A/B testing, slow iteration?)
- Team size and prompt ownership model? (one engineer owns all prompts vs. many contributors?)
- Tooling constraints? (open-source only, existing CI/CD, cloud provider?)
### 3. AI Stack
- LLM provider(s) in use?
- Frameworks in use? (LangChain, LlamaIndex, custom, direct API?)
- Existing test/CI infrastructure?
## How This Skill Works
### Mode 1: Build Prompt Registry
No centralized prompt management today. Design and implement a prompt registry with versioning, environment promotion, and audit trail.
### Mode 2: Build Eval Pipeline
Prompts are stored somewhere but there is no systematic quality testing. Build an evaluation pipeline that catches regressions before production.
### Mode 3: Governed Iteration
Registry and evals exist. Design the full governance workflow: branch, test, eval, review, promote -- with rollback capability.
---
## Mode 1: Build Prompt Registry
**What a prompt registry provides:**
- Single source of truth for all prompts
- Version history with rollback
- Environment promotion (dev to staging to prod)
- Audit trail (who changed what, when, why)
- Variable/template management
### Minimum Viable Registry (File-Based)
For small teams: structured files in version control.
Directory layout:
```
prompts/
registry.yaml # Index of all prompts
summarizer/
v1.0.0.md # Prompt content
v1.1.0.md
classifier/
v1.0.0.md
qa-bot/
v2.1.0.md
```
Registry YAML schema:
```yaml
prompts:
- id: summarizer
description: "Summarize support tickets for agent triage"
owner: platform-team
model: claude-sonnet-4-5
versions:
- version: 1.1.0
file: summarizer/v1.1.0.md
status: production
promoted_at: 2026-03-15
promoted_by: eng@company.com
- version: 1.0.0
file: summarizer/v1.0.0.md
status: archived
```
### Production Registry (Database-Backed)
For larger teams: API-accessible prompt registry with key tables for prompts and prompt_versions tracking slug, content, model, environment, eval_score, and promotion metadata.
To initialize a file-based registry, create the directory structure above and populate the registry YAML with your existing prompts, their current versions, and ownership metadata.
---
## Mode 2: Build Eval Pipeline
**The problem:** Prompt changes are deployed by feel. There is no systematic way to know if a new prompt is better or worse than the current one.
**The solution:** Automated evals that run on every prompt change, similar to unit tests.
### Eval Types
| Type | What it measures | When to use |
|---|---|---|
| **Exact match** | Output equals expected string | Classification, extraction, structured output |
| **Contains check** | Output includes required elements | Key point extraction, summaries |
| **LLM-as-judge** | Another LLM scores quality 1-5 | Open-ended generation, tone, helpfulness |
| **Semantic similarity** | Embedding similarity to golden answer | Paraphrase-tolerant comparisons |
| **Schema validation** | Output conforms to JSON schema | Structured output tasks |
| **Human eval** | Human rates 1-5 on criteria | High-stakes, launch gates |
### Golden Dataset Design
Every prompt needs a golden dataset: a fixed set of input/expected-output pairs that define correct behavior.
Golden dataset requirements:
- Minimum 20 examples for basic coverage, 100+ for production confidence
- Cover edge cases and failure modes, not just happy path
- Reviewed and approved by domain expert, not just the engineer who wrote the prompt
- Versioned alongside the prompt (a prompt change may require golden set updates)
### Eval Pipeline Implementation
The eval runner accepts a prompt version and golden dataset, calls the LLM for each example, evaluates the response against expected output, and returns a result with pass_rate, avg_score, and failure details.
Pass thresholds (calibrate to your use case):
- Classification/extraction: 95% or higher exact match
- Summarization: 0.85 or higher LLM-as-judge score
- Structured output: 100% schema validation
- Open-ended generation: 80% or higher human eval approval
To execute evals, build a runner that iterates through the golden dataset, calls the LLM with the prompt version under test, scores each response against the expected output, and reports aggregate pass rate and failure details.
---
## Mode 3: Governed Iteration
The full prompt deployment lifecycle with gates at each stage:
1. **BRANCH** -- Create feature branch for prompt change
2. **DEVELOP** -- Edit prompt in dev environment, manual testing
3. **EVAL** -- Run eval pipeline vs. golden dataset (automated in CI)
4. **COMPARE** -- Compare new prompt eval score vs. current production score
5. **REVIEW** -- PR review: eval results plus diff of prompt changes
6. **PROMOTE** -- Staging to Production with approval gate
7. **MONITOR** -- Watch production metrics for 24-48h post-deploy
8. **ROLLBACK** -- One-command rollback to previous version if needed
### A/B Testing Prompts
When you want to measure real-user impact, not just eval scores:
- Use stable assignment (same user always gets same variant, based on user_id hash)
- Log every assignment with user_id, prompt_slug, and variant for analysis
- Define success metric before starting (not after)
- Run for minimum 1 week or 1,000 requests per variant
- Check for novelty effect (first-day engagement spike)
- Statistical significance: p<0.05 before declaring a winner
- Monitor latency and cost alongside quality
### Rollback Playbook
One-command rollback promotes the previous version back to production status in the registry, then verify by re-running evals against the restored version.
---
## Proactive Triggers
Surface these without being asked:
- **Prompts hardcoded in application code** -- Prompt changes require code deploys. This slows iteration and mixes concerns. Flag immediately.
- **No golden dataset for production prompts** -- You are flying blind. Any prompt change could silently regress quality.
- **Eval pass rate declining over time** -- Model updates can silently break prompts. Scheduled evals catch this before users do.
- **No prompt rollback capability** -- If a bad prompt reaches production, the team is stuck until a new deploy. Always have rollback.
- **One person owns all prompt knowledge** -- Bus factor risk. Prompt registry and docs equal knowledge that survives team changes.
- **Prompt changes deployed without eval** -- Every uneval'd deploy is a bet. Flag when the team skips evals "just this once."
---
## Output Artifacts
| When you ask for... | You get... |
|---|---|
| Registry design | File structure, schema, promotion workflow, and implementation guidance |
| Eval pipeline | Golden dataset template, eval runner approach, pass threshold recommendations |
| A/B test setup | Variant assignment logic, measurement plan, success metrics, and analysis template |
| Prompt diff review | Side-by-side comparison with eval score delta and deployment recommendation |
| Governance policy | Team-facing policy doc: ownership model, review requirements, deployment gates |
---
## Communication
All output follows the structured standard:
- **Bottom line first** -- risk or recommendation before explanation
- **What + Why + How** -- every finding has all three
- **Actions have owners and deadlines** -- no "the team should consider..."
- **Confidence tagging** -- verified / medium / assumed
---
## Anti-Patterns
| Anti-Pattern | Why It Fails | Better Approach |
|---|---|---|
| Hardcoding prompts in application source code | Prompt changes require code deploys, slowing iteration and coupling concerns | Store prompts in a versioned registry separate from application code |
| Deploying prompt changes without running evals | Silent quality regressions reach users undetected | Gate every prompt change on automated eval pipeline pass before promotion |
| Using a single golden dataset forever | As the product evolves, the golden set drifts from real usage patterns | Review and update the golden dataset quarterly, adding new edge cases from production failures |
| One person owns all prompt knowledge | Bus factor of 1 — when that person leaves, prompt context is lost | Document prompts in a registry with ownership, rationale, and version history |
| A/B testing without a pre-defined success metric | Post-hoc metric selection introduces bias and inconclusive results | Define the primary success metric and sample size requirement before starting the test |
| Skipping rollback capability | A bad prompt in production with no rollback forces an emergency code deploy | Every prompt version promotion must have a one-command rollback to the previous version |
## Related Skills
- **senior-prompt-engineer**: Use when writing or improving individual prompts. NOT for managing prompts in production at scale (that is this skill).
- **llm-cost-optimizer**: Use when reducing LLM API spend. Pairs with this skill -- evals catch quality regressions when you route to cheaper models.
- **rag-architect**: Use when designing retrieval pipelines. Pairs with this skill for governing RAG system prompts and retrieval prompts separately.
- **ci-cd-pipeline-builder**: Use when building CI/CD pipelines. Pairs with this skill for automating eval runs in CI.
- **observability-designer**: Use when designing monitoring. Pairs with this skill for production prompt quality dashboards.
Tìm, đánh giá và lập danh sách khách hàng tiềm năng để tiếp cận, cho B2B SaaS, B2B nói chung hoặc doanh nghiệp nhỏ tại địa phương.
---
name: prospecting
description: When the user wants to find, qualify, and build a list of prospects to reach out to — across B2B SaaS, general B2B, or local small businesses. Also use when the user mentions "prospecting," "build a prospect list," "find prospects," "find leads," "lead gen list," "find SaaS companies that," "find B2B companies," "find local businesses," "ICP-fit accounts," "who should we go after," "outbound list," "target account list," "find clients near me," "businesses without websites," "prospect research," "qualified leads," "find my first customers," "early adopters," "design partners," "beta users," or "who has this problem." Use this for the list-building and qualification phase. For writing the outbound copy after the list is built, see cold-email. For deep competitive research on specific accounts, see competitor-profiling.
metadata:
version: 1.1.0
---
# Prospecting
You are an expert at building qualified prospect lists across four motions: B2B SaaS, general B2B, local small businesses, and early-stage demand-signal discovery (finding your first customers from public pain signals). Your goal is to turn an ICP definition into a verified, scored, ready-to-outreach lead sheet — using the right data sources, qualification signals, and compliance posture for each motion.
## Before Starting
**Check for product marketing context first:**
If `.agents/product-marketing.md` exists (or `.claude/product-marketing.md`, or the legacy `product-marketing-context.md` filename, in older setups), read it before asking questions. Use that context and only ask for information not already covered or specific to this task.
## Pick the Branch
Prospecting motions differ enough that the workflow forks at intake. Pick **one** branch based on who the user is selling to:
| Branch | Sell to | What "qualified" looks like | Primary sources |
|--------|---------|----------------------------|----------------|
| **SaaS** | Other SaaS companies / digital businesses | ICP fit + tech stack match + growth signals (funding, hiring, product velocity) | LinkedIn, BuiltWith, Crunchbase, Apollo, Clay, Clearbit, ProductHunt |
| **B2B** | Non-SaaS B2B (services, manufacturers, enterprises, mid-market) | Industry + size + geographic fit + buying signals (trigger events, vendor changes) | Apollo, ZoomInfo, Clay, Clearbit, LinkedIn Sales Nav, industry directories |
| **Local SMB** | Local small businesses (shops, gyms, restaurants, clinics, salons, services) | Active business + website status + proximity + decision-maker access | Google Maps, Yelp, local directories, Facebook, business websites |
| **Demand-signal** | Early-stage: your first customers, design partners, or beta users | Evidence of the exact pain/demand/timing signal — a cited public source, not just firmographic fit | Forums, communities, reviews, GitHub issues, job posts, launch announcements (via last30days, social-fetch, scraping) |
If the user describes a hybrid motion (e.g., "SMBs that are also SaaS"), pick the dominant branch and pull in qualification signals from the other. If the user is early-stage and needs their *first* customers or design partners — evidence of demand over list coverage — use the **Demand-signal** branch.
For the branch-specific deep dives:
- **SaaS** → see [references/saas-prospecting.md](references/saas-prospecting.md)
- **B2B** → see [references/b2b-prospecting.md](references/b2b-prospecting.md)
- **Local SMB** → see [references/local-prospecting.md](references/local-prospecting.md)
- **Demand-signal** (find your first customers) → see [references/demand-signals.md](references/demand-signals.md)
---
## Shared Framework (all branches)
Every prospecting engagement follows the same five phases. Tools and qualification signals change per branch; the phases don't.
### Phase 1 — Define the ICP
Pull from `product-marketing.md` if available. Otherwise, gather:
1. **Firmographic fit** — industry, company size, revenue band, geography, business model
2. **Technographic fit** (SaaS branch) — what tools they already use, what they're missing
3. **Buying signal** — why now? (trigger event, funding, hiring, new initiative, dissatisfaction with current vendor, recent move/expansion)
4. **Decision-maker profile** — role, seniority, what they care about
5. **Disqualifiers** — what makes a prospect a clear "skip"
Output the ICP as a one-paragraph statement plus a checklist of pass/fail criteria. Don't move to discovery without this.
### Phase 2 — Build the candidate list (discovery)
Source 2–3× more candidates than the user wants in the final list — qualification will cull aggressively.
- **SaaS / B2B**: combine 2–3 sources for cross-verification. Apollo or ZoomInfo for firmographics; Clearbit or Clay for enrichment; LinkedIn Sales Nav for decision-maker mapping.
- **Local SMB**: browser-assisted research starting with Google Maps for the target category in the target area; cross-check with Yelp, the business website, social pages, and public directories.
If the user's list quality bar is high, smaller is better. 25 verified leads beats 250 mostly-junk ones.
### Phase 3 — Qualify each candidate
Score every candidate against the ICP checklist. Add **evidence** (a source URL or two) for each qualification — never assert without backing.
**Confidence levels** (used across all branches):
- **High**: confirmed by at least two independent sources or official business page
- **Medium**: one credible source plus consistent search evidence
- **Low**: incomplete or ambiguous evidence — flag what remains uncertain
For email contacts (B2B / SaaS branches), **always verify deliverability before adding to the final list** — see Truelist integration in [references/data-sources.md](references/data-sources.md). Don't ship leads with invalid or risky emails.
### Phase 4 — Score and prioritize
Apply this rubric for the **SaaS, B2B, and Local SMB** branches. The **Demand-signal** branch scores differently — 0–100 demand-fit, not Hot/Warm/Cold — see [references/demand-signals.md](references/demand-signals.md).
| Score | Definition |
|-------|------------|
| **Hot** | Strong ICP fit + clear buying signal + decision-maker accessible + verified contact |
| **Warm** | ICP fit + softer or older signal + contact verifiable |
| **Cold** | Loose ICP fit OR no clear signal OR contact unverified |
| **Skip** | Disqualifier hit (out of ICP, closed business, duplicate, irrelevant, low confidence) |
Branch-specific signals refine the scoring — see each reference file. Default ratio target: ~20% Hot, ~30% Warm, rest Cold/Skip.
### Phase 5 — Output the lead sheet
(SaaS / B2B / Local SMB. The **Demand-signal** branch ships an evidence report instead — see [references/demand-signals.md](references/demand-signals.md).)
Default to a markdown table in chat. Switch to CSV when the list is >25 rows or the user explicitly asks for a file.
After the table, always add **"Top outreach targets"** — the top 3–5 hot leads with one sentence each on why this lead should be reached out to first.
Columns vary by branch (see reference files), but every lead sheet includes:
- score, business/company name, contact (where applicable), why-it's-a-prospect, source(s), confidence, last verified date
---
## Compliance Guardrails
These apply to every branch. **Read first, every engagement.**
1. **No bulk scraping** of LinkedIn, Google Maps, paywalled sites, or rate-limited APIs. Browser is an assisted research tool, not a scraper.
2. **No CAPTCHA, login wall, or bot protection bypass.** If a site requires it, work with what's publicly visible.
3. **Public business contact channels only.** Use info@, hello@, contact@, and named-role emails (founder, owner) where they're published on the business's own site. Personal/private emails require a lawful basis (existing relationship, opt-in, etc.).
4. **GDPR / CAN-SPAM / CASL aware.** Capture and retain the source URL and date for every contact you add to a list — required for downstream outreach compliance.
5. **No reselling extracted data** from Google Maps, LinkedIn, or any platform whose terms prohibit it. List building for the user's own outreach is fine; productizing the list to sell is not.
6. **Rate limit yourself.** Even on public sources, space requests. Don't fingerprint as a bot.
7. **No breached, leaked, or unprovenanced data.** Don't source prospects from breached datasets, scraped-contact marketplaces, or list brokers with no source lineage. Licensed B2B data providers (Apollo, ZoomInfo, Clearbit, Clay) are fine when used within their ToS and with a lawful basis — the ban is on illicit/unprovenanced data, not on legitimate enrichment vendors.
8. **Never target or infer sensitive traits.** Don't qualify, segment, or personalize on health, financial hardship, political belief, sexuality, religion, or other protected/sensitive attributes — even when a public post reveals them.
For the full compliance reference (GDPR, CAN-SPAM, CASL, LinkedIn ToS, Google Maps ToS, Clay/Apollo/ZoomInfo use restrictions): see [references/compliance.md](references/compliance.md).
---
## Inputs to Collect
If missing, ask once, then infer reasonable defaults and continue:
- **Branch** (SaaS / B2B / Local SMB / Demand-signal) — usually inferable from context; pick Demand-signal for early-stage first-customer discovery
- **ICP description** — pull from `product-marketing.md` if present
- **Target count** — default 25 for SaaS / B2B, 15 for Local SMB
- **Geography** (essential for Local SMB; useful for B2B; less critical for SaaS)
- **Tools the user has access to** — Apollo? Clay? ZoomInfo? Hunter? Truelist? Defaults to what's free + browser
- **Output format** — chat table (default) or CSV
- **Buying signal preference** — what triggers should they prioritize? (funding rounds, hiring, recent move, etc.)
---
## Tool Selection Quick Picks
Full breakdown in [references/data-sources.md](references/data-sources.md). Quick picks:
| If the user has access to... | Use it for |
|------------------------------|------------|
| **Apollo** | B2B / SaaS firmographic + contact discovery |
| **Clay** | Multi-source enrichment, waterfall lookups, custom scoring |
| **Clearbit** | Email-to-company and company enrichment |
| **ZoomInfo** | Enterprise B2B contact + intent data |
| **Hunter or Snov** | Email pattern guessing and verification |
| **Truelist** | Email deliverability validation (before adding to outreach list) |
| **LinkedIn Sales Navigator** | Decision-maker mapping (manual, no scraping) |
| **BuiltWith / Wappalyzer** | Tech stack qualification (SaaS branch) |
| **Crunchbase** | Funding signals (SaaS branch) |
| **GitHub** | Stargazers / forks of competitor or adjacent repos (dev-tool SaaS branch) |
| **Google Maps + browser** | Local SMB discovery |
| **Firecrawl / Browserbase** | Programmatic extraction from individual prospect websites — never from platforms |
**If the user has no enrichment tools**: lean on browser-assisted research with public sources — company website, About page, LinkedIn company page, news mentions. Slower but works.
---
## Output Formats
### Default — chat table
For SaaS / B2B (≤25 rows):
```
| Score | Company | Industry | Size | Signal | Contact | Email status | Source | Confidence |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
```
For Local SMB (≤15 rows) — port from the local-prospector reference:
```
| Score | Business | Category | Area | Website status | Website/Social | Phone | Why it's a prospect | Confidence |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
```
### CSV — when >25 rows or user requests a file
SaaS / B2B columns:
```csv
score,company,domain,industry,size_band,country,signal,contact_name,contact_title,contact_email,email_status,linkedin,source_urls,why_prospect,confidence,verified_date,notes
```
Local SMB columns:
```csv
score,business,category,area,distance_km,website_status,website_url,social_urls,phone,email,source_urls,why_prospect,confidence,verified_date,notes
```
### Always include after the table
- **Top outreach targets**: top 3–5 hot leads with one-sentence outreach rationale each
- **Search parameters**: branch, ICP, location/radius, target count, date generated
- **Open questions**: anything you couldn't verify and the user should look at
---
## Quality Checks (before finalizing)
- [ ] Remove duplicates (by domain for SaaS/B2B, by business + address for Local SMB)
- [ ] Every "Hot" lead has a verified contact + at least one source URL
- [ ] No lead has an email that failed Truelist (or your validator) verification — move to a separate "invalid" bucket and flag for the user
- [ ] No lead labeled "Hot" lacks a clear buying signal
- [ ] Confidence levels honest — "High" requires 2 independent sources, not just two of your own searches
- [ ] No leads sourced from prohibited scraping (LinkedIn at scale, Google Maps bulk extract, etc.)
- [ ] Source URL + date captured for every contact (GDPR / CAN-SPAM lineage)
- [ ] Final count matches user's request, or you've explained why it's smaller (quality bar)
---
## Common Mistakes
1. **Starting discovery without an ICP**. Build candidates against vague criteria and you'll qualify the wrong things.
2. **Treating data sources as authoritative without cross-checks**. Apollo and ZoomInfo are out of date often; verify before scoring as "Hot."
3. **Adding contacts without email verification**. Cold email reputation tanks fast with bounces — always validate.
4. **Bulk scraping LinkedIn or Google Maps**. Real risk: account suspension + ToS violation. Browser as an assisted tool only.
5. **Mixing branches**. Don't apply Local SMB scoring (website status) to a B2B SaaS prospect, or vice versa.
6. **"Hot" labels without buying signals**. ICP fit alone is not enough — the signal is what makes the timing right.
7. **No source URLs**. Every claim should be traceable to a public source. Future outreach depends on this lineage.
8. **Ignoring quiet hours / time zone** when scheduling the downstream outreach (handoff to cold-email).
9. **Forgetting to retain consent / lineage records**. Required for GDPR DSARs and CAN-SPAM audits.
---
## Task-Specific Questions
1. Which branch — SaaS, B2B, Local SMB, or Demand-signal (early-stage, finding your first customers)?
2. What's your ICP? (Or: should I pull from your product-marketing context?)
3. How many qualified leads do you want?
4. What tools do you have access to (Apollo / Clay / ZoomInfo / Hunter / Truelist / browser only)?
5. What's the triggering buying signal you care most about?
6. Geography or radius (Local SMB / B2B)?
7. Chat table or CSV?
---
## Tool Integrations
For implementation, see the [tools registry](../../tools/REGISTRY.md). Key prospecting tools:
| Tool | Best For | MCP | Guide |
|------|----------|:---:|-------|
| **Apollo** | B2B / SaaS firmographic + contact discovery | - | [apollo.md](../../tools/integrations/apollo.md) |
| **Clay** | Multi-source enrichment + waterfall | ✓ | [clay.md](../../tools/integrations/clay.md) |
| **Clearbit** | Email-to-company enrichment | - | [clearbit.md](../../tools/integrations/clearbit.md) |
| **ZoomInfo** | Enterprise B2B contact + intent | ✓ | [zoominfo.md](../../tools/integrations/zoominfo.md) |
| **Hunter** | Email pattern + verification | - | [hunter.md](../../tools/integrations/hunter.md) |
| **Snov** | Email finder + verifier | - | [snov.md](../../tools/integrations/snov.md) |
| **Truelist** | Email deliverability validation | - | [truelist.md](../../tools/integrations/truelist.md) |
| **Outreach** | Sales engagement (post-list) | ✓ | [outreach.md](../../tools/integrations/outreach.md) |
| **RB2B** | Visitor identification (warm intent) | - | [rb2b.md](../../tools/integrations/rb2b.md) |
| **GitHub** | Stargazers/forks/watchers as developer-intent signal | - | [github.md](../../tools/integrations/github.md) |
| **Firecrawl** | Single-target site extraction (prospect's own website) | ✓ | [firecrawl.md](../../tools/integrations/firecrawl.md) |
| **Browserbase** | Real-browser site research when rendering or interaction needed | ✓ | [browserbase.md](../../tools/integrations/browserbase.md) |
---
## Related Skills
- **cold-email**: For writing outbound sequences against the qualified list (the natural next step after prospecting)
- **customer-research**: For understanding why current customers buy — informs the ICP definition
- **competitor-profiling**: For deeper research on individual accounts (different from list-building qualification)
- **revops**: For lead routing, lifecycle, and CRM handoff after prospecting
- **sales-enablement**: For battle cards and one-pagers used in the outreach
- **directory-submissions**: For inbound discovery surfaces (the prospects might find you back)
- **product-marketing**: For the ICP definition that anchors every prospecting engagement
FILE:evals/evals.json
{
"skill_name": "prospecting",
"evals": [
{
"id": 1,
"prompt": "We're a B2B SaaS selling RevOps tooling at $30K ACV. Build me a list of 25 prospects.",
"expected_output": "Should check for product-marketing.md first. Should identify this as the SaaS branch. Should run Phase 1 ICP definition pulling from product-marketing context or asking targeted questions (target industry, headcount range, tech stack signals, funding stage). Should propose discovery sources appropriate for SaaS at $30K ACV: Apollo for breadth, Clay for waterfall enrichment, Crunchbase for funding signals, BuiltWith/Wappalyzer for tech stack, LinkedIn Sales Nav for decision-mapping (manual). Should ask about user's tool access before assuming. Should source 50-75 candidates (2-3x target) before qualifying. Should flag that email validation via Truelist or similar is non-negotiable before final list. Should output SaaS-branch chat table columns (Score | Company | Industry | Size | Signal | Contact | Email status | Confidence) followed by top 3-5 hot leads with one-sentence rationale each. Should reference references/saas-prospecting.md.",
"assertions": [
"Checks for product-marketing.md",
"Identifies SaaS branch",
"Runs Phase 1 ICP definition",
"Recommends multi-source discovery (Apollo, Clay, Crunchbase, BuiltWith)",
"Asks about user's tool access",
"Sources 2-3x candidates before qualifying",
"Requires email validation before final list",
"Outputs SaaS-branch chat table columns",
"Includes top 3-5 outreach targets with rationale",
"References saas-prospecting.md"
],
"files": []
},
{
"id": 2,
"prompt": "Find me 25 SaaS companies that just raised a Series B in the last 60 days and use HubSpot.",
"expected_output": "Should recognize this as a SaaS branch prospecting task with very specific signals. Should identify the trigger event (Series B in last 60 days) and the technographic filter (uses HubSpot). Should recommend a workflow: (1) Crunchbase or Pitchbook for funding signal filter (Series B + date), (2) BuiltWith or Clay's waterfall for tech stack verification (uses HubSpot), (3) cross-check via business websites and LinkedIn. Should note this is a tight ICP that should yield high-confidence matches if data sources are current. Should flag freshness concerns: Crunchbase data depends on self-reporting, BuiltWith refresh cycles aren't real-time. Should recommend cross-source verification for the funding date specifically. Should output a SaaS-branch chat table with the funding round + date in the Signal column. Should include verified email validation before delivering.",
"assertions": [
"Identifies as SaaS branch",
"Identifies funding signal + tech stack filter",
"Recommends Crunchbase or Pitchbook for funding",
"Recommends BuiltWith or Clay for HubSpot verification",
"Notes data freshness concerns",
"Recommends cross-source verification",
"Outputs signal column showing round + date",
"Requires email validation"
],
"files": []
},
{
"id": 3,
"prompt": "I run a marketing agency. Find me 25 mid-market manufacturers in the Midwest US who recently hired a new CMO.",
"expected_output": "Should identify this as the B2B branch (manufacturers, not SaaS). Should run Phase 1 ICP definition: industry (manufacturing, with NAICS code if precision matters), size (mid-market = typically 200-2000 employees), geography (Midwest US states), trigger event (CMO hire in last 90-180 days). Should propose discovery: Apollo or ZoomInfo for firmographic filter, LinkedIn Sales Nav for CMO hire detection (job changes), Google Alerts on press releases for trigger events. Should warn that CMO hires aren't always in public databases — LinkedIn Sales Nav alerts on job changes is the most reliable source. Should output B2B-branch chat table with the CMO trigger as the signal. Should reference references/b2b-prospecting.md. Should mention compliance: GDPR less likely (US-only), CAN-SPAM applies, capture source URL + date for every contact.",
"assertions": [
"Identifies B2B branch (not SaaS)",
"Runs Phase 1 ICP definition with NAICS or industry classification",
"Specifies mid-market size band",
"Specifies Midwest US geography",
"Identifies trigger event (CMO hire)",
"Recommends Apollo/ZoomInfo + LinkedIn Sales Nav",
"Notes CMO hires often only on LinkedIn",
"Outputs B2B-branch chat table",
"Mentions CAN-SPAM and source URL capture",
"References b2b-prospecting.md"
],
"files": []
},
{
"id": 4,
"prompt": "We sell to industrial distributors. Build a list of 25 prospects.",
"expected_output": "Should identify this as the B2B branch. Should run Phase 1 ICP definition asking targeted questions: distributor size, geography, vertical specialty, buying patterns. Should propose discovery: Apollo or ZoomInfo for firmographic depth, industry-specific directories (e.g., NAW for wholesale distributors, ISA for industrial sales agencies), trade show exhibitor lists. Should note state business registries and Chamber of Commerce as verification sources. Should propose trigger events: new location, recent acquisition, leadership change, posting RFPs. Should warn that industrial distributor data is often spotty in major databases — cross-check with company website + LinkedIn for size and ownership signals. Should output B2B-branch chat table. Should note ICP fit precision matters more than initial volume for this kind of niche prospecting.",
"assertions": [
"Identifies B2B branch",
"Runs Phase 1 ICP definition asking targeted questions",
"Recommends industry-specific directories beyond Apollo/ZoomInfo",
"Mentions trade show exhibitor lists",
"Identifies relevant trigger events",
"Warns about data spottiness for industrial",
"Recommends cross-verification with business websites + LinkedIn",
"Notes ICP fit precision over volume"
],
"files": []
},
{
"id": 5,
"prompt": "I build websites for local businesses. Find me 15 prospects near Austin, TX who don't have a website.",
"expected_output": "Should identify as Local SMB branch. Should run Phase 1 ICP definition: business category (ask user — gyms, restaurants, salons, etc. matter), radius (default 20 km from Austin), target count (15). Should run the browser research workflow: search Google Maps for category + Austin, build candidate list from visible results, cross-check via business name + city web search to verify website status. Should apply the 4-tier website status classification (No site found / Social only / Weak site / Has site) — prioritize No site + Social only as Hot. Should score: Hot (no site + active + phone + within radius), Warm (weak site), Cold (has site), Skip (closed/duplicate/out of scope). Should output Local SMB chat table (Score | Business | Category | Area | Distance | Website status | Website/Social | Phone | Why prospect | Confidence). Should add 'Best first outreach targets' top 3 with reasoning. Should reference references/local-prospecting.md. Should warn against bulk-scraping Google Maps (ToS violation) — browser-assisted research only.",
"assertions": [
"Identifies Local SMB branch",
"Asks about business category if not specified",
"Defaults radius to 20km",
"Runs browser research workflow",
"Applies 4-tier website status classification",
"Uses Hot/Warm/Cold/Skip scoring",
"Outputs Local SMB chat table columns",
"Adds top 3 outreach targets",
"References local-prospecting.md",
"Warns against bulk-scraping Google Maps"
],
"files": []
},
{
"id": 6,
"prompt": "I have a list of 200 prospect emails from Apollo. How do I know which ones are deliverable before I start outreach?",
"expected_output": "Should explain the deliverability validation step in Phase 3. Should recommend Truelist (the integration in this pack) for bulk validation. Should explain the email_state classification output: ok (deliverable), email_invalid (bounces, exclude), risky (deliverable with risk like role or disposable, include cautiously), unknown (couldn't determine, skip or re-verify), accept_all (catch-all domain, include cautiously). Should warn that Apollo data accuracy is typically 60-80% — sending without validation will tank sender reputation (bounce rate >2% triggers ISP throttling and reputation damage). Should recommend the workflow: bulk POST to /api/v1/verify or CSV upload → keep ok, include risky/accept_all cautiously, exclude email_invalid, re-verify unknown → hand off to outreach. Should note Truelist also has an official MCP server for agent-driven validation. Should note cold email reputation is hard to recover once damaged — validation is non-negotiable, not optional. Should mention Hunter and Snov as alternatives with built-in verification. Should reference truelist.md integration guide.",
"assertions": [
"Recommends Truelist for bulk validation",
"Explains email_state values (ok, email_invalid, risky, unknown, accept_all)",
"Warns Apollo accuracy is 60-80%",
"Cites 2% bounce rate threshold for reputation damage",
"Recommends workflow: validate, keep ok, exclude email_invalid",
"Mentions Truelist MCP server for agent workflows",
"Mentions cold email reputation is hard to recover",
"References truelist.md or data-sources.md"
],
"files": []
},
{
"id": 7,
"prompt": "I just built a tool that automates failed-payment follow-up for gym owners. I have no customers yet. Help me find my first ten — the people who are actually dealing with this problem right now.",
"expected_output": "Should select the Demand-signal branch (early-stage, first customers, evidence-of-demand) and load references/demand-signals.md — NOT the SMB/B2B list-building branches. Should start with a product brief, then mine the five signal buckets (explicit demand / pain / workaround / switching / timing) across public discourse (forums, communities, reviews, GitHub issues, job posts) — using last30days for recency, social-fetch/scraping to read original pages, not qualifying from snippets. Should score prospects on demand-fit (pain 25 / product fit 25 / timing 20 / reachability 15 / evidence quality 15, 0-100 with bands) rather than ICP-fit Hot/Warm/Cold, and require a cited public signal for every primary-shortlist prospect. Should draft source-based openers but never auto-send. Should produce an evidence report (verdict → ICP → top prospect → shortlist with sources+scores → repeated patterns → 7-day manual outreach plan → limits) and label prospects as 'potential customers based on public signals,' not confirmed buyers. Should honor the compliance guardrails including no data brokers/leaked data and no sensitive-trait targeting.",
"assertions": [
"Selects the Demand-signal branch, not the SMB/B2B/SaaS list-building branches",
"Mines the five signal buckets from public discourse rather than contact databases",
"Uses recency/original-source tooling (last30days, social-fetch, scraping) and does not qualify from snippets",
"Scores on the demand-fit rubric (0-100 weighted), not ICP-fit Hot/Warm/Cold",
"Requires a cited public signal for every primary-shortlist prospect",
"Drafts openers but never auto-sends; labels prospects as potential-based-on-public-signals",
"Produces the evidence report structure with a 7-day manual outreach plan and limits"
],
"files": []
}
]
}
FILE:references/b2b-prospecting.md
# B2B Prospecting Reference
For when the user sells to non-SaaS B2B — services, agencies, manufacturers, mid-market and enterprise companies, professional services firms.
---
## ICP Signals That Matter (B2B branch)
### Firmographic signals
- **Industry / vertical** — NAICS or SIC codes if precision matters
- **Company size** — headcount band, revenue band, location count
- **Geography** — relevant for time zones, regulations, on-site requirements
- **Business model** — service vs product vs distribution; B2B vs B2B2C
- **Ownership** — independent, PE-backed, public, family-owned — affects buying motion
### Buying signals
- **Trigger events**: new C-level hire, recent acquisition or divestiture, IPO/funding, opening a new location, recent rebrand, expansion announcement
- **Vendor signals**: posting RFPs publicly, switching costs in last quarterly report, contract renewal windows
- **Operational signals**: recent layoffs (cost pressure) or rapid hiring (capacity pressure)
- **News mentions**: launching new initiative, entering new market, regulatory change forcing action
- **PR / press**: anything that signals "this company is changing right now"
### Decay signals
- Multiple bankruptcies or PE-stripped operations
- Negative growth + cost-cutting headlines
- Ownership stagnation (small family-owned, no growth incentive)
- Buyer turnover (3+ Marketing Directors in 2 years)
---
## Discovery Sources (B2B branch)
### Tier 1 — primary discovery
- **Apollo**: best general B2B firmographic + contact discovery
- **ZoomInfo**: enterprise B2B + intent signals (mid-market+)
- **LinkedIn Sales Navigator**: industry + role + signal search; the gold standard for decision-maker mapping (manual)
- **Clay**: when you need custom waterfall lookups (e.g., enrich Apollo records with Hunter + Clearbit)
### Tier 2 — industry-specific directories
- **Crunchbase / Pitchbook**: funded businesses
- **D&B Hoovers**: large traditional B2B firmographics
- **State / national business registries**: for verified incorporation data
- **Industry association membership rosters**: trade groups often publish member lists
- **Trade show exhibitor lists**: signals active participation in a vertical
- **Procurement databases** (Procore for construction, e.g.): vertical-specific signals
### Tier 3 — trigger event monitoring
- **Google Alerts / Feedly**: trigger keywords ("acquired," "hires," "expansion," "raises," "announces")
- **PR Newswire / Business Wire**: company-controlled announcements
- **SEC filings** (public companies): material change disclosures
- **State filings**: new entity formation, dissolution
---
## Qualification Checklist (B2B branch)
- [ ] Industry / vertical matches ICP (use a recognized classification if possible)
- [ ] Company size within range (employees or revenue)
- [ ] Geography fits
- [ ] At least one trigger event in last 90–180 days
- [ ] Decision-maker role exists (CEO, COO, VP Operations, Director of X — match buyer profile)
- [ ] Email contact verifiable (named role > info@ catchall)
- [ ] Source URLs captured for firmographic claims
- [ ] No disqualifiers (closed, acquired-paused, multi-bankrupt, off-ICP)
---
## Output Columns (B2B branch)
Recommended CSV columns:
```csv
score,company,domain,industry,naics_code,size_band,revenue_band,country,city,trigger_event,trigger_date,contact_name,contact_title,contact_email,email_status,linkedin_url,source_urls,why_prospect,confidence,verified_date,notes
```
For chat table, condense to: Score | Company | Industry | Size | Trigger | Contact | Email status | Confidence.
---
## Top Outreach Targets Selection (B2B)
Prioritize for the top 3–5 hot leads:
1. **Trigger event recency** — 30 days beats 6 months
2. **Trigger event specificity** — new CMO hire in your buyer's role beats "company in the news"
3. **Decision-maker access** — named contact with verified email + LinkedIn beats role-only
4. **Vertical fit precision** — exact NAICS match beats "adjacent industry"
Each top target rationale names the trigger and decision-maker: "Hired new VP of Marketing 14 days ago; verified email; mid-market manufacturer matching ICP."
---
## Common Mistakes (B2B)
1. **Treating B2B like SaaS** — funding rounds matter less; PE ownership and acquisition activity matter more.
2. **Trying to verify private company revenue precisely** — most public databases approximate. Use size bands, not point estimates.
3. **Ignoring procurement complexity** at enterprise scale — your prospect contact list may not include the actual approver.
4. **Cold-emailing executive assistants** — they're not the buyer and they will flag your outreach as spam.
5. **Source URL hygiene** — without source lineage, you can't defend a contact under GDPR DSAR or CAN-SPAM challenge.
6. **Stopping at one source** — Apollo can be 60% accurate on small businesses. Cross-verify with LinkedIn or the business website.
FILE:references/compliance.md
# Prospecting Compliance Reference
The legal and platform-ToS constraints that apply to prospect list building. Read first, every engagement.
> Operational guidance, not legal advice. For high-volume programs or programs touching EU/UK residents, run your setup past a privacy attorney.
---
## United States — CAN-SPAM (downstream)
CAN-SPAM regulates the cold email **send**, not the list build. But the list build matters because:
- You must be able to identify the source of every email address you contact (required if challenged)
- The "from" line and email content rules apply at send time — but you can't lie about how you got the contact
- Opt-out requests must be honored within 10 business days and tracked
**For prospecting specifically**: capture and retain the source URL + date for every contact you add to a list. CAN-SPAM doesn't require it explicitly, but defending your sender practices does.
---
## EU / UK — GDPR
The strictest applicable framework. Triggers when:
- Your prospect resides in EU/UK
- You're processing personal data (any identifiable info, including business emails tied to a named person)
### Lawful bases for cold B2B outreach
You have three credible options:
1. **Legitimate interest** (most common for B2B). Requires:
- The contact is in a business role likely to be interested in your offer
- The data was collected from a public, business-context source
- You provide a clear opt-out
- You can articulate the legitimate interest test in writing
2. **Consent** — typically not feasible for cold outreach (you don't have consent before first contact)
3. **Existing customer relationship** — only applies to current customers, not prospects
### What you must do
- Capture **source + date + lawful basis** for every contact
- Honor data subject access requests (DSARs) — you must be able to disclose, correct, or delete on request
- Include a privacy notice / opt-out in the first outreach
- Don't store personal data longer than necessary for the legitimate interest
### What disqualifies a list
- Bulk-scraped LinkedIn data — explicit ToS violation + GDPR risk
- Email addresses purchased from a list broker without source provenance
- "Anyone @ this domain" guessed emails sent without verification (multiplies risk + bounces)
---
## Canada — CASL
Stricter than CAN-SPAM. Cold B2B outreach requires:
- **Express consent** (explicit opt-in) — typically not present for cold prospecting
- **OR implied consent** — existing business relationship within 24 months, OR business address publicly published on the company's own site for the purpose of receiving such communications
**Practical implication for Canadian prospects**: relying on the publicly-published-address exception is the most defensible cold prospecting basis in Canada. You must include sender identification, mailing address, and an unsubscribe mechanism in every message.
---
## Platform Terms of Service
### LinkedIn
- **Sales Navigator** as a research tool: fine
- **Scraping LinkedIn at any scale**: explicit ToS violation. Banned accounts are permanent. Don't.
- **Apollo, Clay, and ZoomInfo** claim LinkedIn-overlap data through various legitimate channels — verify their data sources before assuming compliance
- **InMail and Connection Requests**: governed by LinkedIn's own messaging rules, not by CAN-SPAM/GDPR (because LinkedIn-internal)
### Google Maps
- ToS prohibits bulk extraction or productizing Maps data
- Browser-assisted research as a discovery aid: acceptable
- Storing Place IDs or large structured Maps data in your CRM: explicit ToS prohibition
- Use Maps to **find** local businesses, then cross-source from the business's own site for the data you retain
### Apollo / ZoomInfo / Clearbit
- All have their own ToS limiting reselling, downstream sharing, and use cases
- Read your contract — typically you can use the data for your own outreach but not productize it
- Don't share extracts publicly (e.g., on a leaderboard, in a public report)
### Crunchbase
- Free tier is read-only for personal use
- Paid tier permits broader use within contractual scope
- API access requires paid Pro+ tier
---
## Anti-Patterns (Don't Do These)
1. **Bulk-scraping LinkedIn / Google Maps / Yelp**. Browser-assisted research is OK; automated scrapers pointed at these platforms are not. **Firecrawl and Browserbase are fine for an individual prospect's own website** (the URL you found through manual discovery) — not for the platforms hosting prospects.
2. **Buying lists from random vendors** without source provenance. You inherit their legal exposure.
3. **Guessing emails and sending unverified**. Bounce rates over 2% destroy sender reputation; legally, you can't claim a "legitimate interest" basis for an email you fabricated.
4. **Harvesting personal email addresses** (Gmail, personal Outlook, etc.) from public profiles. Personal addresses raise GDPR risk significantly.
5. **Storing data you don't need**. Minimize retention. Don't keep prospect lists forever — GDPR right to deletion applies.
6. **Skipping the lawful basis documentation**. If challenged, you need to show your work. Capture source URL + collection date for every contact.
7. **Reselling prospect lists**. You may not have the right to share them downstream. Read your data provider contracts.
8. **CAPTCHA bypass / login wall bypass**. Even if technically possible, this signals bot behavior and violates virtually every ToS.
---
## Quick Audit Checklist
Before shipping a list to the user (or downstream to cold-email):
- [ ] Every contact has a source URL + collection date
- [ ] No contacts sourced from scraped LinkedIn data
- [ ] No Google Maps Place IDs or large Maps-structured data retained
- [ ] Lawful basis documented (legitimate interest test for B2B, or relevant alternative)
- [ ] Email addresses validated (deliverability check before outreach)
- [ ] Personal addresses (Gmail, etc.) flagged or excluded
- [ ] Source provider contracts permit the intended use case
- [ ] Retention plan documented (when to delete)
- [ ] First outreach will include unsubscribe + privacy notice (downstream concern for cold-email skill, but mention it now)
FILE:references/data-sources.md
# Prospecting Data Sources
Tool selection guide for prospecting across all three branches.
---
## Tool selection by goal
| Goal | Primary tools | Notes |
|------|--------------|-------|
| **Build initial firmographic list (B2B / SaaS)** | Apollo, ZoomInfo, Clay | Apollo for breadth, ZoomInfo for enterprise + intent, Clay for custom workflows |
| **Decision-maker mapping** | LinkedIn Sales Navigator (manual), Apollo, ZoomInfo | Sales Nav is the gold standard. Never bulk scrape it. |
| **Tech stack qualification (SaaS)** | BuiltWith, Wappalyzer | BuiltWith has wider coverage + paid plans for bulk; Wappalyzer is lighter + free for small use |
| **Funding signals (SaaS)** | Crunchbase, Pitchbook | Crunchbase free tier sufficient for early signals; Pitchbook for deeper investor data |
| **Email pattern discovery** | Hunter, Snov, Apollo | Pattern guessing — followed by verification |
| **Email deliverability verification** | Truelist, Hunter, NeverBounce, ZeroBounce | Always verify before adding to outreach lists |
| **Visitor identification (warm intent)** | RB2B, Clearbit Reveal | Anonymous traffic → company identification |
| **Intent data** | ZoomInfo Intent, 6sense, Bombora | Pre-warmed signals; mid-market+ pricing |
| **Trigger event monitoring** | Google Alerts, Feedly, LinkedIn Sales Nav alerts | Free options are sufficient for most |
| **Local business discovery** | Google Maps (manual), Yelp, Facebook Pages | Browser-assisted, not bulk-extracted |
---
## Apollo
**Use for**: General B2B / SaaS firmographic + contact data. Best starting point if you don't already have a list.
**Strengths**:
- Large database (>200M contacts, >60M companies)
- Strong filtering UI (industry, size, technologies, signals)
- Integrated email + LinkedIn finder
- Pay-as-you-go and tiered plans
**Watch out for**:
- Data freshness varies — re-verify before scoring as "Hot"
- Email accuracy ~60–80% — always validate
- Bulk export limits apply
**Integration**: see [apollo.md](../../../tools/integrations/apollo.md)
---
## Clay
**Use for**: Multi-source enrichment, waterfall lookups, custom scoring logic. When list quality matters more than list size.
**Strengths**:
- Waterfall logic: try Apollo first → fallback to ZoomInfo → fallback to Clearbit
- 100+ data provider integrations
- AI-powered enrichment (LLM-driven extraction from URLs)
- Custom columns + scoring formulas
- Native MCP server
**Watch out for**:
- Per-credit pricing can spike on large lists
- Complexity overhead — easy to over-engineer workflows
**Integration**: see [clay.md](../../../tools/integrations/clay.md)
---
## ZoomInfo
**Use for**: Enterprise B2B + intent data. Mid-market+ buyer profiles.
**Strengths**:
- Enterprise-grade firmographic depth
- Intent signals (companies searching topics relevant to your offer)
- Best-in-class for >$50K ACV B2B sales
- Native MCP server
**Watch out for**:
- Expensive ($15K+/yr starter)
- Overkill for SMB prospecting
- Locked into multi-year contracts typically
**Integration**: see [zoominfo.md](../../../tools/integrations/zoominfo.md)
---
## Clearbit
**Use for**: Email → company enrichment, anonymous visitor identification (Clearbit Reveal).
**Strengths**:
- Strong company enrichment (industry, size, funding, tech stack)
- Email lookup by domain
- Reveal: identify anonymous site visitors at company level
- API-first
**Watch out for**:
- HubSpot acquisition (2023) — bundled into HubSpot Breeze Intelligence now
- Standalone API still available but pricing/access depends on tier
**Integration**: see [clearbit.md](../../../tools/integrations/clearbit.md)
---
## Hunter / Snov
**Use for**: Email pattern discovery + lightweight verification on small lists.
**Hunter strengths**:
- Domain-based email discovery
- Built-in deliverability verification
- Free tier reasonable for occasional use
**Snov strengths**:
- Email finder + drip campaigns (overlap with outreach tooling)
- Bulk verification
- Cheaper than Hunter at scale
**Watch out for**:
- Both are pattern-guessing tools — accuracy depends on the target company's email pattern being inferable
- Always run results through a dedicated validator (Truelist or similar) before outreach
**Integrations**: see [hunter.md](../../../tools/integrations/hunter.md), [snov.md](../../../tools/integrations/snov.md)
---
## Truelist
**Use for**: Email deliverability validation before adding contacts to outreach lists. Critical safety step.
**Strengths**:
- Single-email sync verification (`/api/v1/verify_inline`) + bulk async (`/api/v1/verify`)
- Returns `email_state` (ok / email_invalid / risky / unknown / accept_all) + `email_sub_state` (email_ok / is_disposable / is_role / unknown_error / failed_smtp_check) + did-you-mean typo suggestions
- Catches catch-all domains, role accounts, spam traps, disposable providers
- Official MCP server for agent-driven workflows (Claude, Cursor, VS Code)
- Official SDKs in 7 languages + framework integrations (Django, Laravel, Next.js, Rails, React, Svelte, Vue, WordPress)
- Native integrations with Mailchimp, Klaviyo, HubSpot, Zapier, Make, n8n, Clay, Salesforce, more
- Pay-per-email pricing
**Why this matters**: Cold email reputation craters when bounce rates exceed 2%. Validating before sending is non-negotiable. Apollo/ZoomInfo/Hunter data is often 60–80% accurate — Truelist catches the rest.
**Integration**: see [truelist.md](../../../tools/integrations/truelist.md)
---
## LinkedIn Sales Navigator
**Use for**: Manual decision-maker discovery. The gold standard for B2B / SaaS prospecting but only when used as a research tool.
**Strengths**:
- Most accurate decision-maker data in the industry
- Real-time job changes, posts, signals
- Lead lists, alerts, saved searches
- Inmail credits (separate channel from cold email)
**Hard rules**:
- **Never bulk scrape**. LinkedIn aggressively bans scrapers. Account ban risk is real and permanent.
- Use Sales Nav as a research interface — open profiles, read, take notes, capture key data manually.
- Apollo and other tools claim LinkedIn data via partnerships / public mirroring — verify the source legitimacy before assuming compliance.
**Integration**: no MCP or API access at consumer level. Manual research only.
---
## BuiltWith / Wappalyzer
**Use for**: Tech stack qualification (SaaS branch).
**BuiltWith**:
- ~50K+ technologies tracked
- API + bulk lookups (paid)
- Historical data (when stack changed)
**Wappalyzer**:
- Free browser extension; paid API
- Lighter coverage than BuiltWith
- Faster for one-off lookups
Cross-reference both for high-confidence tech stack signals.
---
## Crunchbase
**Use for**: Funding signals (SaaS branch).
**Strengths**:
- Free tier shows recent funding events
- Paid (Pro / Enterprise) unlocks alerts and deep history
- API access for paid users
**Watch out for**:
- Coverage is best for VC-backed companies; bootstrapped + small businesses underrepresented
- Self-reported data — verify funding amounts independently
---
## GitHub (stargazers / forks / watchers)
**Use for**: Developer-intent prospecting. Especially powerful for dev-tool SaaS — stargazers of competitor or category-defining repos are in-market signal.
**Strengths**:
- Public API, no scraping concerns
- High signal quality (a starred repo = explicit interest)
- Forks are an even stronger signal (intent to modify, not just bookmark)
- Bundled `github-prospects.js` CLI handles pagination + enrichment + CSV output
- Free with 5,000 req/hr authenticated rate limit
**Watch out for**:
- Only ~5–20% of users publish email — pair with Apollo/Clay/Hunter for enrichment
- Very-popular repos (100K+ stars) are mostly noise; smaller targeted repos (5K–25K) give better signal density
- Most prospects are individuals, not company contacts directly — need to figure out their company from `company` field or LinkedIn
**Integration**: see [github.md](../../../tools/integrations/github.md)
---
## Firecrawl / Browserbase (single-target site research)
**Use for**: Programmatically extracting content from a **prospect's own website** that you already found via discovery on platforms like Google Maps, Yelp, or LinkedIn. Not for scraping those platforms themselves.
### Firecrawl
- **Best for**: "Just give me the page as markdown" — Local SMB website status checks, B2B company about/team page extraction, structured field extraction
- **Strengths**: Low overhead, returns clean LLM-ready markdown, handles most JS-rendered sites, has an MCP server
- **API + MCP + SDKs**: Node, Python, Go, Rust
### Browserbase
- **Best for**: When you need real Chromium — JS-heavy pages, cookie consent dialogs, form submission to reach a contact page, session state
- **Strengths**: Full browser control via Playwright/Puppeteer; Stagehand provides AI-friendly natural-language extraction; session recordings for debugging
- **API + MCP (Stagehand) + SDKs**: Node, Python
### Critical compliance line
Both tools can technically point at any URL. The hard rule:
- ✓ **OK**: extracting content from a single business's own website (`joescoffeeshop.com`) that you found through manual discovery
- ✗ **NOT OK**: pointing them at `google.com/maps`, LinkedIn search results, Yelp listings, or any platform whose ToS prohibits bulk extraction
Discovery happens on platforms (manual browser-assisted research). Extraction happens on individual public business sites.
**Integrations**: see [firecrawl.md](../../../tools/integrations/firecrawl.md), [browserbase.md](../../../tools/integrations/browserbase.md)
---
## RB2B / Clearbit Reveal
**Use for**: Identifying anonymous site visitors as warm intent signals.
**Strengths**:
- Pixel-based visitor → company identification
- High-intent: they came to your site, they're already in research mode
- Slack / email alerts on key visits
**Watch out for**:
- Privacy/GDPR considerations — verify your privacy policy disclosures
- Person-level identification raises higher concerns than company-level
**Integration**: see [rb2b.md](../../../tools/integrations/rb2b.md)
---
## Free / browser-only fallbacks
When the user has no paid tools, lean on:
- **Google Search** — exact business name + city + role searches
- **LinkedIn** (manual, no scraping) — company pages, employee lookups
- **Crunchbase free tier** — funding events
- **Wappalyzer browser extension** — tech stack at a glance
- **Hunter.io free tier** — 25 lookups/month
- **Google Maps** — for Local SMB discovery
- **Business websites + About pages** — primary source for any claim
- **News sites + press releases** — trigger event monitoring via Google Alerts
Slower than tooled-up workflows, but produces high-quality smaller lists if the user is willing to do the work.
---
## Sequencing recommendations
A typical full-stack prospecting workflow:
1. **Define ICP** from product-marketing context (no tools needed)
2. **Initial list** from Apollo or ZoomInfo (firmographic filter)
3. **Enrich** with Clay (waterfall: tech stack, funding, trigger events)
4. **Decision-maker mapping** in LinkedIn Sales Nav (manual)
5. **Email pattern discovery** with Hunter or Apollo's built-in
6. **Email validation** with Truelist before final list
7. **Hand off** to cold-email skill for outreach copy
Adapt this sequence based on which tools the user actually has.
FILE:references/demand-signals.md
# Demand-Signal Discovery (Find Your First Customers)
The other three branches build a list from who *fits* (firmographics, technographics, proximity). This branch builds a list from who is *already showing the pain* — the early-stage motion where you have a product and a hunch but no customer base yet, and you need your first ten real conversations. You are not filtering a database; you are mining recent public discourse for people describing the exact problem you solve, then linking every prospect to the evidence.
Use this branch when the user is pre-product-market-fit, launching something new, or looking for **design partners, beta users, or first customers** rather than a scaled outbound list. It reuses the shared five phases and every compliance guardrail in SKILL.md; what changes is where you look, how you score, and what you ship.
Pattern credit: the framework here is re-expressed from the open-source `first-customer-finder` Codex skill (Kappaemme, MIT), extended with our live-recency tooling.
## What makes this branch different
| | List-building branches (SaaS / B2B / SMB) | Demand-signal discovery |
|---|---|---|
| Starts from | A firmographic ICP | A described problem |
| Sources | Contact databases (Apollo, ZoomInfo, Clay) | Public discourse (forums, reviews, issues, posts) |
| Contact step | Enrich + verify email deliverability | None — reach them where they already posted |
| Wins on | Coverage at scale | 10 strong evidence-backed matches over a long list |
| Output | A scored lead sheet | An evidence report + manual outreach plan |
A prospect here without a cited pain, need, or timing signal is a speculative fit — it does **not** belong in the primary shortlist. Evidence is the entry ticket.
## Step 1 — Product brief (before any searching)
Define, specifically enough to *reject* weak matches:
- product and the promised outcome
- primary user and the economic buyer (often different)
- the urgent job to be done
- the current alternative or workaround being replaced
- the likely adoption trigger (what makes now the moment)
- geography / language constraint
- clear disqualifiers
Don't start broad collection until the brief is sharp. Pull from `.agents/product-marketing.md` if it exists.
## Step 2 — Mine the five signal buckets
Search several angles, not one query repeated. Adapt wording to how the audience actually talks (mine their vocabulary from organic content first — see the ad-creative hook-system's organic-language note for the same idea).
1. **Explicit demand** — "looking for," "recommend a tool for," "alternative to [X]," "does anything exist that," "how do you all handle."
2. **Pain** — "takes hours," "so manual," "hate that," "keeps breaking," "biggest frustration with," "why is there no."
3. **Workaround** — spreadsheets, copy-paste, a VA, a Zapier chain, a script, a template, any repeated manual step that your product would replace.
4. **Switching** — cancellation, migration, "moving off [competitor]," a missing feature, a pricing complaint, competitor frustration.
5. **Timing** — a public launch, a new hire for the relevant function, expansion, a new workflow or regulation, an integration announcement — a *current* event that makes the product relevant now.
**Use our live-recency edge.** A generic skill relies on whatever a web search surfaces; you have better:
- **last30days** — Reddit, Hacker News, X, YouTube, and web signals from the last 30 days. This is the single highest-value tool for this branch: recency *is* the timing signal.
- **social-fetch** — pull the full content of a specific post/thread you find, normalized.
- **scraping** / **Firecrawl** / **Browserbase** — read the original public page (a forum thread, a GitHub issue, a review), never qualify from a search snippet alone.
- **deep-research** — for a multi-source sweep with adversarial verification when the wedge is broad.
- **competitor-profiling** / **customer-research** — competitor switching signals and review-mining for the pain language.
## Step 3 — Source mix (public only)
Forums and public community threads · public social posts and replies · product and app-marketplace reviews · GitHub issues and feature requests · public company pages, job posts, changelogs, launch announcements · "looking for a tool" posts and directories.
Avoid private groups, gated communities, data brokers, leaked datasets, and any source whose terms prohibit access — the same compliance guardrails as every other branch (see SKILL.md), including the no-sensitive-traits rule.
**Business/professional context only.** Qualify and reach out only where someone is posting in a professional or business capacity about a work problem (a founder in an indie-hackers thread, a developer in a GitHub issue, an ops lead in a subreddit for their role). Exclude personal-distress contexts entirely — health, financial hardship, addiction, grief, or any consumer support forum where people are venting personal problems, even if your product is tangentially relevant. When the motion is genuinely consumer (B2C), a public pain post is not on its own a lawful basis for cold outreach — reach people through the channel's own norms (reply publicly where replying is expected) and never DM a stranger off a personal post.
Quote minimally, paraphrase by default, and link every material pain or timing signal.
## Step 4 — Score on demand-fit (not ICP-fit)
The list-building branches score Hot/Warm/Cold on ICP fit. This branch scores 0–100 on **demand fit** — how strongly the evidence says this specific prospect wants this specific thing now. Score each dimension 0–5:
| Dimension | Weight | What it measures |
|---|---|---|
| **Pain strength** | 25% | Directness, severity, repetition, and cost of the stated problem |
| **Product fit** | 25% | How directly your product solves the evidenced job |
| **Timing** | 20% | Freshness + a current trigger present |
| **Public reachability** | 15% | A natural, relevant public/professional contact path exists |
| **Evidence quality** | 15% | Specificity, source reliability, confidence the signal is really theirs |
```
score = pain/5*25 + fit/5*25 + timing/5*20 + reachability/5*15 + evidence/5*15
```
| Band | Meaning |
|---|---|
| **80–100** | Strong first-customer candidate |
| **65–79** | Promising — validate fast |
| **50–64** | Plausible but missing a material signal |
| **Below 50** | Do not include in the primary shortlist |
An old explicit request can still count — but lower the timing score and label the date. A company that merely matches the industry with no evidenced trigger is *not* a qualified prospect here.
### Prospect stages
- **High intent** — publicly requesting a solution or actively switching
- **Problem aware** — clearly describing the pain or an expensive workaround
- **Trigger present** — a current business event makes the product relevant
- **Potential fit** — ICP match, incomplete evidence → keep *outside* the primary shortlist
### Evidence ledger (per qualified prospect)
Displayed name (company/project/public professional) · source title + URL · visible publication date or "date unavailable" · source type · the concise pain/timing signal · observed evidence vs. inference (label which) · score breakdown · freshness warning when the signal is stale.
## Step 5 — Draft outreach, never send it
Recommend the most natural channel *already associated with the source*, and only where a reply is a normal part of that channel (reply in the public thread, respond via a public professional profile). Don't turn a public post into a private DM the poster didn't invite, and never contact someone off a personal-distress post. Draft one opener, under ~90 words, in this shape:
1. mention the public context naturally
2. connect it to the exact problem
3. explain the product in one sentence
4. ask one low-friction question
Never claim familiarity you don't have, never fabricate personal details, and never auto-send: no messages, connects, follows, comments, form submissions, or CRM records unless the user separately authorizes that action. This is the manual/gated posture from the marketing-loops guardrails.
## Step 6 — Ship the evidence report
Lead with the most actionable evidence, in this order:
1. **Verdict** — does the product have reachable early-customer signal, or not yet? (An honest "not yet, here's why" is a valid answer.)
2. **ICP** — buyer, job, trigger, disqualifiers.
3. **Top prospect** — the single strongest evidence-backed candidate and why now.
4. **Prospect shortlist** — per prospect: source, pain signal, demand-fit score, stage, why-now, channel, opener.
5. **Repeated patterns** — pains and triggers recurring across prospects (these are your positioning and messaging gold).
6. **Seven-day manual outreach plan** — a low-volume validation sequence (e.g., contact the top 3 with one source-based question; share a mockup only after they confirm the pain; target three conversations and one design-partner commitment).
7. **Limits** — what evidence is missing and what must be confirmed through real conversations.
For a shareable standalone HTML version of this report, the JSON→HTML generator pattern in ad-creative's [creative-review-page.md](../../ad-creative/references/creative-review-page.md) is the model (escape every value; keep it self-contained).
## The honesty rules (non-negotiable)
- Every primary prospect links to at least one real public signal. No signal, no shortlist.
- Label the output **"potential customer based on public signals"** — never "interested," "will buy," or "has consented."
- Prefer ten strong matches over a long generic list. Make uncertainty and stale evidence visible.
- Personalize from the cited source, not from invented assumptions.
- Treat the shortlist as a research hypothesis to validate through conversations, not a customer database.
FILE:references/local-prospecting.md
# Local SMB Prospecting Reference
For when the user sells to local small businesses — shops, gyms, restaurants, salons, clinics, professional services, contractors, real estate, fitness studios, dental practices.
Adapted from and generalized beyond the local-client-prospector pattern (browser-assisted discovery + website status classification + proximity scoring).
---
## ICP Signals That Matter (Local SMB branch)
### Operational signals
- **Active business** — Google Business Profile updated, recent reviews, recent hours updates
- **Recent activity** — open right now, regular hours posted, recent photos uploaded by owner
- **Customer engagement** — owner responding to reviews, posts on social, active calendar (for service businesses)
### Online presence signals (the core SMB qualification axis)
The reference local-client-prospector skill uses **website status** as the primary qualification — port this directly. Four classifications:
| Status | Definition | Typical outcome |
|--------|-----------|-----------------|
| **No site found** | No credible standalone website after cross-checked search | **Hot prospect** for web/marketing service |
| **Social only** | Facebook, Instagram, WhatsApp, Linktree, booking portal, marketplace page only — no standalone site | **Hot prospect** for web/marketing service |
| **Weak site** | Standalone site exists but outdated, broken, very thin, non-mobile-friendly, or missing clear contact/conversion flow | **Warm prospect** for refresh / rebuild service |
| **Has site** | Credible, modern standalone site exists | **Low prospect** unless other signals apply (e.g., poor SEO, weak conversion design) |
### Proximity signals
- **Distance** from the user's location or service area
- **Density** — clusters of similar businesses in one area = neighborhood targeting opportunity
- **Travel time** — useful when in-person discovery, install, or service delivery is required
### Decay signals
- Closed permanently (Google Maps banner)
- Reviews paused or business listing reported as closed
- Last activity (review, post) >12 months ago
---
## Discovery Sources (Local SMB branch)
### Primary
- **Google Maps** (browser, manual) — search "category near [location]" and walk the visible results. Cross-check details. Don't bulk-extract.
- **Yelp** — secondary verification; complementary categories
- **Bing Local / Apple Maps** — different coverage on smaller businesses
- **Facebook Pages search** — many SMBs are Facebook-only
### Cross-verification
- **Business's own website** (if any)
- **Industry directories** (e.g., Healthgrades for medical, OpenTable for restaurants, Avvo for legal)
- **Local Chamber of Commerce listings**
- **State business registries** for incorporation status
- **Search results for "[business name] [city]"** to discover non-Maps presence
---
## Browser Research Workflow
1. Open a browser and search Google Maps for the category near `base_location`
2. Build a candidate list from visible local results, search results, and public directories
3. For each candidate, inspect public sources to fill required fields
4. Search the exact business name plus city/town to check whether a standalone website exists
5. Classify website status per the table above
6. Mark confidence: High (2+ sources), Medium (1 source + consistent evidence), Low (incomplete/ambiguous)
When the user explicitly asks for subagents AND subagents are available, split candidates into non-overlapping batches and ask each subagent to verify only website/social/contact status. Don't use subagents for the primary search if it slows progress.
### Optional: programmatic verification with Firecrawl or Browserbase
Once you have a candidate's website URL (found via manual Maps/Yelp discovery), you can speed up website-status classification by hitting the URL programmatically:
- **Firecrawl** for simple "is this site live, modern, mobile-friendly, conversion-flow-equipped" reads — returns clean markdown you can inspect
- **Browserbase** when the candidate site requires JS rendering, has a cookie consent dialog, or you need session state
**Strict line**: use these on the individual business's URL. **Don't** point them at Google Maps, Yelp, or any platform whose ToS prohibits bulk extraction — discovery stays manual.
See [data-sources.md](data-sources.md) for setup details.
---
## Qualification Checklist (Local SMB branch)
- [ ] Business is active (recent reviews or activity in last 6 months)
- [ ] Category matches user's service offering
- [ ] Distance / proximity within target radius
- [ ] Website status classified
- [ ] Phone or contact channel verified
- [ ] At least one cross-source confirms business operates at the listed address
- [ ] Not a duplicate / chain location / out-of-scope category
- [ ] Not closed permanently
---
## Lead Scoring (Local SMB)
Use this simple rubric (matches local-client-prospector pattern):
| Score | Criteria |
|-------|----------|
| **Hot** | No site found OR social-only + phone present + active business + within target radius |
| **Warm** | Weak site, poor online presentation, or marketplace/booking-page only |
| **Cold** | Good website already present OR low confidence |
| **Skip** | Closed, duplicate, outside radius, irrelevant category, or not a business prospect |
---
## Output Columns (Local SMB branch)
Chat table (≤15 rows):
```
| Score | Business | Category | Area | Distance | Website status | Website/Social | Phone | Why it's a prospect | Confidence |
```
CSV:
```csv
score,business,category,area,distance_km,website_status,website_url,social_urls,phone,email,source_urls,why_prospect,confidence,verified_date,notes
```
Rules:
- Keep "Why it's a prospect" short and actionable
- Use `Not found` instead of leaving blank fields
- Include source links sparingly, not all of them
- After the table, add **Best first outreach targets** with the top 3 leads and one practical reason each
- If confidence is low, state exactly what remains uncertain
---
## Top Outreach Targets Selection (Local SMB)
Prioritize for the top 3 hot leads:
1. **No site / social only + phone present** = clearest service opportunity
2. **High review count** = active, established business with real customers
3. **Owner-responded reviews** = engaged owner = more likely to evaluate a vendor
4. **Industry alignment with your service specialty** beats generic category match
Each top target rationale should be one sentence naming the gap and the signal: "No standalone website (cross-checked); 80+ Google reviews with owner replies; 2 km from target area."
---
## Compliance Notes (Local SMB-specific)
The local branch is the most scraping-sensitive of the three motions. Specifically:
- **Google Maps Terms of Service** prohibit bulk extraction. Treat browser visits as research, not as data acquisition.
- **Don't store full Google Maps Place IDs in your CRM** — the ToS limits storage of Maps data.
- **Public business contact channels only**: published phone, contact form, info@ email. Don't reach individual employees through their personal channels.
- **Owner/operator name when published on the business's own site** is OK to use. If you only got it from LinkedIn, mark the source.
---
## Common Mistakes (Local SMB)
1. **Bulk-scraping Google Maps** — fastest way to violate ToS and lose the research channel.
2. **Treating Google Maps data as truth** — listings go stale. Cross-check hours, status, and reviews.
3. **Skipping the website status cross-check** — finding "no site" on Maps doesn't mean no site exists; do an exact-name web search before classifying.
4. **Targeting only the largest businesses** — they're already covered by other providers. The 2–5 employee SMBs are the under-served opportunity.
5. **Generic outreach to all hot leads** — local SMBs respond better to outreach that names their specific gap ("I noticed your menu isn't visible on mobile") than generic pitches.
6. **Ignoring chains and franchises** as Skip — sometimes the franchisee is the buyer and they have local marketing authority. Verify before skipping.
FILE:references/saas-prospecting.md
# SaaS Prospecting Reference
For when the user sells SaaS or digital services to other SaaS companies / digital businesses.
---
## ICP Signals That Matter (SaaS branch)
Beyond standard firmographics (industry, size, geography), SaaS prospects are qualified by:
### Technographic signals
- **Tech stack** — do they use complementary tools (your integration target) or competing tools (a switch opportunity)?
- **Recent stack changes** — adding/removing tools signals active vendor evaluation
- **Custom-built vs off-the-shelf** — DIY tooling often means a buyer who'd benefit from your product
- **Free/freemium plan signals** — using a free competitor means they may be ready to upgrade
### Growth signals
- **Funding round** — Series A / B / C in last 6 months = budget + new hires + tool needs
- **Headcount growth** — 10%+ growth in last quarter signals scaling pressure
- **Hiring signals** — specific role openings (e.g., "Head of RevOps" → ICP for revops tooling)
- **Product velocity** — frequent shipping, new features, blog posts = healthy growth motion
- **Open positions for your buyer's role** — if you sell to Marketing Ops and they're hiring one, that's a signal
### Decay signals (downgrade scoring)
- Layoffs in target department
- Funding round >2 years ago with no follow-up
- Product hasn't shipped in 6+ months
- Team page shows founders only (very early — may not have budget)
---
## Discovery Sources (SaaS branch)
Combine 2+ sources for cross-verification.
### Tier 1 — primary discovery
- **Apollo**: firmographic + technographic + contact data. Good for building large initial lists.
- **Clay**: waterfall enrichment, custom scoring, multi-source merges. Best for high-quality smaller lists.
- **ZoomInfo**: enterprise-grade firmographic + intent signals. Expensive; mid-market+.
- **LinkedIn Sales Navigator**: decision-maker mapping. Use manually, never bulk scrape.
### Tier 2 — technographic / growth signals
- **BuiltWith**: tech stack lookups, find sites using specific tools
- **Wappalyzer**: free browser extension + API; lighter tech stack signal
- **Crunchbase**: funding rounds, headcount, founders
- **Pitchbook**: deeper investor data (enterprise/paid)
- **ProductHunt**: recent launches, builder audience
- **Hacker News / Show HN**: technical builders launching products
### Tier 3 — buying signals
- **Job boards** (LinkedIn Jobs, Indeed, AngelList): role openings as signals
- **RB2B / Clearbit Reveal**: visitor identification (warm anonymous traffic)
- **GitHub stars/forks of competitor or adjacent repos**: developer-level intent signal (see `tools/integrations/github.md` and the `github-prospects.js` CLI). Especially strong for dev-tool SaaS — a developer who starred `vercel/next.js` last week is in-market for adjacent Next.js infrastructure.
- **Recent blog posts / changelog**: product direction signals
- **G2 reviews mentioning competitor switches**: explicit dissatisfaction signal
#### GitHub prospecting pattern (when audience is developers)
For dev-tool SaaS, GitHub is one of the highest-quality discovery channels:
1. Identify 3–5 "anchor" repos: your direct competitors, your category leader, complementary tools your buyer uses
2. Pull stargazers (or forks for stronger intent) via `node tools/clis/github-prospects.js stargazers <owner/repo> --enrich --with-company --format csv`
3. Filter to users with `company` set — these are the easiest to enrich downstream
4. Pair with Apollo/Clay/Hunter to lookup email by name + company
5. Validate with Truelist before adding to outreach list
Tradeoffs: GitHub yields email for only ~5–20% of users directly. The strength is the signal quality — a stargazer of a niche dev tool is genuinely in-market in a way Apollo firmographics alone can't tell you.
---
## Qualification Checklist (SaaS branch)
For each candidate, verify:
- [ ] Industry vertical matches ICP
- [ ] Company size (headcount) within range
- [ ] Tech stack includes (or notably excludes) a target technology
- [ ] Funding stage matches buyer maturity
- [ ] At least one growth signal in last 90 days (funding, hiring, product velocity)
- [ ] Decision-maker role exists at the company (named or inferable from job listings)
- [ ] Email contact verifiable
- [ ] No disqualifiers (closed, acquired-and-paused, layoffs, ICP miss)
---
## Output Columns (SaaS branch)
Recommended CSV columns:
```csv
score,company,domain,industry,size_band,country,funding_stage,last_round_date,tech_stack_match,signal,signal_date,contact_name,contact_title,contact_email,email_status,linkedin_url,source_urls,why_prospect,confidence,verified_date,notes
```
For chat table, condense to: Score | Company | Industry | Size | Signal | Contact | Email status | Confidence.
---
## Top Outreach Targets Selection (SaaS)
Prioritize for the top 3–5 hot leads:
1. **Strongest signal recency** — funding 30 days ago beats funding 9 months ago
2. **Tech stack match strength** — known integration partner beats inferred fit
3. **Decision-maker named with verified email** — beats role-pattern-guessed email
4. **Multi-source confidence** — both Apollo + Crunchbase agree beats one source
Each top target gets a one-sentence outreach rationale that names the specific signal: "Raised Series B 30 days ago; hiring Head of RevOps; verified VP of Ops email."
---
## Common Mistakes (SaaS)
1. **Buying lists from Apollo wholesale** without re-verifying email and re-checking firmographics. Stale data is the norm.
2. **Treating tech stack data as 100% accurate**. BuiltWith and Wappalyzer miss things; Clay's waterfalls miss things. Cross-check.
3. **Targeting Series C+ for early-stage SaaS sellers**. The buyer profile is wrong — too many procurement hoops, too much red tape.
4. **Targeting Series Pre-Seed seed** for products requiring meaningful budget. They have neither budget nor evaluator bandwidth.
5. **Ignoring intent data when it exists** (ZoomInfo Intent, 6sense, etc.) — pre-warm signals beat cold every time.
Hỗ trợ quan hệ công chúng: earned media, thông cáo báo chí, tiếp cận nhà báo và chiến lược truyền thông.
---
name: public-relations
description: "When the user wants help with public relations, earned media, press coverage, journalist outreach, or media strategy (not pull requests). Also use when the user mentions 'PR,' 'public relations,' 'press,' 'press release,' 'press coverage,' 'media outreach,' 'pitch a journalist,' 'get featured,' 'media list,' 'media kit,' 'press kit,' 'newsjacking,' 'news hijack,' 'HARO,' 'Qwoted,' 'Featured,' 'Help A Reporter,' 'reporter request,' 'tech press,' 'TechCrunch,' 'earned media,' 'thought leadership placement,' 'op-ed,' 'guest article,' 'press contacts,' 'podcast prep,' 'going on a podcast,' 'podcast guest,' 'prep me for this podcast,' or 'how do I get press.' Use this for earned media work — finding journalists, pitching stories, newsjacking, prepping podcast appearances, and responding to press requests. For startup/SaaS/AI directory submissions, see directory-submissions. For product launches, see launch. For social-media engagement, see social. For cold-email outreach to prospects, see cold-email."
metadata:
version: 1.1.1
---
# Public Relations & Earned Media
You are an expert in earned media for software products. Your goal is to help the user get covered by journalists, podcasts, and newsletters — efficiently, with respect for the people on the other end of the pitch.
## Before Starting
**Check for product marketing context first:**
If `.agents/product-marketing.md` exists (or `.claude/product-marketing.md`, or the legacy `product-marketing-context.md` filename, in older setups), read it before asking questions. Use that context and only ask for information not already covered or specific to this task.
---
## Core Philosophy
PR is not a substitute for distribution. It's a multiplier for it.
- **Earned media doesn't drive direct conversions.** A TechCrunch hit will not give you 1,000 paying customers. It will give you backlinks, brand legitimacy, AI-citation surface area, and ammo for sales conversations.
- **Pitch journalists like you'd pitch a customer:** specific, useful, fast, and never about you.
- **The story is not your product. The story is the trend, the data, the conflict, or the human.** Your product is the evidence. Every pitchable story bends toward one of three angles — Founding Story, David vs Goliath, or Have an Enemy (a *broken system*, never a competitor). See [references/story-angles.md](references/story-angles.md).
- **Chase press for the compound effect, not the traffic bump.** The bump fades in a day; authority, journalist relationships, and AI-citation surface compound. Build media relationships *before* you need them, and run one core asset through the whole repurposing flywheel.
- **Speed beats polish on reactive PR.** A B+ pitch in the first hour of a story beats an A+ pitch on day three.
### When PR is worth it
- You have **a real story** — proprietary data, a strong opinion, a milestone, a customer with a sharp before/after, or a fresh angle on a trending topic
- You have **founder/exec time** — journalists want quotes from people with skin in the game, not from a PR rep
- You have **a destination** — a press page, blog post, or product launch that converts attention into something useful
### When to skip PR (for now)
- Pre-launch with no story beyond "we exist"
- No one on the team can sustain pitching for 4–6 weeks (PR is a momentum game)
- You don't have a clear ICP — journalists ask "who reads my piece because of this?" and if you can't answer, neither can they
---
## The PR Mix
Four modes. Most teams over-index on one. Run at least three.
| Mode | What it is | Effort | Speed to coverage |
|------|------------|--------|-------------------|
| **Reactive (newsjacking)** | Inject your POV into trending news | Low–medium | Hours to days |
| **Proactive (pitching)** | Build a media list, pitch original stories | High | 2–8 weeks |
| **Inbound (press requests)** | Respond to journalist queries on HARO/Qwoted/Featured | Low | Days to weeks |
| **Owned (press page + media kit)** | Make it easy for journalists to find you | One-time setup | N/A |
**For the story angle taxonomy (Founding Story / David vs Goliath / Have an Enemy), data stories, media relationship-building, and the PR repurposing flywheel** — see [references/story-angles.md](references/story-angles.md)
**For the reactive newsjacking workflow** — see [references/newsjacking.md](references/newsjacking.md)
**For proactive journalist pitching** — see [references/journalist-pitching.md](references/journalist-pitching.md)
**For inbound press-request platforms (HARO, Qwoted, etc.)** — see [references/press-platforms.md](references/press-platforms.md)
**For where to pitch (media outlets, podcasts, newsletters)** — see [references/media-outlets.md](references/media-outlets.md). For startup/SaaS/AI directories, use the separate `directory-submissions` skill — different intent, different list.
**For prepping a podcast appearance you've landed** — see [references/podcast-guest-prep.md](references/podcast-guest-prep.md). Episodes get transcribed and cited by AI assistants, so a good appearance compounds in AI answers for years — prep is an AI-visibility play, not just interview polish.
---
## Owned: Press Page + Media Kit
Set this up once. It's the cheapest PR investment with the highest ROI on every future story.
**Press page (`/press` or `/newsroom`) should include:**
- One-paragraph company description (copy/paste ready)
- Founder bios with headshots (high-res, downloadable)
- Logo pack (SVG + PNG, light + dark, with usage guidelines)
- Product screenshots (high-res)
- Recent coverage list (social proof for the next journalist)
- Founding date, employee count, funding (if disclosed)
- Press contact email (not a form — journalists hate forms)
- Recent press releases / announcements
**One sentence at the top:** "For interview requests or assets, email press@yourcompany.com — we respond within 24 hours."
Then *actually* respond within 24 hours.
---
## Quick Reference: Pitch Quality Bar
Before sending any pitch, the answer to all of these should be yes:
- [ ] Does this journalist cover this beat? (Check their last 5 articles.)
- [ ] Is there a clear news hook — something that just happened or is about to?
- [ ] Could this journalist write a complete story from this email alone? (Data, quotes, customer name, contact.)
- [ ] Is the subject line specific enough to predict the article's headline?
- [ ] Is the pitch under 150 words?
- [ ] Did you avoid the words "revolutionary," "game-changing," "disruptive," and "synergy"?
- [ ] Is the ask clear? (Interview? Embargo? Exclusive? Quote?)
If any answer is no, don't send.
---
## Measurement
What to track:
| Metric | Why |
|--------|-----|
| **Coverage count** (placements / month) | Activity baseline |
| **Domain rating of placements** | Backlink value |
| **Referral traffic from coverage** | Did anyone actually click? |
| **Brand search lift** | Did people search you after reading? |
| **AI citation rate** (ChatGPT, Perplexity quote your brand?) | The new measurement that matters |
| **Sales conversations citing the article** | The only one that matters for revenue |
What not to obsess over: AVE (advertising value equivalency) — it's a vanity metric PR firms invented.
---
## Common Workflows
### "Help me newsjack [trending story]"
Go to [newsjacking.md](references/newsjacking.md), run the scoring rubric, draft 2–3 angles, pick the best, draft the pitch.
### "Find journalists who cover [beat]"
Go to [journalist-pitching.md](references/journalist-pitching.md), use the discovery checklist + dev-browser to research recent articles, build a scored list.
### "What's worth pitching this week?"
Combine: recent product milestones + active news cycles + any data you've collected. Score each potential story by the quality bar above.
### "What's my story angle?" / "How do I get press with no news?"
Go to [story-angles.md](references/story-angles.md). Fit the situation to one of the three angles (Founding Story / David vs Goliath / Have an Enemy), or turn proprietary data into a data story. Remember: a milestone alone isn't a story — milestone *with narrative* is.
### "Respond to this HARO query"
Go to [press-platforms.md](references/press-platforms.md), use the response template, keep it under 200 words.
### "I'm going on [podcast] next week — help me prep"
Go to [podcast-guest-prep.md](references/podcast-guest-prep.md): research the show (RSS feed → site → Apple Podcasts → web), extract the recurring threads and host profiles, map the guest's stories onto them, deliver the brief.
### "Build my press page"
Use the checklist above. Most companies do this in an afternoon and forget about it for a year — that's fine.
FILE:evals/evals.json
{
"skill_name": "public-relations",
"evals": [
{
"id": 1,
"prompt": "We're a tiny 2-person SaaS competing against Notion in the docs space. We don't have any funding news or a big milestone. How do we get press coverage?",
"expected_output": "Should route to references/story-angles.md. Should recognize there's no traditional news hook and instead fit the situation to one of the three story angles, most naturally David vs Goliath (own your size against an incumbent, cite the Superhuman vs Gmail exemplar). Should mention the other two angles — Founding Story (Pieter Levels '12 startups in 12 months,' build-in-public, share revenue) and Have an Enemy (the enemy is a broken system, not the competitor — never 'Notion is bad,' and reference HEY vs Apple's App Store 2020). Should reinforce 'the story is not your product' and that a milestone alone isn't a story — milestone with narrative is. Should note the compound effect (authority, relationships, AI-citation surface) over the traffic bump. May suggest turning proprietary data into a data story and building media relationships before pitching. Should not invent fake news or suggest attacking the competitor by name.",
"assertions": [
"Routes to references/story-angles.md",
"Identifies David vs Goliath as the fitting angle with the Superhuman vs Gmail exemplar",
"Names all three angles (Founding Story, David vs Goliath, Have an Enemy)",
"Clarifies the enemy is a broken system, not a named competitor (HEY vs Apple App Store 2020)",
"Cites Pieter Levels '12 startups in 12 months' for Founding Story",
"Reinforces 'the story is not your product'",
"Notes milestone-with-narrative and/or the compound effect over the traffic bump",
"Does not fabricate news or suggest smearing the competitor by name"
],
"files": []
}
]
}
FILE:references/journalist-pitching.md
# Journalist Pitching — Proactive PR Workflow
Building a media list, scoring journalist fit, and crafting pitches that actually get opened. This is a 4–8 week practice, not a one-shot.
## Contents
- Building the media list
- Scoring journalist fit
- Pitch templates by angle
- Subject lines that get opened
- Voice and structure
- Embargoes, exclusives, and follow-up etiquette
- Pitch killers
- Tooling
---
## Building the Media List
The goal: a list of 20–40 journalists who actually cover your beat. Not 500 names from a database.
### Discovery checklist
For each candidate journalist:
- [ ] Read their **last 5 articles** — are they covering your beat right now?
- [ ] Note their **publication** — does it reach your ICP?
- [ ] Check their **bio** on the outlet site — what topics do they own?
- [ ] Check **X/LinkedIn** for what they're posting about this week
- [ ] Note their **email** (usually on outlet author page, Muck Rack, or company About page)
- [ ] Check **Muck Rack** if available — it shows recent topics and pitch preferences
### Where to find candidates
| Method | How |
|--------|-----|
| **Reverse lookup from coverage you want** | Find 5 articles about competitors / your category, note bylines |
| **Topic search on Muck Rack** | Free tier shows journalists by topic |
| **X / Twitter lists** | "[your niche] reporters" lists already exist |
| **LinkedIn search** | "Journalist" + "[your category]" — filter by recent activity |
| **Newsletter author pages** | Beehiiv, Substack, ConvertKit creators are pitchable |
| **Podcast host research** | Listen to 1 episode before pitching — non-negotiable |
### Don't waste time on
- **Mass media databases** (Cision, Meltwater) for early-stage — overkill and expensive
- **Journalists who haven't posted in 6+ months** — they may have left
- **"Editor-in-chief" generic addresses** — pitches there get ignored or routed to interns
- **Journalists who explicitly state "no PR pitches" in bio** — respect it
---
## Scoring Journalist Fit
Score each journalist 1–10 across four dimensions. Sum and rank. Focus on top 20.
| Dimension | What it measures | Weight |
|-----------|------------------|--------|
| **Beat match** | Do they cover your category specifically? | 3x |
| **Reach** | Outlet's audience size + their byline traction | 2x |
| **Engagement** | Do they respond to pitches publicly / on X? | 2x |
| **Recency** | Have they written about a related topic in last 30d? | 1x |
**Tiering:**
- **Tier 1 (8–10):** Personal pitch with original angle. Custom each time.
- **Tier 2 (5–7):** Standard pitch, lightly customized.
- **Tier 3 (below 5):** Skip or pitch only when story is exceptional.
---
## Pitch Templates by Angle
Six structures that work. Pick the one that matches your story.
### 1. Data story
```
Subject: [Specific stat] — [implication]
Hi [name],
I noticed you covered [recent article] — wanted to share data that might
be relevant.
We [analyzed N / surveyed N / tracked N] and found:
• [Stat 1 with surprise factor]
• [Stat 2]
• [Stat 3]
The most interesting pattern: [one-sentence insight].
Full data + methodology here: [link to one-pager, not your homepage]
Happy to share the raw dataset, jump on a call, or connect you with
[customer who's relevant].
[your name + 1-line credential]
```
### 2. Exclusive launch / milestone
```
Subject: Exclusive: [specific milestone] at [company]
Hi [name],
I have an exclusive on [milestone] that I think fits your [beat] coverage.
The story: [one sentence]
Why it matters: [one sentence — for their readers, not for you]
What's new: [the actual news, not the marketing line]
Embargo until [day, time, timezone] — would love to give you first
window. Press kit + assets: [link]
Free to talk [two specific time options].
[your name]
```
### 3. Op-ed / contributed piece
```
Subject: Op-ed pitch: [provocative thesis]
Hi [name],
I read your piece on [recent article] — sharp take on [specific point].
I'd like to pitch a 700-word op-ed: "[Thesis as a headline]"
Core argument:
• [Point 1]
• [Point 2]
• [Point 3 — the surprising one]
Why me: [1 sentence — credential or unique vantage]
Why now: [1 sentence — the news hook]
Can have a draft to you by [date]. Happy to adapt to your house style.
[your name]
```
### 4. Customer story
```
Subject: Customer story for [their beat] — [specific outcome]
Hi [name],
For your [beat] coverage, I have a [customer type] willing to talk on
the record about [specific outcome].
The hook: [customer] [did something specific] and [measurable result].
The interesting part: [the surprising or counterintuitive detail].
Customer details:
• Name: [name, title, company]
• Available: [windows]
• Willing to share: [data points / screenshots / metrics]
Happy to coordinate the intro.
[your name]
```
### 5. Trend piece / connector
```
Subject: Trend forming in [space] — three signals
Hi [name],
Three things in [space] this month that I think connect:
1. [Signal 1 with link]
2. [Signal 2 with link]
3. [Signal 3 — yours, briefly]
The pattern: [one sentence].
This might be early for a piece, but if you're tracking the space I
wanted to flag it. Happy to share data we've collected or connect you
with others seeing the same.
[your name]
```
### 6. Newsjack response
```
Subject: Re: [their article headline] — quick data point
Hi [name],
Saw your piece on [story] this morning — wanted to add a relevant
data point in case you do a follow-up.
[One-sentence stat or insight].
Source: [our data / our customers / our analysis]
Methodology: [one sentence]
Quotable: "[a sentence you'd be comfortable seeing in print]"
If useful for a follow-up, I'm around all day at this number: [phone].
[your name]
```
---
## Subject Lines That Get Opened
Journalists open pitches based on the subject line alone. Rules:
- **Under 50 characters** — mobile preview cuts off
- **Lead with the specific** — "73% of devs deploy to prod on Fridays" beats "New data on developer workflows"
- **Promise a story, not a product** — "Why [trend]" beats "[Company] launches [thing]"
- **Use prefixes that signal value** — "Exclusive:", "Data:", "Op-ed pitch:", "Re: [their article]"
**Test against this question:** would *you* open this in a 200-email inbox?
**Patterns that work:**
- "[Specific stat] — [implication]" — "73% of agents fail this test"
- "Exclusive: [milestone]" — "Exclusive: Anthropic launches AgentOS"
- "Re: [their headline]" — direct response to recent coverage
- "[Provocative thesis]" — "Why VC funding is bad for AI safety"
**Patterns that get deleted:**
- "Press release: [boring]"
- "[Company] announces [thing]"
- "Story idea for you!"
- "Following up on my previous email"
- Any subject line with "innovative," "disruptive," "revolutionary"
---
## Voice and Structure
### Length
**150 words max for the pitch.** If you can't say it in 150 words, you don't know what your story is yet.
### Structure
1. **One-line context** — why you're emailing them specifically (their recent article, their beat)
2. **The story** — what it is, in one sentence
3. **Why it matters to their readers** — not why it matters to you
4. **Proof** — data, customer, quote, link
5. **The ask** — interview, embargo, quote, link
### Voice
- Sound like a person, not a press release
- Reference their actual recent work — proves you read them
- Don't use emoji unless they do
- Don't open with "I hope this finds you well" — burn it
- Don't ask "did you get my email?" follow-ups (see [Follow-up](#embargoes-exclusives-and-follow-up-etiquette))
### Banned vocabulary
Revolutionary, disruptive, game-changing, paradigm shift, leverage, synergy, robust, seamless, holistic, world-class, best-in-class, next-generation, cutting-edge, AI-powered (unless that's the actual differentiation), at-the-end-of-the-day.
---
## Embargoes, Exclusives, and Follow-Up Etiquette
### Embargoes
An embargo is "you can write this story, but don't publish until [time]."
- **Only offer embargoes to journalists you've worked with** or have strong reputations for honoring them
- State the embargo time clearly: day, time, timezone
- If they break embargo, your relationship with them is over
### Exclusives
"Only you get this story" — powerful tool, use sparingly.
- **First-tier outlet only** — exclusives to tier 2/3 outlets waste the lever
- **Be honest about scope** — "exclusive to [outlet] in the US" is fine
- **Have a parallel plan** — what you publish/pitch the next day after the exclusive runs
### Follow-up cadence
- **Day 0** — initial pitch
- **Day 3** — one follow-up if you have new information ("Just talked to [customer] who can join us")
- **Day 7** — final check-in with a fresh hook ("This came out today, still relevant?")
- **After day 7** — let it go. Re-pitch when you have something genuinely new.
**Never:**
- "Bumping this up" / "Did you see my email?"
- Multi-day silent follow-ups with no new value
- Same pitch reformatted
---
## Pitch Killers
Things that instantly disqualify your pitch:
- Wrong name / wrong outlet (autoreplace fail)
- Pitching topics they explicitly don't cover
- Press release attached as PDF (just paste the key bits)
- Long signature with logos and disclaimers
- CC'ing 5 other journalists on the same email
- "Per my last email" energy
- Asking them to sign an NDA before talking
- Pitching a story you can't actually tell (no customer willing to talk, no data ready to share)
---
## Tooling
### Finding journalist contact info
```bash
# Most journalists' emails follow patterns:
# firstname@outlet.com
# firstname.lastname@outlet.com
# flastname@outlet.com
# Use Hunter.io, RocketReach, or just guess and bounce-check
```
### Researching their recent work (browser-driven)
Use `dev-browser` (persistent session, no rate limits) to:
- Open the journalist's outlet author page → scrape last 5 article headlines + dates
- Open their X/Twitter profile → note recent topics
- Open their LinkedIn → confirm current role
Output what you find as:
```
JOURNALIST PROFILE — [name]
Outlet: [name]
Beat: [topics from last 5 articles]
Recent angle: [pattern you noticed]
Recent X activity: [what they're posting]
Score: [X/40 from rubric]
Best pitch angle: [from template library]
Email: [confirmed]
```
### Maintaining the media list
Store in `.agents/media-list.md` (or `.csv` if you prefer). Update monthly — journalists move jobs constantly.
```markdown
## Tier 1 (top 20)
| Name | Outlet | Beat | Last contact | Last coverage | Email | Score |
|------|--------|------|--------------|---------------|-------|-------|
| ... | ... | ... | 2026-05-15 | none yet | ... | 9/10 |
```
### Pitch tracking
Track in a simple spreadsheet:
- Date sent
- Subject line
- Journalist
- Outlet
- Response (open / reply / pass / coverage)
- What they said
After 30 pitches, you'll see which subject patterns and which angles work for you specifically.
FILE:references/media-outlets.md
# Media Outlets — Where to Pitch
A curated, opinionated list of *where* to pitch for software/SaaS PR. This is the media-outlet slice of resources like submit.co — the journalist-driven half. For startup/SaaS/AI directories (Product Hunt, BetaList, Futurepedia, etc.), use the separate `directory-submissions` skill — different intent, different audience.
## How to use this list
- **Don't pitch a publication. Pitch a journalist at that publication.** See [journalist-pitching.md](journalist-pitching.md) for the discovery workflow.
- **Tier signals quality, not effort** — a small tier-3 outlet might be perfect for your niche
- **Submission/tip URLs are listed where they exist** — but a journalist's email beats a tip form every time
- **This list ages fast** — verify the outlet still exists and the journalist is still there before pitching
---
## Tech & Startup Press (Tier 1)
The big names. High bar to clear, high payoff when you do. Pitch the specific reporter, not the tip line.
| Outlet | Best for | Tip URL |
|--------|---------|---------|
| **TechCrunch** | Funding, product launches, startup news | techcrunch.com/got-a-tip/ |
| **The Verge** | Consumer tech, product reviews, policy | theverge.com/contact |
| **Wired** | Long-form tech, culture, business | wired.com/about/feedback |
| **Fast Company** | Innovation, design, business strategy | fastcompany.com/contact-us |
| **VentureBeat** | AI, enterprise tech, gaming | venturebeat.com/contribute |
| **Ars Technica** | Deep tech, science, policy | arstechnica.com/contact-us |
| **The Information** | Subscription-gated, scoops on tech industry | theinformation.com/about |
| **Bloomberg / Reuters** | Business, finance, big-picture stories | bloomberg.com/feedback |
| **WSJ Tech** | Enterprise, business angle | wsj.com/tips |
| **NYT Tech** | Mainstream tech, culture | nytimes.com/tips |
---
## SaaS & B2B (Tier 1–2)
Lower-profile than the consumer tech outlets but often higher ROI for B2B SaaS.
| Outlet | Best for | Notes |
|--------|----------|-------|
| **SaaStr** | B2B SaaS founders, growth, sales | Jason Lemkin's outlet; pitch contributed posts |
| **First Round Review** | Operator-level B2B insights | High bar; original frameworks only |
| **OpenView** | SaaS metrics, PLG, pricing | Often runs research-backed pieces |
| **Lenny's Newsletter** | Product / growth | Subscriber-only; pitch via Lenny directly on X |
| **Future (a16z)** | Tech + culture pieces | Long-form, original thinking required |
| **Stratechery** | Tech strategy analysis | Don't pitch Ben Thompson; engage via responses |
---
## AI / ML Press (Tier 1–2)
The hottest beat right now. Reporters here are inundated — your angle has to be sharp.
| Outlet | Best for | Notes |
|--------|----------|-------|
| **The Decoder** | AI news, fast-turn | Daily AI news cycle |
| **Import AI** (Jack Clark) | Weekly AI newsletter | Pitch research-backed angles |
| **The Batch** (Andrew Ng) | AI industry analysis | Submitted by deeplearning.ai team |
| **MIT Technology Review** | AI policy, capability, ethics | Higher bar, slower cycle |
| **Hugging Face blog** | Open-source AI tools | Contributed posts welcome |
| **Latent Space** | Practitioner-focused AI/ML | Podcast + newsletter |
---
## Developer & DevTools Press
Where to pitch if your audience is engineers.
| Outlet | Best for | Notes |
|--------|----------|-------|
| **The New Stack** | DevTools, infra, cloud-native | Active contributor program |
| **InfoQ** | Enterprise dev, software architecture | Long-form technical pieces |
| **DEV.to** | Self-published, community-driven | Build credibility before pitching |
| **Hacker Noon** | Tech blogging platform | Easy to publish but low signal |
| **Console** | DevTools newsletter | Curated weekly; submit projects |
| **DevTools FM** | Podcast | Pitch as guest |
---
## Business & Marketing Press
For pitching the business / marketing angle of your story.
| Outlet | Best for | Notes |
|--------|----------|-------|
| **Inc.** | Founder stories, business advice | Contributor program available |
| **Entrepreneur** | Small business, growth | Volume publisher; quality varies |
| **HBR.org** | Original research, frameworks | High bar; long lead time |
| **MarketingProfs** | B2B marketing | Contributor-friendly |
| **Marketing Brew (Morning Brew)** | Daily marketing newsletter | Reporter-driven, pitch directly |
| **Marketing Land / Search Engine Journal** | SEO, SEM, channels | Niche but high-intent audience |
| **Reforge** | Growth, product, retention | Original framework required |
---
## Newsletters (Reporter-Driven)
Newsletters are increasingly the most valuable PR placement — small audiences, but high-intent.
| Newsletter | Audience | How to pitch |
|-----------|---------|--------------|
| **Lenny's Newsletter** | Product/growth, ~600k readers | DM Lenny on X with sharp angle |
| **The Pragmatic Engineer** (Gergely Orosz) | Software eng leaders | Pitch via email; original engineering insights |
| **The Generalist** (Mario Gabriele) | Tech business analysis | Pitch via email; deep angles |
| **Stratechery** (Ben Thompson) | Tech strategy | Don't pitch; engage in replies |
| **Newcomer** (Eric Newcomer) | Tech business + scoops | Tips welcome via email |
| **Platformer** (Casey Newton) | Tech + policy | Pitch via Substack |
| **Big Technology** (Alex Kantrowitz) | Big Tech analysis | Pitch via Substack |
| **Not Boring** (Packy McCormick) | Tech + culture | Tough to crack; original takes only |
**For B2B SaaS:**
- **SaaStr Daily**
- **Demand Curve**
- **Growth Unhinged** (Kyle Poyar)
- **Trends.vc**
**For AI:**
- **Import AI** (Jack Clark)
- **The Batch** (Andrew Ng)
- **The Algorithm** (MIT Tech Review)
- **Interconnects** (Nathan Lambert)
- **AI Tidbits**
---
## Podcasts
A podcast appearance is often higher leverage than a press hit — longer engagement, evergreen replay, audience trust transfer.
### Top SaaS / startup podcasts
- **Lenny's Podcast** — product/growth
- **My First Million** — founder stories, business ideas
- **The Twenty Minute VC** — funding angle
- **SaaStr Podcast** — B2B SaaS
- **Acquired** — deep-dive company stories (don't pitch unless you're a unicorn)
- **The All-In Podcast** — broad tech/business (very hard to get on)
### Top AI podcasts
- **No Priors** (Sarah Guo, Elad Gil)
- **Latent Space**
- **Practical AI**
- **The TWIML AI Podcast**
- **The Cognitive Revolution**
### Top dev / engineering podcasts
- **The Changelog**
- **Software Engineering Daily**
- **DevTools FM**
### How to pitch a podcast
1. **Listen to 3 episodes** — non-negotiable
2. **Find the host's preferred channel** (X DM, email, guest form)
3. **Pitch a topic, not yourself** — "I'd love to come on and talk about [specific angle]" not "I'd love to be a guest"
4. **Bring evidence** — links to other appearances, your unique angle, what listeners will learn
5. **Make it easy** — bio, headshot, suggested questions in the pitch
---
## Industry / Vertical Press
Don't overlook trade press — smaller audience, much higher intent.
| Vertical | Outlets to investigate |
|----------|----------------------|
| **Marketing** | Adweek, Marketing Brew, AdAge, Search Engine Land |
| **Sales** | Sales Hacker, Modern Sales Pros |
| **HR / People Ops** | HR Brew, SHRM, HR Dive |
| **Finance / Fintech** | The Block, CoinDesk, Finextra |
| **Healthcare tech** | STAT, MobiHealthNews, Healthcare IT News |
| **Education tech** | EdSurge, EdScoop |
| **Real estate tech** | Inman, The Real Deal |
| **Legal tech** | Above the Law, Law360 |
| **Climate tech** | Heatmap, Canary Media, Latitude Media |
| **Devtools** | The New Stack, InfoQ, DevOps.com |
For your specific vertical: Google `"top publications" + "[your industry]"` and run the same scoring exercise from [journalist-pitching.md](journalist-pitching.md).
---
## Regional / Local
If your company has a regional angle (HQ location, customer concentration, government contract), local press is underrated.
- **Local business journals** — Bizjournals network covers 40+ US cities
- **Local NPR affiliates** — high-quality, business angle welcomed
- **Local TV business segments** — high reach, easy to get
- **State / regional tech news** — e.g., Built In (Chicago, Austin, etc.), TechBuzz (Utah)
---
## What's NOT On This List (And Why)
- **Product Hunt, BetaList, Indie Hackers** — these are directories, not press. Use the `directory-submissions` skill.
- **Press release wires** (PRNewswire, BusinessWire, GlobeNewswire) — overpriced for early-stage; journalists ignore them. Skip until you have IR / SEC requirements.
- **"As featured in" badge mills** — paid "media coverage" services. Worthless and damaging.
- **Random "guest post" SEO link networks** — Google penalizes these. Don't.
---
## Maintaining This List
This list will go stale. Recommended cadence:
- **Quarterly:** verify your tier-1 contacts are still at the outlet
- **Monthly:** add new outlets/newsletters relevant to your category
- **Per pitch:** confirm the journalist is still there before sending (check X / LinkedIn for "joined [new place]")
Store your live, working version in `.agents/media-list.md` (per [journalist-pitching.md](journalist-pitching.md)).
FILE:references/newsjacking.md
# Newsjacking — Reactive PR Workflow
Injecting your POV into a story that's already trending. Done well: free distribution off a wave of attention. Done badly: cringe at best, brand damage at worst.
## Contents
- When newsjacking works (and when it doesn't)
- The detect → score → angle → pitch loop
- Newsworthiness scoring rubric
- Story angle library
- Speed: the only thing that matters
- Sources & tooling
- Failure modes
---
## When Newsjacking Works
- **Tech/regulatory news in your category** — new law, new platform launch, competitor pivot, big acquisition
- **Industry data drops** — a major report drops, you have a sharper take or contradicting data
- **Public conversation** — a debate or controversy where your expertise is genuinely relevant
- **Seasonal/cyclical moments** — earnings season, year-end reviews, conference weeks
## When to Skip
- **Tragedies, accidents, deaths** — no exceptions. Don't.
- **Politically charged stories** unless your brand explicitly takes political stances
- **You have no genuine expertise** in the area
- **The window is already closed** — if a story is 48h+ old and you weren't first, you're late
- **The angle is "we have a product for this"** — that's marketing, not journalism
---
## The Loop
A repeatable workflow Claude can run on demand or daily.
1. **Detect** — surface trending stories in your category (see [Sources & Tooling](#sources--tooling))
2. **Score** — apply the [newsworthiness rubric](#newsworthiness-scoring-rubric); drop anything below threshold
3. **Angle** — generate 2–3 angles per story using the [angle library](#story-angle-library)
4. **Validate** — sanity-check: do you actually have the expertise/data to back this angle?
5. **Pitch** — draft a tight pitch to 3–5 journalists who cover this beat (see [journalist-pitching.md](journalist-pitching.md))
6. **Post** — also publish on your blog, LinkedIn, X — it builds the trail journalists check before quoting you
Output format Claude should produce:
```
NEWSJACK CANDIDATE — 2026-06-10
Story: "EU passes AI Act amendment requiring agent registration"
Source: TechCrunch, 3h ago
Score: 8/10 (high relevance, fresh, you have proprietary data)
Angles:
1. Data hot take: "Our analysis of 12,000 agent deployments shows 73% would fail this requirement"
2. Contrarian: "Why the registration rule will hurt safety, not improve it"
3. Customer story: "How [customer] is preparing — interview offer"
Recommended: #1 (you have unique data, strongest hook)
Pitch draft: [see journalist-pitching.md for template]
Target journalists: [list with rationale]
```
---
## Newsworthiness Scoring Rubric
Score each candidate 1–10 on five dimensions, multiply by the weight, then sum. Max possible: 80 (10 × the 8x weight total).
| Dimension | What it measures | Weight |
|-----------|------------------|--------|
| **Timeliness** | Story <24h old? Window still open? | 2x |
| **Relevance** | Genuinely in your expertise area? | 2x |
| **Angle uniqueness** | Can you say something no one else is saying? | 2x |
| **Authority** | Do you have data, customers, or experience to back it? | 1x |
| **Reach potential** | Will this story keep growing or has it peaked? | 1x |
**Threshold:** weighted total ≥ 50/80. Below that, skip.
**Auto-disqualify if:**
- The story is about something tragic
- Your angle is "I disagree" with nothing to back it
- You haven't actually formed an opinion — you just want to be quoted
---
## Story Angle Library
Use these templates to generate angles fast.
### 1. Data hot take
*"We analyzed [N] [things] after [event]. Here's what we found."*
Best when you have proprietary data. The journalist gets a stat, you get the citation.
### 2. Contrarian
*"Everyone says [popular take]. Here's why they're wrong."*
Best when you can defend the position with specifics. Weak when it's just contrarianism for attention.
### 3. "We predicted this"
*"Six months ago we wrote [thing] — here's what's happening now and what's next."*
Best when you actually did predict it. Lethal to your credibility if you didn't.
### 4. Customer impact
*"Here's a [customer type] who's directly affected. We can put you in touch."*
Best for B2B. Reporters love named customers willing to talk.
### 5. Insider explainer
*"This story is complicated. Here's what's actually happening."*
Best when most coverage is missing nuance. You're not arguing — you're educating.
### 6. Trend connector
*"This isn't isolated — it's part of a bigger shift we're seeing in [pattern]."*
Best when you have several data points or examples to connect.
### 7. Founder POV
*"As someone who's built in this space for [X years], here's the part most people are missing."*
Best for opinion pieces / op-eds. Weak as a soundbite pitch.
---
## Speed: The Only Thing That Matters
Newsjacking decays fast. Approximate windows:
| Story type | Effective window |
|-----------|------------------|
| Breaking tech news | 4–12 hours |
| Major regulation / policy | 24–48 hours |
| Industry report / data drop | 24–72 hours |
| Conference announcement | Same day |
| Acquisition / funding news | 12–24 hours |
**Implication:** if you can't draft and send within the window, don't bother. Set up the loop so detection → pitch takes <2 hours.
---
## Sources & Tooling
Reuses tooling from the `social` skill's listening workflow. Same install: `brew install jq`.
### Google News RSS (no auth)
```bash
# Replace QUERY with topic (use + for spaces, %22 for quotes)
curl -s "https://news.google.com/rss/search?q=QUERY&hl=en-US&gl=US&ceid=US:en" \
| xmllint --xpath "//item[position()<11]" - 2>/dev/null
```
### Hacker News (Algolia) for tech stories
```bash
SINCE=$(($(date +%s) - 86400))
curl -s "https://hn.algolia.com/api/v1/search_by_date?query=QUERY&tags=story&numericFilters=created_at_i>SINCE" \
| jq '.hits[] | {title, url, points, num_comments, created_at, hn_url: ("https://news.ycombinator.com/item?id="+.objectID)}'
```
### Reddit (for category-specific subs)
```bash
curl -s -A "newsjack/1.0" \
"https://www.reddit.com/r/SUBREDDIT/top.json?t=day&limit=15" \
| jq '.data.children[].data | {title, url, score, num_comments, created_utc}'
```
### Journalist research (browser-driven)
For finding *which* journalists are covering the story right now:
- **dev-browser** → Google News search for the story → click through to articles → note the bylines
- Then go to those journalists' X / LinkedIn / Muck Rack profile to confirm beat and recent coverage
See also [journalist-pitching.md](journalist-pitching.md) for the full discovery workflow.
### Source list
For repeatable monitoring, add a "Newsjacking topics" section to `.agents/listening-sources.md` (template in the `social` skill's references):
```markdown
## Newsjacking topics (Google News RSS)
- "AI agent regulation"
- "[your category] funding"
- "[your competitors] OR [adjacent competitors]"
## Industry data drops (RSS / manual)
- Pitchbook reports
- a16z state of [industry] reports
- [your category] benchmark reports
```
---
## Failure Modes
Things that have ended careers and brands.
- **Tragedy-jacking** — Oreo's 2013 Super Bowl tweet worked. Most attempts since have not. Wartime, disasters, deaths: don't.
- **The forced fit** — "Here's our take on [trending story] — it's actually about [our product]." Journalists see through this instantly.
- **The empty take** — pitching "we have an opinion" without specifics. Journalists need a quote-worthy line, not "we're closely watching this."
- **Speed without judgment** — being first with a bad take is worse than being late with a good one. The 30-minute "is this brand-appropriate?" gut check exists for a reason.
- **Pitching the same angle to 50 journalists** — they talk. Get caught once, lose the relationships.
- **No follow-through** — pitch goes out, journalist responds in 20 minutes, founder takes 6 hours to reply. Story moves on.
---
## Companion Practice: The Public Trail
Every newsjack pitch is stronger if the journalist can find evidence you've been thinking about this publicly. Before pitching:
1. Publish a short post (blog, LinkedIn, X thread) with your take
2. Reference it in the pitch ("more thinking here: [link]")
3. This signals you're not opportunistic — you're an actual voice in the space
If you don't have time to publish, you're probably not ready to pitch.
FILE:references/podcast-guest-prep.md
# Podcast Guest Prep
Build a prep brief before the user appears on a podcast as a guest. The goal: walk in knowing what's top of mind for the show, how it has evolved, who the hosts are, and which of the user's stories map onto what the show cares about *right now*.
**Why prep is worth real effort:** podcast guesting isn't just audience reach. Episodes get transcribed, show notes get published, and both get crawled and cited by AI assistants — when someone asks ChatGPT about your category, the stories you told on a podcast two years ago are part of what it draws on. A good appearance is earned media that compounds in AI answers for years (see the `ai-seo` skill). The stories you tell — and the concrete numbers in them — become the citable record on your brand. Prep accordingly.
## Context to load first
Read `.agents/product-marketing.md` (or `.claude/product-marketing.md`) for the company, positioning, and ICP. That file usually won't have the guest's *story bank*, so also collect — in one batch, not a drip:
1. What did you build before this that comes up in conversation?
2. What are 2–3 stories you tell well, with real numbers attached?
3. What's one opinion you hold that most people in your space disagree with?
Offer to save the answers into the product-marketing context doc so future runs skip the interview.
From their message, establish (ask only if missing and it matters): the podcast name or URL, whether they've appeared before (get the prior episode link — it anchors the progression analysis and the callbacks), and roughly when they're recording.
## Research sequence
Work through sources in this order; each is a fallback for the last:
1. **RSS feed first.** The richest source: full episode descriptions, chapter markers, guest links, dates. Find the feed link on the podcast site (Buzzsprout, Transistor, etc. all expose one). Large-feed fetches may truncate — check whether the oldest episodes you need actually made it.
2. **The podcast website's episode list** for anything the feed missed. These pages often lazy-load older episodes via JavaScript; if pagination returns nothing, note the gap and move on rather than burning time.
3. **Apple Podcasts show page** — reliably renders the latest ~8 episodes with full descriptions.
4. **Web search** for stray episodes, the hosts, and the show's reputation.
Don't fetch every episode page. Descriptions plus chapter lists are almost always enough; only pull a full transcript when a specific episode is central (e.g., a debate the guest should have a position on). Check for published transcript links in the feed.
## What to extract
- **Recent-episode threads** (last ~3 months or 6–8 episodes): per-episode topic summaries, then the *recurring threads* — the questions the hosts keep returning to. Threads matter more than individual episodes; they predict the questions the guest will get.
- **Show progression** (since their last appearance, or ~12–18 months if first time): identify phases and the inflection point where the show's focus shifted. Note whether the show re-invites guests (signals how a return visit fits) and whether hosts launched side projects.
- **Host profiles.** Sources: the show's about pages, hosts' personal sites, LinkedIn, and — often the best source — episodes where the hosts guest on *other* shows and introduce themselves. Capture day job, background, what they've personally been building (mine solo-episode summaries), and social handles. If a host shares the guest's first name, flag it and keep references unambiguous throughout the brief.
- **Prior appearance recap** (if returning): what was actually discussed, with rough timestamps, and how much airtime the guest's current company got. This sets up the "what's changed since" narrative.
## The brief
Write a markdown file and present the short version in chat. Structure:
1. **Big picture** — what kind of show this is now, and the one-paragraph read on how the guest should position themselves
2. **Show progression** — the phases since their last appearance (or show start)
3. **Recent episodes in detail** — per-episode notes, then the recurring threads
4. **Guest angles** — their stories mapped explicitly onto the show's threads, callbacks to any prior appearance, and 2–3 "pocket" items: concrete stories with numbers to have ready
5. **The hosts** — profiles plus rapport hooks (where each host's world overlaps the guest's)
6. **Gaps** — anything unretrievable, and offers to fill them
Keep it tight — a doc they'll skim before recording, not a report.
**Finding angles:** map the story bank onto the show's recurring threads. The shape to look for: a "data moats" thread maps to the guest's proprietary dataset as a live case study; an "AI replacing niche tools" debate maps to a defensibility story from their product history. One well-chosen contrarian take stands out most on shows that have converged on a consensus.
**AI-visibility angle:** since the transcript becomes the record, coach the guest to say the important things in liftable form — the company name next to the category ("we build X, the Y for Z"), and numbers spoken aloud, not gestured at. Same logic as the YouTube text layer in `ai-seo`.
## Follow-ups to offer (don't auto-run)
Transcribing their prior episode for a word-level review, pulling a full transcript of one pivotal recent episode, or drafting likely Q&A.
---
*Distilled and adapted from [ai-visibility-skills](https://github.com/Knowatoa/ai-visibility-skills) by Knowatoa (MIT), reused with credit.*
FILE:references/press-platforms.md
# Press Request Platforms — Inbound PR
Journalists posting "I need a source for X" — you respond, sometimes you get quoted, sometimes you don't. The cheapest PR play available, but only if you treat it seriously.
## Contents
- The major platforms
- Daily triage workflow
- Response template
- What makes a response get selected
- What kills a response
- ROI reality check
---
## The Major Platforms
| Platform | What it is | Cost | Quality |
|----------|-----------|------|---------|
| **[Connectively](https://www.connectively.us)** (formerly HARO) | Daily email digest of journalist queries | Free tier; paid for filters | Mixed — high volume, lots of noise |
| **[Qwoted](https://www.qwoted.com)** | Web app with journalist requests | Free; paid for outreach | Good — better-quality outlets |
| **[Featured](https://featured.com)** | Web app, expert profiles, journalist requests | Free tier; paid pro | Good for thought-leadership snippets |
| **[Help A B2B Writer](https://helpab2bwriter.com)** | Twice-weekly email of B2B queries | Free | High — B2B-focused, low spam |
| **[SourceBottle](https://www.sourcebottle.com)** | Australia-focused but global queries | Free | Variable |
| **[Terkel](https://terkel.io)** | Roundup-style ("we asked 50 experts…") | Free | Volume-heavy, low effort |
| **[JournoRequests](https://twitter.com/journorequests)** | X account aggregating tweets | Free | UK-skewed, real-time |
| **#JournoRequest** (X hashtag) | Live journalist requests | Free | Real-time, fast-moving |
**Recommended starter set:** Connectively + Qwoted + Help A B2B Writer + monitoring `#JournoRequest` on X.
---
## Daily Triage Workflow
These platforms generate volume. Treat it like email triage — fast pass, deep response on the rare matches.
### Step 1 — Filter (5 min)
For each digest / request feed:
- Drop everything where you don't have **direct experience or data**
- Drop everything from outlets your ICP doesn't read
- Drop everything with a deadline you can't meet
- Keep only requests where you can give a **complete, named, on-the-record answer**
Realistic conversion: 50 daily requests → 2–4 worth answering.
### Step 2 — Deep response (15 min per request)
For each keeper:
- Read the request 3 times — what's the *actual* angle?
- Look up the journalist if possible — recent coverage, beat
- Write a custom response (see [template](#response-template))
- Send within their stated deadline (early > late)
### Step 3 — Log
Track in a spreadsheet:
- Date
- Platform
- Journalist + outlet
- Topic
- Response sent (yes/no)
- Outcome (no response / passed / quoted / linked)
After 30 responses, you'll see which topics/platforms convert.
---
## Response Template
Keep responses under 200 words. Journalists are scanning 50+ replies for one quote.
```
Hi [name],
Quick response to your request about [topic].
[Specific credential — 1 sentence. "Built X for 5 years" / "Led marketing at Y" / "Have analyzed N companies in space"]
The most important thing about [topic]: [your actual point in 2 sentences].
[A specific example, story, or data point — this is what gets quoted.]
[If applicable: a contrarian or surprising angle that differentiates from typical answers.]
Happy to expand on any of this, share data, or be quoted directly.
Feel free to use this attribution:
[Your name], [your title], [your company]
Contact for follow-up: [email + phone]
```
**Note the structure:**
1. One-sentence intro
2. One-sentence credential
3. Two-sentence answer
4. Specific example (the quotable part)
5. Optional: differentiator
6. Clear offer
7. Pre-written attribution (saves them 30 seconds)
8. Contact info
---
## What Makes a Response Get Selected
After analyzing hundreds of quoted responses, the patterns:
### Quotable specificity
**Bad:** "Companies should focus on customer experience."
**Good:** "When we A/B tested 47 onboarding flows, the version with a 30-second video at step 3 increased activation by 41%."
The good version is a quote. The bad version is filler.
### Concrete credential
**Bad:** "As a marketing expert..."
**Good:** "I've run growth at three Series B SaaS companies, all in B2B sales tooling."
Specificity beats title-stacking.
### Story over advice
**Bad:** "It's important to track the right metrics."
**Good:** "We almost shut down because we were optimizing for MRR when our real problem was activation. Once we switched to tracking 7-day activation, everything else followed."
Stories make articles. Advice makes filler.
### Pre-formatted for their workflow
- Pre-written attribution
- Multiple quotable lines (let them pick)
- High-res headshot link (don't attach)
- One-line company description
### Time match
**Most quotes come from responses sent in the first 6 hours.** After 24 hours, your chances drop sharply. Treat deadlines as if they're 24h earlier than stated.
---
## What Kills a Response
- **Pitching your product** when they asked for expert commentary
- **Generic advice** that could come from any expert
- **Multiple "experts" from your company** responding to the same request (looks coordinated, often is)
- **Hiring a PR firm to spam responses** — journalists smell it
- **Demanding a link back** to your site — most can't promise links
- **Ignoring the deadline** by 1+ days
- **Long bio sections** before the actual answer
- **Asking to "see the article before publication"** — you don't get to do that
- **Asking what other experts said** so you can differentiate — they won't tell you
---
## ROI Reality Check
Most teams overinvest in these platforms because they're cheap. Be honest:
| Effort | Realistic outcome (90 days) |
|--------|----------------------------|
| 5 hr/week, custom responses | 3–10 quoted placements |
| 1 hr/week, template responses | 0–2 placements |
| Outsourced to PR firm | Lots of submissions, few quotes |
**A quote in a tier-1 outlet is worth:**
- A backlink (DR depends on outlet)
- A sales-collateral asset ("As featured in...")
- AI-citation surface area
- Brand legitimacy in the abstract
**A quote in a tier-3 outlet is worth:**
- A backlink, often nofollow
- Maybe an Instagram screenshot
**Decision rule:** if you can sustain 5 hr/week of quality responses for 90 days, this is worth it. If you can only do 1 hr/week, skip it and invest in [proactive pitching](journalist-pitching.md) instead.
---
## Setup Checklist
Before you start responding:
- [ ] Press page exists and is current (see main SKILL.md)
- [ ] One-line credential is written and rehearsed
- [ ] Headshot is high-res and at a public URL
- [ ] You have 3–5 specific stories / data points ready to deploy
- [ ] You've decided which 2–3 platforms to use (don't try all 7)
- [ ] You've blocked a daily 20-min window for triage
- [ ] You're logging responses in a tracker
Without these, you're spamming and wasting their time and yours.
FILE:references/story-angles.md
# Story Angles — What Earns Press
Distilled from Corey Haines's *Founding Marketing*, ch. 5. Chase press for the **compound effect** — authority, journalist relationships, AI-citation surface — not the traffic bump. The bump fades in a day; the backlinks, the relationships, and the citable record don't.
**The story is not your product.** Journalists write about trends, data, conflict, and humans. Your product is the evidence, never the headline. If the pitch is "we built a thing," there's no story. If the pitch is "here's a shift happening and we're proof of it," there is.
## Contents
- The three story angles
- Newsworthy moments (data stories)
- Build media relationships before you need them
- The PR flywheel (repurposing map)
---
## The Three Story Angles
Every earned-media story worth pitching bends toward one of three shapes. Pick the one that fits your moment — don't force all three.
### 1. Founding Story
The origin, the build, the numbers-in-public. Works because people follow *people*, and a founder with skin in the game is quotable in a way a product never is.
- **When to use:** you're early, you have a sharp personal reason you exist, and you're willing to build in public and share real revenue.
- **Exemplar:** Pieter Levels' "12 startups in 12 months" — shipping in the open, posting MRR screenshots, letting the challenge itself be the story. The build *was* the coverage.
- **How to run it:** share the arc (why you started, what you've learned, where the numbers are now), not the feature list. Revenue transparency is the hook most founders are too scared to use.
### 2. David vs Goliath
Own your size. Being small against an incumbent is an advantage in the story, not a liability — you're the underdog readers root for.
- **When to use:** there's a giant in your category and you do one thing dramatically better than they do.
- **Exemplar:** Superhuman vs Gmail — a tiny team reframing "we're small" into "we're the fast, obsessive alternative to the default everyone tolerates."
- **How to run it:** name the Goliath, name the one thing you beat them at, and let the size gap do the emotional work. Don't hide that you're small — lead with it.
### 3. Have an Enemy
Give the reader something to be against. The enemy is a **broken system**, not a competitor. Attacking a rival looks petty; attacking a genuinely broken status quo looks principled — and journalists cover principled fights.
- **When to use:** there's a systemic wrong in your space you can credibly stand against, and you're willing to take a real position.
- **Exemplar:** HEY vs Apple's App Store (2020) — the fight wasn't "Basecamp vs Apple the company," it was against App Store rules founders saw as broken. It became a press cycle *and* a policy conversation.
- **How to run it:** define the broken system precisely, stake a clear position, and make sure you're actually willing to be quoted taking that stance. A half-hearted enemy reads as a marketing stunt.
**Guardrail:** the enemy must be a system or a norm — never a named competitor. "Company X is bad" is a smear; "this way of doing things is broken" is a movement.
---
## Newsworthy Moments: Data Stories
Beyond the three angles, the most reliably pitchable moment is a **data story** — proprietary numbers no one else has, packaged as an industry benchmark. Journalists can build a whole piece around a stat; you get the citation.
- **Exemplars:** Stripe's *State of New User* / annual data reports; Intercom's benchmark reports. Each turns internal data into an annual, citable, must-cover event.
- **Why it works:** it's original, it's quotable, and it positions you as the source-of-record for your category's numbers. It also compounds in AI answers — benchmark stats get lifted and re-cited for years.
- **How to run it:** find the one number in your data that surprises, wrap it in methodology, and package it as a standalone one-pager (not your homepage). See [journalist-pitching.md](journalist-pitching.md) → the "Data story" template.
**Milestone reminder:** a milestone alone ("we hit $1M ARR") isn't a story — milestone *with narrative* is. Attach the number to a shift, a lesson, or a David-vs-Goliath frame.
---
## Build Media Relationships Before You Need Them
Cold-pitching a journalist the day you have news is the hard way. The founders who get covered built the relationship months earlier. It's a ladder — climb it before you need the favor.
1. **Follow their beat.** Read their last 5 articles. Know what they cover and what they're tired of covering. (See the discovery checklist in [journalist-pitching.md](journalist-pitching.md).)
2. **Add value with no ask.** Reply to their work with a genuinely useful data point or correction. Share their pieces. Answer a question they post on X. Give before you take.
3. **Be a reliable source.** When they need a quote fast, be the person who responds in 20 minutes with a clean, quotable line — even when there's nothing in it for you. Reliability is what turns a contact into a relationship.
Do this for a handful of journalists on your beat and, when you finally have a real story, you're pitching a relationship, not a stranger.
---
## The PR Flywheel (Repurposing Map)
One core asset feeds every other channel. Don't create five things — create one exceptional thing and reformat it five ways. Each format extends the reach of the same idea and gives journalists more of a public trail to find.
```
Core asset (data report / strong POV / founding story)
│
├─→ Thread (X / LinkedIn) — the hook, teased publicly
├─→ Article (your blog) — the full argument, the destination
├─→ Guest post (someone else's audience) — borrow reach
├─→ Podcast appearance — the spoken, citable version
└─→ Video — the durable, AI-crawled version
```
- **Sequence matters less than coverage.** Publish the article as the owned home base, tease it as a thread, pitch the guest post and podcast off the same idea, cut the video from the recording.
- **Every format strengthens the next pitch.** A journalist who can find your thread, article, and podcast on a topic sees a voice in the space — not an opportunist. This is the public trail that makes newsjacking land (see [newsjacking.md](newsjacking.md) → The Public Trail).
- **The asset is the flywheel's fuel.** One strong data story or POV can run this loop for weeks. Weak assets stall it immediately.
Quét và tối ưu SEO cho README.md và trang docs: meta tag, heading, từ khóa, khả năng đọc, link hỏng, cập nhật sitemap.xml.
---
name: seo-auditor
description: |
Scan and optimize documentation files for SEO. Audits README.md files and docs/ pages for
meta tags, headings, keywords, readability, duplicate content, and broken links. Applies
fixes, updates sitemap.xml, and generates a report. Usage: /seo-auditor [path]
---
# /seo-auditor
Systematically scan, audit, and optimize documentation files for SEO. Targets README.md files and docs/ pages — fixes issues in place, preserves rankings on high-performing pages, and generates a final report.
## Usage
```bash
/seo-auditor # Audit all docs/ and root README.md
/seo-auditor docs/skills/ # Audit a specific docs subdirectory
/seo-auditor --report-only # Scan without making changes
```
## What It Does
Execute all 7 phases sequentially. Auto-fix non-destructive issues. Preserve existing high-ranking content. Report everything at the end.
---
## Phase 1: Discovery & Baseline
### 1a. Identify target files
Scan for documentation files that need SEO audit:
```bash
# Find all markdown files in docs/ and root README files
find docs/ -name '*.md' -type f | sort
find . -maxdepth 2 -name 'README.md' -not -path './.codex/*' -not -path './.gemini/*' | sort
```
Classify each file:
- **New/recently modified** — files changed in the last 2 commits (check via `git log`)
- **Index pages** — `index.md` files (high authority, handle with care)
- **Skill pages** — `docs/skills/**/*.md` (generated by `generate-docs.py`)
- **Static pages** — `docs/index.md`, `docs/getting-started.md`, `docs/integrations.md`, etc.
- **README files** — root and domain-level README.md
### 1b. Capture baseline
For each target file, extract current SEO state:
- `title:` frontmatter field → becomes `<title>` tag
- `description:` frontmatter field → becomes `<meta name="description">`
- First `# H1` heading
- All `## H2` and `### H3` subheadings
- Word count
- Internal link count
- External link count
Store baseline in memory for the report.
---
## Phase 2: Meta Tag Audit
For every file with YAML frontmatter, check and fix:
### Title Tag (`title:`)
**Rules:**
- Must exist and be non-empty
- Length: 50-60 characters ideal (Google truncates at ~60)
- Must contain a primary keyword
- Must NOT duplicate another page's title
- For skill pages: should follow the pattern `{Skill Name} — {Differentiator} - {site_name}`
- site_name from `mkdocs.yml` is appended automatically — don't duplicate it in the title
**Auto-fix:** If title is generic (e.g., just the skill name), enrich it with domain context using the DOMAIN_SEO_SUFFIX pattern from `scripts/generate-docs.py`.
### Meta Description (`description:`)
**Rules:**
- Must exist and be non-empty
- Length: 120-160 characters (Google truncates at ~160)
- Must contain the primary keyword naturally
- Must be unique across all pages — no two pages share the same description
- Should include a call-to-action or value proposition
- Must NOT start with "This page..." or "This document..."
**Auto-fix:** If description is missing or generic, generate one from the SKILL.md frontmatter description (if available) or from the first paragraph of content. Use the `extract_description_from_frontmatter()` function from `generate-docs.py` as reference.
### Validation Script
Run on each file that has HTML output in `site/`:
```bash
python3 marketing-skill/seo-audit/scripts/seo_checker.py --file site/{path}/index.html
```
Parse the score. Flag any page scoring below 60.
---
## Phase 3: Content Quality & Readability
For each target file, analyze and improve:
### Heading Structure
**Rules:**
- Exactly one `# H1` per page
- H2s follow H1, H3s follow H2 — no skipping levels
- Headings should contain keywords naturally (not stuffed)
- No duplicate headings on the same page
**Auto-fix:** If heading levels skip (H1 → H3), adjust to proper hierarchy.
### Readability
Run the content scorer on each file:
```bash
python3 marketing-skill/content-production/scripts/content_scorer.py {file_path}
```
Check scores for:
- **Readability** — aim for score ≥ 70
- **Structure** — aim for score ≥ 60
- **Engagement** — aim for score ≥ 50
### Content Quality Rules
- **Paragraphs:** No single paragraph longer than 5 sentences
- **Sentences:** Average sentence length 15-20 words
- **Passive voice:** Less than 15% of sentences
- **Transition words:** At least 30% of sentences use transitions
- **Bullet lists:** Use lists for 3+ items instead of comma-separated inline lists
### AI Content Detection
Run the humanizer scorer on non-generated content (README.md files, static pages):
```bash
python3 marketing-skill/content-humanizer/scripts/humanizer_scorer.py {file_path}
```
Flag pages scoring below 50 (too AI-sounding). For these pages, apply voice techniques from `marketing-skill/content-humanizer/references/voice-techniques.md`:
- Replace AI clichés ("delve into", "leverage", "it's important to note")
- Vary sentence length
- Add specific examples instead of generic statements
- Use active voice
**Important:** Only modify content that was recently created or updated. Do NOT rewrite pages that are ranking well — preserve their content.
---
## Phase 4: Keyword Optimization
### 4a. Identify target keywords per page
Based on the page's purpose and domain:
| Page Type | Primary Keywords | Secondary Keywords |
|-----------|-----------------|-------------------|
| Homepage (docs/index.md) | "Claude Code Skills", "agent plugins" | "Codex skills", "Gemini CLI", "OpenClaw" |
| Skill pages | Skill name + "Claude Code" | "agent skill", "Codex plugin", domain terms |
| Agent pages | Agent name + "AI coding agent" | "Claude Code", "orchestrator" |
| Command pages | Command name + "slash command" | "Claude Code", "AI coding" |
| Getting started | "install Claude Code skills" | platform names |
| Domain index | Domain + "skills" + "plugins" | "Claude Code", platform names |
### 4b. Keyword placement checks
For each page, verify the primary keyword appears in:
- [ ] Title tag (frontmatter `title:`)
- [ ] Meta description (frontmatter `description:`)
- [ ] H1 heading
- [ ] First paragraph (within first 100 words)
- [ ] At least one H2 subheading
- [ ] Image alt text (if images present)
- [ ] URL slug (for new pages only — never change existing URLs)
### 4c. Keyword density
- Primary keyword: 1-2% of total word count
- Secondary keywords: 0.5-1% each
- No keyword stuffing — if density exceeds 3%, reduce it
**Important:** Never change URLs of existing pages. URL changes break incoming links and destroy rankings. Only optimize content and meta tags.
---
## Phase 5: Link Audit
### 5a. Internal links
For each target file, check all markdown links `[text](url)`:
- Verify the target exists (file path resolves)
- Check for broken relative links (`../`, `./`)
- Verify anchor links (`#section-name`) point to existing headings
**Auto-fix:** Use the `rewrite_skill_internal_links()` and `rewrite_relative_links()` functions from `generate-docs.py` as reference. Rewrite broken skill-internal links to GitHub source URLs.
### 5b. Duplicate content detection
Compare meta descriptions across all pages:
```bash
grep -rh '^description:' docs/**/*.md | sort | uniq -d
```
If duplicates found, make each description unique by adding page-specific context.
Compare H1 headings across all pages — no two pages should have the same H1.
### 5c. Orphan page detection
Check if every page in `docs/` is referenced in `mkdocs.yml` nav. Pages not in nav are orphans — they won't appear in navigation and may not be indexed.
```bash
# Find doc pages not in mkdocs nav
find docs -name '*.md' -not -name 'index.md' | while read f; do
slug=$(echo "$f" | sed 's|docs/||')
grep -q "$slug" mkdocs.yml || echo "ORPHAN: $f"
done
```
**Auto-fix:** Add orphan pages to the correct nav section in `mkdocs.yml`.
---
## Phase 6: Sitemap & Build
### 6a. Rebuild the site
```bash
mkdocs build
```
This regenerates `site/sitemap.xml` automatically (MkDocs Material generates it during build).
### 6b. Verify sitemap
Check the generated sitemap:
```bash
python3 marketing-skill/site-architecture/scripts/sitemap_analyzer.py site/sitemap.xml
```
Verify:
- All documentation pages appear in the sitemap
- No broken/404 URLs
- URL count matches expected page count
- Depth distribution is reasonable (no pages deeper than 4 levels)
### 6c. Check for sitemap issues
- **Missing pages:** Pages in `mkdocs.yml` nav that don't appear in sitemap
- **Extra pages:** Pages in sitemap that aren't in nav (orphans)
- **Duplicate URLs:** Same page accessible via multiple URLs
---
## Phase 7: Report
Generate a concise report for the user:
```
╔══════════════════════════════════════════════════════════════╗
║ SEO AUDITOR REPORT ║
╠══════════════════════════════════════════════════════════════╣
║ ║
║ Pages scanned: {n} ║
║ Issues found: {n} ║
║ Auto-fixed: {n} ║
║ Manual review needed: {n} ║
║ ║
║ META TAGS ║
║ Titles optimized: {n} ║
║ Descriptions fixed: {n} ║
║ Duplicate titles: {n} → {n} (fixed) ║
║ Duplicate descs: {n} → {n} (fixed) ║
║ ║
║ CONTENT ║
║ Readability improved: {n} pages ║
║ Heading fixes: {n} ║
║ AI score improved: {n} pages ║
║ ║
║ KEYWORDS ║
║ Pages missing primary keyword in title: {n} ║
║ Pages missing keyword in description: {n} ║
║ Pages with keyword stuffing: {n} ║
║ ║
║ LINKS ║
║ Broken links found: {n} → {n} (fixed) ║
║ Orphan pages: {n} → {n} (added to nav) ║
║ Duplicate content: {n} → {n} (deduplicated) ║
║ ║
║ SITEMAP ║
║ Total URLs: {n} ║
║ Sitemap regenerated: ✅ ║
║ ║
║ PRESERVED (no changes — ranking well) ║
║ {list of pages left untouched} ║
║ ║
╚══════════════════════════════════════════════════════════════╝
```
### Pages to preserve (do NOT modify)
These pages rank well for their target keywords. Only fix critical issues (broken links, missing meta). Do NOT rewrite content:
- `docs/index.md` — homepage, ranks for "Claude Code Skills"
- `docs/getting-started.md` — installation guide
- `docs/integrations.md` — multi-tool support
- Any page the user explicitly marks as "preserve"
---
## Skill References
| Tool | Path | Use |
|------|------|-----|
| SEO Checker | `marketing-skill/seo-audit/scripts/seo_checker.py` | Score HTML pages 0-100 |
| Content Scorer | `marketing-skill/content-production/scripts/content_scorer.py` | Score content readability/structure/engagement |
| Humanizer Scorer | `marketing-skill/content-humanizer/scripts/humanizer_scorer.py` | Detect AI-sounding content |
| Headline Scorer | `marketing-skill/copywriting/scripts/headline_scorer.py` | Score title quality |
| SEO Optimizer | `marketing-skill/content-production/scripts/seo_optimizer.py` | Optimize content for target keyword |
| Sitemap Analyzer | `marketing-skill/site-architecture/scripts/sitemap_analyzer.py` | Analyze sitemap structure |
| Schema Validator | `marketing-skill/schema-markup/scripts/schema_validator.py` | Validate structured data |
| Topic Cluster Mapper | `marketing-skill/content-strategy/scripts/topic_cluster_mapper.py` | Group pages into content clusters |
### Reference Docs
| Reference | Path | Use |
|-----------|------|-----|
| SEO Audit Framework | `marketing-skill/seo-audit/references/seo-audit-reference.md` | Priority order for SEO fixes |
| AI Search Optimization | `marketing-skill/ai-seo/references/content-patterns.md` | Make content citable by AI |
| Content Optimization | `marketing-skill/content-production/references/optimization-checklist.md` | Pre-publish checklist |
| URL Design Guide | `marketing-skill/site-architecture/references/url-design-guide.md` | URL structure best practices |
| Internal Linking | `marketing-skill/site-architecture/references/internal-linking-playbook.md` | Internal linking strategy |
| AI Writing Detection | `marketing-skill/content-humanizer/references/ai-tells-checklist.md` | AI cliché removal |
Triển khai và duy trì hệ thống quản lý chất lượng ISO 13485 cho thiết bị y tế: thiết kế QMS, kiểm soát tài liệu, đánh giá nội bộ, CAPA và hỗ trợ chứng nhận.
---
name: "quality-manager-qms-iso13485"
description: ISO 13485 Quality Management System implementation and maintenance for medical device organizations. Provides QMS design, documentation control, internal auditing, CAPA management, and certification support. Use when working with medical device quality systems, preparing for ISO 13485 audits, managing regulatory compliance documentation, setting up corrective actions, or building audit preparation programs. Useful for quality management, audit preparation, regulatory compliance, medical device documentation, and corrective action workflows.
triggers:
- ISO 13485
- QMS implementation
- quality management system
- document control
- internal audit
- management review
- quality manual
- CAPA process
- process validation
- design control
- supplier qualification
- quality records
---
# Quality Manager - QMS ISO 13485 Specialist
ISO 13485:2016 Quality Management System implementation, maintenance, and certification support for medical device organizations.
---
## Table of Contents
- [QMS Implementation Workflow](#qms-implementation-workflow)
- [Document Control Workflow](#document-control-workflow)
- [Internal Audit Workflow](#internal-audit-workflow)
- [Process Validation Workflow](#process-validation-workflow)
- [Supplier Qualification Workflow](#supplier-qualification-workflow)
- [QMS Process Reference](#qms-process-reference)
- [Decision Frameworks](#decision-frameworks)
- [Tools and References](#tools-and-references)
---
## QMS Implementation Workflow
Implement ISO 13485:2016 compliant quality management system from gap analysis through certification.
### Workflow: Initial QMS Implementation
1. Conduct gap analysis against ISO 13485:2016 requirements
2. Document current state vs. required state for each clause
3. Prioritize gaps by:
- Regulatory criticality
- Risk to product safety
- Resource requirements
4. Develop implementation roadmap with milestones
5. Establish Quality Manual per Clause 4.2.2:
- QMS scope with justified exclusions
- Process interactions
- Procedure references
6. Create required documented procedures — see [Mandatory Documented Procedures](#quick-reference-mandatory-documented-procedures) for the full list
7. Deploy processes with training
8. **Validation:** Gap analysis complete; Quality Manual approved; all required procedures documented and trained
> Use the Gap Analysis Matrix template in [qms-process-templates.md](references/qms-process-templates.md) to document clause-by-clause current state, gaps, priority, and actions.
### QMS Structure
| Level | Document Type | Example |
|-------|---------------|---------|
| 1 | Quality Manual | QM-001 |
| 2 | Procedures | SOP-02-001 |
| 3 | Work Instructions | WI-06-012 |
| 4 | Records | Training records |
---
## Document Control Workflow
Establish and maintain document control per ISO 13485 Clause 4.2.3.
### Workflow: Document Creation and Approval
1. Identify need for new document or revision
2. Assign document number per numbering convention:
- Format: `[TYPE]-[AREA]-[SEQUENCE]-[REV]`
- Example: `SOP-02-001-01`
3. Draft document using approved template
4. Route for review to subject matter experts
5. Collect and address review comments
6. Obtain required approvals based on document type
7. Update Document Master List
8. **Validation:** Document numbered correctly; all reviewers signed; Master List updated
### Document Numbering Convention
| Prefix | Document Type | Approval Authority |
|--------|---------------|-------------------|
| QM | Quality Manual | Management Rep + CEO |
| POL | Policy | Department Head + QA |
| SOP | Procedure | Process Owner + QA |
| WI | Work Instruction | Supervisor + QA |
| TF | Template/Form | Process Owner |
| SPEC | Specification | Engineering + QA |
### Area Codes
| Code | Area | Examples |
|------|------|----------|
| 01 | Quality Management | Quality Manual, policy |
| 02 | Document Control | This procedure |
| 03 | Training | Competency procedures |
| 04 | Design | Design control |
| 05 | Purchasing | Supplier management |
| 06 | Production | Manufacturing |
| 07 | Quality Control | Inspection, testing |
| 08 | CAPA | Corrective actions |
### Document Change Control
| Change Type | Approval Level | Examples |
|-------------|----------------|----------|
| Administrative | Document Control | Typos, formatting |
| Minor | Process Owner + QA | Clarifications |
| Major | Full review cycle | Process changes |
| Emergency | Expedited + retrospective | Safety issues |
### Document Review Schedule
| Document Type | Review Period | Trigger for Unscheduled Review |
|---------------|---------------|-------------------------------|
| Quality Manual | Annual | Organizational change |
| Procedures | Annual | Audit finding, regulation change |
| Work Instructions | 2 years | Process change |
| Forms | 2 years | User feedback |
---
## Internal Audit Workflow
Plan and execute internal audits per ISO 13485 Clause 8.2.4.
### Workflow: Annual Audit Program
1. Identify processes and areas requiring audit coverage
2. Assess risk factors for audit frequency:
- Previous audit findings
- Regulatory changes
- Process changes
- Complaint trends
3. Assign qualified auditors (independent of area audited)
4. Develop annual audit schedule
5. Obtain management approval
6. Communicate schedule to process owners
7. Track completion and reschedule as needed
8. **Validation:** All processes covered; auditors qualified and independent; schedule approved
> Use the Audit Program Template in [qms-process-templates.md](references/qms-process-templates.md) to schedule audits by clause and quarter across processes such as Document Control (4.2.3/4.2.4), Management Review (5.6), Design Control (7.3), Production (7.5), and CAPA (8.5.2/8.5.3).
### Workflow: Individual Audit Execution
1. Prepare audit plan with scope, criteria, and schedule
2. Notify auditee minimum 1 week prior
3. Review procedures and previous audit results
4. Prepare audit checklist
5. Conduct opening meeting
6. Collect evidence through:
- Document review
- Record sampling
- Process observation
- Personnel interviews
7. Classify findings:
- Major NC: Absence or breakdown of system
- Minor NC: Single lapse or deviation
- Observation: Risk of future NC
8. Conduct closing meeting
9. Issue audit report within 5 business days
10. **Validation:** All checklist items addressed; findings supported by evidence; report distributed
### Auditor Qualification Requirements
| Criterion | Requirement |
|-----------|-------------|
| Training | ISO 13485 awareness + auditor training |
| Experience | Minimum 1 audit as observer |
| Independence | Not auditing own work area |
| Competence | Understanding of audited process |
### Finding Classification Guide
| Classification | Criteria | Response Time |
|----------------|----------|---------------|
| Major NC | System absence, total breakdown, regulatory violation | 30 days for CAPA |
| Minor NC | Single instance, partial compliance | 60 days for CAPA |
| Observation | Potential risk, improvement opportunity | Track in next audit |
---
## Process Validation Workflow
Validate special processes per ISO 13485 Clause 7.5.6.
### Workflow: Process Validation Protocol
1. Identify processes requiring validation:
- Output cannot be verified by inspection
- Deficiencies appear only in use
- Sterilization, welding, sealing, software
2. Form validation team with subject matter experts
3. Write validation protocol including:
- Process description and parameters
- Equipment and materials
- Acceptance criteria
- Statistical approach
4. Execute IQ: verify equipment installed correctly and document specifications
5. Execute OQ: test parameter ranges and verify process control
6. Execute PQ: run production conditions and verify output meets requirements
7. Write validation report with conclusions
8. **Validation:** IQ/OQ/PQ complete; acceptance criteria met; validation report approved
### Validation Documentation Requirements
| Phase | Content | Evidence |
|-------|---------|----------|
| Protocol | Objectives, methods, criteria | Approved protocol |
| IQ | Equipment verification | Installation records |
| OQ | Parameter verification | Test results |
| PQ | Performance verification | Production data |
| Report | Summary, conclusions | Approval signatures |
### Revalidation Triggers
| Trigger | Action Required |
|---------|-----------------|
| Equipment change | Assess impact, revalidate affected phases |
| Parameter change | OQ and PQ minimum |
| Material change | Assess impact, PQ minimum |
| Process failure | Full revalidation |
| Periodic | Per validation schedule (typically 3 years) |
### Special Process Examples
| Process | Validation Standard | Critical Parameters |
|---------|--------------------|--------------------|
| EO Sterilization | ISO 11135 | Temperature, humidity, EO concentration, time |
| Steam Sterilization | ISO 17665 | Temperature, pressure, time |
| Radiation Sterilization | ISO 11137 | Dose, dose uniformity |
| Sealing | Internal | Temperature, pressure, dwell time |
| Welding | ISO 11607 | Heat, pressure, speed |
---
## Supplier Qualification Workflow
Evaluate and approve suppliers per ISO 13485 Clause 7.4.
### Workflow: New Supplier Qualification
1. Identify supplier category:
- Category A: Critical (affects safety/performance)
- Category B: Major (affects quality)
- Category C: Minor (indirect impact)
2. Request supplier information:
- Quality certifications
- Product specifications
- Quality history
3. Evaluate supplier based on:
- Quality system (ISO certification)
- Technical capability
- Quality history
- Financial stability
4. For Category A suppliers:
- Conduct on-site audit
- Require quality agreement
5. Calculate qualification score
6. Make approval decision:
- >80: Approved
- 60-80: Conditional approval
- <60: Not approved
7. Add to Approved Supplier List
8. **Validation:** Evaluation criteria scored; qualification records complete; supplier categorized
### Supplier Evaluation Criteria
| Criterion | Weight | Scoring |
|-----------|--------|---------|
| Quality System | 30% | ISO 13485=30, ISO 9001=20, Documented=10, None=0 |
| Quality History | 25% | Reject rate: <1%=25, 1-3%=15, >3%=0 |
| Delivery | 20% | On-time: >95%=20, 90-95%=10, <90%=0 |
| Technical Capability | 15% | Exceeds=15, Meets=10, Marginal=5 |
| Financial Stability | 10% | Strong=10, Adequate=5, Questionable=0 |
### Supplier Category Requirements
| Category | Qualification | Monitoring | Agreement |
|----------|---------------|------------|-----------|
| A - Critical | On-site audit | Annual review | Quality agreement |
| B - Major | Questionnaire | Semi-annual review | Quality requirements |
| C - Minor | Assessment | Issue-based | Standard terms |
### Supplier Performance Metrics
| Metric | Target | Calculation |
|--------|--------|-------------|
| Accept Rate | >98% | (Accepted lots / Total lots) × 100 |
| On-Time Delivery | >95% | (On-time / Total orders) × 100 |
| Response Time | <5 days | Average days to resolve issues |
| Documentation | 100% | (Complete CoCs / Required CoCs) × 100 |
---
## QMS Process Reference
For detailed requirements and audit questions for each ISO 13485:2016 clause, see [iso13485-clause-requirements.md](references/iso13485-clause-requirements.md).
### Management Review Required Inputs (Clause 5.6.2)
| Input | Source | Prepared By |
|-------|--------|-------------|
| Audit results | Internal and external audits | QA Manager |
| Customer feedback | Complaints, surveys | Customer Quality |
| Process performance | Process metrics | Process Owners |
| Product conformity | Inspection data, NCs | QC Manager |
| CAPA status | CAPA system | CAPA Officer |
| Previous actions | Prior review records | QMR |
| Changes affecting QMS | Regulatory, organizational | RA Manager |
| Recommendations | All sources | All Managers |
### Record Retention Requirements
| Record Type | Minimum Retention | Regulatory Basis |
|-------------|-------------------|------------------|
| Device Master Record | Life of device + 2 years | 21 CFR 820.181 |
| Device History Record | Life of device + 2 years | 21 CFR 820.184 |
| Design History File | Life of device + 2 years | 21 CFR 820.30 |
| Complaint Records | Life of device + 2 years | 21 CFR 820.198 |
| Training Records | Employment + 3 years | Best practice |
| Audit Records | 7 years | Best practice |
| CAPA Records | 7 years | Best practice |
| Calibration Records | Equipment life + 2 years | Best practice |
---
## Decision Frameworks
### Exclusion Justification (Clause 4.2.2)
| Clause | Permissible Exclusion | Justification Required |
|--------|----------------------|------------------------|
| 6.4.2 | Contamination control | Product not affected by contamination |
| 7.3 | Design and development | Organization does not design products |
| 7.5.2 | Product cleanliness | No cleanliness requirements |
| 7.5.3 | Installation | No installation activities |
| 7.5.4 | Servicing | No servicing activities |
| 7.5.5 | Sterile products | No sterile products |
### Nonconformity Disposition Decision Tree
```
Nonconforming Product Identified
│
▼
Can it be reworked?
│
Yes──┴──No
│ │
▼ ▼
Is rework Can it be used
procedure as is?
available? │
│ Yes──┴──No
Yes─┴─No │ │
│ │ ▼ ▼
▼ ▼ Concession Scrap or
Rework Create approval return to
per SOP rework needed? supplier
procedure │
Yes─┴─No
│ │
▼ ▼
Customer Use as is
approval with MRB
approval
```
### CAPA Initiation Criteria
| Source | Automatic CAPA | Evaluate for CAPA |
|--------|----------------|-------------------|
| Customer complaint | Safety-related | All others |
| External audit | Major NC | Minor NC |
| Internal audit | Major NC | Repeat minor NC |
| Product NC | Field failure | Trend exceeds threshold |
| Process deviation | Safety impact | Repeated deviations |
---
## Tools and References
### Scripts
| Tool | Purpose | Usage |
|------|---------|-------|
| [qms_audit_checklist.py](scripts/qms_audit_checklist.py) | Generate audit checklists by clause or process | `python qms_audit_checklist.py --help` |
**Audit Checklist Generator Features:**
- Generate clause-specific checklists (e.g., `--clause 7.3`)
- Generate process-based checklists (e.g., `--process design-control`)
- Full system audit checklist (`--audit-type system`)
- Text or JSON output formats
- Interactive mode for guided selection
### References
| Document | Content |
|----------|---------|
| [iso13485-clause-requirements.md](references/iso13485-clause-requirements.md) | Detailed requirements for each ISO 13485:2016 clause with audit questions |
| [qms-process-templates.md](references/qms-process-templates.md) | Ready-to-use templates for gap analysis, audit program, document control, CAPA, supplier, training |
### Quick Reference: Mandatory Documented Procedures
| Procedure | Clause | Key Elements |
|-----------|--------|--------------|
| Document Control | 4.2.3 | Approval, distribution, obsolete control |
| Record Control | 4.2.4 | Identification, retention, disposal |
| Internal Audit | 8.2.4 | Program, auditor qualification, reporting |
| NC Product Control | 8.3 | Identification, segregation, disposition |
| Corrective Action | 8.5.2 | Root cause, implementation, verification |
| Preventive Action | 8.5.3 | Risk identification, implementation |
---
## Related Skills
| Skill | Integration Point |
|-------|-------------------|
| [quality-manager-qmr](../quality-manager-qmr/) | Management review, quality policy |
| [capa-officer](../capa-officer/) | CAPA system management |
| [qms-audit-expert](../qms-audit-expert/) | Advanced audit techniques |
| [quality-documentation-manager](../quality-documentation-manager/) | DHF, DMR, DHR management |
| [risk-management-specialist](../risk-management-specialist/) | ISO 14971 integration |
FILE:references/iso13485-clause-requirements.md
# ISO 13485:2016 Clause Requirements
Detailed requirements for each ISO 13485:2016 clause with implementation guidance and audit criteria.
---
## Table of Contents
- [Clause 4: Quality Management System](#clause-4-quality-management-system)
- [Clause 5: Management Responsibility](#clause-5-management-responsibility)
- [Clause 6: Resource Management](#clause-6-resource-management)
- [Clause 7: Product Realization](#clause-7-product-realization)
- [Clause 8: Measurement, Analysis and Improvement](#clause-8-measurement-analysis-and-improvement)
---
## Clause 4: Quality Management System
### 4.1 General Requirements
| Requirement | Implementation | Evidence |
|-------------|----------------|----------|
| Determine processes needed | Process map showing QMS processes | Documented process map |
| Determine sequence and interaction | Process interaction diagram | Cross-reference matrix |
| Determine criteria for operation | Process metrics and acceptance criteria | Documented criteria per process |
| Ensure resources available | Resource allocation per process | Training records, equipment logs |
| Monitor, measure, analyze | Process monitoring procedures | Trend data, performance reports |
| Implement actions for results | Improvement projects, CAPAs | Action records with verification |
| Document processes | Procedures, work instructions | Controlled document list |
**Audit Questions:**
- How are QMS processes identified and documented?
- What criteria determine if processes are operating effectively?
- How is outsourced process control demonstrated?
### 4.2 Documentation Requirements
#### 4.2.1 General
| Document Type | Requirement | Retention |
|---------------|-------------|-----------|
| Quality Policy | Documented statement of commitment | Life of QMS |
| Quality Objectives | Measurable objectives at relevant functions | Life of QMS |
| Quality Manual | QMS scope and processes | Current version |
| Documented Procedures | Required by standard | Life of QMS + 2 years |
| Records | Evidence of conformity | As defined per record type |
#### 4.2.2 Quality Manual
**Required Content:**
1. Scope of QMS including justification for exclusions
2. Documented procedures or reference to them
3. Description of process interactions
**Quality Manual Template Structure:**
```
QUALITY MANUAL
1. Company Overview
1.1 Company Description
1.2 Scope of QMS
1.3 Exclusions and Justification
2. Quality Policy
3. Quality Objectives
4. QMS Structure
4.1 Process Map
4.2 Process Interactions
4.3 Organizational Chart
5. Procedure References
5.1 Document Control
5.2 Record Control
5.3 Management Review
5.4 Internal Audit
5.5 Nonconformity Control
5.6 CAPA
6. Appendices
6.1 Glossary
6.2 Regulatory Cross-Reference
```
#### 4.2.3 Control of Documents
| Control Element | Requirement | Method |
|-----------------|-------------|--------|
| Approval | Adequate prior to issue | Signature/electronic approval |
| Review and update | Re-approval after changes | Periodic review process |
| Identification of changes | Change history visible | Revision log in document |
| Revision status | Current revision identifiable | Document master list |
| Legibility | Readable and identifiable | Format standards |
| External documents | Identified and controlled | Incoming document log |
| Obsolete documents | Prevented from unintended use | Archive system |
**Document Numbering Convention:**
```
[TYPE]-[AREA]-[SEQUENCE]-[REV]
TYPE:
QM = Quality Manual
SOP = Standard Operating Procedure
WI = Work Instruction
TF = Template/Form
POL = Policy
AREA:
01 = Quality Management
02 = Document Control
03 = Training
04 = Design
05 = Purchasing
06 = Production
07 = Quality Control
08 = CAPA
Example: SOP-02-001-03 = Document Control SOP, Revision 03
```
#### 4.2.4 Control of Records
| Record Category | Minimum Retention | Basis |
|-----------------|-------------------|-------|
| Device Master Record | Life of device + 2 years | 21 CFR 820.181 |
| Device History Record | Life of device + 2 years | 21 CFR 820.184 |
| Design History File | Life of device + 2 years | 21 CFR 820.30 |
| Training Records | Employment + 3 years | Best practice |
| Audit Records | 7 years | Best practice |
| Complaint Records | Life of device + 2 years | 21 CFR 820.198 |
| CAPA Records | 7 years | Best practice |
| Calibration Records | Equipment life + 2 years | Best practice |
| Supplier Records | Relationship + 3 years | Best practice |
---
## Clause 5: Management Responsibility
### 5.1 Management Commitment
| Commitment Area | Evidence Required |
|-----------------|-------------------|
| Communicate importance of requirements | Meeting minutes, communications |
| Establish quality policy | Documented policy, communication records |
| Ensure quality objectives established | Objective documentation |
| Conduct management reviews | Management review records |
| Ensure resources available | Budget records, staffing records |
### 5.2 Customer Focus
| Requirement | Implementation | Verification |
|-------------|----------------|--------------|
| Customer requirements determined | Requirements review process | Contract review records |
| Requirements met | Process controls | Inspection and test data |
| Regulatory requirements met | Regulatory register | Compliance assessments |
| Customer satisfaction enhanced | Feedback collection | Satisfaction data, complaints |
### 5.3 Quality Policy
**Policy Requirements:**
- Appropriate to organization purpose
- Commitment to compliance and effectiveness
- Framework for quality objectives
- Communicated and understood
- Reviewed for continuing suitability
**Sample Quality Policy Elements:**
```
[Company Name] Quality Policy
We are committed to:
- Designing and manufacturing safe, effective medical devices
- Meeting customer and regulatory requirements
- Maintaining an effective Quality Management System
- Continuously improving our processes and products
- Providing resources for QMS effectiveness
Signed: [Executive]
Date: [Date]
Review Date: [Annual]
```
### 5.4 Planning
#### 5.4.1 Quality Objectives
| Objective Criteria | Requirement |
|-------------------|-------------|
| Measurable | Quantifiable targets |
| Consistent with policy | Aligned to policy statements |
| Relevant functions | Cascaded to departments |
| Includes compliance | Regulatory and customer requirements |
| Includes product conformity | Product-related targets |
**Objective Template:**
```
QUALITY OBJECTIVE [Year]
Objective: [Statement]
Metric: [How measured]
Target: [Specific value]
Baseline: [Current performance]
Owner: [Responsible person]
Due Date: [Target date]
Reporting: [Frequency]
```
#### 5.4.2 Quality Management System Planning
**Planning Requirements:**
- QMS meets general requirements (4.1)
- QMS meets quality objectives (5.4.1)
- Integrity maintained during changes
### 5.5 Responsibility, Authority and Communication
#### 5.5.1 Responsibility and Authority
| Role | Responsibilities | Authority |
|------|-----------------|-----------|
| Top Management | QMS commitment, resources, policy | Budget, staffing, strategic decisions |
| Quality Manager | QMS implementation, reporting | Document approval, CAPA approval |
| Department Managers | Process ownership, resources | Process changes, training |
| Process Owners | Process performance, improvements | Procedure changes within scope |
#### 5.5.2 Management Representative
| QMR Responsibility | Activities |
|-------------------|------------|
| QMS establishment | Process definition, documentation |
| QMS implementation | Training, deployment, monitoring |
| QMS maintenance | Audits, reviews, improvements |
| Reporting to top management | Performance reports, recommendations |
| Awareness promotion | Training, communications |
#### 5.5.3 Internal Communication
| Communication Type | Method | Frequency |
|-------------------|--------|-----------|
| Policy and objectives | Posting, training | Annual and on change |
| QMS performance | Dashboards, reports | Monthly |
| Changes affecting quality | Email, meetings | As needed |
| Audit results | Reports, presentations | Per audit |
### 5.6 Management Review
#### 5.6.1 General
| Requirement | Specification |
|-------------|---------------|
| Frequency | Planned intervals (typically quarterly/semi-annually) |
| Purpose | Assess QMS suitability, adequacy, effectiveness |
| Records | Documented meeting records |
#### 5.6.2 Review Input
| Input | Source | Responsible |
|-------|--------|-------------|
| Audit results | Internal/external audits | QA Manager |
| Customer feedback | Complaints, surveys | Customer Quality |
| Process performance | Metrics, yields | Process Owners |
| Product conformity | Inspection data | QC Manager |
| CAPA status | CAPA system | CAPA Officer |
| Previous actions | Prior review records | QMR |
| Changes affecting QMS | Regulatory, organizational | RA, HR |
| Recommendations | All sources | All Managers |
#### 5.6.3 Review Output
| Output | Documentation |
|--------|---------------|
| QMS improvement decisions | Action items with owners |
| Process improvements | Project charters |
| Resource needs | Resource allocation plans |
| Product improvements | Design change requests |
---
## Clause 6: Resource Management
### 6.1 Provision of Resources
**Resource Categories:**
- Human resources (competent personnel)
- Infrastructure (facilities, equipment, software)
- Work environment (environmental conditions)
### 6.2 Human Resources
| Requirement | Implementation | Evidence |
|-------------|----------------|----------|
| Competence determined | Job descriptions, competency matrix | Role definitions |
| Training provided | Training programs | Training records |
| Effectiveness evaluated | Assessments, observations | Competency verification |
| Awareness ensured | Orientation, ongoing training | Acknowledgments |
| Records maintained | Training database | Training files |
**Competency Matrix Template:**
```
COMPETENCY MATRIX
Role: [Job Title]
Department: [Department]
Required Competencies:
| Competency | Requirement Level | Method | Verification |
|------------|------------------|--------|--------------|
| [Skill 1] | Expert/Proficient/Basic | Training/OJT | Assessment |
| [Skill 2] | Expert/Proficient/Basic | Training/OJT | Assessment |
Training Requirements:
| Training | Initial | Refresher | Record |
|----------|---------|-----------|--------|
| ISO 13485 Awareness | Yes | Annual | TR-001 |
| Document Control | Yes | On Change | TR-002 |
```
### 6.3 Infrastructure
| Infrastructure Type | Control Requirements |
|--------------------|---------------------|
| Buildings and workspace | Cleaning, maintenance schedules |
| Process equipment | Maintenance, calibration |
| Supporting services | Utilities, IT systems |
| Information systems | Backup, security, validation |
### 6.4 Work Environment and Contamination Control
| Environment Factor | Control Method | Monitoring |
|-------------------|----------------|------------|
| Temperature | HVAC control | Continuous logging |
| Humidity | HVAC control | Continuous logging |
| Cleanliness | Cleaning procedures | Particle counts |
| Lighting | Lux levels | Periodic verification |
| ESD protection | Grounding, ionization | Periodic testing |
---
## Clause 7: Product Realization
### 7.1 Planning of Product Realization
| Planning Element | Content |
|-----------------|---------|
| Quality objectives for product | Product-specific quality targets |
| Processes and documentation | Process flow, required documents |
| Verification and validation | Test methods, acceptance criteria |
| Records | Required quality records |
| Risk management | Per ISO 14971 |
### 7.2 Customer-Related Processes
#### 7.2.1 Determination of Requirements
| Requirement Type | Source |
|-----------------|--------|
| Customer-specified | Contract, purchase order |
| Not stated but necessary | Intended use analysis |
| Regulatory | Applicable standards, regulations |
| Organization-defined | Internal specifications |
#### 7.2.2 Review of Requirements
| Review Element | Verification |
|----------------|--------------|
| Requirements defined | Complete specification |
| Differences resolved | Documented resolution |
| Ability to meet | Feasibility assessment |
| Risk management | Initial risk assessment |
#### 7.2.3 Communication
| Communication Type | Method |
|-------------------|--------|
| Product information | Catalogs, IFU |
| Inquiries and orders | Sales process |
| Feedback and complaints | Customer feedback system |
| Advisory notices | Field safety notices |
### 7.3 Design and Development
| Stage | Clause | Requirements |
|-------|--------|--------------|
| Planning | 7.3.2 | Stages, reviews, responsibilities |
| Inputs | 7.3.3 | Functional, performance, regulatory |
| Outputs | 7.3.4 | Meet inputs, acceptance criteria |
| Review | 7.3.5 | Evaluate ability to meet requirements |
| Verification | 7.3.6 | Outputs meet inputs |
| Validation | 7.3.7 | Product meets intended use |
| Transfer | 7.3.8 | Verified before production |
| Changes | 7.3.9 | Controlled, reviewed, verified |
### 7.4 Purchasing
#### 7.4.1 Purchasing Process
| Control Element | Implementation |
|-----------------|----------------|
| Supplier evaluation | Qualification procedure |
| Selection criteria | Quality, delivery, cost |
| Monitoring | Performance metrics |
| Re-evaluation | Periodic review |
**Supplier Classification:**
```
Category A: Critical - Affects product safety/performance
- Full qualification audit
- Annual performance review
- Quality agreement required
Category B: Major - Affects product quality
- Qualification questionnaire
- Periodic performance review
- Quality requirements communicated
Category C: Minor - Indirect impact
- Initial assessment
- Issue-based review
- Standard terms
```
#### 7.4.2 Purchasing Information
| Information Required | Purpose |
|---------------------|---------|
| Product specifications | Clear requirements |
| QMS requirements | Supplier system expectations |
| Personnel competence | Where applicable |
| Approval requirements | Where applicable |
#### 7.4.3 Verification of Purchased Product
| Verification Method | Application |
|--------------------|-------------|
| Incoming inspection | Standard verification |
| Source inspection | Critical items |
| Certificate of Conformance | Documented evidence |
| Certificate of Analysis | Material verification |
### 7.5 Production and Service Provision
#### 7.5.1 Control of Production and Service Provision
| Control Element | Implementation |
|-----------------|----------------|
| Product information | Specifications, drawings |
| Work instructions | Where necessary |
| Suitable equipment | Qualified equipment |
| Monitoring devices | Calibrated instruments |
| Implementation of monitoring | Inspections, tests |
| Defined processes | Process parameters |
| Labeling and packaging | Per requirements |
#### 7.5.2 Cleanliness of Product
| Cleanliness Control | Method |
|--------------------|--------|
| Product cleaning | Validated procedures |
| Contamination prevention | Controlled environment |
| Process aids | Qualified, controlled |
#### 7.5.3 Installation Activities
| Requirement | Implementation |
|-------------|----------------|
| Installation requirements | Documented instructions |
| Acceptance criteria | Defined criteria |
| Records | Installation records |
#### 7.5.4 Servicing Activities
| Requirement | Implementation |
|-------------|----------------|
| Documented requirements | Service procedures |
| Reference materials | Service manuals |
| Measurement equipment | Calibrated |
| Records | Service records |
#### 7.5.5 Particular Requirements for Sterile Medical Devices
| Process | Control |
|---------|---------|
| Sterilization validation | Per ISO 11135/11137/17665 |
| Parameter control | Monitoring records |
| Sterile barrier | Validated packaging |
#### 7.5.6 Validation of Processes
| Validation Required When | Evidence |
|-------------------------|----------|
| Output cannot be verified | Validation protocol and report |
| Deficiencies appear only in use | Process capability data |
| Special processes | Qualified operators |
**Process Validation Elements:**
- Equipment qualification (IQ/OQ/PQ)
- Process parameters
- Monitoring methods
- Operator qualification
- Revalidation criteria
#### 7.5.7 Particular Requirements for Validation
| Requirement | Implementation |
|-------------|----------------|
| Documented procedures | Validation SOPs |
| Defined methods | Statistical methods |
| Acceptance criteria | Predefined criteria |
| Software validation | Where applicable |
| Revalidation | Change-triggered |
#### 7.5.8 Identification
| Identification Type | Method |
|--------------------|--------|
| Product | Labels, markings |
| Documentation | Document numbers |
| Unique Device Identification | UDI per regulation |
#### 7.5.9 Traceability
| Traceability Element | Record |
|---------------------|--------|
| Components | Lot/batch numbers |
| Materials | Certificates |
| Work environment | Environmental records |
| Measurement equipment | Calibration records |
| Personnel | Training records |
| Distribution | Shipping records |
#### 7.5.10 Customer Property
| Control | Implementation |
|---------|----------------|
| Identification | Marking, segregation |
| Verification | Incoming inspection |
| Protection | Storage conditions |
| Safeguarding | Security measures |
| Reporting | Loss/damage notification |
#### 7.5.11 Preservation of Product
| Preservation Element | Control |
|---------------------|---------|
| Identification | Labels, markings |
| Handling | Procedures |
| Packaging | Specifications |
| Storage | Conditions, FIFO |
| Protection | Environmental controls |
### 7.6 Control of Monitoring and Measuring Equipment
| Control Element | Implementation |
|-----------------|----------------|
| Calibration | At specified intervals |
| Adjustment | As needed |
| Identification | Calibration status |
| Safeguarding | Protection from damage |
| Software validation | Where applicable |
| Records | Calibration records |
---
## Clause 8: Measurement, Analysis and Improvement
### 8.1 General
**Monitoring and Measurement Requirements:**
- Demonstrate product conformity
- Ensure QMS conformity
- Maintain QMS effectiveness
### 8.2 Monitoring and Measurement
#### 8.2.1 Feedback
| Feedback Source | Collection Method |
|-----------------|-------------------|
| Customer complaints | Complaint system |
| Customer surveys | Periodic surveys |
| Field feedback | Service reports |
| Regulatory feedback | Inspection findings |
#### 8.2.2 Complaint Handling
| Process Step | Requirements |
|--------------|--------------|
| Receipt | Timely logging |
| Investigation | Root cause analysis |
| Corrective action | If warranted |
| Regulatory reporting | If required |
| Trend analysis | Aggregate review |
#### 8.2.3 Reporting to Regulatory Authorities
| Report Type | Trigger | Timeline |
|-------------|---------|----------|
| MDR (Medical Device Report) | Death/serious injury | 30 days (5 if awareness) |
| FSCA (Field Safety Corrective Action) | Safety issue | Without delay |
| Periodic Safety Update | Per regulation | Per schedule |
#### 8.2.4 Internal Audit
| Audit Element | Requirement |
|---------------|-------------|
| Planned program | Risk-based schedule |
| Criteria and scope | Defined per audit |
| Auditor selection | Independent, competent |
| Procedure | Documented process |
| Records | Audit reports, findings |
| Follow-up | CAPA, verification |
**Audit Program Template:**
```
ANNUAL INTERNAL AUDIT PROGRAM
Year: [Year]
| Audit # | Area/Process | Scope | Auditor | Planned Date | Status |
|---------|--------------|-------|---------|--------------|--------|
| IA-01 | Document Control | 4.2.3, 4.2.4 | [Name] | Q1 | |
| IA-02 | Design Control | 7.3 | [Name] | Q2 | |
| IA-03 | Production | 7.5 | [Name] | Q2 | |
| IA-04 | Purchasing | 7.4 | [Name] | Q3 | |
| IA-05 | CAPA | 8.5.2, 8.5.3 | [Name] | Q3 | |
| IA-06 | Management Review | 5.6 | [Name] | Q4 | |
Risk Considerations:
- Previous audit findings
- Regulatory changes
- Process changes
- Complaint trends
```
#### 8.2.5 Monitoring and Measurement of Processes
| Monitoring Type | Method |
|-----------------|--------|
| Process metrics | KPIs, trend analysis |
| Process audits | Internal audits |
| Process reviews | Management review |
#### 8.2.6 Monitoring and Measurement of Product
| Stage | Verification |
|-------|--------------|
| Incoming | Incoming inspection |
| In-process | In-process inspection |
| Final | Final inspection and test |
| Release | Authorized release |
### 8.3 Control of Nonconforming Product
| Control Element | Requirement |
|-----------------|-------------|
| Identification | Clear marking |
| Segregation | Physical separation |
| Documentation | NC record |
| Disposition | Use as is/rework/scrap/return |
| Concession | If accepted |
| Reinspection | After rework |
| Investigation | For detected after delivery |
**Nonconformity Disposition Options:**
```
1. Use As Is (Concession)
- Does not affect safety/performance
- Customer approval if applicable
- Documented justification
2. Rework
- Per approved procedure
- Reinspection required
- Records maintained
3. Scrap/Reject
- Physical destruction or marking
- Prevented from reentry
- Documented disposal
4. Return to Supplier
- Communication with supplier
- Replacement or credit
- Root cause if systemic
```
### 8.4 Analysis of Data
| Data Source | Analysis |
|-------------|----------|
| Feedback | Complaint trends, satisfaction |
| Nonconformity | Defect Pareto, trends |
| Process performance | Capability, trends |
| Supplier | Performance trends |
| Audit | Finding trends |
### 8.5 Improvement
#### 8.5.1 General
**Improvement Sources:**
- Quality policy
- Quality objectives
- Audit results
- Data analysis
- Corrective actions
- Preventive actions
- Management review
#### 8.5.2 Corrective Action
| Process Step | Requirement |
|--------------|-------------|
| Review nonconformity | Including complaints |
| Determine cause | Root cause analysis |
| Evaluate action need | Based on risk |
| Determine action | Proportionate to risk |
| Implement action | Execute plan |
| Document results | Records |
| Review effectiveness | Verification |
#### 8.5.3 Preventive Action
| Process Step | Requirement |
|--------------|-------------|
| Determine potential NC | Risk analysis, trends |
| Evaluate action need | Prevention opportunity |
| Determine action | Proportionate to risk |
| Implement action | Execute plan |
| Document results | Records |
| Review effectiveness | Verification |
FILE:references/qms-process-templates.md
# QMS Process Templates
Ready-to-use templates for ISO 13485 QMS processes including document control, internal audit, CAPA, and supplier management.
---
## Table of Contents
- [Document Control Templates](#document-control-templates)
- [Internal Audit Templates](#internal-audit-templates)
- [CAPA Templates](#capa-templates)
- [Supplier Management Templates](#supplier-management-templates)
- [Training Templates](#training-templates)
- [Nonconformity Templates](#nonconformity-templates)
---
## Document Control Templates
### Document Master List
```
DOCUMENT MASTER LIST
Organization: [Company Name]
Last Updated: [Date]
Maintained By: Document Control
| Doc # | Title | Rev | Effective Date | Status | Owner | Next Review |
|-------|-------|-----|----------------|--------|-------|-------------|
| QM-001 | Quality Manual | 03 | 2024-01-15 | Effective | QMR | 2025-01-15 |
| SOP-01-001 | Document Control | 04 | 2024-03-01 | Effective | QA Mgr | 2025-03-01 |
| SOP-01-002 | Record Control | 02 | 2024-02-01 | Effective | QA Mgr | 2025-02-01 |
| | | | | | | |
Status Values: Draft, Under Review, Effective, Obsolete
```
### Document Change Request
```
DOCUMENT CHANGE REQUEST
DCR Number: DCR-[YYYY]-[NNN]
Date Submitted: [Date]
Submitted By: [Name]
DOCUMENT INFORMATION
Document Number: [Number]
Document Title: [Title]
Current Revision: [Rev]
CHANGE REQUEST
Change Type: [ ] Administrative [ ] Minor [ ] Major [ ] Emergency
Requested Change: [Description of change]
Reason for Change:
[ ] Regulatory requirement
[ ] Process improvement
[ ] Nonconformity/CAPA
[ ] Organizational change
[ ] Error correction
[ ] Other: [Specify]
Justification: [Detailed justification]
IMPACT ASSESSMENT
Training Required: [ ] Yes [ ] No
If yes, who: [Roles/departments]
Other Documents Affected: [List]
Regulatory Filing Impact: [ ] Yes [ ] No
If yes, details: [Explain]
APPROVALS
Requested By: _________________ Date: _______
Document Owner: _________________ Date: _______
QA Approval: _________________ Date: _______
COMPLETION
New Revision: [Rev]
Effective Date: [Date]
Training Completed: [ ] Yes [ ] N/A
Distribution Completed: [ ] Yes
```
### Document Review Record
```
DOCUMENT REVIEW RECORD
Document Number: [Number]
Document Title: [Title]
Current Revision: [Rev]
Review Due Date: [Date]
Review Completed: [Date]
REVIEWERS
| Reviewer | Role | Review Date | Comments | Signature |
|----------|------|-------------|----------|-----------|
| [Name] | [Role] | [Date] | [Comments] | |
| [Name] | [Role] | [Date] | [Comments] | |
REVIEW OUTCOME
[ ] No changes required - document remains current
[ ] Minor changes required - see attached DCR
[ ] Major revision required - see attached DCR
[ ] Document obsolete - initiate retirement
NEXT REVIEW
Next Review Date: [Date]
APPROVAL
Review Completed By: _________________ Date: _______
Approved By: _________________ Date: _______
```
---
## Internal Audit Templates
### Annual Audit Schedule
```
INTERNAL AUDIT SCHEDULE
Year: [Year]
Prepared By: [Name]
Approved By: [Name]
Date: [Date]
AUDIT SCHEDULE
| Audit # | Process/Area | ISO Clauses | Lead Auditor | Q1 | Q2 | Q3 | Q4 |
|---------|--------------|-------------|--------------|----|----|----|----|
| IA-001 | Document Control | 4.2.3, 4.2.4 | [Name] | X | | | |
| IA-002 | Management Review | 5.6 | [Name] | | X | | |
| IA-003 | Training | 6.2 | [Name] | | X | | |
| IA-004 | Design Control | 7.3 | [Name] | | | X | |
| IA-005 | Purchasing | 7.4 | [Name] | | | X | |
| IA-006 | Production | 7.5 | [Name] | | | | X |
| IA-007 | CAPA | 8.5.2, 8.5.3 | [Name] | | | | X |
RISK FACTORS CONSIDERED
[ ] Previous audit findings
[ ] Regulatory changes
[ ] Process changes
[ ] Complaint trends
[ ] Management concerns
SCHEDULE REVISION LOG
| Rev | Date | Change | Approved By |
|-----|------|--------|-------------|
| 00 | [Date] | Initial release | [Name] |
```
### Audit Plan
```
INTERNAL AUDIT PLAN
Audit Number: IA-[YYYY]-[NNN]
Audit Date(s): [Date(s)]
Audit Type: [ ] Process [ ] System [ ] Product
SCOPE
Process/Area: [Name]
ISO 13485 Clauses: [List]
Regulatory Requirements: [If applicable]
Locations: [Locations]
AUDIT TEAM
Lead Auditor: [Name]
Auditor(s): [Names]
Observer(s): [If any]
AUDITEE CONTACTS
Process Owner: [Name]
Other Contacts: [Names]
AUDIT CRITERIA
- ISO 13485:2016
- [Organization procedures]
- [Regulatory requirements]
AUDIT SCHEDULE
| Time | Activity | Participants |
|------|----------|--------------|
| 09:00 | Opening meeting | All |
| 09:30 | Document review | Auditor, Doc Control |
| 10:30 | Process observation | Auditor, Operators |
| 12:00 | Lunch | |
| 13:00 | Record review | Auditor, QA |
| 14:30 | Interviews | Selected personnel |
| 15:30 | Auditor caucus | Audit team |
| 16:00 | Closing meeting | All |
PREPARATION CHECKLIST
[ ] Previous audit reports reviewed
[ ] Procedures reviewed
[ ] Checklist prepared
[ ] Auditees notified
[ ] Resources arranged
```
### Audit Checklist Template
```
INTERNAL AUDIT CHECKLIST
Audit Number: IA-[YYYY]-[NNN]
Process: [Process Name]
Auditor: [Name]
Date: [Date]
INSTRUCTIONS
C = Conforming, NC = Nonconforming, OBS = Observation, N/A = Not Applicable
CHECKLIST
| # | Requirement | Reference | Evidence Reviewed | Finding | Notes |
|---|-------------|-----------|-------------------|---------|-------|
| 1 | Is the procedure current and approved? | 4.2.3 | [Evidence] | C/NC/OBS | |
| 2 | Are personnel trained on the procedure? | 6.2 | [Evidence] | C/NC/OBS | |
| 3 | Are records maintained as required? | 4.2.4 | [Evidence] | C/NC/OBS | |
| 4 | Is the process performed as documented? | 4.1 | [Evidence] | C/NC/OBS | |
| 5 | Are monitoring activities performed? | 8.2.5 | [Evidence] | C/NC/OBS | |
INTERVIEWS CONDUCTED
| Person | Role | Topics Discussed |
|--------|------|------------------|
| [Name] | [Role] | [Topics] |
DOCUMENTS REVIEWED
| Document # | Title | Rev | Findings |
|------------|-------|-----|----------|
| [Number] | [Title] | [Rev] | [Findings] |
RECORDS SAMPLED
| Record Type | Sample Size | Sample IDs | Findings |
|-------------|-------------|------------|----------|
| [Type] | [N] | [IDs] | [Findings] |
AUDITOR SIGNATURE: _________________ Date: _______
```
### Audit Report
```
INTERNAL AUDIT REPORT
Audit Number: IA-[YYYY]-[NNN]
Report Date: [Date]
Report Status: [ ] Draft [ ] Final
AUDIT SUMMARY
Audit Date(s): [Date(s)]
Process/Area: [Name]
ISO Clauses Covered: [List]
Lead Auditor: [Name]
Audit Team: [Names]
AUDIT SCOPE
[Description of scope]
AUDIT OBJECTIVES
[List objectives]
EXECUTIVE SUMMARY
[Brief summary of audit results]
FINDINGS SUMMARY
| Type | Count |
|------|-------|
| Major Nonconformity | [N] |
| Minor Nonconformity | [N] |
| Observation | [N] |
| Opportunity for Improvement | [N] |
DETAILED FINDINGS
FINDING 1
Number: IA-[YYYY]-[NNN]-F01
Classification: [ ] Major NC [ ] Minor NC [ ] Observation [ ] OFI
Requirement: [Clause/requirement reference]
Statement: [Objective description of finding]
Evidence: [Evidence supporting finding]
Auditee Response Due: [Date]
[Repeat for each finding]
POSITIVE OBSERVATIONS
[List areas of good practice observed]
CONCLUSION
[Overall conclusion on process effectiveness]
REPORT DISTRIBUTION
| Name | Role | Date |
|------|------|------|
| [Name] | Process Owner | [Date] |
| [Name] | QA Manager | [Date] |
| [Name] | Management Rep | [Date] |
APPROVALS
Lead Auditor: _________________ Date: _______
QA Manager: _________________ Date: _______
```
---
## CAPA Templates
### CAPA Request Form
```
CORRECTIVE AND PREVENTIVE ACTION REQUEST
CAPA Number: CAPA-[YYYY]-[NNN]
Date Opened: [Date]
Initiated By: [Name]
CAPA TYPE
[ ] Corrective Action (response to existing nonconformity)
[ ] Preventive Action (prevent potential nonconformity)
SOURCE
[ ] Customer complaint: Reference #_______
[ ] Internal audit: Audit #_______
[ ] External audit: Audit #_______
[ ] Nonconformity: NC #_______
[ ] Process deviation
[ ] Management review action
[ ] Trend analysis
[ ] Risk assessment
[ ] Other: _______
CLASSIFICATION
Severity: [ ] Critical [ ] Major [ ] Minor
Regulatory Reportable: [ ] Yes [ ] No
PROBLEM DESCRIPTION
[Detailed description of the problem or potential problem]
IMMEDIATE CONTAINMENT (if applicable)
Actions Taken: [Description]
Date: [Date]
Responsible: [Name]
ASSIGNMENT
Process Owner: [Name]
CAPA Owner: [Name]
Due Date for Root Cause: [Date]
Target Closure Date: [Date]
APPROVAL TO PROCEED
Approved By: _________________ Date: _______
```
### Root Cause Analysis Record
```
ROOT CAUSE ANALYSIS
CAPA Number: CAPA-[YYYY]-[NNN]
Analysis Date: [Date]
Analyst: [Name]
PROBLEM STATEMENT
[Clear, specific statement of the problem]
INVESTIGATION TEAM
| Name | Role | Contribution |
|------|------|--------------|
| [Name] | [Role] | [Area of expertise] |
INVESTIGATION METHOD
[ ] 5 Why Analysis
[ ] Fishbone Diagram
[ ] Fault Tree Analysis
[ ] Human Factors Analysis
[ ] Other: _______
INVESTIGATION DETAILS
5 WHY ANALYSIS
Why 1: [First why]
Answer: [Answer]
Why 2: [Second why based on answer]
Answer: [Answer]
Why 3: [Third why based on answer]
Answer: [Answer]
Why 4: [Fourth why based on answer]
Answer: [Answer]
Why 5: [Fifth why based on answer]
Answer: [Answer]
ROOT CAUSE STATEMENT
[Clear statement of identified root cause]
ROOT CAUSE CATEGORY
[ ] Process/Procedure
[ ] Training/Competency
[ ] Equipment/Material
[ ] Design
[ ] Human Error
[ ] Communication
[ ] Management System
[ ] External Factor
CONTRIBUTING FACTORS
[List any contributing factors]
EVIDENCE SUPPORTING ROOT CAUSE
[List evidence]
APPROVAL
Analysis By: _________________ Date: _______
Reviewed By: _________________ Date: _______
```
### CAPA Action Plan
```
CAPA ACTION PLAN
CAPA Number: CAPA-[YYYY]-[NNN]
Root Cause: [Brief statement]
Plan Date: [Date]
Plan Owner: [Name]
CORRECTIVE/PREVENTIVE ACTIONS
Action 1:
Description: [Detailed action description]
Responsible: [Name]
Due Date: [Date]
Resources Required: [Resources]
Success Criteria: [How completion verified]
Action 2:
Description: [Detailed action description]
Responsible: [Name]
Due Date: [Date]
Resources Required: [Resources]
Success Criteria: [How completion verified]
[Continue for additional actions]
RELATED CHANGES
Documents Affected: [List]
Training Required: [Description]
Process Changes: [Description]
Equipment Changes: [Description]
RISK ASSESSMENT
Residual Risk After Implementation: [ ] High [ ] Medium [ ] Low
Justification: [Explanation]
APPROVAL
Plan Developed By: _________________ Date: _______
Approved By: _________________ Date: _______
```
### CAPA Effectiveness Verification
```
CAPA EFFECTIVENESS VERIFICATION
CAPA Number: CAPA-[YYYY]-[NNN]
Verification Date: [Date]
Verified By: [Name]
ACTIONS COMPLETED
| Action | Completion Date | Evidence |
|--------|-----------------|----------|
| [Action 1] | [Date] | [Reference] |
| [Action 2] | [Date] | [Reference] |
EFFECTIVENESS CRITERIA
[Criteria established during action planning]
VERIFICATION METHOD
[ ] Data analysis (trends, metrics)
[ ] Process audit
[ ] Record review
[ ] Product inspection
[ ] Customer feedback review
[ ] Other: _______
VERIFICATION PERIOD
From: [Date] To: [Date]
VERIFICATION RESULTS
[Detailed results of verification activities]
DATA/EVIDENCE REVIEWED
| Data Type | Period | Result |
|-----------|--------|--------|
| [Type] | [Period] | [Result] |
EFFECTIVENESS CONCLUSION
[ ] Effective - Root cause eliminated, problem resolved
[ ] Partially Effective - Improvement noted, additional action needed
[ ] Not Effective - Problem persists, reopen CAPA
If not effective, describe additional actions:
[Description]
CAPA CLOSURE
[ ] Approved for closure
[ ] Not approved - additional action required
Verified By: _________________ Date: _______
Approved By: _________________ Date: _______
```
---
## Supplier Management Templates
### Approved Supplier List
```
APPROVED SUPPLIER LIST
Organization: [Company Name]
Last Updated: [Date]
Maintained By: [Name]
| Supplier | Supplier # | Category | Products/Services | Status | Qualification Date | Next Review |
|----------|-----------|----------|-------------------|--------|-------------------|-------------|
| [Name] | SUP-001 | A | [Products] | Approved | [Date] | [Date] |
| [Name] | SUP-002 | B | [Products] | Conditional | [Date] | [Date] |
Category:
A = Critical (affects safety/performance)
B = Major (affects quality)
C = Minor (indirect impact)
Status:
Approved = Full use authorized
Conditional = Limited use, monitoring
Probation = Performance issues, enhanced monitoring
Disqualified = Use not authorized
Revision History:
| Rev | Date | Change | Approved By |
|-----|------|--------|-------------|
| 01 | [Date] | Initial release | [Name] |
```
### Supplier Evaluation Form
```
SUPPLIER EVALUATION
Supplier Name: [Name]
Supplier Number: [Number]
Evaluation Date: [Date]
Evaluated By: [Name]
Evaluation Type: [ ] Initial [ ] Periodic [ ] For Cause
SUPPLIER INFORMATION
Address: [Address]
Contact: [Name, Title]
Phone: [Phone]
Email: [Email]
Products/Services: [Description]
PROPOSED CATEGORY
[ ] A - Critical (affects safety/performance)
[ ] B - Major (affects quality)
[ ] C - Minor (indirect impact)
EVALUATION CRITERIA
1. QUALITY MANAGEMENT SYSTEM (30 points max)
[ ] ISO 13485 Certified (30 pts)
[ ] ISO 9001 Certified (20 pts)
[ ] Documented QMS (10 pts)
[ ] No formal QMS (0 pts)
Score: ___/30
2. QUALITY HISTORY (25 points max)
Reject Rate: ___% (0-1% = 25 pts, 1-3% = 15 pts, >3% = 0 pts)
Score: ___/25
3. DELIVERY PERFORMANCE (20 points max)
On-Time Delivery: ___% (>95% = 20 pts, 90-95% = 10 pts, <90% = 0 pts)
Score: ___/20
4. TECHNICAL CAPABILITY (15 points max)
[ ] Exceeds requirements (15 pts)
[ ] Meets requirements (10 pts)
[ ] Marginally meets (5 pts)
Score: ___/15
5. FINANCIAL STABILITY (10 points max)
[ ] Strong (10 pts)
[ ] Adequate (5 pts)
[ ] Questionable (0 pts)
Score: ___/10
TOTAL SCORE: ___/100
QUALIFICATION DECISION
>80 = Approved
60-80 = Conditional (monitoring required)
<60 = Not Approved
Decision: [ ] Approved [ ] Conditional [ ] Not Approved
APPROVAL
Evaluated By: _________________ Date: _______
QA Approval: _________________ Date: _______
```
### Supplier Performance Scorecard
```
SUPPLIER PERFORMANCE SCORECARD
Supplier: [Name]
Supplier #: [Number]
Period: [Q1/Q2/Q3/Q4] [Year]
Prepared By: [Name]
PERFORMANCE METRICS
1. QUALITY (40% weight)
Total Lots Received: [N]
Lots Rejected: [N]
Accept Rate: ___% Target: >98%
Score: ___/40
2. DELIVERY (30% weight)
Total Orders: [N]
On-Time Deliveries: [N]
On-Time Rate: ___% Target: >95%
Score: ___/30
3. RESPONSIVENESS (15% weight)
Issues Reported: [N]
Resolved <5 days: [N]
Response Rate: ___% Target: >90%
Score: ___/15
4. DOCUMENTATION (15% weight)
CoC Required: [N]
CoC Complete: [N]
Documentation Rate: ___% Target: 100%
Score: ___/15
TOTAL SCORE: ___/100
PERFORMANCE TREND
| Period | Quality | Delivery | Response | Docs | Total |
|--------|---------|----------|----------|------|-------|
| Q1 | | | | | |
| Q2 | | | | | |
| Q3 | | | | | |
| Q4 | | | | | |
ISSUES/CONCERNS
[List any quality or delivery issues during period]
ACTIONS REQUIRED
[ ] None - Performance acceptable
[ ] Enhanced monitoring
[ ] Supplier corrective action request
[ ] Supplier audit
[ ] Consider alternative supplier
NEXT REVIEW: [Date]
Prepared By: _________________ Date: _______
Reviewed By: _________________ Date: _______
```
---
## Training Templates
### Training Record
```
EMPLOYEE TRAINING RECORD
Employee Name: [Name]
Employee ID: [ID]
Department: [Department]
Job Title: [Title]
Date of Hire: [Date]
REQUIRED TRAINING
| Training | Requirement | Initial Date | Last Date | Next Due | Status |
|----------|-------------|--------------|-----------|----------|--------|
| ISO 13485 Awareness | Initial + Annual | [Date] | [Date] | [Date] | Current |
| Document Control | Initial + On Change | [Date] | [Date] | [Date] | Current |
| CAPA Procedure | Initial + On Change | [Date] | [Date] | [Date] | Due |
| Job-Specific | Per competency matrix | [Date] | [Date] | [Date] | Current |
TRAINING HISTORY
| Date | Training | Method | Duration | Trainer | Assessment | Result |
|------|----------|--------|----------|---------|------------|--------|
| [Date] | [Title] | Classroom | 2 hrs | [Name] | Written test | Pass |
| [Date] | [Title] | OJT | 4 hrs | [Name] | Observation | Pass |
COMPETENCY VERIFICATION
| Competency | Method | Date | Verified By | Result |
|------------|--------|------|-------------|--------|
| [Skill] | Observation | [Date] | [Name] | Qualified |
| [Skill] | Test | [Date] | [Name] | Qualified |
Employee Signature: _________________ Date: _______
Supervisor Signature: _________________ Date: _______
```
### Training Attendance Record
```
TRAINING ATTENDANCE RECORD
Training Title: [Title]
Training Date: [Date]
Trainer: [Name]
Location: [Location]
Duration: [Hours]
TRAINING CONTENT
[Brief description of content covered]
ATTENDEES
| Name | Employee ID | Department | Signature | Assessment Result |
|------|-------------|------------|-----------|-------------------|
| [Name] | [ID] | [Dept] | | Pass/Fail |
| [Name] | [ID] | [Dept] | | Pass/Fail |
ASSESSMENT METHOD
[ ] Written test (attach copy)
[ ] Practical demonstration
[ ] Verbal Q&A
[ ] Observation
[ ] N/A
TRAINING MATERIALS
[ ] Presentation: [Reference]
[ ] Procedure: [Reference]
[ ] Other: [Reference]
Trainer Signature: _________________ Date: _______
Training Coordinator: _________________ Date: _______
```
---
## Nonconformity Templates
### Nonconformity Report
```
NONCONFORMITY REPORT
NC Number: NC-[YYYY]-[NNN]
Date Identified: [Date]
Identified By: [Name]
NONCONFORMITY TYPE
[ ] Product [ ] Process [ ] Document [ ] System
NONCONFORMITY SOURCE
[ ] Incoming inspection
[ ] In-process inspection
[ ] Final inspection
[ ] Customer complaint
[ ] Internal audit
[ ] External audit
[ ] Other: _______
PRODUCT IDENTIFICATION (if applicable)
Product Name: [Name]
Part Number: [Number]
Lot/Batch: [Number]
Quantity Affected: [N]
NONCONFORMITY DESCRIPTION
[Detailed, objective description of the nonconformity]
REQUIREMENT
[Reference to requirement that was not met]
CONTAINMENT ACTION
Action Taken: [Description]
Quantity Contained: [N]
Location: [Location]
Date: [Date]
By: [Name]
DISPOSITION
[ ] Use As Is - Justification: _______
[ ] Rework - Per procedure: _______
[ ] Scrap - Method: _______
[ ] Return to Supplier - RMA #: _______
[ ] Other: _______
Disposition By: [Name]
Disposition Date: [Date]
CAPA REQUIRED?
[ ] Yes - CAPA #: _______
[ ] No - Justification: _______
CLOSURE
All actions complete: [ ] Yes
NC Closed By: _________________ Date: _______
QA Approval: _________________ Date: _______
```
### Material Review Board Record
```
MATERIAL REVIEW BOARD (MRB) RECORD
MRB Number: MRB-[YYYY]-[NNN]
Date: [Date]
NC Reference: NC-[YYYY]-[NNN]
NONCONFORMING MATERIAL
Product: [Name]
Part Number: [Number]
Lot/Batch: [Number]
Quantity: [N]
NONCONFORMITY DESCRIPTION
[Description from NC report]
MRB PARTICIPANTS
| Name | Role | Signature |
|------|------|-----------|
| [Name] | QA Representative | |
| [Name] | Engineering | |
| [Name] | Production | |
| [Name] | Other | |
DISPOSITION OPTIONS CONSIDERED
1. Use As Is
Technical Justification: [Justification]
Risk Assessment: [Assessment]
2. Rework
Procedure: [Reference]
Feasibility: [Assessment]
3. Scrap
Cost Impact: [Amount]
MRB DECISION
[ ] Use As Is - Customer notification required: [ ] Yes [ ] No
[ ] Rework per: [Procedure reference]
[ ] Scrap
[ ] Return to Supplier
RATIONALE
[Detailed rationale for decision]
APPROVALS
| Role | Name | Signature | Date |
|------|------|-----------|------|
| QA | [Name] | | [Date] |
| Engineering | [Name] | | [Date] |
| Production | [Name] | | [Date] |
FOLLOW-UP ACTIONS
[ ] CAPA initiated: CAPA-_______
[ ] Customer notified: Date: _______
[ ] Supplier notified: Date: _______
[ ] Other: _______
```
FILE:scripts/qms_audit_checklist.py
#!/usr/bin/env python3
"""
QMS Internal Audit Checklist Generator
Generates audit checklists for ISO 13485:2016 clauses and QMS processes.
Supports process audits, system audits, and clause-specific audits.
Usage:
python qms_audit_checklist.py --clause 7.3
python qms_audit_checklist.py --process design-control
python qms_audit_checklist.py --audit-type system --output json
python qms_audit_checklist.py --interactive
"""
import argparse
import json
import sys
from datetime import datetime
from typing import Optional
# ISO 13485:2016 Clause Structure with Audit Questions
ISO13485_CLAUSES = {
"4.1": {
"title": "General Requirements",
"questions": [
"Are QMS processes identified and documented?",
"Is the sequence and interaction of processes defined?",
"Are criteria and methods for process operation determined?",
"Are resources and information available for process operation?",
"Are processes monitored, measured, and analyzed?",
"Are actions taken to achieve planned results?",
"Is outsourced process control documented?",
"Are changes to processes managed?"
]
},
"4.2.1": {
"title": "Documentation Requirements - General",
"questions": [
"Is a quality policy documented?",
"Are quality objectives documented?",
"Is a quality manual maintained?",
"Are required documented procedures established?",
"Are documents needed for process planning and operation maintained?",
"Are required records maintained?",
"Is a medical device file established for each device type?"
]
},
"4.2.2": {
"title": "Quality Manual",
"questions": [
"Does the quality manual include QMS scope?",
"Are exclusions justified?",
"Are documented procedures included or referenced?",
"Is the interaction between processes described?",
"Is the quality manual controlled?"
]
},
"4.2.3": {
"title": "Control of Documents",
"questions": [
"Are documents approved before issue?",
"Are documents reviewed and updated as necessary?",
"Are changes and revision status identified?",
"Are current versions available at points of use?",
"Are documents legible and identifiable?",
"Are external documents identified and controlled?",
"Is unintended use of obsolete documents prevented?",
"Is there a document change control process?"
]
},
"4.2.4": {
"title": "Control of Records",
"questions": [
"Is there a procedure for record control?",
"Are records legible and identifiable?",
"Are records retrievable?",
"Are retention times defined?",
"Is protection from damage ensured?",
"Are confidential records protected?",
"Is record disposal controlled?"
]
},
"5.1": {
"title": "Management Commitment",
"questions": [
"Is there evidence of management commitment to QMS?",
"Is the importance of regulatory requirements communicated?",
"Is a quality policy established?",
"Are quality objectives established?",
"Are management reviews conducted?",
"Are resources provided for QMS?"
]
},
"5.2": {
"title": "Customer Focus",
"questions": [
"Are customer requirements determined?",
"Are applicable regulatory requirements determined?",
"Are customer and regulatory requirements met?",
"Is customer satisfaction enhanced?"
]
},
"5.3": {
"title": "Quality Policy",
"questions": [
"Is the quality policy appropriate to the organization?",
"Does it include commitment to compliance?",
"Does it include commitment to effectiveness?",
"Does it provide framework for quality objectives?",
"Is it communicated and understood?",
"Is it reviewed for continuing suitability?"
]
},
"5.4.1": {
"title": "Quality Objectives",
"questions": [
"Are quality objectives measurable?",
"Are they consistent with quality policy?",
"Are they established at relevant functions?",
"Do they include product requirements?",
"Do they include compliance requirements?"
]
},
"5.4.2": {
"title": "QMS Planning",
"questions": [
"Is QMS planning carried out to meet requirements?",
"Is QMS planning done to meet quality objectives?",
"Is QMS integrity maintained during changes?"
]
},
"5.5.1": {
"title": "Responsibility and Authority",
"questions": [
"Are responsibilities and authorities defined?",
"Are they documented?",
"Are they communicated?",
"Are interrelationships defined?"
]
},
"5.5.2": {
"title": "Management Representative",
"questions": [
"Is a management representative appointed?",
"Is authority to ensure QMS processes established?",
"Is authority to report to top management defined?",
"Is authority to promote awareness of requirements defined?"
]
},
"5.5.3": {
"title": "Internal Communication",
"questions": [
"Are communication processes established?",
"Is QMS effectiveness communicated?",
"Is information communicated appropriately?"
]
},
"5.6": {
"title": "Management Review",
"questions": [
"Are management reviews planned?",
"Are all required inputs reviewed?",
"Are outputs documented?",
"Are action items followed up?",
"Are records maintained?"
]
},
"6.1": {
"title": "Provision of Resources",
"questions": [
"Are resources determined?",
"Are resources provided for QMS?",
"Are resources provided for customer satisfaction?",
"Are resources provided for regulatory compliance?"
]
},
"6.2": {
"title": "Human Resources",
"questions": [
"Is competence defined for personnel?",
"Is training provided to achieve competence?",
"Is training effectiveness evaluated?",
"Is awareness of job relevance ensured?",
"Are training records maintained?"
]
},
"6.3": {
"title": "Infrastructure",
"questions": [
"Is necessary infrastructure determined?",
"Are buildings and workspace adequate?",
"Is process equipment adequate?",
"Are supporting services adequate?",
"Are maintenance requirements documented?"
]
},
"6.4": {
"title": "Work Environment",
"questions": [
"Is work environment determined?",
"Are environmental requirements documented?",
"Is contamination control adequate?",
"Are personnel health and cleanliness controlled?",
"Are environmental conditions monitored?"
]
},
"7.1": {
"title": "Planning of Product Realization",
"questions": [
"Are quality objectives for product defined?",
"Are processes needed determined?",
"Is verification and validation defined?",
"Are records requirements defined?",
"Is risk management applied?"
]
},
"7.2": {
"title": "Customer-Related Processes",
"questions": [
"Are customer requirements determined?",
"Are regulatory requirements determined?",
"Are requirements reviewed before commitment?",
"Are differences resolved before acceptance?",
"Is communication with customers effective?"
]
},
"7.3.1": {
"title": "Design and Development Planning",
"questions": [
"Are design stages determined?",
"Are review activities defined?",
"Are verification activities defined?",
"Are validation activities defined?",
"Are responsibilities assigned?",
"Are interfaces managed?"
]
},
"7.3.2": {
"title": "Design and Development Inputs",
"questions": [
"Are functional requirements defined?",
"Are performance requirements defined?",
"Are safety requirements defined?",
"Are regulatory requirements identified?",
"Are previous design inputs considered?",
"Are risk management outputs included?"
]
},
"7.3.3": {
"title": "Design and Development Outputs",
"questions": [
"Do outputs meet input requirements?",
"Is purchasing information provided?",
"Are acceptance criteria defined?",
"Are essential characteristics specified?",
"Are outputs approved before release?"
]
},
"7.3.4": {
"title": "Design and Development Review",
"questions": [
"Are design reviews conducted at suitable stages?",
"Is ability to meet requirements evaluated?",
"Are problems identified?",
"Are follow-up actions recorded?",
"Are appropriate functions represented?"
]
},
"7.3.5": {
"title": "Design and Development Verification",
"questions": [
"Is verification performed per plan?",
"Do outputs meet inputs?",
"Are verification records maintained?",
"Are verification methods appropriate?"
]
},
"7.3.6": {
"title": "Design and Development Validation",
"questions": [
"Is validation performed per plan?",
"Is product evaluated for intended use?",
"Is clinical evaluation included?",
"Are validation records maintained?",
"Is validation completed before product delivery?"
]
},
"7.3.7": {
"title": "Design and Development Transfer",
"questions": [
"Are outputs verified before transfer?",
"Is manufacturing capability verified?",
"Are transfer activities documented?"
]
},
"7.3.8": {
"title": "Control of Design and Development Changes",
"questions": [
"Are design changes identified?",
"Are changes reviewed?",
"Are changes verified?",
"Are changes validated as appropriate?",
"Is impact on product assessed?",
"Are changes approved before implementation?"
]
},
"7.4.1": {
"title": "Purchasing Process",
"questions": [
"Are suppliers evaluated and selected?",
"Are evaluation criteria established?",
"Is supplier performance monitored?",
"Are re-evaluation criteria defined?",
"Is purchased product verified?"
]
},
"7.4.2": {
"title": "Purchasing Information",
"questions": [
"Is purchasing information adequate?",
"Are product requirements specified?",
"Are QMS requirements specified?",
"Are personnel requirements specified?"
]
},
"7.4.3": {
"title": "Verification of Purchased Product",
"questions": [
"Is incoming inspection adequate?",
"Are verification activities defined?",
"Are verification records maintained?",
"Is source verification defined if applicable?"
]
},
"7.5.1": {
"title": "Control of Production and Service Provision",
"questions": [
"Is product information available?",
"Are work instructions available?",
"Is suitable equipment used?",
"Are monitoring devices available?",
"Is monitoring implemented?",
"Are release activities defined?",
"Are labeling requirements met?"
]
},
"7.5.2": {
"title": "Cleanliness of Product",
"questions": [
"Are cleanliness requirements documented?",
"Is contamination controlled?",
"Are process agents controlled?"
]
},
"7.5.3": {
"title": "Installation Activities",
"questions": [
"Are installation requirements documented?",
"Are acceptance criteria defined?",
"Are installation records maintained?"
]
},
"7.5.4": {
"title": "Servicing Activities",
"questions": [
"Are servicing procedures documented?",
"Are reference materials controlled?",
"Are service records maintained?",
"Is feedback analyzed?"
]
},
"7.5.5": {
"title": "Sterile Medical Devices",
"questions": [
"Is sterilization validated?",
"Are process parameters controlled?",
"Is sterile barrier validated?",
"Are sterilization records maintained?"
]
},
"7.5.6": {
"title": "Validation of Processes",
"questions": [
"Are special processes identified?",
"Are validation procedures documented?",
"Is equipment qualified?",
"Are personnel qualified?",
"Are validation records maintained?",
"Are revalidation criteria defined?"
]
},
"7.5.7": {
"title": "Particular Requirements for Validation",
"questions": [
"Are validation methods defined?",
"Are acceptance criteria established?",
"Is software validation appropriate?",
"Are validation records maintained?"
]
},
"7.5.8": {
"title": "Identification",
"questions": [
"Is product identified throughout realization?",
"Is documentation identified?",
"Is UDI implemented as required?"
]
},
"7.5.9": {
"title": "Traceability",
"questions": [
"Are traceability procedures documented?",
"Are components traceable?",
"Is work environment recorded?",
"Is distribution recorded?",
"Is traceability extent defined?"
]
},
"7.5.10": {
"title": "Customer Property",
"questions": [
"Is customer property identified?",
"Is it verified on receipt?",
"Is it protected and safeguarded?",
"Is loss or damage reported?"
]
},
"7.5.11": {
"title": "Preservation of Product",
"questions": [
"Is product identified?",
"Is handling controlled?",
"Is packaging controlled?",
"Is storage controlled?",
"Is protection adequate?"
]
},
"7.6": {
"title": "Control of Monitoring and Measuring Equipment",
"questions": [
"Is equipment calibrated?",
"Is calibration traceable?",
"Is calibration status identified?",
"Is equipment protected from damage?",
"Is software validated?",
"Are records maintained?"
]
},
"8.1": {
"title": "Measurement, Analysis and Improvement - General",
"questions": [
"Are monitoring activities planned?",
"Are analysis activities planned?",
"Are improvement activities planned?"
]
},
"8.2.1": {
"title": "Feedback",
"questions": [
"Is feedback collected?",
"Is feedback analyzed?",
"Is feedback used for improvement?",
"Is regulatory feedback included?"
]
},
"8.2.2": {
"title": "Complaint Handling",
"questions": [
"Is there a complaint procedure?",
"Are complaints investigated?",
"Are regulatory reports made if required?",
"Is trend analysis performed?",
"Are CAPAs initiated when warranted?"
]
},
"8.2.3": {
"title": "Reporting to Regulatory Authorities",
"questions": [
"Are reporting requirements identified?",
"Are reports submitted timely?",
"Are records maintained?"
]
},
"8.2.4": {
"title": "Internal Audit",
"questions": [
"Is an audit program established?",
"Are audit criteria defined?",
"Are auditors independent?",
"Are auditors competent?",
"Are audit records maintained?",
"Are findings followed up?"
]
},
"8.2.5": {
"title": "Monitoring and Measurement of Processes",
"questions": [
"Are processes monitored?",
"Are suitable methods used?",
"Is process capability demonstrated?",
"Are corrections made when needed?"
]
},
"8.2.6": {
"title": "Monitoring and Measurement of Product",
"questions": [
"Is product inspected?",
"Are acceptance criteria met?",
"Is release authorized?",
"Is traceability to inspection recorded?",
"Are records maintained?"
]
},
"8.3": {
"title": "Control of Nonconforming Product",
"questions": [
"Is nonconforming product identified?",
"Is it documented?",
"Is it evaluated?",
"Is it segregated?",
"Is disposition determined?",
"Is rework verified?",
"Is concession controlled?",
"Is post-delivery NC investigated?"
]
},
"8.4": {
"title": "Analysis of Data",
"questions": [
"Is data collected?",
"Is feedback analyzed?",
"Is conformity data analyzed?",
"Is process data analyzed?",
"Is supplier data analyzed?",
"Are audit results analyzed?"
]
},
"8.5.1": {
"title": "Improvement - General",
"questions": [
"Is continual improvement pursued?",
"Are policy, objectives, audits, data, actions, and reviews used?"
]
},
"8.5.2": {
"title": "Corrective Action",
"questions": [
"Is there a CA procedure?",
"Are NCs reviewed (including complaints)?",
"Is root cause determined?",
"Is action needed evaluated?",
"Is action determined and implemented?",
"Are results documented?",
"Is effectiveness verified?"
]
},
"8.5.3": {
"title": "Preventive Action",
"questions": [
"Is there a PA procedure?",
"Are potential NCs identified?",
"Is action needed evaluated?",
"Is action determined and implemented?",
"Are results documented?",
"Is effectiveness verified?"
]
}
}
# Process-to-Clause Mapping
PROCESS_MAPPING = {
"document-control": ["4.2.1", "4.2.2", "4.2.3", "4.2.4"],
"management-review": ["5.6"],
"internal-audit": ["8.2.4"],
"training": ["6.2"],
"design-control": ["7.3.1", "7.3.2", "7.3.3", "7.3.4", "7.3.5", "7.3.6", "7.3.7", "7.3.8"],
"purchasing": ["7.4.1", "7.4.2", "7.4.3"],
"production": ["7.5.1", "7.5.2", "7.5.6", "7.5.7", "7.5.8", "7.5.9", "7.5.11"],
"capa": ["8.5.2", "8.5.3"],
"nonconformity": ["8.3"],
"calibration": ["7.6"],
"complaint-handling": ["8.2.1", "8.2.2", "8.2.3"],
"risk-management": ["7.1"],
"infrastructure": ["6.3", "6.4"],
"customer-requirements": ["5.2", "7.2"]
}
def get_clause_checklist(clause: str) -> dict:
"""Get audit checklist for a specific clause."""
if clause not in ISO13485_CLAUSES:
return {"error": f"Clause {clause} not found"}
clause_data = ISO13485_CLAUSES[clause]
return {
"clause": clause,
"title": clause_data["title"],
"questions": clause_data["questions"],
"question_count": len(clause_data["questions"])
}
def get_process_checklist(process: str) -> dict:
"""Get audit checklist for a specific process."""
if process not in PROCESS_MAPPING:
available = ", ".join(sorted(PROCESS_MAPPING.keys()))
return {"error": f"Process '{process}' not found. Available: {available}"}
clauses = PROCESS_MAPPING[process]
questions = []
for clause in clauses:
if clause in ISO13485_CLAUSES:
clause_data = ISO13485_CLAUSES[clause]
for q in clause_data["questions"]:
questions.append({
"clause": clause,
"clause_title": clause_data["title"],
"question": q
})
return {
"process": process,
"clauses_covered": clauses,
"questions": questions,
"question_count": len(questions)
}
def get_system_audit_checklist() -> dict:
"""Get complete system audit checklist covering all clauses."""
all_questions = []
for clause, data in sorted(ISO13485_CLAUSES.items()):
for q in data["questions"]:
all_questions.append({
"clause": clause,
"clause_title": data["title"],
"question": q
})
return {
"audit_type": "system",
"clauses_covered": list(ISO13485_CLAUSES.keys()),
"questions": all_questions,
"question_count": len(all_questions)
}
def format_checklist_text(checklist: dict) -> str:
"""Format checklist for text output."""
lines = []
if "error" in checklist:
return f"Error: {checklist['error']}"
lines.append("=" * 70)
lines.append("ISO 13485:2016 INTERNAL AUDIT CHECKLIST")
lines.append(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M')}")
lines.append("=" * 70)
if "clause" in checklist:
lines.append(f"\nClause: {checklist['clause']} - {checklist['title']}")
lines.append("-" * 50)
for i, q in enumerate(checklist["questions"], 1):
lines.append(f"\n{i}. {q}")
lines.append(" [ ] C [ ] NC [ ] OBS [ ] N/A")
lines.append(" Evidence: _________________________________")
lines.append(" Notes: ____________________________________")
elif "process" in checklist:
lines.append(f"\nProcess: {checklist['process'].replace('-', ' ').title()}")
lines.append(f"Clauses Covered: {', '.join(checklist['clauses_covered'])}")
lines.append("-" * 50)
current_clause = None
item_num = 1
for q in checklist["questions"]:
if q["clause"] != current_clause:
current_clause = q["clause"]
lines.append(f"\n--- {q['clause']} {q['clause_title']} ---")
lines.append(f"\n{item_num}. {q['question']}")
lines.append(" [ ] C [ ] NC [ ] OBS [ ] N/A")
lines.append(" Evidence: _________________________________")
lines.append(" Notes: ____________________________________")
item_num += 1
elif "audit_type" in checklist:
lines.append(f"\nAudit Type: Full System Audit")
lines.append(f"Total Clauses: {len(checklist['clauses_covered'])}")
lines.append("-" * 50)
current_clause = None
item_num = 1
for q in checklist["questions"]:
if q["clause"] != current_clause:
current_clause = q["clause"]
lines.append(f"\n{'=' * 40}")
lines.append(f"CLAUSE {q['clause']}: {q['clause_title']}")
lines.append("=" * 40)
lines.append(f"\n{item_num}. {q['question']}")
lines.append(" [ ] C [ ] NC [ ] OBS [ ] N/A")
lines.append(" Evidence: _________________________________")
item_num += 1
lines.append("\n" + "=" * 70)
lines.append(f"Total Questions: {checklist['question_count']}")
lines.append("")
lines.append("Legend: C=Conforming, NC=Nonconforming, OBS=Observation, N/A=Not Applicable")
lines.append("=" * 70)
return "\n".join(lines)
def interactive_mode():
"""Run interactive audit checklist generator."""
print("\n" + "=" * 50)
print("QMS INTERNAL AUDIT CHECKLIST GENERATOR")
print("=" * 50)
print("\nSelect audit type:")
print("1. Clause-specific audit")
print("2. Process audit")
print("3. Full system audit")
print("4. List available processes")
print("5. List all clauses")
print("6. Exit")
choice = input("\nEnter choice (1-6): ").strip()
if choice == "1":
print("\nAvailable clause sections:")
print(" 4.x - Quality Management System")
print(" 5.x - Management Responsibility")
print(" 6.x - Resource Management")
print(" 7.x - Product Realization")
print(" 8.x - Measurement, Analysis, Improvement")
clause = input("\nEnter clause number (e.g., 7.3.1): ").strip()
checklist = get_clause_checklist(clause)
print(format_checklist_text(checklist))
elif choice == "2":
processes = sorted(PROCESS_MAPPING.keys())
print("\nAvailable processes:")
for i, p in enumerate(processes, 1):
clauses = PROCESS_MAPPING[p]
print(f" {i}. {p} (clauses: {', '.join(clauses)})")
process = input("\nEnter process name: ").strip().lower()
checklist = get_process_checklist(process)
print(format_checklist_text(checklist))
elif choice == "3":
print("\nGenerating full system audit checklist...")
checklist = get_system_audit_checklist()
print(format_checklist_text(checklist))
elif choice == "4":
processes = sorted(PROCESS_MAPPING.keys())
print("\nAvailable QMS Processes:")
print("-" * 50)
for p in processes:
clauses = PROCESS_MAPPING[p]
print(f" {p}")
print(f" Clauses: {', '.join(clauses)}")
elif choice == "5":
print("\nISO 13485:2016 Clauses:")
print("-" * 50)
for clause, data in sorted(ISO13485_CLAUSES.items()):
print(f" {clause}: {data['title']} ({len(data['questions'])} questions)")
elif choice == "6":
print("Exiting.")
return
else:
print("Invalid choice.")
def main():
parser = argparse.ArgumentParser(
description="Generate ISO 13485:2016 internal audit checklists",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
python qms_audit_checklist.py --clause 7.3
python qms_audit_checklist.py --process design-control
python qms_audit_checklist.py --audit-type system --output json
python qms_audit_checklist.py --list-processes
python qms_audit_checklist.py --list-clauses
python qms_audit_checklist.py --interactive
"""
)
parser.add_argument(
"--clause",
help="Generate checklist for specific clause (e.g., 7.3.1, 8.5.2)"
)
parser.add_argument(
"--process",
help="Generate checklist for process (e.g., design-control, capa)"
)
parser.add_argument(
"--audit-type",
choices=["clause", "process", "system"],
help="Audit type for checklist generation"
)
parser.add_argument(
"--output",
choices=["text", "json"],
default="text",
help="Output format (default: text)"
)
parser.add_argument(
"--list-processes",
action="store_true",
help="List available QMS processes"
)
parser.add_argument(
"--list-clauses",
action="store_true",
help="List all ISO 13485 clauses"
)
parser.add_argument(
"--interactive",
action="store_true",
help="Run in interactive mode"
)
args = parser.parse_args()
if args.interactive:
interactive_mode()
return
if args.list_processes:
processes = sorted(PROCESS_MAPPING.keys())
if args.output == "json":
result = {p: PROCESS_MAPPING[p] for p in processes}
print(json.dumps(result, indent=2))
else:
print("\nAvailable QMS Processes:")
print("-" * 50)
for p in processes:
clauses = PROCESS_MAPPING[p]
print(f" {p}: {', '.join(clauses)}")
return
if args.list_clauses:
if args.output == "json":
result = {c: {"title": d["title"], "question_count": len(d["questions"])}
for c, d in sorted(ISO13485_CLAUSES.items())}
print(json.dumps(result, indent=2))
else:
print("\nISO 13485:2016 Clauses:")
print("-" * 50)
for clause, data in sorted(ISO13485_CLAUSES.items()):
print(f" {clause}: {data['title']} ({len(data['questions'])} questions)")
return
checklist = None
if args.clause:
checklist = get_clause_checklist(args.clause)
elif args.process:
checklist = get_process_checklist(args.process)
elif args.audit_type == "system":
checklist = get_system_audit_checklist()
else:
parser.print_help()
return
if checklist:
if args.output == "json":
print(json.dumps(checklist, indent=2))
else:
print(format_checklist_text(checklist))
if __name__ == "__main__":
main()
Tạo, tối ưu và phân tích chương trình giới thiệu, affiliate và chiến lược truyền miệng: vòng lan truyền, ưu đãi giới thiệu.
---
name: referrals
description: "When the user wants to create, optimize, or analyze a referral program, affiliate program, or word-of-mouth strategy. Also use when the user mentions 'referral,' 'affiliate,' 'ambassador,' 'word of mouth,' 'viral loop,' 'refer a friend,' 'partner program,' 'referral incentive,' 'how to get referrals,' 'customers referring customers,' or 'affiliate payout.' Use this whenever someone wants existing users or partners to bring in new customers. For launch-specific virality, see launch."
metadata:
version: 2.0.1
---
# Referral & Affiliate Programs
You are an expert in viral growth and referral marketing. Your goal is to help design and optimize programs that turn customers into growth engines.
## Before Starting
**Check for product marketing context first:**
If `.agents/product-marketing.md` exists (or `.claude/product-marketing.md`, or the legacy `product-marketing-context.md` filename, in older setups), read it before asking questions. Use that context and only ask for information not already covered or specific to this task.
Gather this context (ask if not provided):
### 1. Program Type
- Customer referral program, affiliate program, or both?
- B2B or B2C?
- What's the average customer LTV?
- What's your current CAC from other channels?
### 2. Current State
- Existing referral/affiliate program?
- Current referral rate (% who refer)?
- What incentives have you tried?
### 3. Product Fit
- Is your product shareable?
- Does it have network effects?
- Do customers naturally talk about it?
### 4. Resources
- Tools/platforms you use or consider?
- Budget for referral incentives?
---
## Should You Engineer Virality First?
Before building a reward-driven program, check whether virality can be **built into the product** — often cheaper and more durable than paid referrals. But **don't force virality where it doesn't naturally fit.**
Place the product on the **Viral Potential Spectrum**:
- **Natural** (build for it): collaboration tools, communication tools, user-facing outputs — every use exposes the product to non-users.
- **Limited** (don't force it): backend, competitive-advantage, internal-only, and infrastructure products. Invest in referral programs, content, and partnerships instead.
If the product is on the natural end, consider **product-embedded viral mechanisms** (Powered By badges, exposure loops, social sharing, embeds, watermarks) before or alongside a reward program.
**For the spectrum diagnostic, the 7 viral mechanisms, value-presentation and timing best practices, and affiliate power-law mechanics**: See [references/viral-mechanisms.md](references/viral-mechanisms.md)
---
## Referral vs. Affiliate
### Customer Referral Programs
**Best for:**
- Existing customers recommending to their network
- Products with natural word-of-mouth
- Lower-ticket or self-serve products
**Characteristics:**
- Referrer is an existing customer
- One-time or limited rewards
- Higher trust, lower volume
### Affiliate Programs
**Best for:**
- Reaching audiences you don't have access to
- Content creators, influencers, bloggers
- Higher-ticket products that justify commissions
**Characteristics:**
- Affiliates may not be customers
- Ongoing commission relationship
- Higher volume, variable trust
---
## Referral Program Design
### The Referral Loop
```
Trigger Moment → Share Action → Convert Referred → Reward → (Loop)
```
### Step 1: Identify Trigger Moments
**High-intent moments:**
- Right after first "aha" moment
- After achieving a milestone
- After exceptional support
- After renewing or upgrading
### Step 2: Design Share Mechanism
**Ranked by effectiveness:**
1. In-product sharing (highest conversion)
2. Personalized link
3. Email invitation
4. Social sharing
5. Referral code (works offline)
### Step 3: Choose Incentive Structure
**Single-sided rewards** (referrer only): Simpler, works for high-value products
**Double-sided rewards** (both parties): Higher conversion, win-win framing
**Tiered rewards**: Gamifies referral process, increases engagement
**Present the reward with the bigger-*feeling* number** — "lead with the larger number" (say "$10 off," not "40% off," on a low-priced product). Reward at the **aha moment or milestone**, not signup. Reduce friction: one-click share, pre-written messages.
**For examples and incentive sizing**: See [references/program-examples.md](references/program-examples.md)
**For product-embedded virality, value-presentation rules, and affiliate power-law mechanics**: See [references/viral-mechanisms.md](references/viral-mechanisms.md)
---
## Program Optimization
### Improving Referral Rate
**If few customers are referring:**
- Ask at better moments
- Simplify sharing process
- Test different incentive types
- Make referral prominent in product
**If referrals aren't converting:**
- Improve landing experience for referred users
- Strengthen incentive for new users
- Ensure referrer's endorsement is visible
### A/B Tests to Run
**Incentive tests:** Amount, type, single vs. double-sided, timing
**Messaging tests:** Program description, CTA copy, landing page copy
**Placement tests:** Where and when the referral prompt appears
### Common Problems & Fixes
| Problem | Fix |
|---------|-----|
| Low awareness | Add prominent in-app prompts |
| Low share rate | Simplify to one click |
| Low conversion | Optimize referred user experience |
| Fraud/abuse | Add verification, limits |
| One-time referrers | Add tiered/gamified rewards |
---
## Measuring Success
### Key Metrics
**Program health:**
- Active referrers (referred someone in last 30 days)
- Referral conversion rate
- Rewards earned/paid
**Business impact:**
- % of new customers from referrals
- CAC via referral vs. other channels
- LTV of referred customers
- Referral program ROI
### Typical Findings
- Referred customers have 16-25% higher LTV
- Referred customers have 18-37% lower churn
- Referred customers refer others at 2-3x rate
---
## Launch Checklist
### Before Launch
- [ ] Define program goals and success metrics
- [ ] Design incentive structure
- [ ] Build or configure referral tool
- [ ] Create referral landing page
- [ ] Set up tracking and attribution
- [ ] Define fraud prevention rules
- [ ] Create terms and conditions
- [ ] Test complete referral flow
### Launch
- [ ] Announce to existing customers
- [ ] Add in-app referral prompts
- [ ] Update website with program details
- [ ] Brief support team
### Post-Launch (First 30 Days)
- [ ] Review conversion funnel
- [ ] Identify top referrers
- [ ] Gather feedback
- [ ] Fix friction points
- [ ] Send reminder emails to non-referrers
---
## Email Sequences
### Referral Program Launch
```
Subject: You can now earn [reward] for sharing [Product]
We just launched our referral program!
Share [Product] with friends and earn [reward] for each signup.
They get [their reward] too.
[Unique referral link]
1. Share your link
2. Friend signs up
3. You both get [reward]
```
### Referral Nurture Sequence
- Day 7: Remind about referral program
- Day 30: "Know anyone who'd benefit?"
- Day 60: Success story + referral prompt
- After milestone: "You achieved [X]—know others who'd want this?"
---
## Affiliate Programs
**For detailed affiliate program design, commission structures, recruitment, and tools**: See [references/affiliate-programs.md](references/affiliate-programs.md)
**For affiliate power-law mechanics (buyout clauses ~12× monthly commission, the 20/80 super-promoter rule, launch-affiliate tactics)**: See [references/viral-mechanisms.md](references/viral-mechanisms.md)
---
## Task-Specific Questions
1. What type of program (referral, affiliate, or both)?
2. What's your customer LTV and current CAC?
3. Existing program or starting from scratch?
4. What tools/platforms are you considering?
5. What's your budget for rewards/commissions?
6. Is your product naturally shareable?
---
## Tool Integrations
For implementation, see the [tools registry](../../tools/REGISTRY.md). Key tools for referral programs:
| Tool | Best For | Guide |
|------|----------|-------|
| **Rewardful** | Stripe-native affiliate programs | [rewardful.md](../../tools/integrations/rewardful.md) |
| **Tolt** | SaaS affiliate programs | [tolt.md](../../tools/integrations/tolt.md) |
| **Mention Me** | Enterprise referral programs | [mention-me.md](../../tools/integrations/mention-me.md) |
| **Dub.co** | Link tracking and attribution | [dub-co.md](../../tools/integrations/dub-co.md) |
| **Stripe** | Payment processing (for commission tracking) | [stripe.md](../../tools/integrations/stripe.md) |
| **Introw** | Channel partner programs with tiers, deal registration, QBRs | [introw.md](../../tools/integrations/introw.md) |
| **PartnerStack** | Enterprise partner and affiliate programs | [partnerstack.md](../../tools/integrations/partnerstack.md) |
---
## Related Skills
- **launch**: For launching referral program effectively
- **emails**: For referral nurture campaigns
- **marketing-psychology**: For understanding referral motivation
- **analytics**: For tracking referral attribution
FILE:evals/evals.json
{
"skill_name": "referrals",
"evals": [
{
"id": 1,
"prompt": "Help me design a referral program for our SaaS product. We're a $49/month project management tool with about 1,000 customers. We want to encourage word-of-mouth growth.",
"expected_output": "Should check for product-marketing.md first. Should distinguish between referral and affiliate programs (this is referral — existing customers referring peers). Should design the referral loop: trigger point (when to ask for referral), share mechanism (unique link, email invite, social share), conversion flow (what the referred person experiences), and reward structure. Should recommend incentive type: double-sided recommended (both referrer and referred get value). Should suggest specific incentives appropriate for $49/month SaaS (e.g., free month for both). Should include the launch checklist. Should recommend tool integrations (Rewardful, Tolt, etc.).",
"assertions": [
"Checks for product-marketing.md",
"Distinguishes referral from affiliate",
"Designs the referral loop (trigger, share, convert, reward)",
"Recommends double-sided incentive structure",
"Suggests specific incentives for the price point",
"Includes launch checklist",
"Recommends tool integrations"
],
"files": []
},
{
"id": 2,
"prompt": "We have a referral program but only 5% of customers have ever referred someone. How do we increase participation?",
"expected_output": "Should apply the program optimization guidance. Should diagnose low participation: are customers aware of the program? Is the trigger point well-timed? Is the incentive compelling enough? Is sharing easy? Should recommend optimization tactics: better placement/visibility, timing referral asks at peak satisfaction moments, improving the incentive, simplifying the share mechanism, adding referral reminders in email and in-app. Should provide specific experiment ideas to test improvements.",
"assertions": [
"Applies program optimization guidance",
"Diagnoses potential causes of low participation",
"Checks awareness, timing, incentive, and friction",
"Recommends optimization tactics",
"Suggests timing referral asks at satisfaction moments",
"Provides experiment ideas"
],
"files": []
},
{
"id": 3,
"prompt": "should we do referral or affiliate? we sell online courses for $199-499 and want to get other creators and influencers to promote us.",
"expected_output": "Should trigger on casual phrasing. Should apply the referral vs affiliate distinction clearly. For this use case (getting creators/influencers to promote), should recommend an affiliate program (not referral — affiliates are third-party promoters, not existing customers). Should apply the affiliate program section guidance: commission structure for digital products (typically 20-40% for courses), cookie duration, payout terms, affiliate onboarding. Should recommend affiliate platforms/tools appropriate for course creators.",
"assertions": [
"Triggers on casual phrasing",
"Clearly distinguishes referral from affiliate",
"Recommends affiliate for this use case",
"Provides commission structure guidance for courses",
"Addresses cookie duration and payout terms",
"Recommends appropriate affiliate platforms"
],
"files": []
},
{
"id": 4,
"prompt": "What incentive structure works best? We've been offering $10 off for referrers but it's not working. Our product is $29/month.",
"expected_output": "Should evaluate the current incentive: $10 off on a $29/month product is significant but only benefits the referrer (single-sided). Should recommend testing double-sided incentives (both parties get value). Should discuss incentive types: account credit, free months, feature upgrades, cash. Should apply the tiered incentive concept (increasing rewards for multiple referrals). Should provide specific alternative incentive structures to test. Should note that incentive alone may not be the problem — placement and timing matter too.",
"assertions": [
"Evaluates current incentive structure",
"Identifies as single-sided and recommends double-sided",
"Discusses multiple incentive types",
"Applies tiered incentive concept",
"Provides specific alternatives to test",
"Notes incentive may not be the only issue"
],
"files": []
},
{
"id": 5,
"prompt": "How do we measure the success of our referral program? What metrics should we track?",
"expected_output": "Should apply the measuring success framework. Should define key metrics: participation rate (% of customers who refer), share rate (referrals sent per participant), conversion rate (referred visitors who become customers), viral coefficient (k-factor), customer acquisition cost via referral vs other channels, referred customer LTV vs organic customer LTV. Should recommend tracking tools and dashboards. Should provide benchmark ranges for each metric.",
"assertions": [
"Applies measuring success framework",
"Defines participation rate, share rate, conversion rate",
"Includes viral coefficient / k-factor",
"Compares referral CAC to other channels",
"Compares referred customer LTV to organic",
"Recommends tracking approach",
"Provides benchmark ranges"
],
"files": []
},
{
"id": 6,
"prompt": "Can you write the referral invitation emails? I need the email that goes out when someone shares their referral link.",
"expected_output": "Should recognize this overlaps with email writing. Should apply the referral email sequence section from the skill for referral-specific emails. However, for detailed email sequence design (multi-email nurture for referred users), should cross-reference the emails skill. Should provide the referral invitation email but note that broader email sequence work is handled by emails.",
"assertions": [
"Applies referral email section from the skill",
"Provides referral invitation email guidance",
"Cross-references emails for broader email work",
"Provides specific referral email copy or template"
],
"files": []
},
{
"id": 7,
"prompt": "We build a collaborative design tool. We keep hearing 'add a referral program' but I want to know if we can just make the product spread on its own. What are our options?",
"expected_output": "Should place the product on the Viral Potential Spectrum: a collaborative design tool is on the natural end (collaboration + user-facing output), so product-embedded virality fits before or instead of a reward program. Should recommend engineering virality through product design rather than defaulting to a reward program, and warn against forcing virality where it doesn't fit. Should walk through applicable viral mechanisms from the 7: exposure loops (invite/collaboration), embeds (Notion/Figma/Loom style), social sharing (e.g. a #MadeWith hashtag), and Powered By / watermark badges on free-tier output. Should note referral programs are the incentive-driven fallback when the product doesn't spread naturally. Should reference viral-mechanisms.md.",
"assertions": [
"Places the product on the Viral Potential Spectrum (natural end)",
"Recommends product-embedded virality before defaulting to a reward program",
"Warns against forcing virality where it doesn't fit",
"Names applicable mechanisms (exposure loops, embeds, social sharing, badges/watermarks)",
"Frames referral programs as the incentive-driven fallback",
"References viral-mechanisms.md"
],
"files": []
}
]
}
FILE:references/affiliate-programs.md
# Affiliate Program Design
Detailed guidance for building and managing affiliate programs.
## Contents
- Commission Structures
- Cookie Duration
- Affiliate Recruitment
- Affiliate Enablement
- Tools & Platforms (Referral Program Tools, Affiliate Program Tools, Choosing a Tool)
- Fraud Prevention (Common Referral Fraud, Prevention Measures)
## Commission Structures
**Percentage of sale:**
- Standard: 10-30% of first sale or first year
- Works for: E-commerce, SaaS with clear pricing
- Example: "Earn 25% of every sale you refer"
**Flat fee per action:**
- Standard: $5-500 depending on value
- Works for: Lead gen, trials, freemium
- Example: "$50 for every qualified demo"
**Recurring commission:**
- Standard: 10-25% of recurring revenue
- Works for: Subscription products
- Example: "20% of subscription for 12 months"
**Tiered commission:**
- Works for: Motivating high performers
- Example: "20% for 1-10 sales, 25% for 11-25, 30% for 26+"
---
## Cookie Duration
How long after click does affiliate get credit?
| Duration | Use Case |
|----------|----------|
| 24 hours | High-volume, low-consideration purchases |
| 7-14 days | Standard e-commerce |
| 30 days | Standard SaaS/B2B |
| 60-90 days | Long sales cycles, enterprise |
| Lifetime | Premium affiliate relationships |
---
## Affiliate Recruitment
### Where to find affiliates:
- Existing customers who create content
- Industry bloggers and reviewers
- YouTubers in your niche
- Newsletter writers
- Complementary tool companies
- Consultants and agencies
### Outreach template:
```
Subject: Partnership opportunity — [Your Product]
Hi [Name],
I've been following your content on [topic] — particularly [specific piece] — and think there could be a great fit for a partnership.
[Your Product] helps [audience] [achieve outcome], and I think your audience would find it valuable.
We offer [commission structure] for partners, plus [additional benefits: early access, co-marketing, etc.].
Would you be open to learning more?
[Your name]
```
---
## Affiliate Enablement
Provide affiliates with:
- [ ] Unique tracking links/codes
- [ ] Product overview and key benefits
- [ ] Target audience description
- [ ] Comparison to competitors
- [ ] Creative assets (logos, banners, images)
- [ ] Sample copy and talking points
- [ ] Case studies and testimonials
- [ ] Demo access or free account
- [ ] FAQ and objection handling
- [ ] Payment terms and schedule
---
## Tools & Platforms
### Referral Program Tools
**Full-featured platforms:**
- ReferralCandy — E-commerce focused
- Ambassador — Enterprise referral programs
- Friendbuy — E-commerce and subscription
- GrowSurf — SaaS and tech companies
- Mention Me — AI-powered referral marketing
- Viral Loops — Template-based campaigns
**Built-in options:**
- Stripe (basic referral tracking)
- HubSpot (CRM-integrated)
- Segment (tracking and analytics)
### Affiliate Program Tools
**Affiliate networks:**
- ShareASale — Large merchant network
- Impact — Enterprise partnerships
- PartnerStack — SaaS focused
- Tapfiliate — Simple SaaS affiliate tracking
- FirstPromoter — SaaS affiliate management
**Partner Relationship Management (PRM):**
- Introw — Full PRM with deal registration, commissions, tiers, QBRs, and partner engagement tracking ([integration guide](../../../tools/integrations/introw.md))
**Self-hosted:**
- Rewardful — Stripe-integrated affiliates
- Refersion — E-commerce affiliates
### Choosing a Tool
Consider:
- Integration with your payment system
- Fraud detection capabilities
- Payout management
- Reporting and analytics
- Customization options
- Price vs. program scale
---
## Fraud Prevention
### Common Referral Fraud
- Self-referrals (creating fake accounts)
- Referral rings (groups referring each other)
- Coupon sites posting referral codes
- Fake email addresses
- VPN/device spoofing
### Prevention Measures
**Technical:**
- Email verification required
- Device fingerprinting
- IP address monitoring
- Delayed reward payout (after activation)
- Minimum activity threshold
**Policy:**
- Clear terms of service
- Maximum referrals per period
- Reward clawback for refunds/chargebacks
- Manual review for suspicious patterns
**Structural:**
- Require referred user to take meaningful action
- Cap lifetime rewards
- Pay rewards in product credit (less attractive to fraudsters)
FILE:references/program-examples.md
# Referral Program Examples
Real-world examples of successful referral programs.
## Contents
- Dropbox (Classic)
- Uber/Lyft
- Morning Brew
- Notion
- Incentive Types Comparison
- Incentive Sizing Framework
- Viral Coefficient & Metrics (Key Metrics, Calculating Referral Program ROI)
## Dropbox (Classic)
**Program:** Give 500MB storage, get 500MB storage
**Why it worked:**
- Reward directly tied to product value
- Low friction (just an email)
- Both parties benefit equally
- Gamified with progress tracking
---
## Uber/Lyft
**Program:** Give $10 ride credit, get $10 when they ride
**Why it worked:**
- Immediate, clear value
- Double-sided incentive
- Easy to share (code/link)
- Triggered at natural moments
---
## Morning Brew
**Program:** Tiered rewards for subscriber referrals
- 3 referrals: Newsletter stickers
- 5 referrals: T-shirt
- 10 referrals: Mug
- 25 referrals: Hoodie
**Why it worked:**
- Gamification drives ongoing engagement
- Physical rewards are shareable (more referrals)
- Low cost relative to subscriber value
- Built status/identity
---
## Notion
**Program:** $10 credit per referral (education)
**Why it worked:**
- Targeted high-sharing audience (students)
- Product naturally spreads in teams
- Credit keeps users engaged
---
## Incentive Types Comparison
| Type | Pros | Cons | Best For |
|------|------|------|----------|
| Cash/credit | Universally valued | Feels transactional | Marketplaces, fintech |
| Product credit | Drives usage | Only valuable if they'll use it | SaaS, subscriptions |
| Free months | Clear value | May attract freebie-seekers | Subscription products |
| Feature unlock | Low cost to you | Only works for gated features | Freemium products |
| Swag/gifts | Memorable, shareable | Logistics complexity | Brand-focused companies |
| Charity donation | Feel-good | Lower personal motivation | Mission-driven brands |
---
## Incentive Sizing Framework
**Calculate your maximum incentive:**
```
Max Referral Reward = (Customer LTV × Gross Margin) - Target CAC
```
**Example:**
- LTV: $1,200
- Gross margin: 70%
- Target CAC: $200
- Max reward: ($1,200 × 0.70) - $200 = $640
**Typical referral rewards:**
- B2C: $10-50 or 10-25% of first purchase
- B2B SaaS: $50-500 or 1-3 months free
- Enterprise: Higher, often custom
---
## Viral Coefficient & Metrics
### Key Metrics
**Viral coefficient (K-factor):**
```
K = Invitations × Conversion Rate
K > 1 = Viral growth (each user brings more than 1 new user)
K < 1 = Amplified growth (referrals supplement other acquisition)
```
**Example:**
- Average customer sends 3 invitations
- 15% of invitations convert
- K = 3 × 0.15 = 0.45
**Referral rate:**
```
Referral Rate = (Customers who refer) / (Total customers)
```
Benchmarks:
- Good: 10-25% of customers refer
- Great: 25-50%
- Exceptional: 50%+
**Referrals per referrer:**
Benchmarks:
- Average: 1-2 referrals per referrer
- Good: 2-5
- Exceptional: 5+
### Calculating Referral Program ROI
```
Referral Program ROI = (Revenue from referred customers - Program costs) / Program costs
Program costs = Rewards paid + Tool costs + Management time
```
**Track separately:**
- Cost per referred customer (CAC via referral)
- LTV of referred customers (often higher than average)
- Payback period for referral rewards
FILE:references/viral-mechanisms.md
# Viral Mechanisms
Virality can be **engineered through product design**, not just bought with reward programs. But don't force it — decide *whether* virality fits your product before building anything.
## Contents
- Viral Potential Spectrum (the diagnostic)
- The 7 Viral Mechanisms
- Referral Best Practices (presentation, timing, friction)
- Affiliate Mechanics (buyout clauses, the 20/80 power law, launch tactics)
---
## Viral Potential Spectrum
Before engineering virality, place your product on the spectrum. **Don't force virality where it doesn't naturally fit.**
**Natural viral potential (build for it):**
- **Collaboration tools** — value grows when you invite others (docs, whiteboards, project management)
- **Communication tools** — you can't use them alone (email, scheduling, messaging)
- **User-facing outputs** — every use produces something others see (design, video, forms, links)
**Limited viral potential (don't force it):**
- **Backend / infrastructure** — invisible to end users
- **Competitive-advantage tools** — users *hide* that they use them (their edge)
- **Internal-only tools** — never leave the org
- **Infrastructure** — plumbing no one talks about
If you're on the limited end, invest in referral programs, content, and partnerships instead of embedding viral loops that won't fire.
---
## The 7 Viral Mechanisms
Most are **non-incentive** — the loop is built into the product, not paid for.
### 1. "Powered By" Badges
A small attributed badge on user-facing output ("Powered by [Product]"). Every page/form/widget a customer ships becomes an ad. Often free-tier only (paid tier removes it).
### 2. Exposure Loops
The product's normal use exposes it to non-users.
- **Calendly / SavvyCal** — every meeting invite you send shows the tool to the recipient, who often becomes a user.
- **Superhuman email signatures** ("Sent via Superhuman") — works as a **status signal**, not just attribution. The signature signaled early-adopter status, so recipients *wanted* it. Exposure loops are strongest when using the product confers status.
### 3. Social Sharing
Make output natively shareable with a branded hook.
- **#MadeWithGlide** — a hashtag turns every user creation into discoverable social proof.
- One-tap "share to X/LinkedIn" on any milestone, result, or artifact.
### 4. Embed Options
Let users embed their content elsewhere; the embed carries your brand and a link back.
- **Notion, Figma, Loom** — embedded docs, designs, and videos spread the product to every viewer on every host site.
### 5. Watermarks / Mandatory Badges
Like "Powered By" but harder to remove — baked into the output itself.
- **OpusClips** watermark on generated clips.
- **"Made in Webflow"** badge on free-plan sites.
Free tier carries the mark; paid tier removes it. The free users become the distribution.
### 6. Referral Programs
Explicit incentives for referring. Covered in detail in [program-examples.md](program-examples.md) and below. The one *incentive-driven* mechanism on this list — use it when the product itself doesn't naturally spread.
### 7. Product-Driven Word-of-Mouth
The purest form: the product is so good, novel, or useful that people tell others unprompted. Not a mechanism you bolt on — it's earned through the product experience. Engineering the other six makes this easier to trigger.
---
## Referral Best Practices
Detail beyond the core referral loop (trigger → share → convert → reward).
### Value Presentation: Lead With the Larger Number
Frame the reward with whichever number *looks* bigger.
- On a $25 product, say **"$10 off"** — not "40% off."
- On a $500 product, say **"20% off"** — not "$100 off" if the percentage frames better... but usually the absolute dollar figure wins for smaller prices.
- Rule of thumb: **under ~$100, lead with the dollar amount; over ~$100, test the percentage.** Always pick the bigger-*feeling* number.
### Reward Timing: Fire at the Aha / Milestone
Trigger the referral ask (and reward) at the moment the user has just felt the product's value — the **aha moment** or a **milestone** (first success, upgrade, streak). Motivation to share peaks right after value is experienced, not at signup.
### Double-Sided Rewards
Both referrer and referred get value. Higher conversion than single-sided, and gives the referrer a generous, non-selfish reason to share ("here's $10 for you too").
### Friction Reduction
Every extra step kills share rate.
- **One-click sharing** — pre-generated link, no form.
- **Pre-written messages** — draft the email/DM/post copy so the user just hits send.
- In-product placement at the trigger moment, not buried in settings.
---
## Affiliate Mechanics
Detail deferred from the partnerships side — for building an affiliate motion into a referral/partner strategy.
### Buyout Clauses (~12× Monthly Commission)
For high-performing affiliates on **recurring** commissions, include a **buyout clause**: the right to buy out the affiliate's future commission stream for a lump sum, commonly around **12× the monthly commission**. Protects margin on a customer the affiliate referred once but earns on forever, and gives the affiliate an attractive cash-out.
### The 20/80 Affiliate Power Law
Roughly **20% of affiliates drive ~80% of results**. Don't spread effort evenly across a long tail of dormant sign-ups. **Identify super-promoters and invest in them** — higher tiers, custom assets, co-marketing, direct relationship, early access. Recruiting 1,000 passive affiliates is worth less than activating 10 great ones.
### Launch-Affiliate Tactic (Cometly / Demio)
Time affiliate promotion around a **launch or a hard deadline** to concentrate volume. Cometly drove **$251K on a single launch day** by mobilizing affiliates simultaneously; Demio ran launch-window affiliate pushes. The mechanic: give affiliates a shared date, shared assets, and a reason for their audience to act *now* (bonus, cohort, closing offer) so promotion stacks instead of trickling.