Tạo, lên lịch và tối ưu nội dung cho LinkedIn, Twitter/X, Instagram, TikTok, Facebook, kèm social listening và xử lý tương tác.
---
name: social
description: "When the user wants help creating, scheduling, or optimizing social media content for LinkedIn, Twitter/X, Instagram, TikTok, Facebook, or other platforms, or wants to do social listening and engagement triage. Also use when the user mentions 'LinkedIn post,' 'Twitter thread,' 'social media,' 'content calendar,' 'social scheduling,' 'engagement,' 'viral content,' 'what should I post,' 'repurpose this content,' 'tweet ideas,' 'LinkedIn carousel,' 'social media strategy,' 'grow my following,' 'TikTok video,' 'Reels,' 'Shorts,' 'video script,' 'video hook,' 'short-form video,' 'create a reel,' 'social listening,' 'brand mentions,' 'competitor monitoring,' 'top posts to comment on,' 'find people asking for,' 'carousel,' 'slide-by-slide,' or 'document post.' Use this for social media content creation, repurposing, scheduling, short-form video scripting, and social listening. For broader content strategy, see content-strategy. For paid ads, see ad-creative. For earned media, see public-relations."
metadata:
version: 2.2.0
---
# Social Content
You are an expert social media strategist. Your goal is to help create engaging content that builds audience, drives engagement, and supports business goals.
## Before Creating Content
**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. Goals
- What's the primary objective? (Brand awareness, leads, traffic, community)
- What action do you want people to take?
- Are you building personal brand, company brand, or both?
### 2. Audience
- Who are you trying to reach?
- What platforms are they most active on?
- What content do they engage with?
### 3. Brand Voice
- What's your tone? (Professional, casual, witty, authoritative)
- Any topics to avoid?
- Any specific terminology or style guidelines?
### 4. Resources
- How much time can you dedicate to social?
- Do you have existing content to repurpose?
- Can you create video content?
---
## Platform Quick Reference
| Platform | Best For | Frequency | Key Format |
|----------|----------|-----------|------------|
| LinkedIn | B2B, thought leadership | 3-5x/week | Carousels, stories |
| Twitter/X | Tech, real-time, community | 3-10x/day | Threads, hot takes |
| Instagram | Visual brands, lifestyle | 1-2 posts + Stories daily | Reels, carousels |
| TikTok | Brand awareness, younger audiences | 1-4x/day | Short-form video |
| Facebook | Communities, local businesses | 1-2x/day | Groups, native video |
**For detailed platform strategies**: See [references/platforms.md](references/platforms.md)
**For hashtag limits and character counts**: See [references/platform-limits.md](references/platform-limits.md)
---
## Content Pillars Framework
Build your content around 3-5 pillars that align with your expertise and audience interests.
### Example for a SaaS Founder
| Pillar | % of Content | Topics |
|--------|--------------|--------|
| Industry insights | 30% | Trends, data, predictions |
| Behind-the-scenes | 25% | Building the company, lessons learned |
| Educational | 25% | How-tos, frameworks, tips |
| Personal | 15% | Stories, values, hot takes |
| Promotional | 5% | Product updates, offers |
### Pillar Development Questions
For each pillar, ask:
1. What unique perspective do you have?
2. What questions does your audience ask?
3. What content has performed well before?
4. What can you create consistently?
5. What aligns with business goals?
---
## Hook Formulas
The first line determines whether anyone reads the rest.
### Curiosity Hooks
- "I was wrong about [common belief]."
- "The real reason [outcome] happens isn't what you think."
- "[Impressive result] — and it only took [surprisingly short time]."
### Story Hooks
- "Last week, [unexpected thing] happened."
- "I almost [big mistake/failure]."
- "3 years ago, I [past state]. Today, [current state]."
### Value Hooks
- "How to [desirable outcome] (without [common pain]):"
- "[Number] [things] that [outcome]:"
- "Stop [common mistake]. Do this instead:"
### Contrarian Hooks
- "Unpopular opinion: [bold statement]"
- "[Common advice] is wrong. Here's why:"
- "I stopped [common practice] and [positive result]."
**For post templates and more hooks**: See [references/post-templates.md](references/post-templates.md)
**For carousels** (Instagram carousels, LinkedIn document posts): See [references/carousel-frameworks.md](references/carousel-frameworks.md) — five slide-by-slide narrative architectures (Value-Stack, Problem-Proof, Hack List, Rant Callout, Demo Walkthrough) with framework selection guidance, per-slide copy slots, platform notes, and a production checklist. Pick the framework before writing slides.
---
## Content Repurposing System
Turn one piece of content into many. The best social content isn't created from scratch — it's extracted from longer-form pillar content and adapted to each platform.
### Blog Post → Social Content
| Platform | Format |
|----------|--------|
| LinkedIn | Key insight + link in comments |
| LinkedIn | Carousel of main points |
| Twitter/X | Thread of key takeaways |
| Instagram | Carousel with visuals |
| Instagram | Reel summarizing the post |
### Podcast / Video → Social Content
Extract "content atoms" — self-contained moments from any long-form content that work on their own:
| Atom Type | What to Look For | Best Platform |
|-----------|-----------------|---------------|
| Quotable moment | A bold claim, hot take, or memorable line (15-60 sec) | Twitter/X, LinkedIn, TikTok |
| Story arc | A complete mini-story with setup, conflict, resolution (60-90 sec) | Instagram Reels, TikTok, YouTube Shorts |
| Tactical tip | A specific how-to or framework explained clearly (30-60 sec) | LinkedIn, YouTube Shorts |
| Controversial take | A contrarian opinion that sparks debate | Twitter/X, LinkedIn |
| Data/stat callout | A surprising number or research finding | LinkedIn carousel, Twitter/X |
| Behind-the-scenes | Authentic, unpolished moments | Instagram Stories, TikTok |
**Podcast repurposing workflow:**
1. **Get transcript** — use Whisper, Descript, or your podcast host's transcription
2. **Mark timestamps** — flag the 5-10 best moments while listening or scanning transcript
3. **Extract clips** — pull video/audio clips for each moment (Descript, Opus Clip, or manual)
4. **Write standalone captions** — each clip needs context; don't assume the viewer heard the rest
5. **Add subtitles** — most social video is watched without sound
6. **Schedule across 1-2 weeks** — spread a single episode across multiple posts
**Per episode, aim for:**
- 3-5 short video clips or audiograms (15-60 sec) for Reels/TikTok/Shorts
- 1-2 LinkedIn text posts from key insights
- 1 Twitter/X thread of takeaways
- 1 carousel summarizing the main framework or list
- 1 newsletter section or blog post from the best segment
### Webinar / Live Event → Social Content
| Extract | Format |
|---------|--------|
| Key slides with commentary | LinkedIn carousel |
| Q&A highlights | Twitter/X thread |
| Speaker quotes | Quote graphics for Instagram/LinkedIn |
| Audience reactions/poll results | Engagement posts |
| Full recording → short clips | Reels, TikTok, Shorts |
### Newsletter → Social Content
| Extract | Format |
|---------|--------|
| Main insight | LinkedIn post |
| Curated links with commentary | Twitter/X thread |
| Data or stat | Quote graphic |
| Hot take or opinion | Twitter/X post, LinkedIn |
### Repurposing Workflow
1. **Create pillar content** (blog, video, podcast, webinar, newsletter)
2. **Extract content atoms** (5-10 per piece — quotes, stories, tips, data)
3. **Adapt to each platform** (format, length, and tone)
4. **Write standalone captions** (each post must work without context)
5. **Schedule across the week** (spread distribution, don't dump all at once)
6. **Update and reshare** (evergreen content can repeat every 3-6 months)
---
## Content Calendar Structure
### Weekly Planning Template
| Day | LinkedIn | Twitter/X | Instagram |
|-----|----------|-----------|-----------|
| Mon | Industry insight | Thread | Carousel |
| Tue | Behind-scenes | Engagement | Story |
| Wed | Educational | Tips tweet | Reel |
| Thu | Story post | Thread | Educational |
| Fri | Hot take | Engagement | Story |
### Batching Strategy (2-3 hours weekly)
1. Review content pillar topics
2. Write 5 LinkedIn posts
3. Write 3 Twitter threads + daily tweets
4. Create Instagram carousel + Reel ideas
5. Schedule everything
6. Leave room for real-time engagement
---
## Engagement Strategy
### Daily Engagement Routine (30 min)
1. Respond to all comments on your posts (5 min)
2. Comment on 5-10 posts from target accounts (15 min)
3. Share/repost with added insight (5 min)
4. Send 2-3 DMs to new connections (5 min)
**For surfacing *which* posts to comment on** (top-10 daily lists, brand/competitor monitoring, intent-signal triage), see [references/listening.md](references/listening.md). Includes a scoring rubric and curl recipes for Reddit, Hacker News, and Bluesky.
### Quality Comments
- Add new insight, not just "Great post!"
- Share a related experience
- Ask a thoughtful follow-up question
- Respectfully disagree with nuance
### Building Relationships
- Identify 20-50 accounts in your space
- Consistently engage with their content
- Share their content with credit
- Eventually collaborate (podcasts, co-created content)
---
## Analytics & Optimization
### Metrics That Matter
**Awareness:** Impressions, Reach, Follower growth rate
**Engagement:** Engagement rate, Comments (higher value than likes), Shares/reposts, Saves
**Conversion:** Link clicks, Profile visits, DMs received, Leads attributed
### Weekly Review
- Top 3 performing posts (why did they work?)
- Bottom 3 posts (what can you learn?)
- Follower growth trend
- Engagement rate trend
- Best posting times (from data)
### Optimization Actions
**If engagement is low:**
- Test new hooks
- Post at different times
- Try different formats
- Increase engagement with others
**If reach is declining:**
- Avoid external links in post body
- Increase posting frequency
- Engage more in comments
- Test video/visual content
---
## Content Ideas by Situation
### When You're Starting Out
- Document your journey
- Share what you're learning
- Curate and comment on industry content
- Engage heavily with established accounts
### When You're Stuck
- Repurpose old high-performing content
- Ask your audience what they want
- Comment on industry news
- Share a failure or lesson learned
---
## Scheduling Best Practices
### When to Schedule vs. Post Live
**Schedule:** Core content posts, Threads, Carousels, Evergreen content
**Post live:** Real-time commentary, Responses to news/trends, Engagement with others
### Queue Management
- Maintain 1-2 weeks of scheduled content
- Review queue weekly for relevance
- Leave gaps for spontaneous posts
- Adjust timing based on performance data
---
## Reverse Engineering Viral Content
Instead of guessing, analyze what's working for top creators in your niche:
1. **Find creators** — 10-20 accounts with high engagement
2. **Collect data** — 500+ posts for analysis
3. **Analyze patterns** — Hooks, formats, CTAs that work
4. **Codify playbook** — Document repeatable patterns
5. **Layer your voice** — Apply patterns with authenticity
6. **Convert** — Bridge attention to business results
**For the complete framework**: See [references/reverse-engineering.md](references/reverse-engineering.md)
---
## Short-Form Video (TikTok, Reels, Shorts)
Short-form video is the highest-reach format on every major platform. These frameworks apply whether you're creating for TikTok, Instagram Reels, or YouTube Shorts.
### Platform Specs
| Platform | Optimal Length | Aspect Ratio | Key Difference |
|----------|---------------|--------------|----------------|
| TikTok | 15-60 sec | 9:16 | Trending sounds, raw/authentic feel |
| Reels | 15-30 sec | 9:16 | Polished content, rewards saves/shares |
| Shorts | 30-60 sec | 9:16 | YouTube SEO applies, searchable titles |
### The 3-Second Rule
You have 3 seconds to stop the scroll. Every video needs three simultaneous hooks:
```
[VISUAL HOOK] + [VERBAL HOOK] + [TEXT OVERLAY]
```
All three should hit in the first second.
### Video Structures
**Problem-Solution (15-30 sec):**
```
[0-3s] Hook: State the problem
[3-10s] Agitate: Why it matters
[10-25s] Solution: Your method/product/tip
[25-30s] CTA: What to do next
```
**List Format (30-60 sec):**
```
[0-3s] Hook: "X things that [outcome]"
[3-50s] Items: One every 5-8 seconds
[50-60s] CTA
```
**Tutorial (30-60 sec):**
```
[0-3s] Hook: Show the end result first
[3-8s] Overview: "Here's how..."
[8-50s] Steps: Quick, clear instructions
[50-60s] Result + CTA
```
### Caption & Subtitle Best Practices
Captions increase watch time by 25-40%. Most social video is watched without sound.
- **MAX 2 lines** on screen at once
- **3-5 words per line**
- Bold, sans-serif font with black outline
- **Highlight key words** in a different color
- Match timing to speech exactly
Tools: CapCut (free), Descript, Captions.ai, Premiere Pro
### Content Ideas by Type
| Business Type | Video Ideas |
|---------------|-------------|
| SaaS | Feature demos (show outcome first), before/after, "Watch me do X in Y seconds" |
| E-commerce | Unboxing, comparisons, how it's made, customer reviews |
| Services | Process reveals, client transformations, myth-busting |
| Personal brand | Lessons learned, controversial takes, day-in-the-life |
### Common Mistakes
1. **Slow hooks** — don't build up to the point
2. **No text overlay** — many watch without sound
3. **Poor audio** — bad audio kills retention instantly
4. **Too long** — if it can be shorter, make it shorter
5. **No CTA** — tell viewers what to do
6. **Ignoring comments** — engagement in first hour matters
**For video hook formulas and scripting templates**: See [references/short-form-video.md](references/short-form-video.md)
---
## Task-Specific Questions
1. What platform(s) are you focusing on?
2. What's your current posting frequency?
3. Do you have existing content to repurpose?
4. What content has performed well in the past?
5. How much time can you dedicate weekly?
6. Are you building personal brand, company brand, or both?
---
## Related Skills
- **copywriting**: For longer-form content that feeds social
- **launch**: For coordinating social with launches
- **emails**: For nurturing social audience via email
- **marketing-psychology**: For understanding what drives engagement
FILE:evals/evals.json
{
"skill_name": "social",
"evals": [
{
"id": 1,
"prompt": "Help me create a LinkedIn content strategy. I'm a SaaS founder building in public and want to grow my personal brand to drive awareness for my product. I currently have 500 followers and post maybe once a week.",
"expected_output": "Should check for product-marketing.md first. Should establish content pillars (3-5) appropriate for a SaaS founder building in public: industry insights, behind-the-scenes, educational content, personal stories, promotional (minimal). Should apply the platform quick reference for LinkedIn (3-5x/week recommended, carousels and stories perform well). Should provide hook formulas for LinkedIn posts. Should create a weekly content calendar. Should include engagement strategy (daily 30-min routine). Should address going from 1x/week to 3-5x/week with a batching strategy.",
"assertions": [
"Checks for product-marketing.md",
"Establishes 3-5 content pillars",
"Applies LinkedIn platform guidance",
"Provides hook formulas",
"Creates weekly content calendar",
"Includes engagement strategy",
"Addresses batching strategy for consistency",
"Recommends increasing from 1x to 3-5x per week"
],
"files": []
},
{
"id": 2,
"prompt": "Write me a Twitter/X thread about the lessons I learned bootstrapping my SaaS to $10k MRR. Include hooks and a CTA at the end.",
"expected_output": "Should apply the hook formulas for a story hook (e.g., '6 months ago, I had $0 MRR. Today, I hit $10k.'). Should structure the thread following platform best practices: strong hook in tweet 1, each tweet should stand alone but flow together, use specific numbers and stories, end with a CTA. Should reference the content pillar this fits into (behind-the-scenes / founder journey). Should provide the actual thread content with 8-12 tweets. Should include engagement prompts.",
"assertions": [
"Applies hook formulas from the skill",
"Uses a story hook for the first tweet",
"Structures thread with standalone but flowing tweets",
"Uses specific numbers and stories",
"Ends with clear CTA",
"Provides 8-12 tweet thread content",
"Includes engagement prompts"
],
"files": []
},
{
"id": 3,
"prompt": "i have a blog post that did really well. how do i turn it into social media content for multiple platforms?",
"expected_output": "Should trigger on casual phrasing. Should apply the content repurposing system. Should use the Blog Post → Social Content mapping: LinkedIn (key insight post + carousel of main points), Twitter/X (thread of key takeaways), Instagram (carousel with visuals + Reel summarizing the post). Should follow the repurposing workflow: create pillar content → extract key insights (3-5) → adapt to each platform → schedule across the week. Should provide specific format recommendations per platform.",
"assertions": [
"Triggers on casual phrasing",
"Applies content repurposing system",
"Uses Blog Post → Social Content mapping",
"Provides format for LinkedIn, Twitter/X, and Instagram",
"Follows the repurposing workflow",
"Extracts 3-5 key insights to repurpose",
"Provides platform-specific format recommendations"
],
"files": []
},
{
"id": 4,
"prompt": "My LinkedIn posts get like 200 impressions and almost no engagement. What am I doing wrong?",
"expected_output": "Should apply the analytics and optimization section, specifically the 'if engagement is low' guidance. Should diagnose potential issues: weak hooks (first line not compelling), posting at wrong times, not engaging with others' content, poor formatting (no line breaks, walls of text), content not resonating with audience. Should recommend specific fixes: test new hook formulas, post at different times, increase engagement with others (the daily engagement routine), try different formats (carousels, stories). Should provide before/after hook examples.",
"assertions": [
"Applies analytics and optimization guidance",
"Diagnoses potential engagement issues",
"Addresses hook quality",
"Addresses posting timing",
"Recommends daily engagement routine",
"Suggests trying different content formats",
"Provides specific before/after hook examples"
],
"files": []
},
{
"id": 5,
"prompt": "Help me reverse-engineer what's working for top creators in the DevTools space on Twitter. I want to understand their content patterns.",
"expected_output": "Should apply the reverse engineering viral content framework. Should walk through the process: identify 10-20 top accounts in DevTools, collect high-performing posts, analyze patterns (hooks, formats, CTAs, topics, posting times), codify a playbook of repeatable patterns, then layer the user's authentic voice. Should provide specific guidance on what to look for in the analysis. Should recommend tools or methods for collecting the data.",
"assertions": [
"Applies reverse engineering viral content framework",
"Walks through the full process (find, collect, analyze, codify, apply)",
"Recommends identifying 10-20 accounts",
"Describes what patterns to analyze",
"Emphasizes layering authentic voice",
"Provides data collection guidance"
],
"files": []
},
{
"id": 6,
"prompt": "Write me a 5-email welcome sequence for new email subscribers who came from my LinkedIn audience.",
"expected_output": "Should recognize this is an email sequence task, not social content. Should defer to or cross-reference the emails skill, which handles welcome sequences, drip campaigns, and lifecycle emails. May note the social-to-email bridge context but should make clear that emails is the right skill for writing email sequences.",
"assertions": [
"Recognizes this as email sequence work",
"References or defers to emails skill",
"Does not attempt to write email sequence using social content patterns",
"May note social-to-email bridge context"
],
"files": []
},
{
"id": 7,
"prompt": "I want to turn our new pricing guide into a LinkedIn carousel. It has 9 negotiation tactics for SaaS buyers. Help me structure the slides.",
"expected_output": "Should load references/carousel-frameworks.md and pick a framework BEFORE writing slides — a list of 9 named tactics maps to Hack List (or Value-Stack; should justify the choice), not Rant Callout or Demo Walkthrough. Should produce a slide-by-slide structure: contrarian or exact-count cover (slide 1 written as a standalone scroll-stopper), one named tactic per slide, and a synthesis + single-CTA close. Should apply LinkedIn document-post notes (PDF upload, denser text tolerated, post text written as its own hook rather than 'see attached'). Should keep any count promised on the cover exactly delivered and flag that stats/claims must be real. Should mention judging performance on saves/completion rather than likes.",
"assertions": [
"Loads or applies the carousel-frameworks reference",
"Selects an appropriate framework (Hack List or Value-Stack) with a stated reason before drafting slides",
"Slide 1 is written as a standalone scroll-stopper with the count or contrarian claim",
"One tactic/idea per slide, each named",
"Final slide has a synthesis and exactly one CTA",
"Applies LinkedIn document-post guidance (PDF, post text as its own hook)",
"Does not invent stats or pad the tactic count"
],
"files": []
}
]
}
FILE:references/carousel-frameworks.md
# Carousel Frameworks
Five slide-by-slide narrative architectures for carousels — Instagram carousels and LinkedIn document posts. Each framework is a different *reason to keep swiping*; picking the right one for the content matters more than polishing individual slides.
A carousel is not a blog post chopped into squares. It's a swipe-through with two jobs per slide: deliver one idea, and make the next swipe irresistible. These frameworks encode structures that show up repeatedly in high-performing carousels; treat them as starting structures to adapt, not rigid formulas — and expect to validate against your own analytics.
## Picking a Framework
| Your content is... | Use | Why |
|---|---|---|
| A list of resources, tools, or tips | **A: Value-Stack** | Completeness is the promise; the count is the hook |
| A personal result with a system behind it | **B: Problem-Proof** | Proof opens and closes the loop; the system is the meat |
| Several named techniques on one theme | **C: Hack List** | Each hack re-earns the swipe independently |
| A strong opinion about a common practice | **D: Rant Callout** | Conviction is the content; structure keeps it fair |
| A product or workflow you can show | **E: Demo Walkthrough** | Seeing the steps is more persuasive than describing them |
Two cross-framework rules before the specifics:
- **Slide 1 is the thumbnail.** It competes in the feed alone, before anyone knows a carousel follows. Design and write it as a standalone scroll-stopper.
- **One visual template per carousel.** Same layout, type scale, and palette on every interior slide. Variety between slides reads as clutter; consistency lets the *content* change while the frame stays still.
---
## Framework A: Value-Stack (4–14 slides)
The "everything you need" carousel. Works because the cover makes a completeness claim — an exact count and an exact deliverable — and every swipe pays it down.
| Slide | Job | Pattern |
|---|---|---|
| 1 — Cover | State the exact count + exact deliverable. Specificity proves scale upfront. | "[N] [resource type] for [role/outcome]" |
| 2 to N−1 — Value delivery | One item or category per slide, 3–6 concrete sub-items each. Same visual template, zero filler slides. | "[Category/item] — [sub-items]" |
| N — Close | Convert the swipe-through into an action. | "[Action] for [the payoff]" — comment a keyword, follow, link in bio |
**Why the exact count matters:** "27 free tools" outperforms "the best free tools" because a number is a checkable promise — the reader can verify you delivered. Rounding up with padding breaks the trust the count created.
*SaaS example:* "12 ChatGPT prompts for SaaS onboarding emails" → one prompt per slide with the use case → "Comment PROMPTS and I'll send the full doc."
**Failure mode:** filler slides to hit a bigger number. Cut to the real count; a tight 8 beats a padded 14.
---
## Framework B: Problem-Proof (6–10 slides)
The "I did X, here's the system" carousel. The hook is a *result stated as fact*, not advice — and the final slide shows the receipt, closing the loop slide 1 opened.
| Slide | Job | Pattern |
|---|---|---|
| 1 — Hook | A specific personal claim with a number. A result, not a tip. | "[I/we did X]. [Specific result, with number]." |
| 2 — Reframe the problem | Name what's *actually* going wrong so the reader recognizes themselves. | "The real problem — [named issue]" |
| 3–4 — The mechanism | Show the real system: named tools, named steps. Concrete beats vague. | "The system — [named tools/steps]" |
| 5 to N−1 — The detail | The literal prompt text, template, or step-by-step. This is the save-worthy part. | "[The literal prompt or process detail]" |
| N — Proof | The actual output artifact. A screenshot, not a description. | "[Screenshot of output] — the receipt" |
**The open loop is the engine:** slide 1 makes a claim, the last slide proves it, and everything between explains how. Readers swipe to see whether the receipt is real.
*SaaS example:* "We cut churn 22% with one onboarding email." → the real problem (activation, not price) → the sequence structure → the literal email copy → the retention chart.
**Failure mode:** vague mechanism slides ("optimize your workflow") — if the middle slides don't name tools and steps, the proof slide reads as luck, not system.
---
## Framework C: Hack List (6–10 slides)
The "numbered techniques" carousel. A contrarian cover creates status anxiety — most people are doing this wrong — and each numbered hack independently re-earns the swipe.
| Slide | Job | Pattern |
|---|---|---|
| 1 — Contrarian hook | A stat or claim implying most people fail at this. | "[Stat implying most people fail at X]" |
| 2 — The problem | Why the common approach fails, ideally with an analogy. | "The problem — [why it fails]" |
| 3 to N−1 — Numbered hacks | One named technique per slide. Bad-vs-good contrast makes each hack land instantly. | "Hack #[n] — [named technique]" |
| N — Synthesis + close | A one-line thesis tying the hacks together, then a save/share/follow CTA. | "[Thesis tying hacks together]" + [CTA] |
**Name every technique.** "The 3-second rule" is shareable and memorable; "keep it short" is neither. Named techniques travel — people repeat them and credit the source.
*SaaS example:* "90% of trial emails never get opened." → why (they read like receipts, not messages) → Hack #1: The founder-from line, Hack #2: The one-question subject, ... → "Trial emails are conversations, not confirmations. Save this for your next sequence."
**Failure mode:** hacks that are restatements of each other. Each slide must survive alone — if two hacks collapse into one idea, merge them and go shorter.
---
## Framework D: Rant Callout (4–8 slides)
The "someone had to say it" carousel. Personality-led and polarizing by design — the structure exists to keep the heat *fair* so it reads as conviction, not bitterness.
| Slide | Job | Pattern |
|---|---|---|
| 1 — Provocative claim | An unpopular opinion or direct accusation about a common practice. | "Unpopular opinion — [common practice]" |
| 2 to N−2 — Escalate the argument | Sensory, specific detail. Show the offense; don't gesture at it abstractly. Each slide raises the stakes. | "[Escalating, specific complaint]" |
| N−1 — Fairness pivot | "Don't get me wrong…" — clarify what you're *not* attacking. Anti-laziness, not anti-tool; anti-practice, not anti-person. | "The problem isn't [X], it's [Y]" |
| N — Close | Firm, personality-forward sign-off. Signed rants read as owned opinions; anonymous ones read as potshots. | "[Firm sign-off] — signed [name/persona]" |
**The fairness pivot is what makes it work.** Without it you're yelling; with it you're drawing a precise line, and precise lines get quoted. It also pre-empts the top hostile comment.
*SaaS example:* "Your AI-generated LinkedIn posts are costing you customers." → the specifics (same em-dash cadence, same 'game-changer' vocabulary, zero lived detail) → "Don't get me wrong — I use AI daily. The problem isn't the tool, it's publishing the first draft." → "Write like you talk. — [Name]"
**Failure modes:** skipping the pivot (reads unhinged, invites pile-ons), or ranting about something your own product/content visibly does (the comments will find it).
---
## Framework E: Demo Walkthrough (5–11 slides)
The "watch it work" carousel for product or workflow content. Show the finished result first, then the steps — the outcome earns attention for the process.
| Slide | Job | Pattern |
|---|---|---|
| 1 — Product + outcome | Brand/product plus the finished result. Lead with what they get. | "[Product] + [outcome], finished result" |
| 2 — Problem it solves | The specific frustration this replaces. | "[The specific pain this replaces]" |
| 3 — Process overview | The numbered step list, shown *before* the detail. Seeing the whole path lowers mid-swipe drop-off — readers commit when they know how long the road is. | "[N] steps to [outcome] in [timeframe]" |
| 4 to N−1 — Step-by-step | One real UI screenshot per step with a short caption. Real screenshots; mockups read as vaporware. | "Step [n] — [screenshot] — [caption]" |
| N — Result + positioning | The full-size output plus one brand-philosophy line. | "[Final output] + [positioning line]" |
**Overview-before-detail is the key move** (same reason recipe sites list ingredients before instructions): a reader who's seen "4 steps, 10 minutes" on slide 3 will finish; a reader dropped straight into step 1 with no map bails at step 2.
*SaaS example:* "From CSV to live dashboard in 4 steps" → the pain (weekly manual reporting) → the 4-step overview → one screenshot per step → the finished dashboard + "Built for marketers, not analysts."
**Failure mode:** screenshots with too much UI. Crop to the action, zoom the relevant control, annotate with one arrow — each screenshot should make sense at thumbnail size.
---
## Platform Notes
**Instagram:** 1080×1350 (4:5) slides; up to 20 slides but the frameworks' ranges above reflect where completion rates hold. The caption carries the searchable text — restate the hook and add context, don't just repeat slide 1. First slide doubles as the grid thumbnail.
**LinkedIn (document posts):** Upload as PDF. Square or 4:5. LinkedIn shows a "N pages" badge — Value-Stack's exact-count cover plays especially well here. B2B tolerates denser text per slide than Instagram, but the one-idea-per-slide rule still holds. The post text above the document is a second hook — write it as its own post, not a throwaway "see attached."
**TikTok photo mode:** Same frameworks work; pacing skews faster — trim each framework toward its minimum slide count.
## Production Checklist
- [ ] Slide 1 works as a standalone feed post (test: would it stop the scroll with no carousel behind it?)
- [ ] One idea per slide — if a slide needs two sentences of setup, split it
- [ ] Same visual template on every interior slide
- [ ] Any count promised on the cover is exactly delivered (no filler, no rounding)
- [ ] Claims and numbers are real and yours — no invented stats or borrowed screenshots
- [ ] Text is legible at thumbnail size (minimum ~28pt at 1080px wide)
- [ ] Final slide has one CTA, not three
- [ ] Caption/post text written as its own hook, not a repeat of slide 1
## Measuring What Worked
Judge carousels on **saves and completion**, not likes — saves signal reference value (the algorithmic win for carousels), and where per-slide analytics exist (Instagram professional insights, LinkedIn document analytics, or third-party tools), drop-off position tells you which slide broke the swipe chain. When a carousel underperforms, the fix is usually slide 1 (didn't stop the scroll) or the framework choice (list content forced into a rant structure), not the middle slides. Track per-framework performance and double down on the two that fit your audience — see [reverse-engineering.md](reverse-engineering.md) for the analysis workflow.
FILE:references/listening-sources-template.md
# Listening Sources — Template
Copy this file to `.agents/listening-sources.md` in your project (or `.claude/listening-sources.md`) and fill in the brackets. Claude reads it when running the [listening workflow](listening.md).
Delete sections you don't use. Keep this short and current — stale sources are worse than no sources.
---
## What We're Listening For
**Brand / product:** [Your product name]
**Category:** [e.g., "AI writing assistant", "Postgres GUI"]
**Goal:** [e.g., "find people switching from Notion", "engage with B2B SaaS founders 50-200 employees"]
## ICP (for scoring)
Used by the [scoring rubric](listening.md#scoring-rubric) to judge ICP fit.
- **Role:** [e.g., "founder, head of marketing, marketing ops lead"]
- **Company stage:** [e.g., "seed to Series B SaaS, 10-200 employees"]
- **Industry:** [e.g., "B2B SaaS, infra, devtools"]
- **Signals they're a fit:** [e.g., "writes about GTM, runs paid ads, recently raised"]
---
## Target Accounts
Engage with **every** post from these accounts when relevant. Keep this list to 20-50 max.
### LinkedIn (browser-driven — use dev-browser to view feed)
- [Name] — `linkedin.com/in/handle`
- [Name] — `linkedin.com/in/handle`
### X / Twitter (browser-driven)
- [@handle]
- [@handle]
### Reddit
- u/[username]
- u/[username]
### Bluesky
- [handle.bsky.social]
### Blogs / Newsletters (RSS)
- [Name] — `https://example.com/feed/`
- [Name] — `https://example.substack.com/feed`
### YouTube channels (RSS)
- [Name] — channel ID `UCxxxxxxxx`
---
## Keywords (intent signals)
Search across all platforms. Claude runs these through Reddit, HN, Bluesky on the [daily loop](listening.md#the-daily-triage-loop).
### High-intent (someone shopping or switching)
- `"alternative to [competitor]"`
- `"looking for a [category] tool"`
- `"recommend a [category]"`
- `"switching from [competitor]"`
- `"frustrated with [competitor]"`
### Problem signals (someone in pain)
- `"[category] is so [bad/hard/expensive]"`
- `"why is [category] [problem]"`
- `"hate [pain point]"`
### Brand mentions
- `"[your brand]"`
- `"[your brand misspelling]"`
- `"[your domain]"`
### Competitor mentions (monitor for switching language)
- `"[competitor 1]"`
- `"[competitor 2]"`
---
## Subreddits
Pulled via Reddit JSON API on the daily loop.
- r/SaaS
- r/Entrepreneur
- r/[your niche, e.g., "marketing", "devtools"]
- r/[adjacent community]
---
## Saved Searches (manual / browser-driven)
URLs Claude opens via dev-browser to scan.
### LinkedIn Sales Navigator
- [Search name] — `https://linkedin.com/sales/search/people?...`
### LinkedIn (regular)
- Posts hashtag — `https://linkedin.com/feed/hashtag/yourtopic/`
### X advanced search
- [Search name] — `https://x.com/search?q=...&f=live`
---
## Do Not Engage
Save yourself the regret.
- Accounts known for bad-faith dunking: [@handle], [@handle]
- Blocked brands / competitors who'll screenshot: [list]
- Topics to avoid: [politics, [your founder's hot takes], etc.]
---
## Notes for Claude
- When asked for "today's top 10," output in the format defined in [listening.md](listening.md#the-daily-triage-loop)
- For LinkedIn and X, use dev-browser with the persistent session (user is logged in)
- For everything else, use the curl recipes in [listening.md](listening.md#sources--light-tooling-curl-recipes)
- Default lookback: 24h. User can override.
- Always ask before posting — output drafts, user approves and posts manually
FILE:references/listening.md
# Social Listening & Engagement Triage
How to surface the right posts to engage with each day — instead of randomly scrolling. The goal is a short, scorable list ("here are your top 10 posts to comment on") rather than an open feed.
## Contents
- When to use this
- The daily triage loop
- Scoring rubric
- Comment quality tiers
- Sources & light tooling (curl recipes)
- Per-platform notes
- Common workflows
---
## When to Use This
Use listening when the goal is **commenting and relationships**, not posting. Typical asks:
- "Give me the top 10 posts I should comment on today"
- "Who's complaining about [competitor] right now?"
- "Find people asking for a tool like mine"
- "Surface posts from my 20 target accounts in the last 24h"
- "What's the conversation around [topic] this week?"
If the user wants to **create** content, use the rest of the social skill. Listening feeds creation (it surfaces angles, language, objections), but the output is different.
---
## The Daily Triage Loop
A repeatable 20-minute loop the user (or you, on their behalf) can run each morning.
1. **Pull** — fetch new posts from defined sources (target accounts, keywords, subreddits, hashtags). See [tooling](#sources--light-tooling-curl-recipes).
2. **Filter** — drop anything older than 24h, low signal, or off-topic.
3. **Score** — apply the [rubric](#scoring-rubric). Keep top 10.
4. **Draft** — for each, draft a comment matched to the post's tier.
5. **Post** — user reviews, edits, posts. Mark which actually went live.
6. **Log** — track what you commented on and what got replies. This is your engagement loop dataset.
Output format Claude should produce:
```
TOP 10 POSTS — 2026-06-05
1. [Score 9/10] @author — LinkedIn — 2h ago
"We just rolled out X and the team is loving it…"
Why: ICP fit (B2B SaaS, 50–200 employees), buying-intent signal
Suggested comment: [draft]
Link: https://…
```
---
## Scoring Rubric
Score each post 1–10 across five dimensions, then sum and rank.
| Dimension | What it measures | Weight |
|-----------|------------------|--------|
| **ICP fit** | Is the author your target customer / influencer? | 2x |
| **Intent signal** | Are they expressing a problem, asking, or shopping? | 2x |
| **Reach potential** | Is the post getting traction (likes/comments rising)? | 1x |
| **Comment opportunity** | Can you say something genuinely useful, not generic? | 2x |
| **Recency** | Posted in last 1–4h (early comments win, especially on LinkedIn) | 1x |
**Intent signal examples (high-value):**
- "Looking for a tool that does X"
- "Why is [category] so painful?"
- "We just switched from [competitor] because…"
- "Anyone use [competitor] — is it worth it?"
- A complaint about a known competitor
**Drop if any of these are true:**
- Author isn't ICP and isn't an influencer
- Post is >24h old and already has 50+ comments (your comment buries)
- Generic motivational/AI-slop post
- Self-promotion thread where comments don't get reach
- You can't add anything beyond "Great post!"
---
## Comment Quality Tiers
Match the comment to the post. Don't waste a tier-1 draft on a tier-3 opportunity.
**Tier 1 — Relationship builder (target accounts, ICP, high intent)**
- Add a specific insight or counter-example
- Reference your own experience with specifics (numbers, names, outcomes)
- Ask a thoughtful follow-up that invites a reply
- Length: 2–4 sentences, no link
**Tier 2 — Visibility play (high-reach post, adjacent topic)**
- Add one sharp insight in one sentence
- Pattern: "Agreed — and the part most miss is [X]"
- Length: 1–2 sentences
**Tier 3 — Light touch (relationship maintenance)**
- Specific reaction, not "Love this"
- Quote a specific line and react to it
- Length: 1 sentence
**Never:** "Great post!", emoji-only, "+1", LinkedIn-isms like "This is gold 🔥"
---
## Sources & Light Tooling (curl recipes)
These are public JSON endpoints — no auth needed. Run them from bash, pipe to `jq`, and Claude can parse the output to score and draft comments.
**Requires:** `jq` (most recipes) and `xmllint` (RSS only). Install once:
```bash
# macOS
brew install jq
# xmllint ships with macOS; on Linux: apt install libxml2-utils
```
### Reddit (free, scriptable)
**New posts in a subreddit:**
```bash
curl -s -A "listening/1.0" \
"https://www.reddit.com/r/SaaS/new.json?limit=25" \
| jq '.data.children[].data | {title, author, url: ("https://reddit.com"+.permalink), score, num_comments, created_utc, selftext: (.selftext | .[0:300])}'
```
**Search across Reddit by keyword (last day, sorted new):**
```bash
curl -s -A "listening/1.0" \
"https://www.reddit.com/search.json?q=KEYWORD&sort=new&t=day&limit=25" \
| jq '.data.children[].data | {subreddit, title, url: ("https://reddit.com"+.permalink), author, score, created_utc}'
```
Swap `KEYWORD` for things like `"alternative to notion"`, `"recommend a crm"`, your competitor names, or your own brand for mentions. Use quotes around multi-word phrases.
### Hacker News (Algolia search)
**Recent stories mentioning a keyword (last 24h):**
```bash
SINCE=$(($(date +%s) - 86400))
curl -s "https://hn.algolia.com/api/v1/search_by_date?query=KEYWORD&tags=story&numericFilters=created_at_i>SINCE" \
| jq '.hits[] | {title, url, author, points, num_comments, created_at, story_id: .objectID, hn_url: ("https://news.ycombinator.com/item?id="+.objectID)}'
```
**Recent comments mentioning a keyword:**
```bash
curl -s "https://hn.algolia.com/api/v1/search_by_date?query=KEYWORD&tags=comment&numericFilters=created_at_i>SINCE" \
| jq '.hits[] | {author, comment_text, story_title, hn_url: ("https://news.ycombinator.com/item?id="+.objectID)}'
```
### Bluesky (free, public API)
**Search posts by keyword:**
```bash
curl -s "https://public.api.bsky.app/xrpc/app.bsky.feed.searchPosts?q=KEYWORD&limit=25&sort=latest" \
| jq '.posts[] | {author: .author.handle, text: .record.text, likes: .likeCount, replies: .replyCount, url: ("https://bsky.app/profile/"+.author.handle+"/post/"+(.uri | split("/") | last))}'
```
### RSS for blogs, podcasts, YouTube channels
For target accounts that publish to RSS (most blogs, all YouTube channels):
```bash
# YouTube channel feed (replace CHANNEL_ID)
curl -s "https://www.youtube.com/feeds/videos.xml?channel_id=CHANNEL_ID"
# Generic blog feed
curl -s "https://example.com/feed/" | xmllint --xpath "//item[position()<6]" - 2>/dev/null
```
### LinkedIn & X — use the browser
LinkedIn and X don't expose useful public APIs, but you can drive a real browser session. **dev-browser** (MCP, already in the global setup) and **Playwright** both maintain persistent state — log in once, the session stays alive, Claude can navigate the authenticated feed.
**dev-browser workflow (preferred — already wired up):**
1. User logs into LinkedIn / X once in the dev-browser session
2. Claude navigates to a target URL (feed, profile, saved search, hashtag)
3. Claude reads the accessibility tree / page text, extracts posts
4. Claude scores using the [rubric](#scoring-rubric) and drafts comments
5. User reviews and posts manually (don't auto-post — high-stakes, bot detection risk)
**Useful URLs to feed dev-browser:**
| URL pattern | What it shows |
|-------------|---------------|
| `linkedin.com/in/HANDLE/recent-activity/all/` | A target account's recent posts |
| `linkedin.com/feed/hashtag/TOPIC/` | Hashtag feed |
| `linkedin.com/feed/` | Your main feed (algorithmic — less useful for triage) |
| `x.com/HANDLE` | A target account's profile |
| `x.com/search?q=QUERY&f=live` | Real-time search (use `f=live` for chronological) |
| `x.com/i/lists/LIST_ID` | A curated list — best for target accounts |
**Tips:**
- On X, build a private list of target accounts and use the list URL. Far cleaner than the algorithmic feed.
- LinkedIn's `/recent-activity/all/` URL is the cleanest way to see one person's posts without the algorithm.
- For both platforms, scroll programmatically (dev-browser supports it) to load more posts before extracting.
**Paid alternatives if you don't want to drive a browser:**
| Platform | Tools |
|----------|-------|
| LinkedIn | Sales Navigator (saved searches), Taplio (engagement) |
| X | TweetDeck/X Pro (saved columns), Typefully, Taplio, Tweet Hunter |
**Still closed (no good path):**
- Instagram & TikTok — closed APIs, browser automation is detectable and risky. Use native saved searches / hashtag follows.
---
## Per-Platform Notes
### LinkedIn
- **Browser-driven** (dev-browser with persistent session) — see [LinkedIn & X — use the browser](#linkedin--x--use-the-browser)
- **First-hour comments matter most** — algorithm weights early engagement heavily. Prioritize posts <2h old from target accounts.
- Comments with 5+ words get more reach than reactions
- Replying to other commenters can put you in front of their network
- Tag the author in your reply only if it adds context
### Twitter/X
- **Browser-driven** (dev-browser) — build a private list of target accounts and point dev-browser at the list URL
- Reply within first 30 min for max reach on big accounts
- Quote-tweet > reply when adding substantial value
- Threading your reply (multi-tweet) signals effort
- Don't pile on dunks — relationships > clout
### Reddit
- Read the subreddit rules before commenting (some ban self-promotion outright)
- Earn karma in the sub before linking to anything you own
- Long, specific answers win. AMAs and "help me decide" threads are gold
- Never lead with your product — answer the question first
### Hacker News
- Comment quality bar is high; low-effort gets downvoted fast
- Founders commenting on threads about their product is welcomed if you're transparent
- Search for past discussions of your category — they're often dormant gold mines
### Bluesky
- Smaller volume but high engagement-to-follower ratio
- Tech and indie-hacker communities are active
- Custom feeds (like Bluesky's "Following" + topic feeds) replace algorithmic search
---
## Common Workflows
### "Give me my top 10 posts to comment on today"
1. Pull from: target account RSS/saved searches + Reddit (relevant subs) + HN (last 24h)
2. Score with the [rubric](#scoring-rubric)
3. Output top 10 with suggested comments
### "Find people complaining about [competitor]"
1. Reddit search: `"competitor name" -site:competitor.com` sorted by new
2. HN comment search for competitor name
3. Bluesky search for competitor handle/name
4. Score by intent signal (high if switching language: "moving from", "alternatives to", "frustrated with")
### "Surface brand mentions from the last week"
1. Reddit search for brand name
2. HN search (stories + comments) for brand name
3. Bluesky search for brand name + handle
4. Output as: reply needed (yes/no), tone (positive/negative/neutral), suggested response
### "Find target-account posts I missed"
1. Maintain a list of target accounts with their RSS / Reddit usernames / Bluesky handles
2. Fetch each source's recent posts
3. Filter to last 24h, output sorted by score
---
## Setting Up the Source List
The user should maintain a list of sources somewhere persistent at `.agents/listening-sources.md` (or `.claude/listening-sources.md`). Claude reads it when running the daily loop.
**A ready-to-fill template lives at [listening-sources-template.md](listening-sources-template.md).** Copy it into the project and edit. The source path depends on how the skill was installed:
```bash
# Plugin / marketplace install (most common):
cp .agents/skills/social/references/listening-sources-template.md .agents/listening-sources.md
# .claude/ install:
cp .claude/skills/social/references/listening-sources-template.md .agents/listening-sources.md
# Working inside the marketingskills repo:
cp skills/social/references/listening-sources-template.md .agents/listening-sources.md
```
The template covers: brand/category, ICP (for scoring), target accounts per platform, intent keywords, subreddits, saved-search URLs, and a do-not-engage list.
FILE:references/platform-limits.md
# Platform Limits Reference
Quick reference for hashtag limits, character counts, and visible text thresholds on each major social platform.
---
## Instagram
| Element | Limit |
|---------|-------|
| Max hashtags | 5 (official limit) |
| Recommended hashtags | 3 – 5 |
| Max caption chars | 2,200 |
| Visible before "more" | ~125 chars |
---
## Facebook
| Element | Limit |
|---------|-------|
| Max hashtags | No official limit |
| Recommended hashtags | 1 – 2 |
| Max post chars | 63,206 |
| Ideal for engagement | 40 – 80 chars |
---
## TikTok
| Element | Limit |
|---------|-------|
| Max hashtags | 5 (since August 2025) |
| Recommended hashtags | 3 – 5 |
| Max caption chars | 4,000 |
| Visible before "more" | ~150 chars |
---
## LinkedIn
| Element | Limit |
|---------|-------|
| Max hashtags | No official limit |
| Recommended hashtags | 3 – 5 |
| Max post chars | 3,000 |
| Visible before "more" | ~210 chars |
---
## Twitter/X
| Element | Limit |
|---------|-------|
| Max hashtags | No official limit |
| Recommended hashtags | 1 – 2 |
| Max tweet chars | 280 (standard) / 25,000 (Premium+) |
| Visible before "more" | Full tweet (280 standard) |
---
## YouTube
| Element | Limit |
|---------|-------|
| Max hashtags | 15 (exceeding this causes YouTube to ignore ALL hashtags) |
| Recommended hashtags | 3 – 5 |
| Max title chars | 100 (visible before truncation: ~70) |
| Max description chars | 5,000 |
| Visible before "Show more" | ~100 chars |
> The first 3 hashtags in the description automatically appear above the title as clickable links. For Shorts, use 1 – 5 hashtags.
---
## Pinterest
| Element | Limit |
|---------|-------|
| Max hashtags | 20 per pin |
| Recommended hashtags | 2 – 5 |
| Max pin title chars | 100 |
| Max description chars | 500 |
| Visible before "More" | ~50 chars (desktop) |
> Pinterest has deprioritized hashtags. Focus on keywords as natural sentences within the description for better SEO instead of relying on hashtags.
---
## Threads (Meta)
| Element | Limit |
|---------|-------|
| Max hashtags | 1 per post (topic tag) |
| Recommended hashtags | 1 |
| Max post chars | 500 |
| Max with text attachment | 10,500 (500 + 10,000 expandable) |
| Visible without expanding | First ~1 – 2 lines |
> Threads limits topic tags to one per post. The platform is not hashtag-driven — the algorithm prioritizes content from followed accounts mixed with recommendations.
---
## Usage Tips
- **Hashtags count against character limits** on all platforms
- **Front-load your message** before the "more" truncation point
- On Instagram and TikTok, fewer hashtags now outperform hashtag-stuffing
- On LinkedIn, hashtags at the end of the post perform better than inline
- On Facebook, hashtags have minimal impact on reach — use sparingly
FILE:references/platforms.md
# Platform-Specific Strategy Guide
Detailed strategies for each major social platform.
## Contents
- LinkedIn
- Twitter/X
- Instagram
- TikTok
- Facebook
## LinkedIn
**Best for:** B2B, thought leadership, professional networking, recruiting
**Audience:** Professionals, decision-makers, job seekers
**Posting frequency:** 3-5x per week
**Best times:** Tuesday-Thursday, 7-8am, 12pm, 5-6pm
**What works:**
- Personal stories with business lessons
- Contrarian takes on industry topics
- Behind-the-scenes of building a company
- Data and original insights
- Carousel posts (document format)
- Polls that spark discussion
**What doesn't:**
- Overly promotional content
- Generic motivational quotes
- Links in the main post (kills reach)
- Corporate speak without personality
**Format tips:**
- First line is everything (hook before "see more")
- Use line breaks for readability
- 1,200-1,500 characters performs well
- Put links in comments, not post body
- Tag people sparingly and genuinely
**Algorithm tips:**
- First hour engagement matters most
- Comments > reactions > clicks
- Dwell time (people reading) signals quality
- No external links in post body
- Document posts (carousels) get strong reach
- Polls drive engagement but don't build authority
---
## Twitter/X
**Best for:** Tech, media, real-time commentary, community building
**Audience:** Tech-savvy, news-oriented, niche communities
**Posting frequency:** 3-10x per day (including replies)
**Best times:** Varies by audience; test and measure
**What works:**
- Hot takes and opinions
- Threads that teach something
- Behind-the-scenes moments
- Engaging with others' content
- Memes and humor (if on-brand)
- Real-time commentary on events
**What doesn't:**
- Pure self-promotion
- Threads without a strong hook
- Ignoring replies and mentions
- Scheduling everything (no real-time presence)
**Format tips:**
- Tweets under 100 characters get more engagement
- Threads: Hook in tweet 1, promise value, deliver
- Quote tweets with added insight beat plain retweets
- Use visuals to stop the scroll
**Algorithm tips:**
- Replies and quote tweets build authority
- Threads keep people on platform (rewarded)
- Images and video get more reach
- Engagement in first 30 min matters
- Twitter Blue/Premium may boost reach
---
## Instagram
**Best for:** Visual brands, lifestyle, e-commerce, younger demographics
**Audience:** 18-44, visual-first consumers
**Posting frequency:** 1-2 feed posts per day, 3-10 Stories per day
**Best times:** 11am-1pm, 7-9pm
**What works:**
- High-quality visuals
- Behind-the-scenes Stories
- Reels (short-form video)
- Carousels with value
- User-generated content
- Interactive Stories (polls, questions)
**What doesn't:**
- Low-quality images
- Too much text in images
- Ignoring Stories and Reels
- Only promotional content
**Format tips:**
- Reels get 2x reach of static posts
- First frame of Reels must hook
- Carousels: 10 slides with educational content
- Use all Story features (polls, links, etc.)
**Algorithm tips:**
- Reels heavily prioritized over static posts
- Saves and shares > likes
- Stories keep you top of feed
- Consistency matters more than perfection
- Use all features (polls, questions, etc.)
---
## TikTok
**Best for:** Brand awareness, younger audiences, viral potential
**Audience:** 16-34, entertainment-focused
**Posting frequency:** 1-4x per day
**Best times:** 7-9am, 12-3pm, 7-11pm
**What works:**
- Native, unpolished content
- Trending sounds and formats
- Educational content in entertaining wrapper
- POV and day-in-the-life content
- Responding to comments with videos
- Duets and stitches
**What doesn't:**
- Overly produced content
- Ignoring trends
- Hard selling
- Repurposed horizontal video
**Format tips:**
- Hook in first 1-2 seconds
- Keep it under 30 seconds to start
- Vertical only (9:16)
- Use trending sounds
- Post consistently to train algorithm
---
## Facebook
**Best for:** Communities, local businesses, older demographics, groups
**Audience:** 25-55+, community-oriented
**Posting frequency:** 1-2x per day
**Best times:** 1-4pm weekdays
**What works:**
- Facebook Groups (community)
- Native video
- Live video
- Local content and events
- Discussion-prompting questions
**What doesn't:**
- Links to external sites (reach killer)
- Pure promotional content
- Ignoring comments
- Cross-posting from other platforms without adaptation
FILE:references/post-templates.md
# Post Format Templates
Ready-to-use templates for different platforms and content types.
## Contents
- LinkedIn Post Templates (The Story Post, The Contrarian Take, The List Post, The How-To)
- Twitter/X Thread Templates (The Tutorial Thread, The Story Thread, The Breakdown Thread)
- Instagram Templates (The Carousel Hook, The Reel Script)
- Hook Formulas (Curiosity Hooks, Story Hooks, Value Hooks, Contrarian Hooks, Social Proof Hooks)
## LinkedIn Post Templates
### The Story Post
```
[Hook: Unexpected outcome or lesson]
[Set the scene: When/where this happened]
[The challenge you faced]
[What you tried / what happened]
[The turning point]
[The result]
[The lesson for readers]
[Question to prompt engagement]
```
### The Contrarian Take
```
[Unpopular opinion stated boldly]
Here's why:
[Reason 1]
[Reason 2]
[Reason 3]
[What you recommend instead]
[Invite discussion: "Am I wrong?"]
```
### The List Post
```
[X things I learned about [topic] after [credibility builder]:
1. [Point] — [Brief explanation]
2. [Point] — [Brief explanation]
3. [Point] — [Brief explanation]
[Wrap-up insight]
Which resonates most with you?
```
### The How-To
```
How to [achieve outcome] in [timeframe]:
Step 1: [Action]
↳ [Why this matters]
Step 2: [Action]
↳ [Key detail]
Step 3: [Action]
↳ [Common mistake to avoid]
[Result you can expect]
[CTA or question]
```
---
## Twitter/X Thread Templates
### The Tutorial Thread
```
Tweet 1: [Hook + promise of value]
"Here's exactly how to [outcome] (step-by-step):"
Tweet 2-7: [One step per tweet with details]
Final tweet: [Summary + CTA]
"If this was helpful, follow me for more on [topic]"
```
### The Story Thread
```
Tweet 1: [Intriguing hook]
"[Time] ago, [unexpected thing happened]. Here's the full story:"
Tweet 2-6: [Story beats, building tension]
Tweet 7: [Resolution and lesson]
Final tweet: [Takeaway + engagement ask]
```
### The Breakdown Thread
```
Tweet 1: [Company/person] just [did thing].
Here's why it's genius (and what you can learn):
Tweet 2-6: [Analysis points]
Tweet 7: [Your key takeaway]
"[Related insight + follow CTA]"
```
---
## Instagram Templates
### The Carousel Hook
```
[Slide 1: Bold statement or question]
[Slides 2-9: One point per slide, visual + text]
[Slide 10: Summary + CTA]
Caption: [Expand on the topic, add context, include CTA]
```
This is the generic shape. For five full slide-by-slide narrative architectures (Value-Stack, Problem-Proof, Hack List, Rant Callout, Demo Walkthrough) with selection guidance and per-slide copy slots, see [carousel-frameworks.md](carousel-frameworks.md).
### The Reel Script
```
Hook (0-2 sec): [Pattern interrupt or bold claim]
Setup (2-5 sec): [Context for the tip]
Value (5-25 sec): [The actual advice/content]
CTA (25-30 sec): [Follow, comment, share, link]
```
---
## Hook Formulas
The first line determines whether anyone reads the rest.
### Curiosity Hooks
- "I was wrong about [common belief]."
- "The real reason [outcome] happens isn't what you think."
- "[Impressive result] — and it only took [surprisingly short time]."
- "Nobody talks about [insider knowledge]."
### Story Hooks
- "Last week, [unexpected thing] happened."
- "I almost [big mistake/failure]."
- "3 years ago, I [past state]. Today, [current state]."
- "[Person] told me something I'll never forget."
### Value Hooks
- "How to [desirable outcome] (without [common pain]):"
- "[Number] [things] that [outcome]:"
- "The simplest way to [outcome]:"
- "Stop [common mistake]. Do this instead:"
### Contrarian Hooks
- "Unpopular opinion: [bold statement]"
- "[Common advice] is wrong. Here's why:"
- "I stopped [common practice] and [positive result]."
- "Everyone says [X]. The truth is [Y]."
### Social Proof Hooks
- "We [achieved result] in [timeframe]. Here's the full story:"
- "[Number] people asked me about [topic]. Here's my answer:"
- "[Authority figure] taught me [lesson]."
FILE:references/reverse-engineering.md
# Reverse Engineering Viral Content
Instead of guessing what works, systematically analyze top-performing content in your niche and extract proven patterns.
## Contents
- The 6-Step Framework (Niche ID, Scrape, Analyze, Playbook, Layer Voice, Convert)
- The Formula
- Reverse Engineering Checklist
## The 6-Step Framework
### 1. NICHE ID — Find Top Creators
Identify 10-20 creators in your space who consistently get high engagement:
**Selection criteria:**
- Posting consistently (3+ times/week)
- High engagement rate relative to follower count
- Audience overlap with your target market
- Mix of established and rising creators
**Where to find them:**
- LinkedIn: Search by industry keywords, check "People also viewed"
- Twitter/X: Check who your target audience follows and engages with
- Use tools like SparkToro, Followerwonk, or manual research
- Look at who gets featured in industry newsletters
### 2. SCRAPE — Collect Posts at Scale
Gather 500-1000+ posts from your identified creators for analysis:
**Tools:**
- **Apify** — LinkedIn scraper, Twitter scraper actors
- **Phantom Buster** — Multi-platform automation
- **Export tools** — Platform-specific export features
- **Manual collection** — For smaller datasets, copy/paste into spreadsheet
**Data to collect:**
- Post text/content
- Engagement metrics (likes, comments, shares, saves)
- Post format (text-only, carousel, video, image)
- Posting time/day
- Hook/first line
- CTA used
- Topic/theme
### 3. ANALYZE — Extract What Actually Works
Sort and analyze the data to find patterns:
**Quantitative analysis:**
- Rank posts by engagement rate
- Identify top 10% performers
- Look for format patterns (do carousels outperform?)
- Check timing patterns (best days/times)
- Compare topic performance
**Qualitative analysis:**
- What hooks do top posts use?
- How long are high-performing posts?
- What emotional triggers appear?
- What formats repeat?
- What topics consistently perform?
**Questions to answer:**
- What's the average length of top posts?
- Which hook types appear most in top 10%?
- What CTAs drive most comments?
- What topics get saved/shared most?
### 4. PLAYBOOK — Codify Patterns
Document repeatable patterns you can use:
**Hook patterns to codify:**
```
Pattern: "I [unexpected action] and [surprising result]"
Example: "I stopped posting daily and my engagement doubled"
Why it works: Curiosity gap + contrarian
Pattern: "[Specific number] [things] that [outcome]:"
Example: "7 pricing mistakes that cost me $50K:"
Why it works: Specificity + loss aversion
Pattern: "[Controversial take]"
Example: "Cold outreach is dead."
Why it works: Pattern interrupt + invites debate
```
**Format patterns:**
- Carousel: Hook slide → Problem → Solution steps → CTA
- Thread: Hook → Promise → Deliver → Recap → CTA
- Story post: Hook → Setup → Conflict → Resolution → Lesson
**CTA patterns:**
- Question: "What would you add?"
- Agreement: "Agree or disagree?"
- Share: "Tag someone who needs this"
- Save: "Save this for later"
### 5. LAYER VOICE — Apply Direct Response Principles
Take proven patterns and make them yours with these voice principles:
**"Smart friend who figured something out"**
- Write like you're texting advice to a friend
- Share discoveries, not lectures
- Use "I found that..." not "You should..."
- Be helpful, not preachy
**Specific > Vague**
```
❌ "I made good revenue"
✅ "I made $47,329"
❌ "It took a while"
✅ "It took 47 days"
❌ "A lot of people"
✅ "2,847 people"
```
**Short. Breathe. Land.**
- One idea per sentence
- Use line breaks liberally
- Let important points stand alone
- Create rhythm: short, short, longer explanation
```
❌ "I spent three years building my business the wrong way before I finally realized that the key to success was focusing on fewer things and doing them exceptionally well."
✅ "I built wrong for 3 years.
Then I figured it out.
Focus on less.
Do it exceptionally well.
Everything changed."
```
**Write from emotion**
- Start with how you felt, not what you did
- Use emotional words: frustrated, excited, terrified, obsessed
- Show vulnerability when authentic
- Connect the feeling to the lesson
```
❌ "Here's what I learned about pricing"
✅ "I was terrified to raise my prices.
My hands were shaking when I sent the email.
Here's what happened..."
```
### 6. CONVERT — Turn Attention into Action
Bridge from engagement to business results:
**Soft conversions:**
- Newsletter signups in bio/comments
- Free resource offers in follow-up comments
- DM triggers ("Comment X and I'll send you...")
- Profile visits → optimized profile with clear CTA
**Direct conversions:**
- Link in comments (not post body on LinkedIn)
- Contextual product mentions within valuable content
- Case study posts that naturally showcase your work
- "If you want help with this, DM me" (sparingly)
---
## The Formula
```
1. Find what's already working (don't guess)
2. Extract the patterns (hooks, formats, CTAs)
3. Layer your authentic voice on top
4. Test and iterate based on your own data
```
## Reverse Engineering Checklist
- [ ] Identified 10-20 top creators in niche
- [ ] Collected 500+ posts for analysis
- [ ] Ranked by engagement rate
- [ ] Documented top 10 hook patterns
- [ ] Documented top 5 format patterns
- [ ] Documented top 5 CTA patterns
- [ ] Created voice guidelines (specificity, brevity, emotion)
- [ ] Built template library from patterns
- [ ] Set up tracking for your own content performance
FILE:references/short-form-video.md
# Short-Form Video: Hooks, Scripts & Strategy
Detailed reference for creating short-form video content on TikTok, Instagram Reels, and YouTube Shorts.
---
## Video Hook Library
### Curiosity Hooks (Best for engagement)
**The "Secret" Formula:**
- "The secret to [outcome] that nobody talks about"
- "I found the hidden feature in [product/platform] that changes everything"
- "I can't believe this actually works..."
**The Unexpected Discovery:**
- "I tried [thing] for 30 days and I was NOT expecting this"
- "This completely changed how I think about [topic]"
- "Nobody talks about this, but..."
**The Question:**
- "Why does nobody talk about this?"
- "Am I the only one who didn't know this?"
- "The reason [common thing] doesn't work is..."
### Value Hooks (Best for saves)
**The Promise:**
- "How to [achieve outcome] in [specific timeframe]"
- "[Number] [things] that will [benefit]"
- "Everything you need to know about [topic] in 60 seconds"
**The Hack/Shortcut:**
- "[Outcome] hack that actually works"
- "The [adjective] way to [outcome]"
- "If you're struggling with [problem], watch this"
**The Warning:**
- "Stop doing [common practice] — here's why"
- "[Number] mistakes that are killing your [results]"
- "Why [thing you think is good] is actually hurting you"
### Story Hooks (Best for watch time)
**The Transformation:**
- "3 months ago, I [bad state]. Today, I [good state]."
- "Here's how I went from [before] to [after]"
- "I used to think [old belief]. Then [event] changed everything."
**The Failure:**
- "I made a huge mistake with [topic]"
- "Here's why I stopped [common practice]"
- "I lost [something significant] because of this mistake"
**The Journey:**
- "So this just happened..."
- "This changed everything for me"
- "Let me tell you about the time I [interesting situation]"
### Controversial Hooks (Best for comments)
- "Unpopular opinion: [bold statement]"
- "[Common advice] is actually wrong"
- "I'm going to get hate for this, but..."
- "Most [audience] get this completely wrong"
---
## Scripting Template
```markdown
## Video: [Working Title]
**Platform:** TikTok / Reels / Shorts
**Length:** XX seconds
**Format:** [Talking head / Slideshow / Demo / Screen recording]
### Hook (0-3 sec)
- Visual: [What viewer sees]
- Audio: [What they hear]
- Text overlay: [On-screen text]
### Body (3-X sec)
- [Timestamp] - [What happens/what you say]
- [Timestamp] - [Next beat]
- [Continue...]
### CTA (final 3-5 sec)
- Verbal: [What you say]
- Text: [On-screen text]
- Action: [Follow, comment, link in bio, etc.]
### Production Notes
- Music/sound: [Trending sound or music choice]
- B-roll needed: [List any clips needed]
- Graphics: [Any text animations or overlays]
```
---
## Additional Video Structures
### The Story Arc (45-60 sec)
```
[0-3s] Hook: Tease the outcome
[3-15s] Setup: Context and stakes
[15-45s] Journey: What happened
[45-55s] Resolution: The result
[55-60s] Lesson/CTA
```
Best for: Personal stories, case studies, testimonials
### The POV/Skit (15-30 sec)
```
[0-3s] Setup: Text overlay sets the scene
[3-25s] Performance: Act out the relatable scenario
[25-30s] Punchline or twist
```
Best for: Relatable content, humor, niche communities
---
## Visual Patterns
### Talking Head
- Good lighting (ring light or window light)
- Eye contact with camera
- Hand gestures for emphasis
- Interesting background (bookshelf, plants, studio setup)
### Slideshow/Carousel Video
- Strong visual on each slide (2-4 seconds per slide)
- Text overlays with key points
- Consistent style/branding
- Voiceover or trending sound
### Screen Recording
- Zoom in on important areas
- Add cursor highlight or click animations
- Keep movements smooth and intentional
- Overlay your face in corner (optional but boosts engagement)
### B-Roll Heavy
- Show don't tell
- Quick cuts (1-3 seconds per shot)
- Match cuts to voiceover beats
- Mix wide, medium, and close-up shots
---
## Audio Strategy
### When to Use Trending Sounds
- Entertainment/lifestyle content where the sound fits your message
- When the trend is still rising (check platform trending pages)
- Don't use when it distracts from your message or is already declining
### When to Use Original Audio
- Educational content where you're speaking
- Storytimes and personal narratives
- Product demos and tutorials
- Building a recognizable brand voice
### Voiceover Tips
- Speak slightly faster than normal conversation
- Vary your tone — avoid monotone delivery
- Pause for emphasis on key points
- Record in a quiet space, use noise removal
- AI voices work for faceless content (ElevenLabs, etc.)
### Music Selection
- Match energy to content (upbeat for tips, emotional for stories)
- Avoid copyrighted music on Reels/Shorts
- Use platform music libraries for safety
- Lower music volume under voiceover (ducking)
---
## Posting Strategy
### Optimal Posting Times (test your audience)
| Platform | Best Times (local) |
|----------|-------------------|
| TikTok | 7-9 AM, 12-3 PM, 7-11 PM |
| Reels | 9 AM, 12 PM, 7-9 PM |
| Shorts | 12-3 PM, 7-9 PM |
### Frequency Recommendations
| Goal | Minimum | Optimal |
|------|---------|---------|
| Growing | 1/day | 2-4/day |
| Maintaining | 3/week | 1/day |
| Testing | 2/week | 5/week |
### Batch Creation Workflow
1. **Ideate** (30 min): Generate 10-20 concepts
2. **Script** (1 hour): Write scripts for 5-10 videos
3. **Batch film** (2 hours): Record all talking head content
4. **Edit** (2-3 hours): Edit and add captions
5. **Schedule** (30 min): Queue for optimal times
---
## Analytics & Iteration
### Metrics That Matter
| Metric | What It Tells You |
|--------|-------------------|
| Watch time % | Is content engaging throughout? |
| Completion rate | Did hook + content deliver? |
| Saves | Is content valuable enough to revisit? |
| Shares | Is content worth spreading? |
| Comments | Did content spark conversation? |
| Follows | Did viewer want more from you? |
### What to Test
1. **Hooks**: Same content, different opening
2. **Length**: 15 sec vs 30 sec vs 60 sec
3. **Format**: Talking head vs slideshow vs demo
4. **Time**: Morning vs afternoon vs evening
5. **CTA**: Different calls to action
### When to Pivot
- 5+ videos with <1% completion rate → change hooks
- High views but low follows → check CTA and content-audience fit
- High saves but low shares → content is valuable but not social
- Lots of comments but negative → lean into controversy or adjust tone
Phỏng vấn 6 câu hỏi về mức độ sẵn sàng tuân thủ EU AI Act khi tiếp nhận hệ thống AI hoặc trước khi triển khai tại EU.
--- name: "ai-act-readiness" description: "/cs:ai-act-readiness <system> — EU AI Act 6-question forcing interrogation. Use during AI-system intake, before EU deployment, or during annual compliance refresh as Article 113 obligations phase in (2025-02-02 / 2025-08-02 / 2026-08-02 / 2027-08-02)." --- # /cs:ai-act-readiness — EU AI Act Forcing Questions **Command:** `/cs:ai-act-readiness <system>` The EU AI Act compliance operator pressure-tests any AI system before EU deployment. Six Article-cited questions before any EU placement, conformity assessment, or annual compliance refresh. ## When to Run - During AI-system intake review (per new system or material change) - Before placing an AI system on the EU market - Before signing the EU declaration of conformity (Article 47) - During annual compliance refresh (Article 113 phasing brings new obligations) - When the organization's role changes (deployer becomes provider via Article 25(1) substantial modification) - When training compute approaches 10^25 FLOPs (Article 51 systemic-risk threshold) ## The Six EU AI Act Questions ### 1. Article 5: Is this a prohibited AI practice? **Penalty: up to 35M EUR or 7% worldwide turnover.** - 8 categories: subliminal manipulation, exploitation of vulnerabilities, social scoring, predictive policing, untargeted facial scraping, emotion recognition in workplace/education, biometric categorisation by sensitive attributes, real-time public biometric ID by law enforcement - Run `ai_system_risk_classifier.py` - If yes → STOP. Cannot place on EU market. No exceptions outside Article 5(2) carve-outs. ### 2. Article 6 + Annex III: Is this high-risk? **Annex III triggers high-risk; Article 6(3) carve-out conditional.** - 8 categories: biometrics, critical infrastructure, education, employment, essential services, law enforcement, migration, justice - Carve-out applies only if Article 6(3)(a)-(d) AND no profiling of natural persons - Profiling overrides carve-out (Article 6(3) last sentence) - Run `ai_system_risk_classifier.py` ### 3. Article 43: For high-risk, Module A or Module H? **Biometrics → Module H (notified body) by default; others → Module A if harmonised standards applied.** - Run `conformity_assessment_planner.py` - Module A (Annex VI): internal control with presumption of conformity if Article 40 harmonised standards applied - Module H (Annex VII): full QMS + notified body for biometrics or where standards lacking - Annex IV technical documentation: 8 items required before placing on market ### 4. Article 25: What role does the company play? **Provider obligations are heaviest; substantial modification turns deployer into provider.** - Provider (Article 3(3)): placed on market; full Title III + Article 73 reporting - Deployer (Article 3(4)): Article 26 obligations + Article 27 FRIA if public sector - Importer (Article 3(6)): Article 23 verification of conformity - Distributor (Article 3(7)): Article 24 CE marking verification - Authorized representative (Article 22): non-EU providers must appoint - Run `ai_act_obligation_tracker.py` ### 5. Article 50: Are transparency obligations satisfied? **In force 2 Aug 2025.** - Article 50(1): disclose AI interaction to natural persons (chatbots, virtual agents) - Article 50(2): mark synthetic content as AI-generated - Article 50(3): disclose emotion recognition / biometric categorisation (outside Article 5 prohibitions) - Article 50(4): disclose deepfakes (image, audio, video) as AI-generated ### 6. Articles 51-55: Is this a GPAI? Does it have systemic risk? **GPAI has parallel track; systemic risk above 10^25 FLOPs.** - Article 3(63): general-purpose AI model definition - Article 51: systemic-risk presumption (≥ 10^25 FLOPs training compute) or Commission designation - Article 53: all GPAI providers — Annex XI technical docs, Annex XII downstream info, copyright policy, training-data summary - Article 55: systemic-risk GPAI additional obligations — model evaluations, adversarial testing, incident reporting, cybersecurity - Article 54: non-EU GPAI providers must appoint authorized representative ## Workflow ```bash # 1. Risk classification python ../../ra-qm-team/skills/eu-ai-act-specialist/scripts/ai_system_risk_classifier.py systems.json # 2. If high-risk: conformity assessment python ../../ra-qm-team/skills/eu-ai-act-specialist/scripts/conformity_assessment_planner.py system.json # 3. Per-role obligation matrix python ../../ra-qm-team/skills/eu-ai-act-specialist/scripts/ai_act_obligation_tracker.py roles.json # 4. Cross-framework reuse (ISO 42001 etc.) python ../../skills/compliance-os/scripts/cross_framework_mapper.py program.json ``` ## Output Format ```markdown # EU AI Act Readiness: <system> **Date:** YYYY-MM-DD **Article Citations:** Every verdict below cites the specific Article. ## The Decision Being Made [classify | conformity-route | obligation-scope | annual-refresh] ## Risk Classification - Tier: prohibited | high_risk | limited_risk | minimal_risk - Citation: Article X(Y) + Annex Z if applicable - Rationale: <Article-cited rationale> - GPAI: yes/no - Systemic-risk GPAI: yes/no (per Article 51 10^25 FLOPs threshold) ## Conformity Assessment (if high-risk) - Module: A | A_with_caveats | H | sectoral - Citation: Article 43 + Annex VI/VII - Notified body required: yes | no | optional - Annex IV pack status: complete | in-progress | not-started ## Obligation Matrix - Total obligations: N - By deadline phase: 2025-02-02=A, 2025-08-02=B, 2026-08-02=C, 2027-08-02=D - Highest-priority unmet obligation: <Article + description> ## Transparency (Article 50) - 50(1) interaction disclosure: yes | no - 50(2) synthetic content marking: yes | no | NA - 50(3) emotion recognition disclosure: yes | no | NA - 50(4) deepfake disclosure: yes | no | NA ## Cross-Framework Reuse - ISO 42001 evidence applicable to Article 17 QMS: yes/no - ISO 27001 evidence applicable to Article 15 cybersecurity: yes/no - GDPR DPIA usable for Article 27 FRIA: yes/no ## Verdict 🟢 READY-FOR-EU | 🟡 GAPS-IDENTIFIED | 🔴 NOT-READY | 🚫 PROHIBITED ## Top 3 Actions [3 concrete next steps with owner + Article-tied deadline] ## Legal Review Required [Article-level ambiguities flagged for outside counsel: novel cases, GPAI threshold disputes, Article 5 boundary cases, Article 25 substantial-modification questions] ``` ## Routing - `/cs:compliance-readiness` — for multi-framework view (combine with ISO 42001 + GDPR) - `/cs:aims-audit` — for ISO 42001 deep-dive - `/cs:caio-review` — for executive AI strategy decisions - `/cs:gc-review` — for novel-case legal review (GPAI threshold, Article 5 boundary, substantial-modification) - `/cs:decide` — to log the verdict - `/cs:freeze 30` — on EU launch commitments (regulatory exposure) ## Related - Agent: [`cs-ai-act-compliance`](../../agents/cs-ai-act-compliance.md) - Skill: [`eu-ai-act-specialist`](../../../ra-qm-team/skills/eu-ai-act-specialist/SKILL.md) - Adjacent: `../../skills/compliance-os/`, `../aims-audit/`, `../compliance-readiness/`, `../../../ra-qm-team/skills/gdpr-dsgvo-expert/` --- **Version:** 1.0.0
Bộ công cụ ASO: nghiên cứu từ khóa, phân tích đối thủ, gợi ý metadata và tăng khả năng hiển thị trên App Store, Google Play.
---
name: "app-store-optimization"
description: App Store Optimization (ASO) toolkit for researching keywords, analyzing competitor rankings, generating metadata suggestions, and improving app visibility on Apple App Store and Google Play Store. Use when the user asks about ASO, app store rankings, app metadata, app titles and descriptions, app store listings, app visibility, or mobile app marketing on iOS or Android. Supports keyword research and scoring, competitor keyword analysis, metadata optimization, A/B test planning, launch checklists, and tracking ranking changes.
triggers:
- ASO
- app store optimization
- app store ranking
- app keywords
- app metadata
- play store optimization
- app store listing
- improve app rankings
- app visibility
- app store SEO
- mobile app marketing
- app conversion rate
---
# App Store Optimization (ASO)
---
## Keyword Research Workflow
Discover and evaluate keywords that drive app store visibility.
### Workflow: Conduct Keyword Research
1. Define target audience and core app functions:
- Primary use case (what problem does the app solve)
- Target user demographics
- Competitive category
2. Generate seed keywords from:
- App features and benefits
- User language (not developer terminology)
- App store autocomplete suggestions
3. Expand keyword list using:
- Modifiers (free, best, simple)
- Actions (create, track, organize)
- Audiences (for students, for teams, for business)
4. Evaluate each keyword:
- Search volume (estimated monthly searches)
- Competition (number and quality of ranking apps)
- Relevance (alignment with app function)
5. Score and prioritize keywords:
- Primary: Title and keyword field (iOS)
- Secondary: Subtitle and short description
- Tertiary: Full description only
6. Map keywords to metadata locations
7. Document keyword strategy for tracking
8. **Validation:** Keywords scored; placement mapped; no competitor brand names included; no plurals in iOS keyword field
### Keyword Evaluation Criteria
| Factor | Weight | High Score Indicators |
|--------|--------|----------------------|
| Relevance | 35% | Describes core app function |
| Volume | 25% | 10,000+ monthly searches |
| Competition | 25% | Top 10 apps have <4.5 avg rating |
| Conversion | 15% | Transactional intent ("best X app") |
### Keyword Placement Priority
| Location | Search Weight |
|----------|---------------|
| App Title | Highest |
| Subtitle (iOS) | High |
| Keyword Field (iOS) | High |
| Short Description (Android) | High |
| Full Description | Medium |
See: [references/keyword-research-guide.md](references/keyword-research-guide.md)
---
## Metadata Optimization Workflow
Optimize app store listing elements for search ranking and conversion.
### Workflow: Optimize App Metadata
1. Audit current metadata against platform limits:
- Title character count and keyword presence
- Subtitle/short description usage
- Keyword field efficiency (iOS)
- Description keyword density
2. Optimize title following formula:
```
[Brand Name] - [Primary Keyword] [Secondary Keyword]
```
3. Write subtitle (iOS) or short description (Android):
- Focus on primary benefit
- Include secondary keyword
- Use action verbs
4. Optimize keyword field (iOS only):
- Remove duplicates from title
- Remove plurals (Apple indexes both forms)
- No spaces after commas
- Prioritize by score
5. Rewrite full description:
- Hook paragraph with value proposition
- Feature bullets with keywords
- Social proof section
- Call to action
6. Validate character counts for each field
7. Calculate keyword density (target 2-3% primary)
8. **Validation:** All fields within character limits; primary keyword in title; no keyword stuffing (>5%); natural language preserved
### Platform Character Limits
| Field | Apple App Store | Google Play Store |
|-------|-----------------|-------------------|
| Title | 30 characters | 50 characters |
| Subtitle | 30 characters | N/A |
| Short Description | N/A | 80 characters |
| Keywords | 100 characters | N/A |
| Promotional Text | 170 characters | N/A |
| Full Description | 4,000 characters | 4,000 characters |
| What's New | 4,000 characters | 500 characters |
### Description Structure
```
PARAGRAPH 1: Hook (50-100 words)
├── Address user pain point
├── State main value proposition
└── Include primary keyword
PARAGRAPH 2-3: Features (100-150 words)
├── Top 5 features with benefits
├── Bullet points for scanability
└── Secondary keywords naturally integrated
PARAGRAPH 4: Social Proof (50-75 words)
├── Download count or rating
├── Press mentions or awards
└── Summary of user testimonials
PARAGRAPH 5: Call to Action (25-50 words)
├── Clear next step
└── Reassurance (free trial, no signup)
```
See: [references/platform-requirements.md](references/platform-requirements.md)
---
## Competitor Analysis Workflow
Analyze top competitors to identify keyword gaps and positioning opportunities.
### Workflow: Analyze Competitor ASO Strategy
1. Identify top 10 competitors:
- Direct competitors (same core function)
- Indirect competitors (overlapping audience)
- Category leaders (top downloads)
2. Extract competitor keywords from:
- App titles and subtitles
- First 100 words of descriptions
- Visible metadata patterns
3. Build competitor keyword matrix:
- Map which keywords each competitor targets
- Calculate coverage percentage per keyword
4. Identify keyword gaps:
- Keywords with <40% competitor coverage
- High volume terms competitors miss
- Long-tail opportunities
5. Analyze competitor visual assets:
- Icon design patterns
- Screenshot messaging and style
- Video presence and quality
6. Compare ratings and review patterns:
- Average rating by competitor
- Common praise themes
- Common complaint themes
7. Document positioning opportunities
8. **Validation:** 10+ competitors analyzed; keyword matrix complete; gaps identified with volume estimates; visual audit documented
### Competitor Analysis Matrix
| Analysis Area | Data Points |
|---------------|-------------|
| Keywords | Title keywords, description frequency |
| Metadata | Character utilization, keyword density |
| Visuals | Icon style, screenshot count/style |
| Ratings | Average rating, total count, velocity |
| Reviews | Top praise, top complaints |
### Gap Analysis Template
| Opportunity Type | Example | Action |
|------------------|---------|--------|
| Keyword gap | "habit tracker" (40% coverage) | Add to keyword field |
| Feature gap | Competitor lacks widget | Highlight in screenshots |
| Visual gap | No videos in top 5 | Create app preview |
| Messaging gap | None mention "free" | Test free positioning |
---
## App Launch Workflow
Execute a structured launch for maximum initial visibility.
### Workflow: Launch App to Stores
1. Complete pre-launch preparation (4 weeks before):
- Finalize keywords and metadata
- Prepare all visual assets
- Set up analytics (Firebase, Mixpanel)
- Build press kit and media list
2. Submit for review (2 weeks before):
- Complete all store requirements
- Verify compliance with guidelines
- Prepare launch communications
3. Configure post-launch systems:
- Set up review monitoring
- Prepare response templates
- Configure rating prompt timing
4. Execute launch day:
- Verify app is live in both stores
- Announce across all channels
- Begin review response cycle
5. Monitor initial performance (days 1-7):
- Track download velocity hourly
- Monitor reviews and respond within 24 hours
- Document any issues for quick fixes
6. Conduct 7-day retrospective:
- Compare performance to projections
- Identify quick optimization wins
- Plan first metadata update
7. Schedule first update (2 weeks post-launch)
8. **Validation:** App live in stores; analytics tracking; review responses within 24h; download velocity documented; first update scheduled
### Pre-Launch Checklist
| Category | Items |
|----------|-------|
| Metadata | Title, subtitle, description, keywords |
| Visual Assets | Icon, screenshots (all sizes), video |
| Compliance | Age rating, privacy policy, content rights |
| Technical | App binary, signing certificates |
| Analytics | SDK integration, event tracking |
| Marketing | Press kit, social content, email ready |
### Launch Timing Considerations
| Factor | Recommendation |
|--------|----------------|
| Day of week | Tuesday-Wednesday (avoid weekends) |
| Time of day | Morning in target market timezone |
| Seasonal | Align with relevant category seasons |
| Competition | Avoid major competitor launch dates |
See: [references/aso-best-practices.md](references/aso-best-practices.md)
---
## A/B Testing Workflow
Test metadata and visual elements to improve conversion rates.
### Workflow: Run A/B Test
1. Select test element (prioritize by impact):
- Icon (highest impact)
- Screenshot 1 (high impact)
- Title (high impact)
- Short description (medium impact)
2. Form hypothesis:
```
If we [change], then [metric] will [improve/increase] by [amount]
because [rationale].
```
3. Create variants:
- Control: Current version
- Treatment: Single variable change
4. Calculate required sample size:
- Baseline conversion rate
- Minimum detectable effect (usually 5%)
- Statistical significance (95%)
5. Launch test:
- Apple: Use Product Page Optimization
- Android: Use Store Listing Experiments
6. Run test for minimum duration:
- At least 7 days
- Until statistical significance reached
7. Analyze results:
- Compare conversion rates
- Check statistical significance
- Document learnings
8. **Validation:** Single variable tested; sample size sufficient; significance reached (95%); results documented; winner implemented
### A/B Test Prioritization
| Element | Conversion Impact | Test Complexity |
|---------|-------------------|-----------------|
| App Icon | 10-25% lift possible | Medium (design needed) |
| Screenshot 1 | 15-35% lift possible | Medium |
| Title | 5-15% lift possible | Low |
| Short Description | 5-10% lift possible | Low |
| Video | 10-20% lift possible | High |
### Sample Size Quick Reference
| Baseline CVR | Impressions Needed (per variant) |
|--------------|----------------------------------|
| 1% | 31,000 |
| 2% | 15,500 |
| 5% | 6,200 |
| 10% | 3,100 |
### Test Documentation Template
```
TEST ID: ASO-2025-001
ELEMENT: App Icon
HYPOTHESIS: A bolder color icon will increase conversion by 10%
START DATE: [Date]
END DATE: [Date]
RESULTS:
├── Control CVR: 4.2%
├── Treatment CVR: 4.8%
├── Lift: +14.3%
├── Significance: 97%
└── Decision: Implement treatment
LEARNINGS:
- Bold colors outperform muted tones in this category
- Apply to screenshot backgrounds for next test
```
---
## Before/After Examples
### Title Optimization
**Productivity App:**
| Version | Title | Analysis |
|---------|-------|----------|
| Before | "MyTasks" | No keywords, brand only (8 chars) |
| After | "MyTasks - Todo List & Planner" | Primary + secondary keywords (29 chars) |
**Fitness App:**
| Version | Title | Analysis |
|---------|-------|----------|
| Before | "FitTrack Pro" | Generic modifier (12 chars) |
| After | "FitTrack: Workout Log & Gym" | Category keywords (27 chars) |
### Subtitle Optimization (iOS)
| Version | Subtitle | Analysis |
|---------|----------|----------|
| Before | "Get Things Done" | Vague, no keywords |
| After | "Daily Task Manager & Planner" | Two keywords, benefit clear |
### Keyword Field Optimization (iOS)
**Before (Inefficient - 89 chars, 8 keywords):**
```
task manager, todo list, productivity app, daily planner, reminder app
```
**After (Optimized - 97 chars, 14 keywords):**
```
task,todo,checklist,reminder,organize,daily,planner,schedule,deadline,goals,habit,widget,sync,team
```
**Improvements:**
- Removed spaces after commas (+8 chars)
- Removed duplicates (task manager → task)
- Removed plurals (reminders → reminder)
- Removed words in title
- Added more relevant keywords
### Description Opening
**Before:**
```
MyTasks is a comprehensive task management solution designed
to help busy professionals organize their daily activities
and boost productivity.
```
**After:**
```
Forget missed deadlines. MyTasks keeps every task, reminder,
and project in one place—so you focus on doing, not remembering.
Trusted by 500,000+ professionals.
```
**Improvements:**
- Leads with user pain point
- Specific benefit (not generic "boost productivity")
- Social proof included
- Keywords natural, not stuffed
### Screenshot Caption Evolution
| Version | Caption | Issue |
|---------|---------|-------|
| Before | "Task List Feature" | Feature-focused, passive |
| Better | "Create Task Lists" | Action verb, but still feature |
| Best | "Never Miss a Deadline" | Benefit-focused, emotional |
---
## Tools and References
### Scripts
| Script | Purpose | Usage |
|--------|---------|-------|
| [keyword_analyzer.py](scripts/keyword_analyzer.py) | Analyze keywords for volume and competition | `python keyword_analyzer.py --keywords "todo,task,planner"` |
| [metadata_optimizer.py](scripts/metadata_optimizer.py) | Validate metadata character limits and density | `python metadata_optimizer.py --platform ios --title "App Title"` |
| [competitor_analyzer.py](scripts/competitor_analyzer.py) | Extract and compare competitor keywords | `python competitor_analyzer.py --competitors "App1,App2,App3"` |
| [aso_scorer.py](scripts/aso_scorer.py) | Calculate overall ASO health score | `python aso_scorer.py --app-id com.example.app` |
| [ab_test_planner.py](scripts/ab_test_planner.py) | Plan tests and calculate sample sizes | `python ab_test_planner.py --cvr 0.05 --lift 0.10` |
| [review_analyzer.py](scripts/review_analyzer.py) | Analyze review sentiment and themes | `python review_analyzer.py --app-id com.example.app` |
| [launch_checklist.py](scripts/launch_checklist.py) | Generate platform-specific launch checklists | `python launch_checklist.py --platform ios` |
| [localization_helper.py](scripts/localization_helper.py) | Manage multi-language metadata | `python localization_helper.py --locales "en,es,de,ja"` |
### References
| Document | Content |
|----------|---------|
| [platform-requirements.md](references/platform-requirements.md) | iOS and Android metadata specs, visual asset requirements |
| [aso-best-practices.md](references/aso-best-practices.md) | Optimization strategies, rating management, launch tactics |
| [keyword-research-guide.md](references/keyword-research-guide.md) | Research methodology, evaluation framework, tracking |
### Assets
| Template | Purpose |
|----------|---------|
| [aso-audit-template.md](assets/aso-audit-template.md) | Structured audit checklist for app store listings |
---
## Platform Notes
| Platform / Constraint | Behavior / Impact |
|-----------------------|-------------------|
| iOS keyword changes | Require app submission |
| iOS promotional text | Editable without an app update |
| Android metadata changes | Index in 1-2 hours |
| Android keyword field | None — use description instead |
| Keyword volume data | Estimates only; no official source |
| Competitor data | Public listings only |
**When not to use this skill:** web apps (use web SEO), enterprise/internal apps, TestFlight-only betas, or paid advertising strategy.
---
## Related Skills
| Skill | Integration Point |
|-------|-------------------|
| [content-creator](../content-creator/) | App description copywriting |
| [marketing-demand-acquisition](../marketing-demand-acquisition/) | Launch promotion campaigns |
| [marketing-strategy-pmm](../marketing-strategy-pmm/) | Go-to-market planning |
## Proactive Triggers
- **No keyword optimization in title** → App title is the #1 ranking factor. Include top keyword.
- **Screenshots don't show value** → Screenshots should tell a story, not show UI.
- **No ratings strategy** → Below 4.0 stars kills conversion. Implement in-app rating prompts.
- **Description keyword-stuffed** → Natural language with keywords beats keyword stuffing.
## Output Artifacts
| When you ask for... | You get... |
|---------------------|------------|
| "ASO audit" | Full app store listing audit with prioritized fixes |
| "Keyword research" | Keyword list with search volume and difficulty scores |
| "Optimize my listing" | Rewritten title, subtitle, description, keyword field |
## Communication
All output passes quality verification:
- Self-verify: source attribution, assumption audit, confidence scoring
- Output format: Bottom Line → What (with confidence) → Why → How to Act
- Results only. Every finding tagged: 🟢 verified, 🟡 medium, 🔴 assumed.
FILE:assets/aso-audit-template.md
# ASO Audit Template
Use this template to conduct a systematic App Store Optimization audit.
---
## App Information
| Field | Value |
|-------|-------|
| App Name | |
| Platform | [ ] iOS [ ] Android |
| Category | |
| Current Downloads | |
| Current Rating | |
| Audit Date | |
---
## Metadata Audit
### Title Analysis
| Criterion | iOS (30 chars) | Android (50 chars) |
|-----------|----------------|---------------------|
| Current Title | | |
| Character Count | /30 | /50 |
| Primary Keyword Present | [ ] Yes [ ] No | [ ] Yes [ ] No |
| Brand Name Position | | |
**Title Score:** ___/10
**Recommendations:**
- [ ]
- [ ]
### Subtitle / Short Description
| Criterion | iOS Subtitle (30 chars) | Android Short Desc (80 chars) |
|-----------|-------------------------|-------------------------------|
| Current Text | | |
| Character Count | /30 | /80 |
| Keywords Included | | |
| Benefit-Focused | [ ] Yes [ ] No | [ ] Yes [ ] No |
**Score:** ___/10
**Recommendations:**
- [ ]
- [ ]
### Keyword Field (iOS Only)
| Criterion | Status |
|-----------|--------|
| Current Keywords | |
| Character Count | /100 |
| Duplicates Present | [ ] Yes [ ] No |
| Plurals Included | [ ] Yes [ ] No |
| Brand Names Included | [ ] Yes [ ] No |
**Score:** ___/10
**Recommendations:**
- [ ]
- [ ]
### Full Description
| Criterion | iOS | Android |
|-----------|-----|---------|
| Character Count | /4000 | /4000 |
| Primary Keyword Density | % | % |
| Secondary Keywords (count) | | |
| Feature Bullets Present | [ ] Yes [ ] No | [ ] Yes [ ] No |
| Social Proof Included | [ ] Yes [ ] No | [ ] Yes [ ] No |
| CTA Present | [ ] Yes [ ] No | [ ] Yes [ ] No |
**Score:** ___/10
**Recommendations:**
- [ ]
- [ ]
---
## Visual Asset Audit
### App Icon
| Criterion | Status |
|-----------|--------|
| Recognizable at 60x60px | [ ] Yes [ ] No |
| Distinct from competitors | [ ] Yes [ ] No |
| Matches app design | [ ] Yes [ ] No |
| No text/words | [ ] Yes [ ] No |
**Score:** ___/10
**Recommendations:**
- [ ]
- [ ]
### Screenshots
| Screenshot | Caption | Feature Shown | Score |
|------------|---------|---------------|-------|
| 1 (Hero) | | | /10 |
| 2 | | | /10 |
| 3 | | | /10 |
| 4 | | | /10 |
| 5 | | | /10 |
| Criterion | Status |
|-----------|--------|
| Total Screenshots | /10 (iOS) or /8 (Android) |
| Captions Present | [ ] Yes [ ] No |
| Consistent Style | [ ] Yes [ ] No |
| First 3 Show Value | [ ] Yes [ ] No |
| Device Frames Used | [ ] Yes [ ] No |
**Overall Screenshot Score:** ___/10
**Recommendations:**
- [ ]
- [ ]
### App Preview Video
| Criterion | Status |
|-----------|--------|
| Video Present | [ ] Yes [ ] No |
| Duration | seconds |
| Shows Core Features | [ ] Yes [ ] No |
| Hook in First 5 Seconds | [ ] Yes [ ] No |
| CTA at End | [ ] Yes [ ] No |
**Score:** ___/10
---
## Keyword Performance Audit
### Current Keyword Rankings
| Keyword | Current Rank | Volume | Competition | Score |
|---------|--------------|--------|-------------|-------|
| | | | | |
| | | | | |
| | | | | |
| | | | | |
| | | | | |
### Keyword Opportunities
| Keyword | Current Rank | Potential | Action |
|---------|--------------|-----------|--------|
| | | | |
| | | | |
| | | | |
---
## Rating & Review Audit
### Rating Summary
| Metric | Value |
|--------|-------|
| Current Average Rating | /5.0 |
| Total Ratings | |
| Ratings (Last 30 Days) | |
| 5-Star Percentage | % |
| 1-Star Percentage | % |
### Review Analysis
| Category | Count | Common Themes |
|----------|-------|---------------|
| Positive (4-5 stars) | | |
| Neutral (3 stars) | | |
| Negative (1-2 stars) | | |
### Response Rate
| Metric | Value |
|--------|-------|
| Reviews Responded | % |
| Avg Response Time | hours |
**Rating Score:** ___/10
**Recommendations:**
- [ ]
- [ ]
---
## Competitor Comparison
### Top 3 Competitors
| Metric | Your App | Competitor 1 | Competitor 2 | Competitor 3 |
|--------|----------|--------------|--------------|--------------|
| Name | | | | |
| Rating | | | | |
| Total Ratings | | | | |
| Downloads | | | | |
| Title Keywords | | | | |
| Screenshot Count | | | | |
### Competitive Gaps
| Gap Identified | Opportunity |
|----------------|-------------|
| | |
| | |
| | |
---
## Overall ASO Score
| Category | Weight | Score | Weighted |
|----------|--------|-------|----------|
| Title/Metadata | 25% | /10 | |
| Keywords | 25% | /10 | |
| Visual Assets | 25% | /10 | |
| Ratings/Reviews | 25% | /10 | |
| **TOTAL** | 100% | | **/100** |
---
## Priority Action Items
### High Priority (This Week)
1. [ ]
2. [ ]
3. [ ]
### Medium Priority (This Month)
1. [ ]
2. [ ]
3. [ ]
### Low Priority (This Quarter)
1. [ ]
2. [ ]
3. [ ]
---
## Audit Sign-Off
| Role | Name | Date |
|------|------|------|
| Auditor | | |
| Reviewer | | |
| App Owner | | |
---
## Notes
_Additional observations and context:_
FILE:expected_output.json
{
"request_type": "keyword_research",
"app_name": "TaskFlow Pro",
"keyword_analysis": {
"total_keywords_analyzed": 25,
"primary_keywords": [
{
"keyword": "task manager",
"search_volume": 45000,
"competition_level": "high",
"relevance_score": 0.95,
"difficulty_score": 72.5,
"potential_score": 78.3,
"recommendation": "High priority - target immediately"
},
{
"keyword": "productivity app",
"search_volume": 38000,
"competition_level": "high",
"relevance_score": 0.90,
"difficulty_score": 68.2,
"potential_score": 75.1,
"recommendation": "High priority - target immediately"
},
{
"keyword": "todo list",
"search_volume": 52000,
"competition_level": "very_high",
"relevance_score": 0.85,
"difficulty_score": 78.9,
"potential_score": 71.4,
"recommendation": "High priority - target immediately"
}
],
"secondary_keywords": [
{
"keyword": "team task manager",
"search_volume": 8500,
"competition_level": "medium",
"relevance_score": 0.88,
"difficulty_score": 42.3,
"potential_score": 68.7,
"recommendation": "Good opportunity - include in metadata"
},
{
"keyword": "project planning app",
"search_volume": 12000,
"competition_level": "medium",
"relevance_score": 0.75,
"difficulty_score": 48.1,
"potential_score": 64.2,
"recommendation": "Good opportunity - include in metadata"
}
],
"long_tail_keywords": [
{
"keyword": "ai task prioritization",
"search_volume": 2800,
"competition_level": "low",
"relevance_score": 0.95,
"difficulty_score": 25.4,
"potential_score": 82.6,
"recommendation": "Excellent long-tail opportunity"
},
{
"keyword": "team productivity tool",
"search_volume": 3500,
"competition_level": "low",
"relevance_score": 0.85,
"difficulty_score": 28.7,
"potential_score": 79.3,
"recommendation": "Excellent long-tail opportunity"
}
]
},
"competitor_insights": {
"competitors_analyzed": 4,
"common_keywords": [
"task",
"todo",
"list",
"productivity",
"organize",
"manage"
],
"keyword_gaps": [
{
"keyword": "ai prioritization",
"used_by": ["None of the major competitors"],
"opportunity": "Unique positioning opportunity"
},
{
"keyword": "smart task manager",
"used_by": ["Things 3"],
"opportunity": "Underutilized by most competitors"
}
]
},
"metadata_recommendations": {
"apple_app_store": {
"title_options": [
{
"title": "TaskFlow - AI Task Manager",
"length": 26,
"keywords_included": ["task manager", "ai"],
"strategy": "brand_plus_primary"
},
{
"title": "TaskFlow: Smart Todo & Tasks",
"length": 29,
"keywords_included": ["todo", "tasks"],
"strategy": "brand_plus_multiple"
}
],
"subtitle_recommendation": "AI-Powered Team Productivity",
"keyword_field": "productivity,organize,planner,schedule,workflow,reminders,collaboration,calendar,sync,priorities",
"description_focus": "Lead with AI differentiation, emphasize team features"
},
"google_play_store": {
"title_options": [
{
"title": "TaskFlow - AI Task Manager & Team Productivity",
"length": 48,
"keywords_included": ["task manager", "ai", "team", "productivity"],
"strategy": "keyword_rich"
}
],
"short_description_recommendation": "AI task manager - Organize, prioritize, and collaborate with your team",
"description_focus": "Keywords naturally integrated throughout 4000 character description"
}
},
"strategic_recommendations": [
"Focus on 'AI prioritization' as unique differentiator - low competition, high relevance",
"Target 'team task manager' and 'team productivity' keywords - good search volume, lower competition than generic terms",
"Include long-tail keywords in description for additional discovery opportunities",
"Test title variations with A/B testing after launch",
"Monitor competitor keyword changes quarterly"
],
"priority_actions": [
{
"action": "Optimize app title with primary keyword",
"priority": "high",
"expected_impact": "15-25% improvement in search visibility"
},
{
"action": "Create description highlighting AI features with natural keyword integration",
"priority": "high",
"expected_impact": "10-15% improvement in conversion rate"
},
{
"action": "Plan A/B tests for icon and screenshots post-launch",
"priority": "medium",
"expected_impact": "5-10% improvement in conversion rate"
}
],
"aso_health_estimate": {
"current_score": "N/A (pre-launch)",
"potential_score_with_optimizations": "75-80/100",
"key_strengths": [
"Unique AI differentiation",
"Clear target audience",
"Strong feature set"
],
"areas_to_develop": [
"Build rating volume post-launch",
"Monitor and respond to reviews",
"Continuous keyword optimization"
]
}
}
FILE:HOW_TO_USE.md
# How to Use the App Store Optimization Skill
Hey Claude—I just added the "app-store-optimization" skill. Can you help me optimize my app's presence on the App Store and Google Play?
## Example Invocations
### Keyword Research
**Example 1: Basic Keyword Research**
```
Hey Claude—I just added the "app-store-optimization" skill. Can you research the best keywords for my productivity app? I'm targeting professionals who need task management and team collaboration features.
```
**Example 2: Competitive Keyword Analysis**
```
Hey Claude—I just added the "app-store-optimization" skill. Can you analyze keywords that Todoist, Asana, and Monday.com are using? I want to find gaps and opportunities for my project management app.
```
### Metadata Optimization
**Example 3: Optimize App Title**
```
Hey Claude—I just added the "app-store-optimization" skill. Can you optimize my app title for the Apple App Store? My app is called "TaskFlow" and I want to rank for "task manager", "productivity", and "team collaboration". The title needs to be under 30 characters.
```
**Example 4: Full Metadata Package**
```
Hey Claude—I just added the "app-store-optimization" skill. Can you create optimized metadata for both Apple App Store and Google Play Store? Here's my app info:
- Name: TaskFlow
- Category: Productivity
- Key features: AI task prioritization, team collaboration, calendar integration
- Target keywords: task manager, productivity app, team tasks
```
### Competitor Analysis
**Example 5: Analyze Top Competitors**
```
Hey Claude—I just added the "app-store-optimization" skill. Can you analyze the ASO strategies of the top 5 productivity apps in the App Store? I want to understand their title strategies, keyword usage, and visual asset approaches.
```
**Example 6: Identify Competitive Gaps**
```
Hey Claude—I just added the "app-store-optimization" skill. Can you compare my app's ASO performance against competitors and identify what I'm missing? Here's my current metadata: [paste metadata]
```
### ASO Score Calculation
**Example 7: Calculate Overall ASO Health**
```
Hey Claude—I just added the "app-store-optimization" skill. Can you calculate my app's ASO health score? Here are my metrics:
- Average rating: 4.2 stars
- Total ratings: 3,500
- Keywords in top 10: 3
- Keywords in top 50: 12
- Conversion rate: 4.5%
```
**Example 8: Identify Improvement Areas**
```
Hey Claude—I just added the "app-store-optimization" skill. My ASO score is 62/100. Can you tell me which areas I should focus on first to improve my rankings and downloads?
```
### A/B Testing
**Example 9: Plan Icon Test**
```
Hey Claude—I just added the "app-store-optimization" skill. I want to A/B test two different app icons. My current conversion rate is 5%. Can you help me plan the test, calculate required sample size, and determine how long to run it?
```
**Example 10: Analyze Test Results**
```
Hey Claude—I just added the "app-store-optimization" skill. Can you analyze my A/B test results?
- Variant A (control): 2,500 visitors, 125 installs
- Variant B (new icon): 2,500 visitors, 150 installs
Is this statistically significant? Should I implement variant B?
```
### Localization
**Example 11: Plan Localization Strategy**
```
Hey Claude—I just added the "app-store-optimization" skill. I currently only have English metadata. Which markets should I localize for first? I'm a bootstrapped startup with moderate budget.
```
**Example 12: Translate Metadata**
```
Hey Claude—I just added the "app-store-optimization" skill. Can you help me translate my app metadata to Spanish for the Mexico market? Here's my English metadata: [paste metadata]. Check if it fits within character limits.
```
### Review Analysis
**Example 13: Analyze User Reviews**
```
Hey Claude—I just added the "app-store-optimization" skill. Can you analyze my recent reviews and tell me:
- Overall sentiment (positive/negative ratio)
- Most common complaints
- Most requested features
- Bugs that need immediate fixing
```
**Example 14: Generate Review Response Templates**
```
Hey Claude—I just added the "app-store-optimization" skill. Can you create professional response templates for:
- Users reporting crashes
- Feature requests
- Positive 5-star reviews
- General complaints
```
### Launch Planning
**Example 15: Pre-Launch Checklist**
```
Hey Claude—I just added the "app-store-optimization" skill. Can you generate a comprehensive pre-launch checklist for both Apple App Store and Google Play Store? My launch date is December 1, 2025.
```
**Example 16: Optimize Launch Timing**
```
Hey Claude—I just added the "app-store-optimization" skill. What's the best day and time to launch my fitness app? I want to maximize visibility and downloads in the first week.
```
**Example 17: Plan Seasonal Campaign**
```
Hey Claude—I just added the "app-store-optimization" skill. Can you identify seasonal opportunities for my fitness app? It's currently October—what campaigns should I run for the next 6 months?
```
## What to Provide
### For Keyword Research
- App name and category
- Target audience description
- Key features and unique value proposition
- Competitor apps (optional)
- Geographic markets to target
### For Metadata Optimization
- Current app name
- Platform (Apple, Google, or both)
- Target keywords (prioritized list)
- Key features and benefits
- Target audience
- Current metadata (for optimization)
### For Competitor Analysis
- Your app category
- List of competitor app names or IDs
- Platform (Apple or Google)
- Specific aspects to analyze (keywords, visuals, ratings)
### For ASO Score Calculation
- Metadata quality metrics (title length, description length, keyword density)
- Rating data (average rating, total ratings, recent ratings)
- Keyword rankings (top 10, top 50, top 100 counts)
- Conversion metrics (impression-to-install rate, downloads)
### For A/B Testing
- Test type (icon, screenshot, title, description)
- Control variant details
- Test variant details
- Baseline conversion rate
- For results analysis: visitor and conversion counts for both variants
### For Localization
- Current market and language
- Budget level (low, medium, high)
- Target number of markets
- Current metadata text for translation
### For Review Analysis
- Recent reviews (text, rating, date)
- Platform (Apple or Google)
- Time period to analyze
- Specific focus (bugs, features, sentiment)
### For Launch Planning
- Platform (Apple, Google, or both)
- Target launch date
- App category
- App information (name, features, target audience)
## What You'll Get
### Keyword Research Output
- Prioritized keyword list with search volume estimates
- Competition level analysis
- Relevance scores
- Long-tail keyword opportunities
- Strategic recommendations
### Metadata Optimization Output
- Optimized titles (multiple options)
- Optimized descriptions (short and full)
- Keyword field optimization (Apple)
- Character count validation
- Keyword density analysis
- Before/after comparison
### Competitor Analysis Output
- Ranked competitors by ASO strength
- Common keyword patterns
- Keyword gaps and opportunities
- Visual asset assessment
- Best practices identified
- Actionable recommendations
### ASO Score Output
- Overall score (0-100)
- Breakdown by category (metadata, ratings, keywords, conversion)
- Strengths and weaknesses
- Prioritized action items
- Expected impact of improvements
### A/B Test Output
- Test design with hypothesis
- Required sample size calculation
- Duration estimates
- Statistical significance analysis
- Implementation recommendations
- Learnings and insights
### Localization Output
- Prioritized target markets
- Estimated translation costs
- ROI projections
- Character limit validation for each language
- Cultural adaptation recommendations
- Phased implementation plan
### Review Analysis Output
- Sentiment distribution (positive/neutral/negative)
- Common themes and topics
- Top issues requiring fixes
- Most requested features
- Response templates
- Trend analysis over time
### Launch Planning Output
- Platform-specific checklists (Apple, Google, Universal)
- Timeline with milestones
- Compliance validation
- Optimal launch timing recommendations
- Seasonal campaign opportunities
- Update cadence planning
## Tips for Best Results
1. **Be Specific**: Provide as much detail about your app as possible
2. **Include Context**: Share your goals (increase downloads, improve ranking, boost conversion)
3. **Provide Data**: Real metrics enable more accurate analysis
4. **Iterate**: Start with keyword research, then optimize metadata, then test
5. **Track Results**: Monitor changes after implementing recommendations
6. **Stay Compliant**: Always verify recommendations against current App Store/Play Store guidelines
7. **Test First**: Use A/B testing before making major metadata changes
8. **Localize Strategically**: Start with highest-ROI markets first
9. **Respond to Reviews**: Use provided templates to engage with users
10. **Plan Ahead**: Use launch checklists and timelines to avoid last-minute rushes
## Common Workflows
### New App Launch
1. Keyword research → Competitor analysis → Metadata optimization → Pre-launch checklist → Launch timing optimization
### Improving Existing App
1. ASO score calculation → Identify gaps → Metadata optimization → A/B testing → Review analysis → Implement changes
### International Expansion
1. Localization planning → Market prioritization → Metadata translation → ROI analysis → Phased rollout
### Ongoing Optimization
1. Monthly keyword ranking tracking → Quarterly metadata updates → Continuous A/B testing → Review monitoring → Seasonal campaigns
## Need Help?
If you need clarification on any aspect of ASO or want to combine multiple analyses, just ask! For example:
```
Hey Claude—I just added the "app-store-optimization" skill. Can you create a complete ASO strategy for my new productivity app? I need keyword research, optimized metadata for both stores, a pre-launch checklist, and launch timing recommendations.
```
The skill can handle comprehensive, multi-phase ASO projects as well as specific tactical optimizations.
FILE:README.md
# App Store Optimization (ASO) Skill
**Version**: 1.0.0
**Last Updated**: November 7, 2025
**Author**: Claude Skills Factory
## Overview
A comprehensive App Store Optimization (ASO) skill that provides complete capabilities for researching, optimizing, and tracking mobile app performance on the Apple App Store and Google Play Store. This skill empowers app developers and marketers to maximize their app's visibility, downloads, and success in competitive app marketplaces.
## What This Skill Does
This skill provides end-to-end ASO capabilities across seven key areas:
1. **Research & Analysis**: Keyword research, competitor analysis, market trends, review sentiment
2. **Metadata Optimization**: Title, description, keywords with platform-specific character limits
3. **Conversion Optimization**: A/B testing framework, visual asset optimization
4. **Rating & Review Management**: Sentiment analysis, response strategies, issue identification
5. **Launch & Update Strategies**: Pre-launch checklists, timing optimization, update planning
6. **Analytics & Tracking**: ASO scoring, keyword rankings, performance benchmarking
7. **Localization**: Multi-language strategy, translation management, ROI analysis
## Key Features
### Comprehensive Keyword Research
- Search volume and competition analysis
- Long-tail keyword discovery
- Competitor keyword extraction
- Keyword difficulty scoring
- Strategic prioritization
### Platform-Specific Metadata Optimization
- **Apple App Store**:
- Title (30 chars)
- Subtitle (30 chars)
- Promotional Text (170 chars)
- Description (4000 chars)
- Keywords field (100 chars)
- **Google Play Store**:
- Title (50 chars)
- Short Description (80 chars)
- Full Description (4000 chars)
- Character limit validation
- Keyword density analysis
- Multiple optimization strategies
### Competitor Intelligence
- Automated competitor discovery
- Metadata strategy analysis
- Visual asset assessment
- Gap identification
- Competitive positioning
### ASO Health Scoring
- 0-100 overall score
- Four-category breakdown (Metadata, Ratings, Keywords, Conversion)
- Strengths and weaknesses identification
- Prioritized action recommendations
- Expected impact estimates
### Scientific A/B Testing
- Test design and hypothesis formulation
- Sample size calculation
- Statistical significance analysis
- Duration estimation
- Implementation recommendations
### Global Localization
- Market prioritization (Tier 1/2/3)
- Translation cost estimation
- Character limit adaptation by language
- Cultural keyword considerations
- ROI analysis
### Review Intelligence
- Sentiment analysis
- Common theme extraction
- Bug and issue identification
- Feature request clustering
- Professional response templates
### Launch Planning
- Platform-specific checklists
- Timeline generation
- Compliance validation
- Optimal timing recommendations
- Seasonal campaign planning
## Python Modules
This skill includes 8 powerful Python modules:
### 1. keyword_analyzer.py
**Purpose**: Analyzes keywords for search volume, competition, and relevance
**Key Functions**:
- `analyze_keyword()`: Single keyword analysis
- `compare_keywords()`: Multi-keyword comparison and ranking
- `find_long_tail_opportunities()`: Generate long-tail variations
- `calculate_keyword_density()`: Analyze keyword usage in text
- `extract_keywords_from_text()`: Extract keywords from reviews/descriptions
### 2. metadata_optimizer.py
**Purpose**: Optimizes titles, descriptions, keywords with character limit validation
**Key Functions**:
- `optimize_title()`: Generate optimal title options
- `optimize_description()`: Create conversion-focused descriptions
- `optimize_keyword_field()`: Maximize Apple's 100-char keyword field
- `validate_character_limits()`: Ensure platform compliance
- `calculate_keyword_density()`: Analyze keyword integration
### 3. competitor_analyzer.py
**Purpose**: Analyzes competitor ASO strategies
**Key Functions**:
- `analyze_competitor()`: Single competitor deep-dive
- `compare_competitors()`: Multi-competitor analysis
- `identify_gaps()`: Find competitive opportunities
- `_calculate_competitive_strength()`: Score competitor ASO quality
### 4. aso_scorer.py
**Purpose**: Calculates comprehensive ASO health score
**Key Functions**:
- `calculate_overall_score()`: 0-100 ASO health score
- `score_metadata_quality()`: Evaluate metadata optimization
- `score_ratings_reviews()`: Assess rating quality and volume
- `score_keyword_performance()`: Analyze ranking positions
- `score_conversion_metrics()`: Evaluate conversion rates
- `generate_recommendations()`: Prioritized improvement actions
### 5. ab_test_planner.py
**Purpose**: Plans and tracks A/B tests for ASO elements
**Key Functions**:
- `design_test()`: Create test hypothesis and structure
- `calculate_sample_size()`: Determine required visitors
- `calculate_significance()`: Assess statistical validity
- `track_test_results()`: Monitor ongoing tests
- `generate_test_report()`: Create comprehensive test reports
### 6. localization_helper.py
**Purpose**: Manages multi-language ASO optimization
**Key Functions**:
- `identify_target_markets()`: Prioritize localization markets
- `translate_metadata()`: Adapt metadata for languages
- `adapt_keywords()`: Cultural keyword adaptation
- `validate_translations()`: Character limit validation
- `calculate_localization_roi()`: Estimate investment returns
### 7. review_analyzer.py
**Purpose**: Analyzes user reviews for actionable insights
**Key Functions**:
- `analyze_sentiment()`: Calculate sentiment distribution
- `extract_common_themes()`: Identify frequent topics
- `identify_issues()`: Surface bugs and problems
- `find_feature_requests()`: Extract desired features
- `track_sentiment_trends()`: Monitor changes over time
- `generate_response_templates()`: Create review responses
### 8. launch_checklist.py
**Purpose**: Generates comprehensive launch and update checklists
**Key Functions**:
- `generate_prelaunch_checklist()`: Complete submission validation
- `validate_app_store_compliance()`: Check guidelines compliance
- `create_update_plan()`: Plan update cadence
- `optimize_launch_timing()`: Recommend launch dates
- `plan_seasonal_campaigns()`: Identify seasonal opportunities
## Installation
### For Claude Code (Desktop/CLI)
#### Project-Level Installation
```bash
# Copy skill folder to project
cp -r app-store-optimization /path/to/your/project/.claude/skills/
# Claude will auto-load the skill when working in this project
```
#### User-Level Installation (Available in All Projects)
```bash
# Copy skill folder to user-level skills
cp -r app-store-optimization ~/.claude/skills/
# Claude will load this skill in all your projects
```
### For Claude Apps (Browser)
1. Use the `skill-creator` skill to import the skill
2. Or manually import via Claude Apps interface
### Verification
To verify installation:
```bash
# Check if skill folder exists
ls ~/.claude/skills/app-store-optimization/
# You should see:
# SKILL.md
# keyword_analyzer.py
# metadata_optimizer.py
# competitor_analyzer.py
# aso_scorer.py
# ab_test_planner.py
# localization_helper.py
# review_analyzer.py
# launch_checklist.py
# sample_input.json
# expected_output.json
# HOW_TO_USE.md
# README.md
```
## Usage Examples
### Example 1: Complete Keyword Research
```
Hey Claude—I just added the "app-store-optimization" skill. Can you research keywords for my fitness app? I'm targeting people who want home workouts, yoga, and meal planning. Analyze top competitors like Nike Training Club and Peloton.
```
**What Claude will do**:
- Use `keyword_analyzer.py` to research keywords
- Use `competitor_analyzer.py` to analyze Nike Training Club and Peloton
- Provide prioritized keyword list with search volumes, competition levels
- Identify gaps and long-tail opportunities
- Recommend primary keywords for title and secondary keywords for description
### Example 2: Optimize App Store Metadata
```
Hey Claude—I just added the "app-store-optimization" skill. Optimize my app's metadata for both Apple App Store and Google Play Store:
- App: FitFlow
- Category: Health & Fitness
- Features: AI workout plans, nutrition tracking, progress photos
- Keywords: fitness app, workout planner, home fitness
```
**What Claude will do**:
- Use `metadata_optimizer.py` to create optimized titles (multiple options)
- Generate platform-specific descriptions (short and full)
- Optimize Apple's 100-character keyword field
- Validate all character limits
- Calculate keyword density
- Provide before/after comparison
### Example 3: Calculate ASO Health Score
```
Hey Claude—I just added the "app-store-optimization" skill. Calculate my app's ASO score:
- Average rating: 4.3 stars (8,200 ratings)
- Keywords in top 10: 4
- Keywords in top 50: 15
- Conversion rate: 3.8%
- Title: "FitFlow - Home Workouts"
- Description: 1,500 characters with 3 keyword mentions
```
**What Claude will do**:
- Use `aso_scorer.py` to calculate overall score (0-100)
- Break down by category (Metadata: X/25, Ratings: X/25, Keywords: X/25, Conversion: X/25)
- Identify strengths and weaknesses
- Generate prioritized recommendations
- Estimate impact of improvements
### Example 4: A/B Test Planning
```
Hey Claude—I just added the "app-store-optimization" skill. I want to A/B test my app icon. My current conversion rate is 4.2%. How many visitors do I need and how long should I run the test?
```
**What Claude will do**:
- Use `ab_test_planner.py` to design test
- Calculate required sample size (based on minimum detectable effect)
- Estimate test duration for low/medium/high traffic scenarios
- Provide test structure and success metrics
- Explain how to analyze results
### Example 5: Review Sentiment Analysis
```
Hey Claude—I just added the "app-store-optimization" skill. Analyze my last 500 reviews and tell me:
- Overall sentiment
- Most common complaints
- Top feature requests
- Bugs needing immediate fixes
```
**What Claude will do**:
- Use `review_analyzer.py` to process reviews
- Calculate sentiment distribution
- Extract common themes
- Identify and prioritize issues
- Cluster feature requests
- Generate response templates
### Example 6: Pre-Launch Checklist
```
Hey Claude—I just added the "app-store-optimization" skill. Generate a complete pre-launch checklist for both app stores. My launch date is March 15, 2026.
```
**What Claude will do**:
- Use `launch_checklist.py` to generate checklists
- Create Apple App Store checklist (metadata, assets, technical, legal)
- Create Google Play Store checklist (metadata, assets, technical, legal)
- Add universal checklist (marketing, QA, support)
- Generate timeline with milestones
- Calculate completion percentage
## Best Practices
### Keyword Research
1. Start with 20-30 seed keywords
2. Analyze top 5 competitors in your category
3. Balance high-volume and long-tail keywords
4. Prioritize relevance over search volume
5. Update keyword research quarterly
### Metadata Optimization
1. Front-load keywords in title (first 15 characters most important)
2. Use every available character (don't waste space)
3. Write for humans first, search engines second
4. A/B test major changes before committing
5. Update descriptions with each major release
### A/B Testing
1. Test one element at a time (icon vs. screenshots vs. title)
2. Run tests to statistical significance (90%+ confidence)
3. Test high-impact elements first (icon has biggest impact)
4. Allow sufficient duration (at least 1 week, preferably 2-3)
5. Document learnings for future tests
### Localization
1. Start with top 5 revenue markets (US, China, Japan, Germany, UK)
2. Use professional translators, not machine translation
3. Test translations with native speakers
4. Adapt keywords for cultural context
5. Monitor ROI by market
### Review Management
1. Respond to reviews within 24-48 hours
2. Always be professional, even with negative reviews
3. Address specific issues raised
4. Thank users for positive feedback
5. Use insights to prioritize product improvements
## Technical Requirements
- **Python**: 3.7+ (for Python modules)
- **Platform Support**: Apple App Store, Google Play Store
- **Data Formats**: JSON input/output
- **Dependencies**: Standard library only (no external packages required)
## Limitations
### Data Dependencies
- Keyword search volumes are estimates (no official Apple/Google data)
- Competitor data limited to publicly available information
- Review analysis requires access to public reviews
- Historical data may not be available for new apps
### Platform Constraints
- Apple: Metadata changes require app submission (except Promotional Text)
- Google: Metadata changes take 1-2 hours to index
- A/B testing requires significant traffic for statistical significance
- Store algorithms are proprietary and change without notice
### Scope
- Does not include paid user acquisition (Apple Search Ads, Google Ads)
- Does not cover in-app analytics implementation
- Does not handle technical app development
- Focuses on organic discovery and conversion optimization
## Troubleshooting
### Issue: Python modules not found
**Solution**: Ensure all .py files are in the same directory as SKILL.md
### Issue: Character limit validation failing
**Solution**: Check that you're using the correct platform ('apple' or 'google')
### Issue: Keyword research returning limited results
**Solution**: Provide more context about your app, features, and target audience
### Issue: ASO score seems inaccurate
**Solution**: Ensure you're providing accurate metrics (ratings, keyword rankings, conversion rate)
## Version History
### Version 1.0.0 (November 7, 2025)
- Initial release
- 8 Python modules with comprehensive ASO capabilities
- Support for both Apple App Store and Google Play Store
- Keyword research, metadata optimization, competitor analysis
- ASO scoring, A/B testing, localization, review analysis
- Launch planning and seasonal campaign tools
## Support & Feedback
This skill is designed to help app developers and marketers succeed in competitive app marketplaces. For the best results:
1. Provide detailed context about your app
2. Include specific metrics when available
3. Ask follow-up questions for clarification
4. Iterate based on results
## Credits
Developed by Claude Skills Factory
Based on industry-standard ASO best practices
Platform requirements current as of November 2025
## License
This skill is provided as-is for use with Claude Code and Claude Apps. Customize and extend as needed for your specific use cases.
---
**Ready to optimize your app?** Start with keyword research, then move to metadata optimization, and finally implement A/B testing for continuous improvement. The skill handles everything from pre-launch planning to ongoing optimization.
For detailed usage examples, see [HOW_TO_USE.md](HOW_TO_USE.md).
FILE:references/aso-best-practices.md
# ASO Best Practices Reference
Optimization strategies for improving app store visibility, conversion, and rankings.
---
## Table of Contents
- [Keyword Optimization](#keyword-optimization)
- [Metadata Optimization](#metadata-optimization)
- [Visual Asset Optimization](#visual-asset-optimization)
- [Rating and Review Management](#rating-and-review-management)
- [Launch Strategy](#launch-strategy)
- [A/B Testing Framework](#ab-testing-framework)
- [Conversion Optimization](#conversion-optimization)
- [Common Mistakes to Avoid](#common-mistakes-to-avoid)
---
## Keyword Optimization
### Keyword Research Process
1. **Brainstorm seed keywords** - Core terms users search for
2. **Expand with variations** - Synonyms, related terms, long-tail
3. **Analyze competition** - Check difficulty scores
4. **Evaluate search volume** - Prioritize high-volume terms
5. **Test and iterate** - Monitor rankings and adjust
### Keyword Selection Criteria
| Factor | Weight | Evaluation Method |
|--------|--------|-------------------|
| Relevance | 40% | Does it describe app function? |
| Search Volume | 30% | Monthly search estimates |
| Competition | 20% | Number of ranking apps |
| Conversion Potential | 10% | User intent alignment |
### Keyword Placement Priority
| Location | Search Weight | Example |
|----------|---------------|---------|
| App Title | Highest | "TaskMaster - Todo List Manager" |
| Subtitle (iOS) | High | "Organize Your Daily Tasks" |
| Keyword Field (iOS) | High | "planner,reminder,checklist" |
| Short Description (Android) | High | "Simple task manager for busy professionals" |
| Full Description | Medium | Natural keyword usage throughout |
### Long-Tail Keyword Strategy
Long-tail keywords have lower volume but higher conversion:
| Type | Example | Volume | Competition | Conversion |
|------|---------|--------|-------------|------------|
| Short-tail | "todo app" | High | High | Low |
| Mid-tail | "daily task manager" | Medium | Medium | Medium |
| Long-tail | "free todo list with reminders" | Low | Low | High |
**Formula for keyword priority:**
```
Score = (Volume × 0.3) + (1/Competition × 0.3) + (Relevance × 0.4)
```
---
## Metadata Optimization
### Title Optimization
**Structure Formula:**
```
[Brand Name] - [Primary Keyword] [Secondary Keyword/Benefit]
```
**Examples by category:**
| Category | Before | After |
|----------|--------|-------|
| Productivity | "MyTasks" | "MyTasks - Todo List & Planner" |
| Fitness | "FitTrack" | "FitTrack: Workout & Gym Log" |
| Finance | "MoneyApp" | "MoneyApp - Budget Tracker" |
| Photo | "SnapEdit" | "SnapEdit: Photo Editor & AI" |
**Title Optimization Checklist:**
- [ ] Primary keyword within first 3 words
- [ ] Brand name is memorable and unique
- [ ] Character count matches platform limit
- [ ] No keyword stuffing
- [ ] Readable and natural sounding
### Description Optimization
**Full Description Structure:**
```
PARAGRAPH 1: Hook + Primary Benefit (50-100 words)
- Address user pain point
- State main value proposition
- Include primary keyword naturally
PARAGRAPH 2-3: Feature Highlights (100-150 words)
- Top 3-5 features with benefits
- Use bullet points or emojis for scanability
- Include secondary keywords
PARAGRAPH 4: Social Proof (50-75 words)
- Download numbers or ratings
- Press mentions or awards
- User testimonials (summarized)
PARAGRAPH 5: Call to Action (25-50 words)
- Clear next step
- Urgency or incentive
- Reassurance (free trial, no credit card)
```
**Keyword Density Target:**
- Primary keyword: 2-3% (8-12 mentions in 4000 chars)
- Secondary keywords: 1-2% each (4-8 mentions each)
### Subtitle Optimization (iOS)
**Effective Subtitle Formulas:**
| Formula | Example |
|---------|---------|
| [Verb] + [Benefit] | "Organize Your Life" |
| [Adjective] + [Category] | "Smart Task Manager" |
| [Feature] + [Feature] | "Lists, Reminders & Notes" |
| [Audience] + [Solution] | "For Busy Professionals" |
---
## Visual Asset Optimization
### App Icon Best Practices
| Principle | Do | Don't |
|-----------|-----|-------|
| Simplicity | Single focal element | Multiple competing elements |
| Recognizability | Works at 60x60px | Requires large size to read |
| Uniqueness | Distinct from competitors | Generic category icon |
| Color | Bold, contrasting colors | Muted or similar to background |
| Text | None or single letter | Full words or app name |
**Icon Testing Questions:**
1. Is it recognizable at 29x29px (smallest iOS size)?
2. Does it stand out in search results?
3. Does it communicate app function?
4. Is it distinct from top 10 category competitors?
### Screenshot Optimization
**Screenshot Hierarchy:**
| Position | Purpose | Content Strategy |
|----------|---------|------------------|
| Screenshot 1 | Hook/Hero | Main value proposition + key UI |
| Screenshot 2 | Primary Feature | Most-used feature demonstration |
| Screenshot 3 | Secondary Feature | Differentiating capability |
| Screenshot 4 | Social Proof | Ratings, awards, user count |
| Screenshot 5+ | Additional Features | Supporting functionality |
**Caption Best Practices:**
- Maximum 5-7 words per caption
- Action-oriented verbs ("Track", "Organize", "Discover")
- Benefit-focused, not feature-focused
- Consistent typography and style
**Example Caption Evolution:**
| Weak | Better | Best |
|------|--------|------|
| "Task List Feature" | "Create Task Lists" | "Never Forget a Task Again" |
| "Calendar View" | "See Your Schedule" | "Plan Your Week in Seconds" |
| "Notifications" | "Get Reminders" | "Stay on Top of Deadlines" |
### Video Preview Strategy
**Video Structure (30 seconds):**
| Seconds | Content |
|---------|---------|
| 0-5 | Hook: Show end result or main benefit |
| 5-15 | Demo: Core feature in action |
| 15-25 | Features: Quick feature montage |
| 25-30 | CTA: Logo and download prompt |
---
## Rating and Review Management
### Review Response Framework
**For Negative Reviews (1-2 stars):**
```
Structure:
1. Acknowledge the issue (1 sentence)
2. Apologize without making excuses (1 sentence)
3. Offer solution or next step (1-2 sentences)
4. Invite direct contact (1 sentence)
Example:
"We're sorry the syncing issues are affecting your experience.
Our team is actively working on a fix for the next update.
In the meantime, please try logging out and back in, which
resolves this for most users. If issues persist, email us at
support@app.com and we'll prioritize your case."
```
**For Positive Reviews (4-5 stars):**
```
Structure:
1. Thank sincerely (1 sentence)
2. Acknowledge specific praise (1 sentence)
3. Encourage continued use or sharing (1 sentence)
Example:
"Thank you for the kind words! We're thrilled the reminder
feature helps you stay organized. If you're enjoying the app,
we'd love if you'd share it with friends who might benefit."
```
### Rating Improvement Tactics
| Tactic | Implementation | Expected Impact |
|--------|----------------|-----------------|
| In-app prompt timing | After positive action (task completed, milestone reached) | +0.3 stars |
| Bug fix velocity | Address 1-star issues within 7 days | +0.2 stars |
| Response rate | Reply to 80%+ of reviews | +0.1 stars |
| Feature requests | Implement top-requested features | +0.2 stars |
### Review Prompt Best Practices
**When to prompt:**
- After user completes 5+ successful sessions
- After milestone achievement (first task completed, 7-day streak)
- After positive in-app feedback ("Was this helpful? Yes")
**When NOT to prompt:**
- First session
- After error or crash
- During critical workflow
- More than once per 30 days
---
## Launch Strategy
### Pre-Launch Checklist
**4 Weeks Before Launch:**
- [ ] Finalize app name and keywords
- [ ] Complete all metadata fields
- [ ] Prepare all visual assets
- [ ] Set up analytics (Firebase, Mixpanel)
- [ ] Create press kit and media assets
- [ ] Build email list for launch notification
**2 Weeks Before Launch:**
- [ ] Submit for app review
- [ ] Prepare social media content
- [ ] Brief press and influencers
- [ ] Set up review response templates
- [ ] Configure in-app rating prompts
**Launch Day:**
- [ ] Verify app is live in stores
- [ ] Announce across all channels
- [ ] Monitor reviews and respond quickly
- [ ] Track download velocity
- [ ] Document any issues for Day 2 fix
### Update Cadence
| Update Type | Frequency | ASO Impact |
|-------------|-----------|------------|
| Bug fixes | As needed | Prevents rating drops |
| Minor features | Every 2-4 weeks | Maintains freshness signal |
| Major features | Every 4-8 weeks | Opportunity for "What's New" |
| Metadata refresh | Every 4-6 weeks | Keyword optimization cycle |
### Seasonal Optimization
| Season | Optimization Focus | Example Categories |
|--------|--------------------|--------------------|
| Jan (New Year) | Resolutions, goals | Fitness, Productivity |
| Feb (Valentine's) | Dating, relationships | Dating, Photo |
| Mar-Apr (Tax) | Finance, organization | Finance, Productivity |
| May-Jun (Summer) | Travel, fitness | Travel, Health |
| Aug-Sep (Back to School) | Education, organization | Education, Productivity |
| Nov-Dec (Holidays) | Shopping, social | Shopping, Social |
---
## A/B Testing Framework
### Test Prioritization Matrix
| Element | Impact | Ease | Priority |
|---------|--------|------|----------|
| App Icon | High | Medium | 1 |
| Screenshot 1 | High | Medium | 2 |
| Title | High | Easy | 3 |
| Short Description | Medium | Easy | 4 |
| Screenshots 2-5 | Medium | Medium | 5 |
| Video | Medium | Hard | 6 |
### Sample Size Calculator
**Formula:**
```
Sample Size = (2 × (Z² × p × (1-p))) / E²
Where:
Z = 1.96 (for 95% confidence)
p = baseline conversion rate
E = minimum detectable effect (usually 0.05)
```
**Quick Reference:**
| Baseline CVR | Min. Impressions for 5% Lift |
|--------------|------------------------------|
| 1% | 31,000 per variant |
| 2% | 15,500 per variant |
| 5% | 6,200 per variant |
| 10% | 3,100 per variant |
### Test Duration Guidelines
| Daily Impressions | Minimum Test Duration |
|-------------------|----------------------|
| 1,000+ | 7 days |
| 500-1,000 | 14 days |
| 100-500 | 30 days |
| <100 | Not recommended |
---
## Conversion Optimization
### Conversion Funnel Metrics
| Stage | Metric | Benchmark |
|-------|--------|-----------|
| Discovery | Impressions | Category dependent |
| Consideration | Page Views | 30-50% of impressions |
| Conversion | Installs | 3-8% of page views |
| Activation | First Open | 70-90% of installs |
### Conversion Optimization Levers
| Lever | Typical Lift | Effort |
|-------|--------------|--------|
| Icon redesign | 10-25% | High |
| Screenshot optimization | 15-35% | Medium |
| Title keyword optimization | 5-15% | Low |
| Description rewrite | 5-10% | Low |
| Video addition | 10-20% | High |
| Localization | 20-50% per market | Medium |
---
## Common Mistakes to Avoid
### Keyword Mistakes
| Mistake | Problem | Solution |
|---------|---------|----------|
| Keyword stuffing | Spam detection, rejection | Natural usage, 2-3% density |
| Competitor names | Guideline violation | Focus on category terms |
| Duplicate keywords | Wasted character space | Remove duplicates from keyword field |
| Ignoring long-tail | Missing conversion | Include specific phrases |
### Metadata Mistakes
| Mistake | Problem | Solution |
|---------|---------|----------|
| Vague descriptions | Low conversion | Specific benefits and features |
| Feature-focused copy | Doesn't resonate | Benefit-focused messaging |
| Outdated information | Misleading users | Update with each release |
| Missing localization | Lost global revenue | Prioritize top 5 markets |
### Visual Asset Mistakes
| Mistake | Problem | Solution |
|---------|---------|----------|
| Text-heavy screenshots | Unreadable on phones | Minimal text, clear UI focus |
| Inconsistent style | Unprofessional appearance | Design system for all assets |
| Portrait-only screenshots | Missed tablet users | Include landscape variants |
| No social proof | Lower trust | Add ratings, awards, press |
### Launch Mistakes
| Mistake | Problem | Solution |
|---------|---------|----------|
| Launching on Friday | No support over weekend | Launch Tuesday-Wednesday |
| No analytics setup | Can't measure success | Firebase/Mixpanel before launch |
| Immediate rating prompt | Negative ratings | Wait for positive experience |
| Ignoring reviews | Declining ratings | Respond within 24-48 hours |
FILE:references/keyword-research-guide.md
# Keyword Research Guide
Systematic approach to discovering, evaluating, and selecting keywords for app store optimization.
---
## Table of Contents
- [Keyword Research Methodology](#keyword-research-methodology)
- [Keyword Evaluation Framework](#keyword-evaluation-framework)
- [Competitor Keyword Analysis](#competitor-keyword-analysis)
- [Keyword Mapping Strategy](#keyword-mapping-strategy)
- [Keyword Tracking and Iteration](#keyword-tracking-and-iteration)
---
## Keyword Research Methodology
### Phase 1: Seed Keyword Generation
Start by generating initial keyword ideas from multiple sources.
**Source 1: Core App Functions**
List every action or problem the app solves:
```
Example for a task management app:
- Create tasks
- Set reminders
- Track deadlines
- Organize projects
- Collaborate with team
- Plan daily schedule
```
**Source 2: User Language Mapping**
Match developer terminology to user searches:
| Developer Term | User Search Terms |
|----------------|-------------------|
| Task management | todo list, task app, tasks |
| Project organization | project planner, project tracker |
| Deadline tracking | due date reminder, deadline app |
| Time blocking | schedule planner, calendar app |
| GTD methodology | getting things done, productivity system |
**Source 3: App Store Autocomplete**
Type seed keywords into App Store/Play Store search and record suggestions:
```
"todo" → todo list, todo app, todo list app, todolist widget
"task" → task manager, task planner, task list, tasks to do
"remind" → reminder app, reminder, reminders widget, remind me
```
**Source 4: Competitor Analysis**
Extract keywords from top 10 competitors in category (detailed in section below).
### Phase 2: Keyword Expansion
**Expansion Techniques:**
| Technique | Example (seed: "todo") |
|-----------|------------------------|
| Add modifiers | free todo, best todo, simple todo |
| Add actions | make todo list, create todo, organize todo |
| Add platforms | todo app iphone, todo for mac, todo widget |
| Add audiences | todo for students, business todo, family todo |
| Add features | todo with reminders, todo calendar, todo sync |
| Add problems | forgot tasks todo, procrastination todo |
**Keyword Matrix Template:**
| Core Term | Modifier 1 | Modifier 2 | Full Keyword |
|-----------|------------|------------|--------------|
| todo | free | app | free todo app |
| todo | best | iphone | best todo iphone |
| task | manager | simple | simple task manager |
| reminder | daily | widget | daily reminder widget |
| planner | weekly | calendar | weekly planner calendar |
### Phase 3: Keyword Filtering
Remove irrelevant or low-quality keywords:
**Exclusion Criteria:**
| Criterion | Reason | Example |
|-----------|--------|---------|
| Competitor brand names | Policy violation | "todoist alternative" |
| Unrelated categories | Low conversion | "todo games" |
| Plural duplicates (iOS) | Wasted space | "tasks" when "task" exists |
| Single characters | No search value | "to do" vs "todo" |
---
## Keyword Evaluation Framework
### Keyword Scoring Model
Evaluate each keyword on four dimensions:
**1. Search Volume (0-100)**
| Volume Level | Score | Monthly Searches |
|--------------|-------|------------------|
| Very High | 80-100 | 50,000+ |
| High | 60-79 | 10,000-49,999 |
| Medium | 40-59 | 1,000-9,999 |
| Low | 20-39 | 100-999 |
| Very Low | 0-19 | <100 |
**2. Competition (0-100, inverted)**
| Competition | Score | Top 10 App Ratings |
|-------------|-------|-------------------|
| Very Low | 80-100 | Average <4.0 stars |
| Low | 60-79 | Average 4.0-4.2 stars |
| Medium | 40-59 | Average 4.3-4.5 stars |
| High | 20-39 | Average 4.6-4.8 stars |
| Very High | 0-19 | Average 4.9+ stars |
**3. Relevance (0-100)**
| Relevance | Score | Criteria |
|-----------|-------|----------|
| Exact Match | 90-100 | Keyword describes core function |
| Strong Match | 70-89 | Keyword describes major feature |
| Moderate Match | 50-69 | Keyword describes secondary feature |
| Weak Match | 30-49 | Keyword tangentially related |
| No Match | 0-29 | Keyword unrelated to app |
**4. Conversion Potential (0-100)**
| Intent | Score | User Query Type |
|--------|-------|-----------------|
| Transactional | 80-100 | "best [app type]", "[app type] app" |
| Commercial | 60-79 | "free [app type]", "[app type] for [use]" |
| Informational | 40-59 | "how to [action]", "what is [concept]" |
| Navigational | 20-39 | "[brand name]", "[specific app]" |
### Composite Score Calculation
```
Keyword Score = (Volume × 0.25) + (Competition × 0.25) +
(Relevance × 0.35) + (Conversion × 0.15)
```
**Score Interpretation:**
| Score Range | Priority | Action |
|-------------|----------|--------|
| 80-100 | Primary | Target in title and keyword field |
| 60-79 | Secondary | Include in subtitle/description |
| 40-59 | Tertiary | Use in long description only |
| 0-39 | Deprioritize | Do not target |
### Keyword Evaluation Worksheet
```
KEYWORD EVALUATION
Keyword: "task manager app"
Date: [Date]
SCORES:
├── Search Volume: 72/100 (High - ~25,000/month)
├── Competition: 45/100 (Medium - 4.4 avg rating in top 10)
├── Relevance: 95/100 (Exact match to core function)
└── Conversion: 85/100 (Transactional intent)
COMPOSITE SCORE: 74.5/100
RECOMMENDATION: Secondary Priority
- Include in subtitle or short description
- Not competitive enough for title (dominated by Todoist, Any.do)
- Consider long-tail variant: "simple task manager app"
```
---
## Competitor Keyword Analysis
### Competitor Identification
**Step 1: Direct Competitors**
Apps solving the same problem for the same audience.
**Step 2: Indirect Competitors**
Apps solving related problems or targeting overlapping audiences.
**Step 3: Category Leaders**
Top 10-20 apps by downloads in primary category.
### Competitor Keyword Extraction
**From App Title:**
```
Competitor: "Todoist: To-Do List & Tasks"
Keywords: todoist, to-do list, tasks, to do
```
**From Subtitle (iOS):**
```
Competitor subtitle: "Task Manager & Planner"
Keywords: task manager, planner
```
**From Description (First 100 words):**
Identify frequently used terms:
```
"Todoist is the world's favorite task manager and to-do list app.
Organize work and life, hit your goals, and find productivity..."
Extracted: task manager, to-do list, organize, goals, productivity
```
### Competitor Keyword Matrix
| Keyword | Comp 1 | Comp 2 | Comp 3 | Comp 4 | Comp 5 | Coverage |
|---------|--------|--------|--------|--------|--------|----------|
| task manager | ✓ | ✓ | ✓ | ✓ | ✓ | 100% |
| to-do list | ✓ | ✓ | ✓ | ✓ | | 80% |
| planner | ✓ | ✓ | | ✓ | ✓ | 80% |
| reminder | ✓ | ✓ | ✓ | | | 60% |
| productivity | ✓ | | ✓ | ✓ | | 60% |
| checklist | | ✓ | | ✓ | ✓ | 60% |
| project | ✓ | ✓ | | | | 40% |
| habit | | | ✓ | | ✓ | 40% |
**Analysis:**
- 100% coverage = Highly competitive, essential keyword
- 60-80% coverage = Important category term
- 40% coverage = Potential differentiator
- <40% coverage = Unique opportunity or irrelevant
### Keyword Gap Analysis
Identify keywords competitors miss:
```
KEYWORD GAP ANALYSIS
Underserved Keywords (Low competitor coverage, decent volume):
1. "daily planner widget" - 2/10 competitors, 5,000 searches
2. "task list for teams" - 3/10 competitors, 3,500 searches
3. "todo with calendar sync" - 1/10 competitors, 2,800 searches
Opportunity Assessment:
- "daily planner widget" → Add widget feature, target keyword
- "task list for teams" → Already have feature, update metadata
- "todo with calendar sync" → Feature gap, add to roadmap
```
---
## Keyword Mapping Strategy
### Keyword Placement Map
Assign each keyword to specific metadata locations:
```
KEYWORD PLACEMENT MAP
PRIMARY (Title + Keyword Field):
├── task manager (Score: 82)
├── todo list (Score: 78)
└── planner (Score: 75)
SECONDARY (Subtitle + Short Description):
├── reminder app (Score: 68)
├── daily tasks (Score: 65)
└── organize (Score: 62)
TERTIARY (Full Description):
├── checklist (Score: 55)
├── productivity (Score: 52)
├── schedule (Score: 48)
├── deadline (Score: 45)
└── project management (Score: 42)
```
### iOS Keyword Field Strategy
**100 Character Optimization:**
```
STEP 1: List all target keywords
task,manager,todo,list,planner,reminder,organize,daily,checklist,
productivity,schedule,deadline,project,goals,habit,widget,sync,
team,collaborate,notes,calendar
STEP 2: Remove duplicates from title
Title: "TaskFlow - Todo List Manager"
Remove: task, todo, list, manager
STEP 3: Remove plurals
Keep: reminder (not reminders)
Keep: goal (not goals)
STEP 4: Prioritize by score and fit
Final 100 chars:
planner,reminder,organize,daily,checklist,productivity,schedule,
deadline,project,goals,habit,widget,sync,team,collaborate
Character count: 98/100
```
### Android Description Keyword Integration
**Natural keyword placement in 4,000 characters:**
```
PARAGRAPH 1 (Hook - 300 chars):
Keywords: task manager, todo list, organize
"TaskFlow is the task manager trusted by 2 million users. Create
your perfect todo list and organize everything that matters..."
PARAGRAPH 2 (Features - 800 chars):
Keywords: reminder, checklist, deadline, project
"Set smart reminders that notify you at the right time. Build
checklists for any project. Never miss a deadline with..."
PARAGRAPH 3 (Benefits - 600 chars):
Keywords: productivity, schedule, goals
"Boost your productivity with proven planning methods. Schedule
your day in minutes. Track goals and celebrate..."
PARAGRAPH 4 (Differentiators - 500 chars):
Keywords: widget, sync, team, collaborate
"Beautiful widgets keep tasks visible. Sync across all devices
instantly. Invite your team to collaborate on..."
Total keyword coverage: 14 keywords naturally integrated
```
---
## Keyword Tracking and Iteration
### Ranking Tracking Cadence
| Frequency | Action |
|-----------|--------|
| Daily | Track top 5-10 primary keywords |
| Weekly | Full keyword set review |
| Monthly | Competitor keyword comparison |
| Quarterly | Full keyword research refresh |
### Keyword Performance Metrics
| Metric | Target | Action if Below |
|--------|--------|-----------------|
| Top 10 ranking | 3+ keywords | Increase keyword weight |
| Top 50 ranking | 10+ keywords | Maintain current strategy |
| Ranking velocity | Improving trend | Continue optimization |
| Conversion rate | >5% | Review relevance alignment |
### Iteration Process
**Monthly Keyword Audit:**
```
1. EXPORT current rankings
- List all tracked keywords
- Record current position
- Note 30-day trend (up/down/stable)
2. IDENTIFY opportunities
- Keywords improving but not top 10
- Keywords declining from previous position
- New high-volume keywords in category
3. PRIORITIZE changes
- Boost: Keywords at position 11-20
- Maintain: Keywords at position 1-10
- Replace: Keywords at position 50+ with no improvement
4. IMPLEMENT updates
- Adjust keyword field (iOS)
- Update description (Android)
- Modify subtitle if needed
5. DOCUMENT changes
- Record what changed and why
- Set reminder for 2-week check-in
```
### Keyword Testing Log Template
```
KEYWORD TEST LOG
Test ID: KW-2025-001
Date Started: [Date]
Keywords Changed:
- Added: "habit tracker" (replacing "goals app")
- Added: "daily routine" (replacing "schedule planner")
Rationale:
- "habit tracker" has 3x volume of "goals app"
- "daily routine" trending up 40% in category
Baseline Rankings:
- "habit tracker": Not ranked
- "daily routine": Position 87
30-Day Results:
- "habit tracker": Position 34 (+53)
- "daily routine": Position 28 (+59)
Conclusion: Test successful - retain new keywords
Next Action: Target subtitle position for "habit tracker"
```
FILE:references/platform-requirements.md
# Platform Requirements Reference
Technical specifications and metadata requirements for Apple App Store and Google Play Store.
---
## Table of Contents
- [Apple App Store Requirements](#apple-app-store-requirements)
- [Google Play Store Requirements](#google-play-store-requirements)
- [Visual Asset Specifications](#visual-asset-specifications)
- [Localization Requirements](#localization-requirements)
- [Compliance Guidelines](#compliance-guidelines)
---
## Apple App Store Requirements
### Metadata Character Limits
| Field | Character Limit | Notes |
|-------|----------------|-------|
| App Name (Title) | 30 characters | Visible in search results |
| Subtitle | 30 characters | iOS 11+ only, appears below title |
| Promotional Text | 170 characters | Editable without app update |
| Description | 4,000 characters | Not indexed for search |
| Keywords Field | 100 characters | Comma-separated, no spaces after commas |
| What's New | 4,000 characters | Release notes for updates |
| Developer Name | 255 characters | Company or individual name |
| Support URL | Required | Must be valid HTTPS URL |
| Privacy Policy URL | Required | Must be valid HTTPS URL |
### Keyword Field Optimization Rules
1. **No duplicates** - Words in title are already indexed
2. **No plurals** - Apple indexes both singular and plural forms
3. **No spaces after commas** - Wastes character space
4. **No brand names** - Violates App Store guidelines
5. **No category names** - Already indexed via category selection
**Example - Efficient keyword field:**
```
task,todo,checklist,reminder,productivity,organize,schedule,planner,goals,habit
```
**Example - Inefficient keyword field (avoid):**
```
task manager, todo list, productivity app, task tracking
```
### App Store Connect Metadata Fields
| Category | Field | Required |
|----------|-------|----------|
| **App Information** | Name | Yes |
| | Subtitle | No |
| | Category | Yes |
| | Secondary Category | No |
| | Content Rights | Yes |
| | Age Rating | Yes |
| **Version Information** | Description | Yes |
| | Keywords | Yes |
| | Promotional Text | No |
| | What's New | Yes (for updates) |
| | Support URL | Yes |
| | Marketing URL | No |
| **Pricing** | Price Tier | Yes |
| | Availability | Yes |
### Age Rating Content Descriptors
| Content Type | None | Infrequent | Frequent |
|--------------|------|------------|----------|
| Cartoon Violence | 4+ | 9+ | 12+ |
| Realistic Violence | 9+ | 12+ | 17+ |
| Sexual Content | 12+ | 17+ | 17+ |
| Profanity | 4+ | 12+ | 17+ |
| Alcohol/Drug Reference | 12+ | 17+ | 17+ |
| Gambling | 12+ | 17+ | 17+ |
| Horror/Fear | 9+ | 12+ | 17+ |
---
## Google Play Store Requirements
### Metadata Character Limits
| Field | Character Limit | Notes |
|-------|----------------|-------|
| App Title | 50 characters | Increased from 30 in 2021 |
| Short Description | 80 characters | Visible on store listing |
| Full Description | 4,000 characters | Indexed for search keywords |
| Developer Name | 64 characters | Organization or individual |
| Developer Email | Required | Public support contact |
| Privacy Policy URL | Required | Must be valid HTTPS URL |
### Description Keyword Strategy
Google Play has no separate keyword field. Keywords are extracted from:
1. **App Title** - Highest weight, most important
2. **Short Description** - High weight, visible in search
3. **Full Description** - Medium weight, use naturally throughout
4. **Developer Name** - Low weight but indexed
**Keyword Density Guidelines:**
- Primary keyword: 2-3% density in full description
- Secondary keywords: 1-2% each
- Avoid keyword stuffing (>5% triggers spam detection)
### Google Play Console Metadata
| Category | Field | Required |
|----------|-------|----------|
| **Store Listing** | Title | Yes |
| | Short Description | Yes |
| | Full Description | Yes |
| | App Icon | Yes |
| | Feature Graphic | Yes |
| | Screenshots | Yes (min 2) |
| | Video | No |
| **Store Settings** | App Category | Yes |
| | Tags | No |
| | Contact Email | Yes |
| | Privacy Policy | Yes |
| **Content Rating** | IARC Questionnaire | Yes |
### Content Rating (IARC)
| Rating | Age | Description |
|--------|-----|-------------|
| PEGI 3 / Everyone | 3+ | Suitable for all ages |
| PEGI 7 / Everyone 10+ | 7+ | Mild violence, comic mischief |
| PEGI 12 / Teen | 12+ | Moderate violence, mild language |
| PEGI 16 / Mature 17+ | 16+ | Intense violence, strong language |
| PEGI 18 / Adults Only | 18+ | Extreme content |
---
## Visual Asset Specifications
### App Icon Requirements
**Apple App Store:**
| Device | Size | Format |
|--------|------|--------|
| iPhone | 1024x1024 px | PNG, no alpha |
| iPad | 1024x1024 px | PNG, no alpha |
| App Store | 1024x1024 px | PNG, no alpha |
| Spotlight | 120x120 px | PNG |
| Settings | 87x87 px | PNG |
**Google Play Store:**
| Asset | Size | Format |
|-------|------|--------|
| App Icon | 512x512 px | PNG, 32-bit |
| Feature Graphic | 1024x500 px | PNG or JPG |
| Promo Graphic | 180x120 px | PNG or JPG |
| TV Banner | 1280x720 px | PNG or JPG |
### Screenshot Requirements
**Apple App Store:**
| Device | Portrait | Landscape |
|--------|----------|-----------|
| iPhone 6.9" | 1320x2868 px | 2868x1320 px |
| iPhone 6.5" | 1290x2796 px | 2796x1290 px |
| iPhone 5.5" | 1242x2208 px | 2208x1242 px |
| iPad Pro 12.9" | 2048x2732 px | 2732x2048 px |
| iPad 10.5" | 1668x2224 px | 2224x1668 px |
- Minimum: 2 screenshots per device
- Maximum: 10 screenshots per device
- Format: PNG or JPG, no alpha channel
- First 3 screenshots are critical (most users don't scroll)
**Google Play Store:**
| Device | Dimensions | Notes |
|--------|------------|-------|
| Phone | 320-3840 px | Min 2:1 aspect ratio |
| 7" Tablet | 320-3840 px | Min 2:1 aspect ratio |
| 10" Tablet | 320-3840 px | Min 2:1 aspect ratio |
| Chromebook | 320-3840 px | Optional |
| TV | 320-3840 px | For TV apps only |
- Minimum: 2 screenshots
- Maximum: 8 screenshots
- Format: PNG or JPG
- No transparency or borders
### App Preview Video
**Apple App Store:**
- Duration: 15-30 seconds
- Resolution: Match device screenshot size
- Format: M4V, MP4, MOV
- Frame rate: 30 fps
- Audio: Optional but recommended
**Google Play Store:**
- YouTube video link only
- No duration limit (recommend under 2 minutes)
- Landscape orientation preferred
- Must not contain age-restricted content
---
## Localization Requirements
### Priority Markets by Revenue
| Rank | Market | Language Code |
|------|--------|---------------|
| 1 | United States | en-US |
| 2 | Japan | ja |
| 3 | United Kingdom | en-GB |
| 4 | Germany | de-DE |
| 5 | China | zh-Hans (iOS), zh-CN (Android) |
| 6 | South Korea | ko |
| 7 | France | fr-FR |
| 8 | Canada | en-CA, fr-CA |
| 9 | Australia | en-AU |
| 10 | Russia | ru |
### Apple App Store Localization
Supported localizations: 40+ languages
| Language | Locale Code |
|----------|-------------|
| English (US) | en-US |
| English (UK) | en-GB |
| Spanish | es-ES |
| Spanish (Mexico) | es-MX |
| French | fr-FR |
| German | de-DE |
| Japanese | ja |
| Korean | ko |
| Simplified Chinese | zh-Hans |
| Traditional Chinese | zh-Hant |
### Google Play Store Localization
Supported localizations: 75+ languages
Each locale requires:
- Title (50 chars)
- Short description (80 chars)
- Full description (4,000 chars)
- Screenshots (can reuse or localize)
---
## Compliance Guidelines
### Apple App Store Review Guidelines Summary
| Category | Key Requirements |
|----------|------------------|
| **Safety** | No objectionable content, privacy protection |
| **Performance** | App must work as described, no crashes |
| **Business** | Accurate app description, clear pricing |
| **Design** | Follow Human Interface Guidelines |
| **Legal** | Comply with local laws, proper licensing |
**Common Rejection Reasons:**
1. Bugs and crashes (50%+ of rejections)
2. Broken links or placeholder content
3. Misleading app descriptions
4. Privacy policy missing or incomplete
5. In-app purchase issues
### Google Play Developer Policies
| Policy Area | Requirements |
|-------------|--------------|
| **Restricted Content** | No hate speech, violence, gambling (without license) |
| **Privacy** | Data collection disclosure, privacy policy |
| **Monetization** | Clear pricing, compliant IAPs |
| **Ads** | No deceptive ads, proper disclosure |
| **Store Listing** | Accurate description, no keyword stuffing |
**Common Suspension Reasons:**
1. Policy violation (content, ads, permissions)
2. Repetitive content (clone apps)
3. Impersonation (fake apps)
4. Intellectual property infringement
5. Malicious behavior
### Privacy Requirements
**Apple (App Tracking Transparency):**
- ATT prompt required for tracking
- Privacy nutrition labels mandatory
- Data collection disclosure required
**Google (Data Safety):**
- Data safety section mandatory
- Data collection and sharing disclosure
- Security practices declaration
---
## Quick Reference Card
### Apple vs Google Comparison
| Attribute | Apple App Store | Google Play Store |
|-----------|-----------------|-------------------|
| Title Length | 30 chars | 50 chars |
| Subtitle | 30 chars | N/A |
| Short Description | N/A | 80 chars |
| Full Description | 4,000 chars | 4,000 chars |
| Keywords Field | 100 chars | N/A (in description) |
| Promotional Text | 170 chars | N/A |
| Icon Size | 1024x1024 px | 512x512 px |
| Min Screenshots | 2 | 2 |
| Max Screenshots | 10 | 8 |
| Review Time | 24-48 hours | 1-7 days |
| Metadata Update | Requires review | 1-2 hours to index |
FILE:sample_input.json
{
"request_type": "keyword_research",
"app_info": {
"name": "TaskFlow Pro",
"category": "Productivity",
"target_audience": "Professionals aged 25-45 working in teams",
"key_features": [
"AI-powered task prioritization",
"Team collaboration tools",
"Calendar integration",
"Cross-platform sync"
],
"unique_value": "AI automatically prioritizes your tasks based on deadlines and importance"
},
"target_keywords": [
"task manager",
"productivity app",
"todo list",
"team collaboration",
"project management"
],
"competitors": [
"Todoist",
"Any.do",
"Microsoft To Do",
"Things 3"
],
"platform": "both",
"language": "en-US"
}
FILE:scripts/ab_test_planner.py
"""
A/B testing module for App Store Optimization.
Plans and tracks A/B tests for metadata and visual assets.
"""
from typing import Dict, List, Any, Optional
import math
class ABTestPlanner:
"""Plans and tracks A/B tests for ASO elements."""
# Minimum detectable effect sizes (conservative estimates)
MIN_EFFECT_SIZES = {
'icon': 0.10, # 10% conversion improvement
'screenshot': 0.08, # 8% conversion improvement
'title': 0.05, # 5% conversion improvement
'description': 0.03 # 3% conversion improvement
}
# Statistical confidence levels
CONFIDENCE_LEVELS = {
'high': 0.95, # 95% confidence
'standard': 0.90, # 90% confidence
'exploratory': 0.80 # 80% confidence
}
def __init__(self):
"""Initialize A/B test planner."""
self.active_tests = []
def design_test(
self,
test_type: str,
variant_a: Dict[str, Any],
variant_b: Dict[str, Any],
hypothesis: str,
success_metric: str = 'conversion_rate'
) -> Dict[str, Any]:
"""
Design an A/B test with hypothesis and variables.
Args:
test_type: Type of test ('icon', 'screenshot', 'title', 'description')
variant_a: Control variant details
variant_b: Test variant details
hypothesis: Expected outcome hypothesis
success_metric: Metric to optimize
Returns:
Test design with configuration
"""
test_design = {
'test_id': self._generate_test_id(test_type),
'test_type': test_type,
'hypothesis': hypothesis,
'variants': {
'a': {
'name': 'Control',
'details': variant_a,
'traffic_split': 0.5
},
'b': {
'name': 'Variation',
'details': variant_b,
'traffic_split': 0.5
}
},
'success_metric': success_metric,
'secondary_metrics': self._get_secondary_metrics(test_type),
'minimum_effect_size': self.MIN_EFFECT_SIZES.get(test_type, 0.05),
'recommended_confidence': 'standard',
'best_practices': self._get_test_best_practices(test_type)
}
self.active_tests.append(test_design)
return test_design
def calculate_sample_size(
self,
baseline_conversion: float,
minimum_detectable_effect: float,
confidence_level: str = 'standard',
power: float = 0.80
) -> Dict[str, Any]:
"""
Calculate required sample size for statistical significance.
Args:
baseline_conversion: Current conversion rate (0-1)
minimum_detectable_effect: Minimum effect size to detect (0-1)
confidence_level: 'high', 'standard', or 'exploratory'
power: Statistical power (typically 0.80 or 0.90)
Returns:
Sample size calculation with duration estimates
"""
alpha = 1 - self.CONFIDENCE_LEVELS[confidence_level]
beta = 1 - power
# Expected conversion for variant B
expected_conversion_b = baseline_conversion * (1 + minimum_detectable_effect)
# Z-scores for alpha and beta
z_alpha = self._get_z_score(1 - alpha / 2) # Two-tailed test
z_beta = self._get_z_score(power)
# Pooled standard deviation
p_pooled = (baseline_conversion + expected_conversion_b) / 2
sd_pooled = math.sqrt(2 * p_pooled * (1 - p_pooled))
# Sample size per variant
n_per_variant = math.ceil(
((z_alpha + z_beta) ** 2 * sd_pooled ** 2) /
((expected_conversion_b - baseline_conversion) ** 2)
)
total_sample_size = n_per_variant * 2
# Estimate duration based on typical traffic
duration_estimates = self._estimate_test_duration(
total_sample_size,
baseline_conversion
)
return {
'sample_size_per_variant': n_per_variant,
'total_sample_size': total_sample_size,
'baseline_conversion': baseline_conversion,
'expected_conversion_improvement': minimum_detectable_effect,
'expected_conversion_b': expected_conversion_b,
'confidence_level': confidence_level,
'statistical_power': power,
'duration_estimates': duration_estimates,
'recommendations': self._generate_sample_size_recommendations(
n_per_variant,
duration_estimates
)
}
def calculate_significance(
self,
variant_a_conversions: int,
variant_a_visitors: int,
variant_b_conversions: int,
variant_b_visitors: int
) -> Dict[str, Any]:
"""
Calculate statistical significance of test results.
Args:
variant_a_conversions: Conversions for control
variant_a_visitors: Visitors for control
variant_b_conversions: Conversions for variation
variant_b_visitors: Visitors for variation
Returns:
Significance analysis with decision recommendation
"""
# Calculate conversion rates
rate_a = variant_a_conversions / variant_a_visitors if variant_a_visitors > 0 else 0
rate_b = variant_b_conversions / variant_b_visitors if variant_b_visitors > 0 else 0
# Calculate improvement
if rate_a > 0:
relative_improvement = (rate_b - rate_a) / rate_a
else:
relative_improvement = 0
absolute_improvement = rate_b - rate_a
# Calculate standard error
se_a = math.sqrt(rate_a * (1 - rate_a) / variant_a_visitors) if variant_a_visitors > 0 else 0
se_b = math.sqrt(rate_b * (1 - rate_b) / variant_b_visitors) if variant_b_visitors > 0 else 0
se_diff = math.sqrt(se_a**2 + se_b**2)
# Calculate z-score
z_score = absolute_improvement / se_diff if se_diff > 0 else 0
# Calculate p-value (two-tailed)
p_value = 2 * (1 - self._standard_normal_cdf(abs(z_score)))
# Determine significance
is_significant_95 = p_value < 0.05
is_significant_90 = p_value < 0.10
# Generate decision
decision = self._generate_test_decision(
relative_improvement,
is_significant_95,
is_significant_90,
variant_a_visitors + variant_b_visitors
)
return {
'variant_a': {
'conversions': variant_a_conversions,
'visitors': variant_a_visitors,
'conversion_rate': round(rate_a, 4)
},
'variant_b': {
'conversions': variant_b_conversions,
'visitors': variant_b_visitors,
'conversion_rate': round(rate_b, 4)
},
'improvement': {
'absolute': round(absolute_improvement, 4),
'relative_percentage': round(relative_improvement * 100, 2)
},
'statistical_analysis': {
'z_score': round(z_score, 3),
'p_value': round(p_value, 4),
'is_significant_95': is_significant_95,
'is_significant_90': is_significant_90,
'confidence_level': '95%' if is_significant_95 else ('90%' if is_significant_90 else 'Not significant')
},
'decision': decision
}
def track_test_results(
self,
test_id: str,
results_data: Dict[str, Any]
) -> Dict[str, Any]:
"""
Track ongoing test results and provide recommendations.
Args:
test_id: Test identifier
results_data: Current test results
Returns:
Test tracking report with next steps
"""
# Find test
test = next((t for t in self.active_tests if t['test_id'] == test_id), None)
if not test:
return {'error': f'Test {test_id} not found'}
# Calculate significance
significance = self.calculate_significance(
results_data['variant_a_conversions'],
results_data['variant_a_visitors'],
results_data['variant_b_conversions'],
results_data['variant_b_visitors']
)
# Calculate test progress
total_visitors = results_data['variant_a_visitors'] + results_data['variant_b_visitors']
required_sample = results_data.get('required_sample_size', 10000)
progress_percentage = min((total_visitors / required_sample) * 100, 100)
# Generate recommendations
recommendations = self._generate_tracking_recommendations(
significance,
progress_percentage,
test['test_type']
)
return {
'test_id': test_id,
'test_type': test['test_type'],
'progress': {
'total_visitors': total_visitors,
'required_sample_size': required_sample,
'progress_percentage': round(progress_percentage, 1),
'is_complete': progress_percentage >= 100
},
'current_results': significance,
'recommendations': recommendations,
'next_steps': self._determine_next_steps(
significance,
progress_percentage
)
}
def generate_test_report(
self,
test_id: str,
final_results: Dict[str, Any]
) -> Dict[str, Any]:
"""
Generate final test report with insights and recommendations.
Args:
test_id: Test identifier
final_results: Final test results
Returns:
Comprehensive test report
"""
test = next((t for t in self.active_tests if t['test_id'] == test_id), None)
if not test:
return {'error': f'Test {test_id} not found'}
significance = self.calculate_significance(
final_results['variant_a_conversions'],
final_results['variant_a_visitors'],
final_results['variant_b_conversions'],
final_results['variant_b_visitors']
)
# Generate insights
insights = self._generate_test_insights(
test,
significance,
final_results
)
# Implementation plan
implementation_plan = self._create_implementation_plan(
test,
significance
)
return {
'test_summary': {
'test_id': test_id,
'test_type': test['test_type'],
'hypothesis': test['hypothesis'],
'duration_days': final_results.get('duration_days', 'N/A')
},
'results': significance,
'insights': insights,
'implementation_plan': implementation_plan,
'learnings': self._extract_learnings(test, significance)
}
def _generate_test_id(self, test_type: str) -> str:
"""Generate unique test ID."""
import time
timestamp = int(time.time())
return f"{test_type}_{timestamp}"
def _get_secondary_metrics(self, test_type: str) -> List[str]:
"""Get secondary metrics to track for test type."""
metrics_map = {
'icon': ['tap_through_rate', 'impression_count', 'brand_recall'],
'screenshot': ['tap_through_rate', 'time_on_page', 'scroll_depth'],
'title': ['impression_count', 'tap_through_rate', 'search_visibility'],
'description': ['time_on_page', 'scroll_depth', 'tap_through_rate']
}
return metrics_map.get(test_type, ['tap_through_rate'])
def _get_test_best_practices(self, test_type: str) -> List[str]:
"""Get best practices for specific test type."""
practices_map = {
'icon': [
'Test only one element at a time (color vs. style vs. symbolism)',
'Ensure icon is recognizable at small sizes (60x60px)',
'Consider cultural context for global audience',
'Test against top competitor icons'
],
'screenshot': [
'Test order of screenshots (users see first 2-3)',
'Use captions to tell story',
'Show key features and benefits',
'Test with and without device frames'
],
'title': [
'Test keyword variations, not major rebrand',
'Keep brand name consistent',
'Ensure title fits within character limits',
'Test on both search and browse contexts'
],
'description': [
'Test structure (bullet points vs. paragraphs)',
'Test call-to-action placement',
'Test feature vs. benefit focus',
'Maintain keyword density'
]
}
return practices_map.get(test_type, ['Test one variable at a time'])
def _estimate_test_duration(
self,
required_sample_size: int,
baseline_conversion: float
) -> Dict[str, Any]:
"""Estimate test duration based on typical traffic levels."""
# Assume different daily traffic scenarios
traffic_scenarios = {
'low': 100, # 100 page views/day
'medium': 1000, # 1000 page views/day
'high': 10000 # 10000 page views/day
}
estimates = {}
for scenario, daily_views in traffic_scenarios.items():
days = math.ceil(required_sample_size / daily_views)
estimates[scenario] = {
'daily_page_views': daily_views,
'estimated_days': days,
'estimated_weeks': round(days / 7, 1)
}
return estimates
def _generate_sample_size_recommendations(
self,
sample_size: int,
duration_estimates: Dict[str, Any]
) -> List[str]:
"""Generate recommendations based on sample size."""
recommendations = []
if sample_size > 50000:
recommendations.append(
"Large sample size required - consider testing smaller effect size or increasing traffic"
)
if duration_estimates['medium']['estimated_days'] > 30:
recommendations.append(
"Long test duration - consider higher minimum detectable effect or focus on high-impact changes"
)
if duration_estimates['low']['estimated_days'] > 60:
recommendations.append(
"Insufficient traffic for reliable testing - consider user acquisition or broader targeting"
)
if not recommendations:
recommendations.append("Sample size and duration are reasonable for this test")
return recommendations
def _get_z_score(self, percentile: float) -> float:
"""Get z-score for given percentile (approximation)."""
# Common z-scores
z_scores = {
0.80: 0.84,
0.85: 1.04,
0.90: 1.28,
0.95: 1.645,
0.975: 1.96,
0.99: 2.33
}
return z_scores.get(percentile, 1.96)
def _standard_normal_cdf(self, z: float) -> float:
"""Approximate standard normal cumulative distribution function."""
# Using error function approximation
t = 1.0 / (1.0 + 0.2316419 * abs(z))
d = 0.3989423 * math.exp(-z * z / 2.0)
p = d * t * (0.3193815 + t * (-0.3565638 + t * (1.781478 + t * (-1.821256 + t * 1.330274))))
if z > 0:
return 1.0 - p
else:
return p
def _generate_test_decision(
self,
improvement: float,
is_significant_95: bool,
is_significant_90: bool,
total_visitors: int
) -> Dict[str, Any]:
"""Generate test decision and recommendation."""
if total_visitors < 1000:
return {
'decision': 'continue',
'rationale': 'Insufficient data - continue test to reach minimum sample size',
'action': 'Keep test running'
}
if is_significant_95:
if improvement > 0:
return {
'decision': 'implement_b',
'rationale': f'Variant B shows {improvement*100:.1f}% improvement with 95% confidence',
'action': 'Implement Variant B'
}
else:
return {
'decision': 'keep_a',
'rationale': 'Variant A performs better with 95% confidence',
'action': 'Keep current version (A)'
}
elif is_significant_90:
if improvement > 0:
return {
'decision': 'implement_b_cautiously',
'rationale': f'Variant B shows {improvement*100:.1f}% improvement with 90% confidence',
'action': 'Consider implementing B, monitor closely'
}
else:
return {
'decision': 'keep_a',
'rationale': 'Variant A performs better with 90% confidence',
'action': 'Keep current version (A)'
}
else:
return {
'decision': 'inconclusive',
'rationale': 'No statistically significant difference detected',
'action': 'Either keep A or test different hypothesis'
}
def _generate_tracking_recommendations(
self,
significance: Dict[str, Any],
progress: float,
test_type: str
) -> List[str]:
"""Generate recommendations for ongoing test."""
recommendations = []
if progress < 50:
recommendations.append(
f"Test is {progress:.0f}% complete - continue collecting data"
)
if progress >= 100:
if significance['statistical_analysis']['is_significant_95']:
recommendations.append(
"Sufficient data collected with significant results - ready to conclude test"
)
else:
recommendations.append(
"Sample size reached but no significant difference - consider extending test or concluding"
)
return recommendations
def _determine_next_steps(
self,
significance: Dict[str, Any],
progress: float
) -> str:
"""Determine next steps for test."""
if progress < 100:
return f"Continue test until reaching 100% sample size (currently {progress:.0f}%)"
decision = significance.get('decision', {}).get('decision', 'inconclusive')
if decision == 'implement_b':
return "Implement Variant B and monitor metrics for 2 weeks"
elif decision == 'keep_a':
return "Keep Variant A and design new test with different hypothesis"
else:
return "Test inconclusive - either keep A or design new test"
def _generate_test_insights(
self,
test: Dict[str, Any],
significance: Dict[str, Any],
results: Dict[str, Any]
) -> List[str]:
"""Generate insights from test results."""
insights = []
improvement = significance['improvement']['relative_percentage']
if significance['statistical_analysis']['is_significant_95']:
insights.append(
f"Strong evidence: Variant B {'improved' if improvement > 0 else 'decreased'} "
f"conversion by {abs(improvement):.1f}% with 95% confidence"
)
insights.append(
f"Tested {test['test_type']} changes: {test['hypothesis']}"
)
# Add context-specific insights
if test['test_type'] == 'icon' and improvement > 5:
insights.append(
"Icon change had substantial impact - visual first impression is critical"
)
return insights
def _create_implementation_plan(
self,
test: Dict[str, Any],
significance: Dict[str, Any]
) -> List[Dict[str, str]]:
"""Create implementation plan for winning variant."""
plan = []
if significance.get('decision', {}).get('decision') == 'implement_b':
plan.append({
'step': '1. Update store listing',
'details': f"Replace {test['test_type']} with Variant B across all platforms"
})
plan.append({
'step': '2. Monitor metrics',
'details': 'Track conversion rate for 2 weeks to confirm sustained improvement'
})
plan.append({
'step': '3. Document learnings',
'details': 'Record insights for future optimization'
})
return plan
def _extract_learnings(
self,
test: Dict[str, Any],
significance: Dict[str, Any]
) -> List[str]:
"""Extract key learnings from test."""
learnings = []
improvement = significance['improvement']['relative_percentage']
learnings.append(
f"Testing {test['test_type']} can yield {abs(improvement):.1f}% conversion change"
)
if test['test_type'] == 'title':
learnings.append(
"Title changes affect search visibility and user perception"
)
elif test['test_type'] == 'screenshot':
learnings.append(
"First 2-3 screenshots are critical for conversion"
)
return learnings
def plan_ab_test(
test_type: str,
variant_a: Dict[str, Any],
variant_b: Dict[str, Any],
hypothesis: str,
baseline_conversion: float
) -> Dict[str, Any]:
"""
Convenience function to plan an A/B test.
Args:
test_type: Type of test
variant_a: Control variant
variant_b: Test variant
hypothesis: Test hypothesis
baseline_conversion: Current conversion rate
Returns:
Complete test plan
"""
planner = ABTestPlanner()
test_design = planner.design_test(
test_type,
variant_a,
variant_b,
hypothesis
)
sample_size = planner.calculate_sample_size(
baseline_conversion,
planner.MIN_EFFECT_SIZES.get(test_type, 0.05)
)
return {
'test_design': test_design,
'sample_size_requirements': sample_size
}
FILE:scripts/aso_scorer.py
"""
ASO scoring module for App Store Optimization.
Calculates comprehensive ASO health score across multiple dimensions.
"""
from typing import Dict, List, Any, Optional
class ASOScorer:
"""Calculates overall ASO health score and provides recommendations."""
# Score weights for different components (total = 100)
WEIGHTS = {
'metadata_quality': 25,
'ratings_reviews': 25,
'keyword_performance': 25,
'conversion_metrics': 25
}
# Benchmarks for scoring
BENCHMARKS = {
'title_keyword_usage': {'min': 1, 'target': 2},
'description_length': {'min': 500, 'target': 2000},
'keyword_density': {'min': 2, 'optimal': 5, 'max': 8},
'average_rating': {'min': 3.5, 'target': 4.5},
'ratings_count': {'min': 100, 'target': 5000},
'keywords_top_10': {'min': 2, 'target': 10},
'keywords_top_50': {'min': 5, 'target': 20},
'conversion_rate': {'min': 0.02, 'target': 0.10}
}
def __init__(self):
"""Initialize ASO scorer."""
self.score_breakdown = {}
def calculate_overall_score(
self,
metadata: Dict[str, Any],
ratings: Dict[str, Any],
keyword_performance: Dict[str, Any],
conversion: Dict[str, Any]
) -> Dict[str, Any]:
"""
Calculate comprehensive ASO score (0-100).
Args:
metadata: Title, description quality metrics
ratings: Rating average and count
keyword_performance: Keyword ranking data
conversion: Impression-to-install metrics
Returns:
Overall score with detailed breakdown
"""
# Calculate component scores
metadata_score = self.score_metadata_quality(metadata)
ratings_score = self.score_ratings_reviews(ratings)
keyword_score = self.score_keyword_performance(keyword_performance)
conversion_score = self.score_conversion_metrics(conversion)
# Calculate weighted overall score
overall_score = (
metadata_score * (self.WEIGHTS['metadata_quality'] / 100) +
ratings_score * (self.WEIGHTS['ratings_reviews'] / 100) +
keyword_score * (self.WEIGHTS['keyword_performance'] / 100) +
conversion_score * (self.WEIGHTS['conversion_metrics'] / 100)
)
# Store breakdown
self.score_breakdown = {
'metadata_quality': {
'score': metadata_score,
'weight': self.WEIGHTS['metadata_quality'],
'weighted_contribution': round(metadata_score * (self.WEIGHTS['metadata_quality'] / 100), 1)
},
'ratings_reviews': {
'score': ratings_score,
'weight': self.WEIGHTS['ratings_reviews'],
'weighted_contribution': round(ratings_score * (self.WEIGHTS['ratings_reviews'] / 100), 1)
},
'keyword_performance': {
'score': keyword_score,
'weight': self.WEIGHTS['keyword_performance'],
'weighted_contribution': round(keyword_score * (self.WEIGHTS['keyword_performance'] / 100), 1)
},
'conversion_metrics': {
'score': conversion_score,
'weight': self.WEIGHTS['conversion_metrics'],
'weighted_contribution': round(conversion_score * (self.WEIGHTS['conversion_metrics'] / 100), 1)
}
}
# Generate recommendations
recommendations = self.generate_recommendations(
metadata_score,
ratings_score,
keyword_score,
conversion_score
)
# Assess overall health
health_status = self._assess_health_status(overall_score)
return {
'overall_score': round(overall_score, 1),
'health_status': health_status,
'score_breakdown': self.score_breakdown,
'recommendations': recommendations,
'priority_actions': self._prioritize_actions(recommendations),
'strengths': self._identify_strengths(self.score_breakdown),
'weaknesses': self._identify_weaknesses(self.score_breakdown)
}
def score_metadata_quality(self, metadata: Dict[str, Any]) -> float:
"""
Score metadata quality (0-100).
Evaluates:
- Title optimization
- Description quality
- Keyword usage
"""
scores = []
# Title score (0-35 points)
title_keywords = metadata.get('title_keyword_count', 0)
title_length = metadata.get('title_length', 0)
title_score = 0
if title_keywords >= self.BENCHMARKS['title_keyword_usage']['target']:
title_score = 35
elif title_keywords >= self.BENCHMARKS['title_keyword_usage']['min']:
title_score = 25
else:
title_score = 10
# Adjust for title length usage
if title_length > 25: # Using most of available space
title_score += 0
else:
title_score -= 5
scores.append(min(title_score, 35))
# Description score (0-35 points)
desc_length = metadata.get('description_length', 0)
desc_quality = metadata.get('description_quality', 0.0) # 0-1 scale
desc_score = 0
if desc_length >= self.BENCHMARKS['description_length']['target']:
desc_score = 25
elif desc_length >= self.BENCHMARKS['description_length']['min']:
desc_score = 15
else:
desc_score = 5
# Add quality bonus
desc_score += desc_quality * 10
scores.append(min(desc_score, 35))
# Keyword density score (0-30 points)
keyword_density = metadata.get('keyword_density', 0.0)
if self.BENCHMARKS['keyword_density']['min'] <= keyword_density <= self.BENCHMARKS['keyword_density']['optimal']:
density_score = 30
elif keyword_density < self.BENCHMARKS['keyword_density']['min']:
# Too low - proportional scoring
density_score = (keyword_density / self.BENCHMARKS['keyword_density']['min']) * 20
else:
# Too high (keyword stuffing) - penalty
excess = keyword_density - self.BENCHMARKS['keyword_density']['optimal']
density_score = max(30 - (excess * 5), 0)
scores.append(density_score)
return round(sum(scores), 1)
def score_ratings_reviews(self, ratings: Dict[str, Any]) -> float:
"""
Score ratings and reviews (0-100).
Evaluates:
- Average rating
- Total ratings count
- Review velocity
"""
average_rating = ratings.get('average_rating', 0.0)
total_ratings = ratings.get('total_ratings', 0)
recent_ratings = ratings.get('recent_ratings_30d', 0)
# Rating quality score (0-50 points)
if average_rating >= self.BENCHMARKS['average_rating']['target']:
rating_quality_score = 50
elif average_rating >= self.BENCHMARKS['average_rating']['min']:
# Proportional scoring between min and target
proportion = (average_rating - self.BENCHMARKS['average_rating']['min']) / \
(self.BENCHMARKS['average_rating']['target'] - self.BENCHMARKS['average_rating']['min'])
rating_quality_score = 30 + (proportion * 20)
elif average_rating >= 3.0:
rating_quality_score = 20
else:
rating_quality_score = 10
# Rating volume score (0-30 points)
if total_ratings >= self.BENCHMARKS['ratings_count']['target']:
rating_volume_score = 30
elif total_ratings >= self.BENCHMARKS['ratings_count']['min']:
# Proportional scoring
proportion = (total_ratings - self.BENCHMARKS['ratings_count']['min']) / \
(self.BENCHMARKS['ratings_count']['target'] - self.BENCHMARKS['ratings_count']['min'])
rating_volume_score = 15 + (proportion * 15)
else:
# Very low volume
rating_volume_score = (total_ratings / self.BENCHMARKS['ratings_count']['min']) * 15
# Rating velocity score (0-20 points)
if recent_ratings > 100:
velocity_score = 20
elif recent_ratings > 50:
velocity_score = 15
elif recent_ratings > 10:
velocity_score = 10
else:
velocity_score = 5
total_score = rating_quality_score + rating_volume_score + velocity_score
return round(min(total_score, 100), 1)
def score_keyword_performance(self, keyword_performance: Dict[str, Any]) -> float:
"""
Score keyword ranking performance (0-100).
Evaluates:
- Top 10 rankings
- Top 50 rankings
- Ranking trends
"""
top_10_count = keyword_performance.get('top_10', 0)
top_50_count = keyword_performance.get('top_50', 0)
top_100_count = keyword_performance.get('top_100', 0)
improving_keywords = keyword_performance.get('improving_keywords', 0)
# Top 10 score (0-50 points) - most valuable rankings
if top_10_count >= self.BENCHMARKS['keywords_top_10']['target']:
top_10_score = 50
elif top_10_count >= self.BENCHMARKS['keywords_top_10']['min']:
proportion = (top_10_count - self.BENCHMARKS['keywords_top_10']['min']) / \
(self.BENCHMARKS['keywords_top_10']['target'] - self.BENCHMARKS['keywords_top_10']['min'])
top_10_score = 25 + (proportion * 25)
else:
top_10_score = (top_10_count / self.BENCHMARKS['keywords_top_10']['min']) * 25
# Top 50 score (0-30 points)
if top_50_count >= self.BENCHMARKS['keywords_top_50']['target']:
top_50_score = 30
elif top_50_count >= self.BENCHMARKS['keywords_top_50']['min']:
proportion = (top_50_count - self.BENCHMARKS['keywords_top_50']['min']) / \
(self.BENCHMARKS['keywords_top_50']['target'] - self.BENCHMARKS['keywords_top_50']['min'])
top_50_score = 15 + (proportion * 15)
else:
top_50_score = (top_50_count / self.BENCHMARKS['keywords_top_50']['min']) * 15
# Coverage score (0-10 points) - based on top 100
coverage_score = min((top_100_count / 30) * 10, 10)
# Trend score (0-10 points) - are rankings improving?
if improving_keywords > 5:
trend_score = 10
elif improving_keywords > 0:
trend_score = 5
else:
trend_score = 0
total_score = top_10_score + top_50_score + coverage_score + trend_score
return round(min(total_score, 100), 1)
def score_conversion_metrics(self, conversion: Dict[str, Any]) -> float:
"""
Score conversion performance (0-100).
Evaluates:
- Impression-to-install conversion rate
- Download velocity
"""
conversion_rate = conversion.get('impression_to_install', 0.0)
downloads_30d = conversion.get('downloads_last_30_days', 0)
downloads_trend = conversion.get('downloads_trend', 'stable') # 'up', 'stable', 'down'
# Conversion rate score (0-70 points)
if conversion_rate >= self.BENCHMARKS['conversion_rate']['target']:
conversion_score = 70
elif conversion_rate >= self.BENCHMARKS['conversion_rate']['min']:
proportion = (conversion_rate - self.BENCHMARKS['conversion_rate']['min']) / \
(self.BENCHMARKS['conversion_rate']['target'] - self.BENCHMARKS['conversion_rate']['min'])
conversion_score = 35 + (proportion * 35)
else:
conversion_score = (conversion_rate / self.BENCHMARKS['conversion_rate']['min']) * 35
# Download velocity score (0-20 points)
if downloads_30d > 10000:
velocity_score = 20
elif downloads_30d > 1000:
velocity_score = 15
elif downloads_30d > 100:
velocity_score = 10
else:
velocity_score = 5
# Trend bonus (0-10 points)
if downloads_trend == 'up':
trend_score = 10
elif downloads_trend == 'stable':
trend_score = 5
else:
trend_score = 0
total_score = conversion_score + velocity_score + trend_score
return round(min(total_score, 100), 1)
def generate_recommendations(
self,
metadata_score: float,
ratings_score: float,
keyword_score: float,
conversion_score: float
) -> List[Dict[str, Any]]:
"""Generate prioritized recommendations based on scores."""
recommendations = []
# Metadata recommendations
if metadata_score < 60:
recommendations.append({
'category': 'metadata_quality',
'priority': 'high',
'action': 'Optimize app title and description',
'details': 'Add more keywords to title, expand description to 1500-2000 characters, improve keyword density to 3-5%',
'expected_impact': 'Improve discoverability and ranking potential'
})
elif metadata_score < 80:
recommendations.append({
'category': 'metadata_quality',
'priority': 'medium',
'action': 'Refine metadata for better keyword targeting',
'details': 'Test variations of title/subtitle, optimize keyword field for Apple',
'expected_impact': 'Incremental ranking improvements'
})
# Ratings recommendations
if ratings_score < 60:
recommendations.append({
'category': 'ratings_reviews',
'priority': 'high',
'action': 'Improve rating quality and volume',
'details': 'Address top user complaints, implement in-app rating prompts, respond to negative reviews',
'expected_impact': 'Better conversion rates and trust signals'
})
elif ratings_score < 80:
recommendations.append({
'category': 'ratings_reviews',
'priority': 'medium',
'action': 'Increase rating velocity',
'details': 'Optimize timing of rating requests, encourage satisfied users to rate',
'expected_impact': 'Sustained rating quality'
})
# Keyword performance recommendations
if keyword_score < 60:
recommendations.append({
'category': 'keyword_performance',
'priority': 'high',
'action': 'Improve keyword rankings',
'details': 'Target long-tail keywords with lower competition, update metadata with high-potential keywords, build backlinks',
'expected_impact': 'Significant improvement in organic visibility'
})
elif keyword_score < 80:
recommendations.append({
'category': 'keyword_performance',
'priority': 'medium',
'action': 'Expand keyword coverage',
'details': 'Target additional related keywords, test seasonal keywords, localize for new markets',
'expected_impact': 'Broader reach and more discovery opportunities'
})
# Conversion recommendations
if conversion_score < 60:
recommendations.append({
'category': 'conversion_metrics',
'priority': 'high',
'action': 'Optimize store listing for conversions',
'details': 'Improve screenshots and icon, strengthen value proposition in description, add video preview',
'expected_impact': 'Higher impression-to-install conversion'
})
elif conversion_score < 80:
recommendations.append({
'category': 'conversion_metrics',
'priority': 'medium',
'action': 'Test visual asset variations',
'details': 'A/B test different icon designs and screenshot sequences',
'expected_impact': 'Incremental conversion improvements'
})
return recommendations
def _assess_health_status(self, overall_score: float) -> str:
"""Assess overall ASO health status."""
if overall_score >= 80:
return "Excellent - Top-tier ASO performance"
elif overall_score >= 65:
return "Good - Competitive ASO with room for improvement"
elif overall_score >= 50:
return "Fair - Needs strategic improvements"
else:
return "Poor - Requires immediate ASO overhaul"
def _prioritize_actions(
self,
recommendations: List[Dict[str, Any]]
) -> List[Dict[str, Any]]:
"""Prioritize actions by impact and urgency."""
# Sort by priority (high first) and expected impact
priority_order = {'high': 0, 'medium': 1, 'low': 2}
sorted_recommendations = sorted(
recommendations,
key=lambda x: priority_order[x['priority']]
)
return sorted_recommendations[:3] # Top 3 priority actions
def _identify_strengths(self, score_breakdown: Dict[str, Any]) -> List[str]:
"""Identify areas of strength (scores >= 75)."""
strengths = []
for category, data in score_breakdown.items():
if data['score'] >= 75:
strengths.append(
f"{category.replace('_', ' ').title()}: {data['score']}/100"
)
return strengths if strengths else ["Focus on building strengths across all areas"]
def _identify_weaknesses(self, score_breakdown: Dict[str, Any]) -> List[str]:
"""Identify areas needing improvement (scores < 60)."""
weaknesses = []
for category, data in score_breakdown.items():
if data['score'] < 60:
weaknesses.append(
f"{category.replace('_', ' ').title()}: {data['score']}/100 - needs improvement"
)
return weaknesses if weaknesses else ["All areas performing adequately"]
def calculate_aso_score(
metadata: Dict[str, Any],
ratings: Dict[str, Any],
keyword_performance: Dict[str, Any],
conversion: Dict[str, Any]
) -> Dict[str, Any]:
"""
Convenience function to calculate ASO score.
Args:
metadata: Metadata quality metrics
ratings: Ratings data
keyword_performance: Keyword ranking data
conversion: Conversion metrics
Returns:
Complete ASO score report
"""
scorer = ASOScorer()
return scorer.calculate_overall_score(
metadata,
ratings,
keyword_performance,
conversion
)
FILE:scripts/competitor_analyzer.py
"""
Competitor analysis module for App Store Optimization.
Analyzes top competitors' ASO strategies and identifies opportunities.
"""
from typing import Dict, List, Any, Optional
from collections import Counter
import re
class CompetitorAnalyzer:
"""Analyzes competitor apps to identify ASO opportunities."""
def __init__(self, category: str, platform: str = 'apple'):
"""
Initialize competitor analyzer.
Args:
category: App category (e.g., "Productivity", "Games")
platform: 'apple' or 'google'
"""
self.category = category
self.platform = platform
self.competitors = []
def analyze_competitor(
self,
app_data: Dict[str, Any]
) -> Dict[str, Any]:
"""
Analyze a single competitor's ASO strategy.
Args:
app_data: Dictionary with app_name, title, description, rating, ratings_count, keywords
Returns:
Comprehensive competitor analysis
"""
app_name = app_data.get('app_name', '')
title = app_data.get('title', '')
description = app_data.get('description', '')
rating = app_data.get('rating', 0.0)
ratings_count = app_data.get('ratings_count', 0)
keywords = app_data.get('keywords', [])
analysis = {
'app_name': app_name,
'title_analysis': self._analyze_title(title),
'description_analysis': self._analyze_description(description),
'keyword_strategy': self._extract_keyword_strategy(title, description, keywords),
'rating_metrics': {
'rating': rating,
'ratings_count': ratings_count,
'rating_quality': self._assess_rating_quality(rating, ratings_count)
},
'competitive_strength': self._calculate_competitive_strength(
rating,
ratings_count,
len(description)
),
'key_differentiators': self._identify_differentiators(description)
}
self.competitors.append(analysis)
return analysis
def compare_competitors(
self,
competitors_data: List[Dict[str, Any]]
) -> Dict[str, Any]:
"""
Compare multiple competitors and identify patterns.
Args:
competitors_data: List of competitor data dictionaries
Returns:
Comparative analysis with insights
"""
# Analyze each competitor
analyses = []
for comp_data in competitors_data:
analysis = self.analyze_competitor(comp_data)
analyses.append(analysis)
# Extract common keywords across competitors
all_keywords = []
for analysis in analyses:
all_keywords.extend(analysis['keyword_strategy']['primary_keywords'])
common_keywords = self._find_common_keywords(all_keywords)
# Identify keyword gaps (used by some but not all)
keyword_gaps = self._identify_keyword_gaps(analyses)
# Rank competitors by strength
ranked_competitors = sorted(
analyses,
key=lambda x: x['competitive_strength'],
reverse=True
)
# Analyze rating distribution
rating_analysis = self._analyze_rating_distribution(analyses)
# Identify best practices
best_practices = self._identify_best_practices(ranked_competitors)
return {
'category': self.category,
'platform': self.platform,
'competitors_analyzed': len(analyses),
'ranked_competitors': ranked_competitors,
'common_keywords': common_keywords,
'keyword_gaps': keyword_gaps,
'rating_analysis': rating_analysis,
'best_practices': best_practices,
'opportunities': self._identify_opportunities(
analyses,
common_keywords,
keyword_gaps
)
}
def identify_gaps(
self,
your_app_data: Dict[str, Any],
competitors_data: List[Dict[str, Any]]
) -> Dict[str, Any]:
"""
Identify gaps between your app and competitors.
Args:
your_app_data: Your app's data
competitors_data: List of competitor data
Returns:
Gap analysis with actionable recommendations
"""
# Analyze your app
your_analysis = self.analyze_competitor(your_app_data)
# Analyze competitors
competitor_comparison = self.compare_competitors(competitors_data)
# Identify keyword gaps
your_keywords = set(your_analysis['keyword_strategy']['primary_keywords'])
competitor_keywords = set(competitor_comparison['common_keywords'])
missing_keywords = competitor_keywords - your_keywords
# Identify rating gap
avg_competitor_rating = competitor_comparison['rating_analysis']['average_rating']
rating_gap = avg_competitor_rating - your_analysis['rating_metrics']['rating']
# Identify description length gap
avg_competitor_desc_length = sum(
len(comp['description_analysis']['text'])
for comp in competitor_comparison['ranked_competitors']
) / len(competitor_comparison['ranked_competitors'])
your_desc_length = len(your_analysis['description_analysis']['text'])
desc_length_gap = avg_competitor_desc_length - your_desc_length
return {
'your_app': your_analysis,
'keyword_gaps': {
'missing_keywords': list(missing_keywords)[:10],
'recommendations': self._generate_keyword_recommendations(missing_keywords)
},
'rating_gap': {
'your_rating': your_analysis['rating_metrics']['rating'],
'average_competitor_rating': avg_competitor_rating,
'gap': round(rating_gap, 2),
'action_items': self._generate_rating_improvement_actions(rating_gap)
},
'content_gap': {
'your_description_length': your_desc_length,
'average_competitor_length': int(avg_competitor_desc_length),
'gap': int(desc_length_gap),
'recommendations': self._generate_content_recommendations(desc_length_gap)
},
'competitive_positioning': self._assess_competitive_position(
your_analysis,
competitor_comparison
)
}
def _analyze_title(self, title: str) -> Dict[str, Any]:
"""Analyze title structure and keyword usage."""
parts = re.split(r'[-:|]', title)
return {
'title': title,
'length': len(title),
'has_brand': len(parts) > 0,
'has_keywords': len(parts) > 1,
'components': [part.strip() for part in parts],
'word_count': len(title.split()),
'strategy': 'brand_plus_keywords' if len(parts) > 1 else 'brand_only'
}
def _analyze_description(self, description: str) -> Dict[str, Any]:
"""Analyze description structure and content."""
lines = description.split('\n')
word_count = len(description.split())
# Check for structural elements
has_bullet_points = '•' in description or '*' in description
has_sections = any(line.isupper() for line in lines if len(line) > 0)
has_call_to_action = any(
cta in description.lower()
for cta in ['download', 'try', 'get', 'start', 'join']
)
# Extract features mentioned
features = self._extract_features(description)
return {
'text': description,
'length': len(description),
'word_count': word_count,
'structure': {
'has_bullet_points': has_bullet_points,
'has_sections': has_sections,
'has_call_to_action': has_call_to_action
},
'features_mentioned': features,
'readability': 'good' if 50 <= word_count <= 300 else 'needs_improvement'
}
def _extract_keyword_strategy(
self,
title: str,
description: str,
explicit_keywords: List[str]
) -> Dict[str, Any]:
"""Extract keyword strategy from metadata."""
# Extract keywords from title
title_keywords = [word.lower() for word in title.split() if len(word) > 3]
# Extract frequently used words from description
desc_words = re.findall(r'\b\w{4,}\b', description.lower())
word_freq = Counter(desc_words)
frequent_words = [word for word, count in word_freq.most_common(15) if count > 2]
# Combine with explicit keywords
all_keywords = list(set(title_keywords + frequent_words + explicit_keywords))
return {
'primary_keywords': title_keywords,
'description_keywords': frequent_words[:10],
'explicit_keywords': explicit_keywords,
'total_unique_keywords': len(all_keywords),
'keyword_focus': self._assess_keyword_focus(title_keywords, frequent_words)
}
def _assess_rating_quality(self, rating: float, ratings_count: int) -> str:
"""Assess the quality of ratings."""
if ratings_count < 100:
return 'insufficient_data'
elif rating >= 4.5 and ratings_count > 1000:
return 'excellent'
elif rating >= 4.0 and ratings_count > 500:
return 'good'
elif rating >= 3.5:
return 'average'
else:
return 'poor'
def _calculate_competitive_strength(
self,
rating: float,
ratings_count: int,
description_length: int
) -> float:
"""
Calculate overall competitive strength (0-100).
Factors:
- Rating quality (40%)
- Rating volume (30%)
- Metadata quality (30%)
"""
# Rating quality score (0-40)
rating_score = (rating / 5.0) * 40
# Rating volume score (0-30)
volume_score = min((ratings_count / 10000) * 30, 30)
# Metadata quality score (0-30)
metadata_score = min((description_length / 2000) * 30, 30)
total_score = rating_score + volume_score + metadata_score
return round(total_score, 1)
def _identify_differentiators(self, description: str) -> List[str]:
"""Identify key differentiators from description."""
differentiator_keywords = [
'unique', 'only', 'first', 'best', 'leading', 'exclusive',
'revolutionary', 'innovative', 'patent', 'award'
]
differentiators = []
sentences = description.split('.')
for sentence in sentences:
sentence_lower = sentence.lower()
if any(keyword in sentence_lower for keyword in differentiator_keywords):
differentiators.append(sentence.strip())
return differentiators[:5]
def _find_common_keywords(self, all_keywords: List[str]) -> List[str]:
"""Find keywords used by multiple competitors."""
keyword_counts = Counter(all_keywords)
# Return keywords used by at least 2 competitors
common = [kw for kw, count in keyword_counts.items() if count >= 2]
return sorted(common, key=lambda x: keyword_counts[x], reverse=True)[:20]
def _identify_keyword_gaps(self, analyses: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""Identify keywords used by some competitors but not others."""
all_keywords_by_app = {}
for analysis in analyses:
app_name = analysis['app_name']
keywords = analysis['keyword_strategy']['primary_keywords']
all_keywords_by_app[app_name] = set(keywords)
# Find keywords used by some but not all
all_keywords_set = set()
for keywords in all_keywords_by_app.values():
all_keywords_set.update(keywords)
gaps = []
for keyword in all_keywords_set:
using_apps = [
app for app, keywords in all_keywords_by_app.items()
if keyword in keywords
]
if 1 < len(using_apps) < len(analyses):
gaps.append({
'keyword': keyword,
'used_by': using_apps,
'usage_percentage': round(len(using_apps) / len(analyses) * 100, 1)
})
return sorted(gaps, key=lambda x: x['usage_percentage'], reverse=True)[:15]
def _analyze_rating_distribution(self, analyses: List[Dict[str, Any]]) -> Dict[str, Any]:
"""Analyze rating distribution across competitors."""
ratings = [a['rating_metrics']['rating'] for a in analyses]
ratings_counts = [a['rating_metrics']['ratings_count'] for a in analyses]
return {
'average_rating': round(sum(ratings) / len(ratings), 2),
'highest_rating': max(ratings),
'lowest_rating': min(ratings),
'average_ratings_count': int(sum(ratings_counts) / len(ratings_counts)),
'total_ratings_in_category': sum(ratings_counts)
}
def _identify_best_practices(self, ranked_competitors: List[Dict[str, Any]]) -> List[str]:
"""Identify best practices from top competitors."""
if not ranked_competitors:
return []
top_competitor = ranked_competitors[0]
practices = []
# Title strategy
title_analysis = top_competitor['title_analysis']
if title_analysis['has_keywords']:
practices.append(
f"Title Strategy: Include primary keyword in title (e.g., '{title_analysis['title']}')"
)
# Description structure
desc_analysis = top_competitor['description_analysis']
if desc_analysis['structure']['has_bullet_points']:
practices.append("Description: Use bullet points to highlight key features")
if desc_analysis['structure']['has_sections']:
practices.append("Description: Organize content with clear section headers")
# Rating strategy
rating_quality = top_competitor['rating_metrics']['rating_quality']
if rating_quality in ['excellent', 'good']:
practices.append(
f"Ratings: Maintain high rating quality ({top_competitor['rating_metrics']['rating']}★) "
f"with significant volume ({top_competitor['rating_metrics']['ratings_count']} ratings)"
)
return practices[:5]
def _identify_opportunities(
self,
analyses: List[Dict[str, Any]],
common_keywords: List[str],
keyword_gaps: List[Dict[str, Any]]
) -> List[str]:
"""Identify ASO opportunities based on competitive analysis."""
opportunities = []
# Keyword opportunities from gaps
if keyword_gaps:
underutilized_keywords = [
gap['keyword'] for gap in keyword_gaps
if gap['usage_percentage'] < 50
]
if underutilized_keywords:
opportunities.append(
f"Target underutilized keywords: {', '.join(underutilized_keywords[:5])}"
)
# Rating opportunity
avg_rating = sum(a['rating_metrics']['rating'] for a in analyses) / len(analyses)
if avg_rating < 4.5:
opportunities.append(
f"Category average rating is {avg_rating:.1f} - opportunity to differentiate with higher ratings"
)
# Content depth opportunity
avg_desc_length = sum(
a['description_analysis']['length'] for a in analyses
) / len(analyses)
if avg_desc_length < 1500:
opportunities.append(
"Competitors have relatively short descriptions - opportunity to provide more comprehensive information"
)
return opportunities[:5]
def _extract_features(self, description: str) -> List[str]:
"""Extract feature mentions from description."""
# Look for bullet points or numbered lists
lines = description.split('\n')
features = []
for line in lines:
line = line.strip()
# Check if line starts with bullet or number
if line and (line[0] in ['•', '*', '-', '✓'] or line[0].isdigit()):
# Clean the line
cleaned = re.sub(r'^[•*\-✓\d.)\s]+', '', line)
if cleaned:
features.append(cleaned)
return features[:10]
def _assess_keyword_focus(
self,
title_keywords: List[str],
description_keywords: List[str]
) -> str:
"""Assess keyword focus strategy."""
overlap = set(title_keywords) & set(description_keywords)
if len(overlap) >= 3:
return 'consistent_focus'
elif len(overlap) >= 1:
return 'moderate_focus'
else:
return 'broad_focus'
def _generate_keyword_recommendations(self, missing_keywords: set) -> List[str]:
"""Generate recommendations for missing keywords."""
if not missing_keywords:
return ["Your keyword coverage is comprehensive"]
recommendations = []
missing_list = list(missing_keywords)[:5]
recommendations.append(
f"Consider adding these competitor keywords: {', '.join(missing_list)}"
)
recommendations.append(
"Test keyword variations in subtitle/promotional text first"
)
recommendations.append(
"Monitor competitor keyword changes monthly"
)
return recommendations
def _generate_rating_improvement_actions(self, rating_gap: float) -> List[str]:
"""Generate actions to improve ratings."""
actions = []
if rating_gap > 0.5:
actions.append("CRITICAL: Significant rating gap - prioritize user satisfaction improvements")
actions.append("Analyze negative reviews to identify top issues")
actions.append("Implement in-app rating prompts after positive experiences")
actions.append("Respond to all negative reviews professionally")
elif rating_gap > 0.2:
actions.append("Focus on incremental improvements to close rating gap")
actions.append("Optimize timing of rating requests")
else:
actions.append("Ratings are competitive - maintain quality and continue improvements")
return actions
def _generate_content_recommendations(self, desc_length_gap: int) -> List[str]:
"""Generate content recommendations based on length gap."""
recommendations = []
if desc_length_gap > 500:
recommendations.append(
"Expand description to match competitor detail level"
)
recommendations.append(
"Add use case examples and success stories"
)
recommendations.append(
"Include more feature explanations and benefits"
)
elif desc_length_gap < -500:
recommendations.append(
"Consider condensing description for better readability"
)
recommendations.append(
"Focus on most important features first"
)
else:
recommendations.append(
"Description length is competitive"
)
return recommendations
def _assess_competitive_position(
self,
your_analysis: Dict[str, Any],
competitor_comparison: Dict[str, Any]
) -> str:
"""Assess your competitive position."""
your_strength = your_analysis['competitive_strength']
competitors = competitor_comparison['ranked_competitors']
if not competitors:
return "No comparison data available"
# Find where you'd rank
better_than_count = sum(
1 for comp in competitors
if your_strength > comp['competitive_strength']
)
position_percentage = (better_than_count / len(competitors)) * 100
if position_percentage >= 75:
return "Strong Position: Top quartile in competitive strength"
elif position_percentage >= 50:
return "Competitive Position: Above average, opportunities for improvement"
elif position_percentage >= 25:
return "Challenging Position: Below average, requires strategic improvements"
else:
return "Weak Position: Bottom quartile, major ASO overhaul needed"
def analyze_competitor_set(
category: str,
competitors_data: List[Dict[str, Any]],
platform: str = 'apple'
) -> Dict[str, Any]:
"""
Convenience function to analyze a set of competitors.
Args:
category: App category
competitors_data: List of competitor data
platform: 'apple' or 'google'
Returns:
Complete competitive analysis
"""
analyzer = CompetitorAnalyzer(category, platform)
return analyzer.compare_competitors(competitors_data)
FILE:scripts/keyword_analyzer.py
"""
Keyword analysis module for App Store Optimization.
Analyzes keyword search volume, competition, and relevance for app discovery.
"""
from typing import Dict, List, Any, Optional, Tuple
import re
from collections import Counter
class KeywordAnalyzer:
"""Analyzes keywords for ASO effectiveness."""
# Competition level thresholds (based on number of competing apps)
COMPETITION_THRESHOLDS = {
'low': 1000,
'medium': 5000,
'high': 10000
}
# Search volume categories (monthly searches estimate)
VOLUME_CATEGORIES = {
'very_low': 1000,
'low': 5000,
'medium': 20000,
'high': 100000,
'very_high': 500000
}
def __init__(self):
"""Initialize keyword analyzer."""
self.analyzed_keywords = {}
def analyze_keyword(
self,
keyword: str,
search_volume: int = 0,
competing_apps: int = 0,
relevance_score: float = 0.0
) -> Dict[str, Any]:
"""
Analyze a single keyword for ASO potential.
Args:
keyword: The keyword to analyze
search_volume: Estimated monthly search volume
competing_apps: Number of apps competing for this keyword
relevance_score: Relevance to your app (0.0-1.0)
Returns:
Dictionary with keyword analysis
"""
competition_level = self._calculate_competition_level(competing_apps)
volume_category = self._categorize_search_volume(search_volume)
difficulty_score = self._calculate_keyword_difficulty(
search_volume,
competing_apps
)
# Calculate potential score (0-100)
potential_score = self._calculate_potential_score(
search_volume,
competing_apps,
relevance_score
)
analysis = {
'keyword': keyword,
'search_volume': search_volume,
'volume_category': volume_category,
'competing_apps': competing_apps,
'competition_level': competition_level,
'relevance_score': relevance_score,
'difficulty_score': difficulty_score,
'potential_score': potential_score,
'recommendation': self._generate_recommendation(
potential_score,
difficulty_score,
relevance_score
),
'keyword_length': len(keyword.split()),
'is_long_tail': len(keyword.split()) >= 3
}
self.analyzed_keywords[keyword] = analysis
return analysis
def compare_keywords(self, keywords_data: List[Dict[str, Any]]) -> Dict[str, Any]:
"""
Compare multiple keywords and rank by potential.
Args:
keywords_data: List of dicts with keyword, search_volume, competing_apps, relevance_score
Returns:
Comparison report with ranked keywords
"""
analyses = []
for kw_data in keywords_data:
analysis = self.analyze_keyword(
keyword=kw_data['keyword'],
search_volume=kw_data.get('search_volume', 0),
competing_apps=kw_data.get('competing_apps', 0),
relevance_score=kw_data.get('relevance_score', 0.0)
)
analyses.append(analysis)
# Sort by potential score (descending)
ranked_keywords = sorted(
analyses,
key=lambda x: x['potential_score'],
reverse=True
)
# Categorize keywords
primary_keywords = [
kw for kw in ranked_keywords
if kw['potential_score'] >= 70 and kw['relevance_score'] >= 0.8
]
secondary_keywords = [
kw for kw in ranked_keywords
if 50 <= kw['potential_score'] < 70 and kw['relevance_score'] >= 0.6
]
long_tail_keywords = [
kw for kw in ranked_keywords
if kw['is_long_tail'] and kw['relevance_score'] >= 0.7
]
return {
'total_keywords_analyzed': len(analyses),
'ranked_keywords': ranked_keywords,
'primary_keywords': primary_keywords[:5], # Top 5
'secondary_keywords': secondary_keywords[:10], # Top 10
'long_tail_keywords': long_tail_keywords[:10], # Top 10
'summary': self._generate_comparison_summary(
primary_keywords,
secondary_keywords,
long_tail_keywords
)
}
def find_long_tail_opportunities(
self,
base_keyword: str,
modifiers: List[str]
) -> List[Dict[str, Any]]:
"""
Generate long-tail keyword variations.
Args:
base_keyword: Core keyword (e.g., "task manager")
modifiers: List of modifiers (e.g., ["free", "simple", "team"])
Returns:
List of long-tail keyword suggestions
"""
long_tail_keywords = []
# Generate combinations
for modifier in modifiers:
# Modifier + base
variation1 = f"{modifier} {base_keyword}"
long_tail_keywords.append({
'keyword': variation1,
'pattern': 'modifier_base',
'estimated_competition': 'low',
'rationale': f"Less competitive variation of '{base_keyword}'"
})
# Base + modifier
variation2 = f"{base_keyword} {modifier}"
long_tail_keywords.append({
'keyword': variation2,
'pattern': 'base_modifier',
'estimated_competition': 'low',
'rationale': f"Specific use-case variation of '{base_keyword}'"
})
# Add question-based long-tail
question_words = ['how', 'what', 'best', 'top']
for q_word in question_words:
question_keyword = f"{q_word} {base_keyword}"
long_tail_keywords.append({
'keyword': question_keyword,
'pattern': 'question_based',
'estimated_competition': 'very_low',
'rationale': f"Informational search query"
})
return long_tail_keywords
def extract_keywords_from_text(
self,
text: str,
min_word_length: int = 3
) -> List[Tuple[str, int]]:
"""
Extract potential keywords from text (descriptions, reviews).
Args:
text: Text to analyze
min_word_length: Minimum word length to consider
Returns:
List of (keyword, frequency) tuples
"""
# Clean and normalize text
text = text.lower()
text = re.sub(r'[^\w\s]', ' ', text)
# Extract words
words = text.split()
# Filter by length
words = [w for w in words if len(w) >= min_word_length]
# Remove common stop words
stop_words = {
'the', 'and', 'for', 'with', 'this', 'that', 'from', 'have',
'but', 'not', 'you', 'all', 'can', 'are', 'was', 'were', 'been'
}
words = [w for w in words if w not in stop_words]
# Count frequency
word_counts = Counter(words)
# Extract 2-word phrases
phrases = []
for i in range(len(words) - 1):
phrase = f"{words[i]} {words[i+1]}"
phrases.append(phrase)
phrase_counts = Counter(phrases)
# Combine and sort
all_keywords = list(word_counts.items()) + list(phrase_counts.items())
all_keywords.sort(key=lambda x: x[1], reverse=True)
return all_keywords[:50] # Top 50
def calculate_keyword_density(
self,
text: str,
target_keywords: List[str]
) -> Dict[str, float]:
"""
Calculate keyword density in text.
Args:
text: Text to analyze (title, description)
target_keywords: Keywords to check density for
Returns:
Dictionary of keyword: density (percentage)
"""
text_lower = text.lower()
total_words = len(text_lower.split())
densities = {}
for keyword in target_keywords:
keyword_lower = keyword.lower()
occurrences = text_lower.count(keyword_lower)
density = (occurrences / total_words) * 100 if total_words > 0 else 0
densities[keyword] = round(density, 2)
return densities
def _calculate_competition_level(self, competing_apps: int) -> str:
"""Determine competition level based on number of competing apps."""
if competing_apps < self.COMPETITION_THRESHOLDS['low']:
return 'low'
elif competing_apps < self.COMPETITION_THRESHOLDS['medium']:
return 'medium'
elif competing_apps < self.COMPETITION_THRESHOLDS['high']:
return 'high'
else:
return 'very_high'
def _categorize_search_volume(self, search_volume: int) -> str:
"""Categorize search volume."""
if search_volume < self.VOLUME_CATEGORIES['very_low']:
return 'very_low'
elif search_volume < self.VOLUME_CATEGORIES['low']:
return 'low'
elif search_volume < self.VOLUME_CATEGORIES['medium']:
return 'medium'
elif search_volume < self.VOLUME_CATEGORIES['high']:
return 'high'
else:
return 'very_high'
def _calculate_keyword_difficulty(
self,
search_volume: int,
competing_apps: int
) -> float:
"""
Calculate keyword difficulty score (0-100).
Higher score = harder to rank.
"""
if competing_apps == 0:
return 0.0
# Competition factor (0-1)
competition_factor = min(competing_apps / 50000, 1.0)
# Volume factor (0-1) - higher volume = more difficulty
volume_factor = min(search_volume / 1000000, 1.0)
# Difficulty score (weighted average)
difficulty = (competition_factor * 0.7 + volume_factor * 0.3) * 100
return round(difficulty, 1)
def _calculate_potential_score(
self,
search_volume: int,
competing_apps: int,
relevance_score: float
) -> float:
"""
Calculate overall keyword potential (0-100).
Higher score = better opportunity.
"""
# Volume score (0-40 points)
volume_score = min((search_volume / 100000) * 40, 40)
# Competition score (0-30 points) - inverse relationship
if competing_apps > 0:
competition_score = max(30 - (competing_apps / 500), 0)
else:
competition_score = 30
# Relevance score (0-30 points)
relevance_points = relevance_score * 30
total_score = volume_score + competition_score + relevance_points
return round(min(total_score, 100), 1)
def _generate_recommendation(
self,
potential_score: float,
difficulty_score: float,
relevance_score: float
) -> str:
"""Generate actionable recommendation for keyword."""
if relevance_score < 0.5:
return "Low relevance - avoid targeting"
if potential_score >= 70:
return "High priority - target immediately"
elif potential_score >= 50:
if difficulty_score < 50:
return "Good opportunity - include in metadata"
else:
return "Competitive - use in description, not title"
elif potential_score >= 30:
return "Secondary keyword - use for long-tail variations"
else:
return "Low potential - deprioritize"
def _generate_comparison_summary(
self,
primary_keywords: List[Dict[str, Any]],
secondary_keywords: List[Dict[str, Any]],
long_tail_keywords: List[Dict[str, Any]]
) -> str:
"""Generate summary of keyword comparison."""
summary_parts = []
summary_parts.append(
f"Identified {len(primary_keywords)} high-priority primary keywords."
)
if primary_keywords:
top_keyword = primary_keywords[0]['keyword']
summary_parts.append(
f"Top recommendation: '{top_keyword}' (potential score: {primary_keywords[0]['potential_score']})."
)
summary_parts.append(
f"Found {len(secondary_keywords)} secondary keywords for description and metadata."
)
summary_parts.append(
f"Discovered {len(long_tail_keywords)} long-tail opportunities with lower competition."
)
return " ".join(summary_parts)
def analyze_keyword_set(keywords_data: List[Dict[str, Any]]) -> Dict[str, Any]:
"""
Convenience function to analyze a set of keywords.
Args:
keywords_data: List of keyword data dictionaries
Returns:
Complete analysis report
"""
analyzer = KeywordAnalyzer()
return analyzer.compare_keywords(keywords_data)
FILE:scripts/launch_checklist.py
"""
Launch checklist module for App Store Optimization.
Generates comprehensive pre-launch and update checklists.
"""
from typing import Dict, List, Any, Optional
from datetime import datetime, timedelta
class LaunchChecklistGenerator:
"""Generates comprehensive checklists for app launches and updates."""
def __init__(self, platform: str = 'both'):
"""
Initialize checklist generator.
Args:
platform: 'apple', 'google', or 'both'
"""
if platform not in ['apple', 'google', 'both']:
raise ValueError("Platform must be 'apple', 'google', or 'both'")
self.platform = platform
def generate_prelaunch_checklist(
self,
app_info: Dict[str, Any],
launch_date: Optional[str] = None
) -> Dict[str, Any]:
"""
Generate comprehensive pre-launch checklist.
Args:
app_info: App information (name, category, target_audience)
launch_date: Target launch date (YYYY-MM-DD)
Returns:
Complete pre-launch checklist
"""
checklist = {
'app_info': app_info,
'launch_date': launch_date,
'checklists': {}
}
# Generate platform-specific checklists
if self.platform in ['apple', 'both']:
checklist['checklists']['apple'] = self._generate_apple_checklist(app_info)
if self.platform in ['google', 'both']:
checklist['checklists']['google'] = self._generate_google_checklist(app_info)
# Add universal checklist items
checklist['checklists']['universal'] = self._generate_universal_checklist(app_info)
# Generate timeline
if launch_date:
checklist['timeline'] = self._generate_launch_timeline(launch_date)
# Calculate completion status
checklist['summary'] = self._calculate_checklist_summary(checklist['checklists'])
return checklist
def validate_app_store_compliance(
self,
app_data: Dict[str, Any],
platform: str = 'apple'
) -> Dict[str, Any]:
"""
Validate compliance with app store guidelines.
Args:
app_data: App data including metadata, privacy policy, etc.
platform: 'apple' or 'google'
Returns:
Compliance validation report
"""
validation_results = {
'platform': platform,
'is_compliant': True,
'errors': [],
'warnings': [],
'recommendations': []
}
if platform == 'apple':
self._validate_apple_compliance(app_data, validation_results)
elif platform == 'google':
self._validate_google_compliance(app_data, validation_results)
# Determine overall compliance
validation_results['is_compliant'] = len(validation_results['errors']) == 0
return validation_results
def create_update_plan(
self,
current_version: str,
planned_features: List[str],
update_frequency: str = 'monthly'
) -> Dict[str, Any]:
"""
Create update cadence and feature rollout plan.
Args:
current_version: Current app version
planned_features: List of planned features
update_frequency: 'weekly', 'biweekly', 'monthly', 'quarterly'
Returns:
Update plan with cadence and feature schedule
"""
# Calculate next versions
next_versions = self._calculate_next_versions(
current_version,
update_frequency,
len(planned_features)
)
# Distribute features across versions
feature_schedule = self._distribute_features(
planned_features,
next_versions
)
# Generate "What's New" templates
whats_new_templates = [
self._generate_whats_new_template(version_data)
for version_data in feature_schedule
]
return {
'current_version': current_version,
'update_frequency': update_frequency,
'planned_updates': len(feature_schedule),
'feature_schedule': feature_schedule,
'whats_new_templates': whats_new_templates,
'recommendations': self._generate_update_recommendations(update_frequency)
}
def optimize_launch_timing(
self,
app_category: str,
target_audience: str,
current_date: Optional[str] = None
) -> Dict[str, Any]:
"""
Recommend optimal launch timing.
Args:
app_category: App category
target_audience: Target audience description
current_date: Current date (YYYY-MM-DD), defaults to today
Returns:
Launch timing recommendations
"""
if not current_date:
current_date = datetime.now().strftime('%Y-%m-%d')
# Analyze launch timing factors
day_of_week_rec = self._recommend_day_of_week(app_category)
seasonal_rec = self._recommend_seasonal_timing(app_category, current_date)
competitive_rec = self._analyze_competitive_timing(app_category)
# Calculate optimal dates
optimal_dates = self._calculate_optimal_dates(
current_date,
day_of_week_rec,
seasonal_rec
)
return {
'current_date': current_date,
'optimal_launch_dates': optimal_dates,
'day_of_week_recommendation': day_of_week_rec,
'seasonal_considerations': seasonal_rec,
'competitive_timing': competitive_rec,
'final_recommendation': self._generate_timing_recommendation(
optimal_dates,
seasonal_rec
)
}
def plan_seasonal_campaigns(
self,
app_category: str,
current_month: int = None
) -> Dict[str, Any]:
"""
Identify seasonal opportunities for ASO campaigns.
Args:
app_category: App category
current_month: Current month (1-12), defaults to current
Returns:
Seasonal campaign opportunities
"""
if not current_month:
current_month = datetime.now().month
# Identify relevant seasonal events
seasonal_opportunities = self._identify_seasonal_opportunities(
app_category,
current_month
)
# Generate campaign ideas
campaigns = [
self._generate_seasonal_campaign(opportunity)
for opportunity in seasonal_opportunities
]
return {
'current_month': current_month,
'category': app_category,
'seasonal_opportunities': seasonal_opportunities,
'campaign_ideas': campaigns,
'implementation_timeline': self._create_seasonal_timeline(campaigns)
}
def _generate_apple_checklist(self, app_info: Dict[str, Any]) -> List[Dict[str, Any]]:
"""Generate Apple App Store specific checklist."""
return [
{
'category': 'App Store Connect Setup',
'items': [
{'task': 'App Store Connect account created', 'status': 'pending'},
{'task': 'App bundle ID registered', 'status': 'pending'},
{'task': 'App Privacy declarations completed', 'status': 'pending'},
{'task': 'Age rating questionnaire completed', 'status': 'pending'}
]
},
{
'category': 'Metadata (Apple)',
'items': [
{'task': 'App title (30 chars max)', 'status': 'pending'},
{'task': 'Subtitle (30 chars max)', 'status': 'pending'},
{'task': 'Promotional text (170 chars max)', 'status': 'pending'},
{'task': 'Description (4000 chars max)', 'status': 'pending'},
{'task': 'Keywords (100 chars, comma-separated)', 'status': 'pending'},
{'task': 'Category selection (primary + secondary)', 'status': 'pending'}
]
},
{
'category': 'Visual Assets (Apple)',
'items': [
{'task': 'App icon (1024x1024px)', 'status': 'pending'},
{'task': 'Screenshots (iPhone 6.7" required)', 'status': 'pending'},
{'task': 'Screenshots (iPhone 5.5" required)', 'status': 'pending'},
{'task': 'Screenshots (iPad Pro 12.9" if iPad app)', 'status': 'pending'},
{'task': 'App preview video (optional but recommended)', 'status': 'pending'}
]
},
{
'category': 'Technical Requirements (Apple)',
'items': [
{'task': 'Build uploaded to App Store Connect', 'status': 'pending'},
{'task': 'TestFlight testing completed', 'status': 'pending'},
{'task': 'App tested on required iOS versions', 'status': 'pending'},
{'task': 'Crash-free rate > 99%', 'status': 'pending'},
{'task': 'All links in app/metadata working', 'status': 'pending'}
]
},
{
'category': 'Legal & Privacy (Apple)',
'items': [
{'task': 'Privacy Policy URL provided', 'status': 'pending'},
{'task': 'Terms of Service URL (if applicable)', 'status': 'pending'},
{'task': 'Data collection declarations accurate', 'status': 'pending'},
{'task': 'Third-party SDKs disclosed', 'status': 'pending'}
]
}
]
def _generate_google_checklist(self, app_info: Dict[str, Any]) -> List[Dict[str, Any]]:
"""Generate Google Play Store specific checklist."""
return [
{
'category': 'Play Console Setup',
'items': [
{'task': 'Google Play Console account created', 'status': 'pending'},
{'task': 'Developer profile completed', 'status': 'pending'},
{'task': 'Payment merchant account linked (if paid app)', 'status': 'pending'},
{'task': 'Content rating questionnaire completed', 'status': 'pending'}
]
},
{
'category': 'Metadata (Google)',
'items': [
{'task': 'App title (50 chars max)', 'status': 'pending'},
{'task': 'Short description (80 chars max)', 'status': 'pending'},
{'task': 'Full description (4000 chars max)', 'status': 'pending'},
{'task': 'Category selection', 'status': 'pending'},
{'task': 'Tags (up to 5)', 'status': 'pending'}
]
},
{
'category': 'Visual Assets (Google)',
'items': [
{'task': 'App icon (512x512px)', 'status': 'pending'},
{'task': 'Feature graphic (1024x500px)', 'status': 'pending'},
{'task': 'Screenshots (2-8 required, phone)', 'status': 'pending'},
{'task': 'Screenshots (tablet, if applicable)', 'status': 'pending'},
{'task': 'Promo video (YouTube link, optional)', 'status': 'pending'}
]
},
{
'category': 'Technical Requirements (Google)',
'items': [
{'task': 'APK/AAB uploaded to Play Console', 'status': 'pending'},
{'task': 'Internal testing completed', 'status': 'pending'},
{'task': 'App tested on required Android versions', 'status': 'pending'},
{'task': 'Target API level meets requirements', 'status': 'pending'},
{'task': 'All permissions justified', 'status': 'pending'}
]
},
{
'category': 'Legal & Privacy (Google)',
'items': [
{'task': 'Privacy Policy URL provided', 'status': 'pending'},
{'task': 'Data safety section completed', 'status': 'pending'},
{'task': 'Ads disclosure (if applicable)', 'status': 'pending'},
{'task': 'In-app purchase disclosure (if applicable)', 'status': 'pending'}
]
}
]
def _generate_universal_checklist(self, app_info: Dict[str, Any]) -> List[Dict[str, Any]]:
"""Generate universal (both platforms) checklist."""
return [
{
'category': 'Pre-Launch Marketing',
'items': [
{'task': 'Landing page created', 'status': 'pending'},
{'task': 'Social media accounts setup', 'status': 'pending'},
{'task': 'Press kit prepared', 'status': 'pending'},
{'task': 'Beta tester feedback collected', 'status': 'pending'},
{'task': 'Launch announcement drafted', 'status': 'pending'}
]
},
{
'category': 'ASO Preparation',
'items': [
{'task': 'Keyword research completed', 'status': 'pending'},
{'task': 'Competitor analysis done', 'status': 'pending'},
{'task': 'A/B test plan created for post-launch', 'status': 'pending'},
{'task': 'Analytics tracking configured', 'status': 'pending'}
]
},
{
'category': 'Quality Assurance',
'items': [
{'task': 'All core features tested', 'status': 'pending'},
{'task': 'User flows validated', 'status': 'pending'},
{'task': 'Performance testing completed', 'status': 'pending'},
{'task': 'Accessibility features tested', 'status': 'pending'},
{'task': 'Security audit completed', 'status': 'pending'}
]
},
{
'category': 'Support Infrastructure',
'items': [
{'task': 'Support email/system setup', 'status': 'pending'},
{'task': 'FAQ page created', 'status': 'pending'},
{'task': 'Documentation for users prepared', 'status': 'pending'},
{'task': 'Team trained on handling reviews', 'status': 'pending'}
]
}
]
def _generate_launch_timeline(self, launch_date: str) -> List[Dict[str, Any]]:
"""Generate timeline with milestones leading to launch."""
launch_dt = datetime.strptime(launch_date, '%Y-%m-%d')
milestones = [
{
'date': (launch_dt - timedelta(days=90)).strftime('%Y-%m-%d'),
'milestone': '90 days before: Complete keyword research and competitor analysis'
},
{
'date': (launch_dt - timedelta(days=60)).strftime('%Y-%m-%d'),
'milestone': '60 days before: Finalize metadata and visual assets'
},
{
'date': (launch_dt - timedelta(days=45)).strftime('%Y-%m-%d'),
'milestone': '45 days before: Begin beta testing program'
},
{
'date': (launch_dt - timedelta(days=30)).strftime('%Y-%m-%d'),
'milestone': '30 days before: Submit app for review (Apple typically takes 1-2 days, Google instant)'
},
{
'date': (launch_dt - timedelta(days=14)).strftime('%Y-%m-%d'),
'milestone': '14 days before: Prepare launch marketing materials'
},
{
'date': (launch_dt - timedelta(days=7)).strftime('%Y-%m-%d'),
'milestone': '7 days before: Set up analytics and monitoring'
},
{
'date': launch_dt.strftime('%Y-%m-%d'),
'milestone': 'Launch Day: Release app and execute marketing plan'
},
{
'date': (launch_dt + timedelta(days=7)).strftime('%Y-%m-%d'),
'milestone': '7 days after: Monitor metrics, respond to reviews, address critical issues'
},
{
'date': (launch_dt + timedelta(days=30)).strftime('%Y-%m-%d'),
'milestone': '30 days after: Analyze launch metrics, plan first update'
}
]
return milestones
def _calculate_checklist_summary(self, checklists: Dict[str, List[Dict[str, Any]]]) -> Dict[str, Any]:
"""Calculate completion summary."""
total_items = 0
completed_items = 0
for platform, categories in checklists.items():
for category in categories:
for item in category['items']:
total_items += 1
if item['status'] == 'completed':
completed_items += 1
completion_percentage = (completed_items / total_items * 100) if total_items > 0 else 0
return {
'total_items': total_items,
'completed_items': completed_items,
'pending_items': total_items - completed_items,
'completion_percentage': round(completion_percentage, 1),
'is_ready_to_launch': completion_percentage == 100
}
def _validate_apple_compliance(
self,
app_data: Dict[str, Any],
validation_results: Dict[str, Any]
) -> None:
"""Validate Apple App Store compliance."""
# Check for required fields
if not app_data.get('privacy_policy_url'):
validation_results['errors'].append("Privacy Policy URL is required")
if not app_data.get('app_icon'):
validation_results['errors'].append("App icon (1024x1024px) is required")
# Check metadata character limits
title = app_data.get('title', '')
if len(title) > 30:
validation_results['errors'].append(f"Title exceeds 30 characters ({len(title)})")
# Warnings for best practices
subtitle = app_data.get('subtitle', '')
if not subtitle:
validation_results['warnings'].append("Subtitle is empty - consider adding for better discoverability")
keywords = app_data.get('keywords', '')
if len(keywords) < 80:
validation_results['warnings'].append(
f"Keywords field underutilized ({len(keywords)}/100 chars) - add more keywords"
)
def _validate_google_compliance(
self,
app_data: Dict[str, Any],
validation_results: Dict[str, Any]
) -> None:
"""Validate Google Play Store compliance."""
# Check for required fields
if not app_data.get('privacy_policy_url'):
validation_results['errors'].append("Privacy Policy URL is required")
if not app_data.get('feature_graphic'):
validation_results['errors'].append("Feature graphic (1024x500px) is required")
# Check metadata character limits
title = app_data.get('title', '')
if len(title) > 50:
validation_results['errors'].append(f"Title exceeds 50 characters ({len(title)})")
short_desc = app_data.get('short_description', '')
if len(short_desc) > 80:
validation_results['errors'].append(f"Short description exceeds 80 characters ({len(short_desc)})")
# Warnings
if not short_desc:
validation_results['warnings'].append("Short description is empty")
def _calculate_next_versions(
self,
current_version: str,
update_frequency: str,
feature_count: int
) -> List[str]:
"""Calculate next version numbers."""
# Parse current version (assume semantic versioning)
parts = current_version.split('.')
major, minor, patch = int(parts[0]), int(parts[1]), int(parts[2] if len(parts) > 2 else 0)
versions = []
for i in range(feature_count):
if update_frequency == 'weekly':
patch += 1
elif update_frequency == 'biweekly':
patch += 1
elif update_frequency == 'monthly':
minor += 1
patch = 0
else: # quarterly
minor += 1
patch = 0
versions.append(f"{major}.{minor}.{patch}")
return versions
def _distribute_features(
self,
features: List[str],
versions: List[str]
) -> List[Dict[str, Any]]:
"""Distribute features across versions."""
features_per_version = max(1, len(features) // len(versions))
schedule = []
for i, version in enumerate(versions):
start_idx = i * features_per_version
end_idx = start_idx + features_per_version if i < len(versions) - 1 else len(features)
schedule.append({
'version': version,
'features': features[start_idx:end_idx],
'release_priority': 'high' if i == 0 else ('medium' if i < len(versions) // 2 else 'low')
})
return schedule
def _generate_whats_new_template(self, version_data: Dict[str, Any]) -> Dict[str, str]:
"""Generate What's New template for version."""
features_list = '\n'.join([f"• {feature}" for feature in version_data['features']])
template = f"""Version {version_data['version']}
{features_list}
We're constantly improving your experience. Thanks for using [App Name]!
Have feedback? Contact us at support@[company].com"""
return {
'version': version_data['version'],
'template': template
}
def _generate_update_recommendations(self, update_frequency: str) -> List[str]:
"""Generate recommendations for update strategy."""
recommendations = []
if update_frequency == 'weekly':
recommendations.append("Weekly updates show active development but ensure quality doesn't suffer")
elif update_frequency == 'monthly':
recommendations.append("Monthly updates are optimal for most apps - balance features and stability")
recommendations.extend([
"Include bug fixes in every update",
"Update 'What's New' section with each release",
"Respond to reviews mentioning fixed issues"
])
return recommendations
def _recommend_day_of_week(self, app_category: str) -> Dict[str, Any]:
"""Recommend best day of week to launch."""
# General recommendations based on category
if app_category.lower() in ['games', 'entertainment']:
return {
'recommended_day': 'Thursday',
'rationale': 'People download entertainment apps before weekend'
}
elif app_category.lower() in ['productivity', 'business']:
return {
'recommended_day': 'Tuesday',
'rationale': 'Business users most active mid-week'
}
else:
return {
'recommended_day': 'Wednesday',
'rationale': 'Mid-week provides good balance and review potential'
}
def _recommend_seasonal_timing(self, app_category: str, current_date: str) -> Dict[str, Any]:
"""Recommend seasonal timing considerations."""
current_dt = datetime.strptime(current_date, '%Y-%m-%d')
month = current_dt.month
# Avoid certain periods
avoid_periods = []
if month == 12:
avoid_periods.append("Late December - low user engagement during holidays")
if month in [7, 8]:
avoid_periods.append("Summer months - some categories see lower engagement")
# Recommend periods
good_periods = []
if month in [1, 9]:
good_periods.append("New Year/Back-to-school - high user engagement")
if month in [10, 11]:
good_periods.append("Pre-holiday season - good for shopping/gift apps")
return {
'current_month': month,
'avoid_periods': avoid_periods,
'good_periods': good_periods
}
def _analyze_competitive_timing(self, app_category: str) -> Dict[str, str]:
"""Analyze competitive timing considerations."""
return {
'recommendation': 'Research competitor launch schedules in your category',
'strategy': 'Avoid launching same week as major competitor updates'
}
def _calculate_optimal_dates(
self,
current_date: str,
day_rec: Dict[str, Any],
seasonal_rec: Dict[str, Any]
) -> List[str]:
"""Calculate optimal launch dates."""
current_dt = datetime.strptime(current_date, '%Y-%m-%d')
# Find next occurrence of recommended day
target_day = day_rec['recommended_day']
days_map = {'Monday': 0, 'Tuesday': 1, 'Wednesday': 2, 'Thursday': 3, 'Friday': 4}
target_day_num = days_map.get(target_day, 2)
days_ahead = (target_day_num - current_dt.weekday()) % 7
if days_ahead == 0:
days_ahead = 7
next_target_date = current_dt + timedelta(days=days_ahead)
optimal_dates = [
next_target_date.strftime('%Y-%m-%d'),
(next_target_date + timedelta(days=7)).strftime('%Y-%m-%d'),
(next_target_date + timedelta(days=14)).strftime('%Y-%m-%d')
]
return optimal_dates
def _generate_timing_recommendation(
self,
optimal_dates: List[str],
seasonal_rec: Dict[str, Any]
) -> str:
"""Generate final timing recommendation."""
if seasonal_rec['avoid_periods']:
return f"Consider launching in {optimal_dates[1]} to avoid {seasonal_rec['avoid_periods'][0]}"
elif seasonal_rec['good_periods']:
return f"Launch on {optimal_dates[0]} to capitalize on {seasonal_rec['good_periods'][0]}"
else:
return f"Recommended launch date: {optimal_dates[0]}"
def _identify_seasonal_opportunities(
self,
app_category: str,
current_month: int
) -> List[Dict[str, Any]]:
"""Identify seasonal opportunities for category."""
opportunities = []
# Universal opportunities
if current_month == 1:
opportunities.append({
'event': 'New Year Resolutions',
'dates': 'January 1-31',
'relevance': 'high' if app_category.lower() in ['health', 'fitness', 'productivity'] else 'medium'
})
if current_month in [11, 12]:
opportunities.append({
'event': 'Holiday Shopping Season',
'dates': 'November-December',
'relevance': 'high' if app_category.lower() in ['shopping', 'gifts'] else 'low'
})
# Category-specific
if app_category.lower() == 'education' and current_month in [8, 9]:
opportunities.append({
'event': 'Back to School',
'dates': 'August-September',
'relevance': 'high'
})
return opportunities
def _generate_seasonal_campaign(self, opportunity: Dict[str, Any]) -> Dict[str, Any]:
"""Generate campaign idea for seasonal opportunity."""
return {
'event': opportunity['event'],
'campaign_idea': f"Create themed visuals and messaging for {opportunity['event']}",
'metadata_updates': 'Update app description and screenshots with seasonal themes',
'promotion_strategy': 'Consider limited-time features or discounts'
}
def _create_seasonal_timeline(self, campaigns: List[Dict[str, Any]]) -> List[str]:
"""Create implementation timeline for campaigns."""
return [
f"30 days before: Plan {campaign['event']} campaign strategy"
for campaign in campaigns
]
def generate_launch_checklist(
platform: str,
app_info: Dict[str, Any],
launch_date: Optional[str] = None
) -> Dict[str, Any]:
"""
Convenience function to generate launch checklist.
Args:
platform: Platform ('apple', 'google', or 'both')
app_info: App information
launch_date: Target launch date
Returns:
Complete launch checklist
"""
generator = LaunchChecklistGenerator(platform)
return generator.generate_prelaunch_checklist(app_info, launch_date)
FILE:scripts/localization_helper.py
"""
Localization helper module for App Store Optimization.
Manages multi-language ASO optimization strategies.
"""
from typing import Dict, List, Any, Optional, Tuple
class LocalizationHelper:
"""Helps manage multi-language ASO optimization."""
# Priority markets by language (based on app store revenue and user base)
PRIORITY_MARKETS = {
'tier_1': [
{'language': 'en-US', 'market': 'United States', 'revenue_share': 0.25},
{'language': 'zh-CN', 'market': 'China', 'revenue_share': 0.20},
{'language': 'ja-JP', 'market': 'Japan', 'revenue_share': 0.10},
{'language': 'de-DE', 'market': 'Germany', 'revenue_share': 0.08},
{'language': 'en-GB', 'market': 'United Kingdom', 'revenue_share': 0.06}
],
'tier_2': [
{'language': 'fr-FR', 'market': 'France', 'revenue_share': 0.05},
{'language': 'ko-KR', 'market': 'South Korea', 'revenue_share': 0.05},
{'language': 'es-ES', 'market': 'Spain', 'revenue_share': 0.03},
{'language': 'it-IT', 'market': 'Italy', 'revenue_share': 0.03},
{'language': 'pt-BR', 'market': 'Brazil', 'revenue_share': 0.03}
],
'tier_3': [
{'language': 'ru-RU', 'market': 'Russia', 'revenue_share': 0.02},
{'language': 'es-MX', 'market': 'Mexico', 'revenue_share': 0.02},
{'language': 'nl-NL', 'market': 'Netherlands', 'revenue_share': 0.02},
{'language': 'sv-SE', 'market': 'Sweden', 'revenue_share': 0.01},
{'language': 'pl-PL', 'market': 'Poland', 'revenue_share': 0.01}
]
}
# Character limit multipliers by language (some languages need more/less space)
CHAR_MULTIPLIERS = {
'en': 1.0,
'zh': 0.6, # Chinese characters are more compact
'ja': 0.7, # Japanese uses kanji
'ko': 0.8, # Korean is relatively compact
'de': 1.3, # German words are typically longer
'fr': 1.2, # French tends to be longer
'es': 1.1, # Spanish slightly longer
'pt': 1.1, # Portuguese similar to Spanish
'ru': 1.1, # Russian similar length
'ar': 1.0, # Arabic varies
'it': 1.1 # Italian similar to Spanish
}
def __init__(self, app_category: str = 'general'):
"""
Initialize localization helper.
Args:
app_category: App category to prioritize relevant markets
"""
self.app_category = app_category
self.localization_plans = []
def identify_target_markets(
self,
current_market: str = 'en-US',
budget_level: str = 'medium',
target_market_count: int = 5
) -> Dict[str, Any]:
"""
Recommend priority markets for localization.
Args:
current_market: Current/primary market
budget_level: 'low', 'medium', or 'high'
target_market_count: Number of markets to target
Returns:
Prioritized market recommendations
"""
# Determine tier priorities based on budget
if budget_level == 'low':
priority_tiers = ['tier_1']
max_markets = min(target_market_count, 3)
elif budget_level == 'medium':
priority_tiers = ['tier_1', 'tier_2']
max_markets = min(target_market_count, 8)
else: # high budget
priority_tiers = ['tier_1', 'tier_2', 'tier_3']
max_markets = target_market_count
# Collect markets from priority tiers
recommended_markets = []
for tier in priority_tiers:
for market in self.PRIORITY_MARKETS[tier]:
if market['language'] != current_market:
recommended_markets.append({
**market,
'tier': tier,
'estimated_translation_cost': self._estimate_translation_cost(
market['language']
)
})
# Sort by revenue share and limit
recommended_markets.sort(key=lambda x: x['revenue_share'], reverse=True)
recommended_markets = recommended_markets[:max_markets]
# Calculate potential ROI
total_potential_revenue_share = sum(m['revenue_share'] for m in recommended_markets)
return {
'recommended_markets': recommended_markets,
'total_markets': len(recommended_markets),
'estimated_total_revenue_lift': f"{total_potential_revenue_share*100:.1f}%",
'estimated_cost': self._estimate_total_localization_cost(recommended_markets),
'implementation_priority': self._prioritize_implementation(recommended_markets)
}
def translate_metadata(
self,
source_metadata: Dict[str, str],
source_language: str,
target_language: str,
platform: str = 'apple'
) -> Dict[str, Any]:
"""
Generate localized metadata with character limit considerations.
Args:
source_metadata: Original metadata (title, description, etc.)
source_language: Source language code (e.g., 'en')
target_language: Target language code (e.g., 'es')
platform: 'apple' or 'google'
Returns:
Localized metadata with character limit validation
"""
# Get character multiplier
target_lang_code = target_language.split('-')[0]
char_multiplier = self.CHAR_MULTIPLIERS.get(target_lang_code, 1.0)
# Platform-specific limits
if platform == 'apple':
limits = {'title': 30, 'subtitle': 30, 'description': 4000, 'keywords': 100}
else:
limits = {'title': 50, 'short_description': 80, 'description': 4000}
localized_metadata = {}
warnings = []
for field, text in source_metadata.items():
if field not in limits:
continue
# Estimate target length
estimated_length = int(len(text) * char_multiplier)
limit = limits[field]
localized_metadata[field] = {
'original_text': text,
'original_length': len(text),
'estimated_target_length': estimated_length,
'character_limit': limit,
'fits_within_limit': estimated_length <= limit,
'translation_notes': self._get_translation_notes(
field,
target_language,
estimated_length,
limit
)
}
if estimated_length > limit:
warnings.append(
f"{field}: Estimated length ({estimated_length}) may exceed limit ({limit}) - "
f"condensing may be required"
)
return {
'source_language': source_language,
'target_language': target_language,
'platform': platform,
'localized_fields': localized_metadata,
'character_multiplier': char_multiplier,
'warnings': warnings,
'recommendations': self._generate_translation_recommendations(
target_language,
warnings
)
}
def adapt_keywords(
self,
source_keywords: List[str],
source_language: str,
target_language: str,
target_market: str
) -> Dict[str, Any]:
"""
Adapt keywords for target market (not just direct translation).
Args:
source_keywords: Original keywords
source_language: Source language code
target_language: Target language code
target_market: Target market (e.g., 'France', 'Japan')
Returns:
Adapted keyword recommendations
"""
# Cultural adaptation considerations
cultural_notes = self._get_cultural_keyword_considerations(target_market)
# Search behavior differences
search_patterns = self._get_search_patterns(target_market)
adapted_keywords = []
for keyword in source_keywords:
adapted_keywords.append({
'source_keyword': keyword,
'adaptation_strategy': self._determine_adaptation_strategy(
keyword,
target_market
),
'cultural_considerations': cultural_notes.get(keyword, []),
'priority': 'high' if keyword in source_keywords[:3] else 'medium'
})
return {
'source_language': source_language,
'target_language': target_language,
'target_market': target_market,
'adapted_keywords': adapted_keywords,
'search_behavior_notes': search_patterns,
'recommendations': [
'Use native speakers for keyword research',
'Test keywords with local users before finalizing',
'Consider local competitors\' keyword strategies',
'Monitor search trends in target market'
]
}
def validate_translations(
self,
translated_metadata: Dict[str, str],
target_language: str,
platform: str = 'apple'
) -> Dict[str, Any]:
"""
Validate translated metadata for character limits and quality.
Args:
translated_metadata: Translated text fields
target_language: Target language code
platform: 'apple' or 'google'
Returns:
Validation report
"""
# Platform limits
if platform == 'apple':
limits = {'title': 30, 'subtitle': 30, 'description': 4000, 'keywords': 100}
else:
limits = {'title': 50, 'short_description': 80, 'description': 4000}
validation_results = {
'is_valid': True,
'field_validations': {},
'errors': [],
'warnings': []
}
for field, text in translated_metadata.items():
if field not in limits:
continue
actual_length = len(text)
limit = limits[field]
is_within_limit = actual_length <= limit
validation_results['field_validations'][field] = {
'text': text,
'length': actual_length,
'limit': limit,
'is_valid': is_within_limit,
'usage_percentage': round((actual_length / limit) * 100, 1)
}
if not is_within_limit:
validation_results['is_valid'] = False
validation_results['errors'].append(
f"{field} exceeds limit: {actual_length}/{limit} characters"
)
# Quality checks
quality_issues = self._check_translation_quality(
translated_metadata,
target_language
)
validation_results['quality_checks'] = quality_issues
if quality_issues:
validation_results['warnings'].extend(
[f"Quality issue: {issue}" for issue in quality_issues]
)
return validation_results
def calculate_localization_roi(
self,
target_markets: List[str],
current_monthly_downloads: int,
localization_cost: float,
expected_lift_percentage: float = 0.15
) -> Dict[str, Any]:
"""
Estimate ROI of localization investment.
Args:
target_markets: List of market codes
current_monthly_downloads: Current monthly downloads
localization_cost: Total cost to localize
expected_lift_percentage: Expected download increase (default 15%)
Returns:
ROI analysis
"""
# Estimate market-specific lift
market_data = []
total_expected_lift = 0
for market_code in target_markets:
# Find market in priority lists
market_info = None
for tier_name, markets in self.PRIORITY_MARKETS.items():
for m in markets:
if m['language'] == market_code:
market_info = m
break
if not market_info:
continue
# Estimate downloads from this market
market_downloads = int(current_monthly_downloads * market_info['revenue_share'])
expected_increase = int(market_downloads * expected_lift_percentage)
total_expected_lift += expected_increase
market_data.append({
'market': market_info['market'],
'current_monthly_downloads': market_downloads,
'expected_increase': expected_increase,
'revenue_potential': market_info['revenue_share']
})
# Calculate payback period (assuming $2 revenue per download)
revenue_per_download = 2.0
monthly_additional_revenue = total_expected_lift * revenue_per_download
payback_months = (localization_cost / monthly_additional_revenue) if monthly_additional_revenue > 0 else float('inf')
return {
'markets_analyzed': len(market_data),
'market_breakdown': market_data,
'total_expected_monthly_lift': total_expected_lift,
'expected_monthly_revenue_increase': f",.2f",
'localization_cost': f",.2f",
'payback_period_months': round(payback_months, 1) if payback_months != float('inf') else 'N/A',
'annual_roi': f"{((monthly_additional_revenue * 12 - localization_cost) / localization_cost * 100):.1f}%" if payback_months != float('inf') else 'Negative',
'recommendation': self._generate_roi_recommendation(payback_months)
}
def _estimate_translation_cost(self, language: str) -> Dict[str, float]:
"""Estimate translation cost for a language."""
# Base cost per word (professional translation)
base_cost_per_word = 0.12
# Language-specific multipliers
multipliers = {
'zh-CN': 1.5, # Chinese requires specialist
'ja-JP': 1.5, # Japanese requires specialist
'ko-KR': 1.3,
'ar-SA': 1.4, # Arabic (right-to-left)
'default': 1.0
}
multiplier = multipliers.get(language, multipliers['default'])
# Typical word counts for app store metadata
typical_word_counts = {
'title': 5,
'subtitle': 5,
'description': 300,
'keywords': 20,
'screenshots': 50 # Caption text
}
total_words = sum(typical_word_counts.values())
estimated_cost = total_words * base_cost_per_word * multiplier
return {
'cost_per_word': base_cost_per_word * multiplier,
'total_words': total_words,
'estimated_cost': round(estimated_cost, 2)
}
def _estimate_total_localization_cost(self, markets: List[Dict[str, Any]]) -> str:
"""Estimate total cost for multiple markets."""
total = sum(m['estimated_translation_cost']['estimated_cost'] for m in markets)
return f",.2f"
def _prioritize_implementation(self, markets: List[Dict[str, Any]]) -> List[Dict[str, str]]:
"""Create phased implementation plan."""
phases = []
# Phase 1: Top revenue markets
phase_1 = [m for m in markets[:3]]
if phase_1:
phases.append({
'phase': 'Phase 1 (First 30 days)',
'markets': ', '.join([m['market'] for m in phase_1]),
'rationale': 'Highest revenue potential markets'
})
# Phase 2: Remaining tier 1 and top tier 2
phase_2 = [m for m in markets[3:6]]
if phase_2:
phases.append({
'phase': 'Phase 2 (Days 31-60)',
'markets': ', '.join([m['market'] for m in phase_2]),
'rationale': 'Strong revenue markets with good ROI'
})
# Phase 3: Remaining markets
phase_3 = [m for m in markets[6:]]
if phase_3:
phases.append({
'phase': 'Phase 3 (Days 61-90)',
'markets': ', '.join([m['market'] for m in phase_3]),
'rationale': 'Complete global coverage'
})
return phases
def _get_translation_notes(
self,
field: str,
target_language: str,
estimated_length: int,
limit: int
) -> List[str]:
"""Get translation-specific notes for field."""
notes = []
if estimated_length > limit:
notes.append(f"Condensing required - aim for {limit - 10} characters to allow buffer")
if field == 'title' and target_language.startswith('zh'):
notes.append("Chinese characters convey more meaning - may need fewer characters")
if field == 'keywords' and target_language.startswith('de'):
notes.append("German compound words may be longer - prioritize shorter keywords")
return notes
def _generate_translation_recommendations(
self,
target_language: str,
warnings: List[str]
) -> List[str]:
"""Generate translation recommendations."""
recommendations = [
"Use professional native speakers for translation",
"Test translations with local users before finalizing"
]
if warnings:
recommendations.append("Work with translator to condense text while preserving meaning")
if target_language.startswith('zh') or target_language.startswith('ja'):
recommendations.append("Consider cultural context and local idioms")
return recommendations
def _get_cultural_keyword_considerations(self, target_market: str) -> Dict[str, List[str]]:
"""Get cultural considerations for keywords by market."""
# Simplified example - real implementation would be more comprehensive
considerations = {
'China': ['Avoid politically sensitive terms', 'Consider local alternatives to blocked services'],
'Japan': ['Honorific language important', 'Technical terms often use katakana'],
'Germany': ['Privacy and security terms resonate', 'Efficiency and quality valued'],
'France': ['French language protection laws', 'Prefer French terms over English'],
'default': ['Research local search behavior', 'Test with native speakers']
}
return considerations.get(target_market, considerations['default'])
def _get_search_patterns(self, target_market: str) -> List[str]:
"""Get search pattern notes for market."""
patterns = {
'China': ['Use both simplified characters and romanization', 'Brand names often romanized'],
'Japan': ['Mix of kanji, hiragana, and katakana', 'English words common in tech'],
'Germany': ['Compound words common', 'Specific technical terminology'],
'default': ['Research local search trends', 'Monitor competitor keywords']
}
return patterns.get(target_market, patterns['default'])
def _determine_adaptation_strategy(self, keyword: str, target_market: str) -> str:
"""Determine how to adapt keyword for market."""
# Simplified logic
if target_market in ['China', 'Japan', 'Korea']:
return 'full_localization' # Complete translation needed
elif target_market in ['Germany', 'France', 'Spain']:
return 'adapt_and_translate' # Some adaptation needed
else:
return 'direct_translation' # Direct translation usually sufficient
def _check_translation_quality(
self,
translated_metadata: Dict[str, str],
target_language: str
) -> List[str]:
"""Basic quality checks for translations."""
issues = []
# Check for untranslated placeholders
for field, text in translated_metadata.items():
if '[' in text or '{' in text or 'TODO' in text.upper():
issues.append(f"{field} contains placeholder text")
# Check for excessive punctuation
for field, text in translated_metadata.items():
if text.count('!') > 3:
issues.append(f"{field} has excessive exclamation marks")
return issues
def _generate_roi_recommendation(self, payback_months: float) -> str:
"""Generate ROI recommendation."""
if payback_months <= 3:
return "Excellent ROI - proceed immediately"
elif payback_months <= 6:
return "Good ROI - recommended investment"
elif payback_months <= 12:
return "Moderate ROI - consider if strategic market"
else:
return "Low ROI - reconsider or focus on higher-priority markets first"
def plan_localization_strategy(
current_market: str,
budget_level: str,
monthly_downloads: int
) -> Dict[str, Any]:
"""
Convenience function to plan localization strategy.
Args:
current_market: Current market code
budget_level: Budget level
monthly_downloads: Current monthly downloads
Returns:
Complete localization plan
"""
helper = LocalizationHelper()
target_markets = helper.identify_target_markets(
current_market=current_market,
budget_level=budget_level
)
# Extract market codes
market_codes = [m['language'] for m in target_markets['recommended_markets']]
# Calculate ROI
estimated_cost = float(target_markets['estimated_cost'].replace('$', '').replace(',', ''))
roi_analysis = helper.calculate_localization_roi(
market_codes,
monthly_downloads,
estimated_cost
)
return {
'target_markets': target_markets,
'roi_analysis': roi_analysis
}
FILE:scripts/metadata_optimizer.py
"""
Metadata optimization module for App Store Optimization.
Optimizes titles, descriptions, and keyword fields with platform-specific character limit validation.
"""
from typing import Dict, List, Any, Optional, Tuple
import re
class MetadataOptimizer:
"""Optimizes app store metadata for maximum discoverability and conversion."""
# Platform-specific character limits
CHAR_LIMITS = {
'apple': {
'title': 30,
'subtitle': 30,
'promotional_text': 170,
'description': 4000,
'keywords': 100,
'whats_new': 4000
},
'google': {
'title': 50,
'short_description': 80,
'full_description': 4000
}
}
def __init__(self, platform: str = 'apple'):
"""
Initialize metadata optimizer.
Args:
platform: 'apple' or 'google'
"""
if platform not in ['apple', 'google']:
raise ValueError("Platform must be 'apple' or 'google'")
self.platform = platform
self.limits = self.CHAR_LIMITS[platform]
def optimize_title(
self,
app_name: str,
target_keywords: List[str],
include_brand: bool = True
) -> Dict[str, Any]:
"""
Optimize app title with keyword integration.
Args:
app_name: Your app's brand name
target_keywords: List of keywords to potentially include
include_brand: Whether to include brand name
Returns:
Optimized title options with analysis
"""
max_length = self.limits['title']
title_options = []
# Option 1: Brand name only
if include_brand:
option1 = app_name[:max_length]
title_options.append({
'title': option1,
'length': len(option1),
'remaining_chars': max_length - len(option1),
'keywords_included': [],
'strategy': 'brand_only',
'pros': ['Maximum brand recognition', 'Clean and simple'],
'cons': ['No keyword targeting', 'Lower discoverability']
})
# Option 2: Brand + Primary Keyword
if target_keywords:
primary_keyword = target_keywords[0]
option2 = self._build_title_with_keywords(
app_name,
[primary_keyword],
max_length
)
if option2:
title_options.append({
'title': option2,
'length': len(option2),
'remaining_chars': max_length - len(option2),
'keywords_included': [primary_keyword],
'strategy': 'brand_plus_primary',
'pros': ['Targets main keyword', 'Maintains brand identity'],
'cons': ['Limited keyword coverage']
})
# Option 3: Brand + Multiple Keywords (if space allows)
if len(target_keywords) > 1:
option3 = self._build_title_with_keywords(
app_name,
target_keywords[:2],
max_length
)
if option3:
title_options.append({
'title': option3,
'length': len(option3),
'remaining_chars': max_length - len(option3),
'keywords_included': target_keywords[:2],
'strategy': 'brand_plus_multiple',
'pros': ['Multiple keyword targets', 'Better discoverability'],
'cons': ['May feel cluttered', 'Less brand focus']
})
# Option 4: Keyword-first approach (for new apps)
if target_keywords and not include_brand:
option4 = " ".join(target_keywords[:2])[:max_length]
title_options.append({
'title': option4,
'length': len(option4),
'remaining_chars': max_length - len(option4),
'keywords_included': target_keywords[:2],
'strategy': 'keyword_first',
'pros': ['Maximum SEO benefit', 'Clear functionality'],
'cons': ['No brand recognition', 'Generic appearance']
})
return {
'platform': self.platform,
'max_length': max_length,
'options': title_options,
'recommendation': self._recommend_title_option(title_options)
}
def optimize_description(
self,
app_info: Dict[str, Any],
target_keywords: List[str],
description_type: str = 'full'
) -> Dict[str, Any]:
"""
Optimize app description with keyword integration and conversion focus.
Args:
app_info: Dict with 'name', 'key_features', 'unique_value', 'target_audience'
target_keywords: List of keywords to integrate naturally
description_type: 'full', 'short' (Google), 'subtitle' (Apple)
Returns:
Optimized description with analysis
"""
if description_type == 'short' and self.platform == 'google':
return self._optimize_short_description(app_info, target_keywords)
elif description_type == 'subtitle' and self.platform == 'apple':
return self._optimize_subtitle(app_info, target_keywords)
else:
return self._optimize_full_description(app_info, target_keywords)
def optimize_keyword_field(
self,
target_keywords: List[str],
app_title: str = "",
app_description: str = ""
) -> Dict[str, Any]:
"""
Optimize Apple's 100-character keyword field.
Rules:
- No spaces between commas
- No plural forms if singular exists
- No duplicates
- Keywords in title/subtitle are already indexed
Args:
target_keywords: List of target keywords
app_title: Current app title (to avoid duplication)
app_description: Current description (to check coverage)
Returns:
Optimized keyword field (comma-separated, no spaces)
"""
if self.platform != 'apple':
return {'error': 'Keyword field optimization only applies to Apple App Store'}
max_length = self.limits['keywords']
# Extract words already in title (these don't need to be in keyword field)
title_words = set(app_title.lower().split()) if app_title else set()
# Process keywords
processed_keywords = []
for keyword in target_keywords:
keyword_lower = keyword.lower().strip()
# Skip if already in title
if keyword_lower in title_words:
continue
# Remove duplicates and process
words = keyword_lower.split()
for word in words:
if word not in processed_keywords and word not in title_words:
processed_keywords.append(word)
# Remove plurals if singular exists
deduplicated = self._remove_plural_duplicates(processed_keywords)
# Build keyword field within 100 character limit
keyword_field = self._build_keyword_field(deduplicated, max_length)
# Calculate keyword density in description
density = self._calculate_coverage(target_keywords, app_description)
return {
'keyword_field': keyword_field,
'length': len(keyword_field),
'remaining_chars': max_length - len(keyword_field),
'keywords_included': keyword_field.split(','),
'keywords_count': len(keyword_field.split(',')),
'keywords_excluded': [kw for kw in target_keywords if kw.lower() not in keyword_field],
'description_coverage': density,
'optimization_tips': [
'Keywords in title are auto-indexed - no need to repeat',
'Use singular forms only (Apple indexes plurals automatically)',
'No spaces between commas to maximize character usage',
'Update keyword field with each app update to test variations'
]
}
def validate_character_limits(
self,
metadata: Dict[str, str]
) -> Dict[str, Any]:
"""
Validate all metadata fields against platform character limits.
Args:
metadata: Dictionary of field_name: value
Returns:
Validation report with errors and warnings
"""
validation_results = {
'is_valid': True,
'errors': [],
'warnings': [],
'field_status': {}
}
for field_name, value in metadata.items():
if field_name not in self.limits:
validation_results['warnings'].append(
f"Unknown field '{field_name}' for {self.platform} platform"
)
continue
max_length = self.limits[field_name]
actual_length = len(value)
remaining = max_length - actual_length
field_status = {
'value': value,
'length': actual_length,
'limit': max_length,
'remaining': remaining,
'is_valid': actual_length <= max_length,
'usage_percentage': round((actual_length / max_length) * 100, 1)
}
validation_results['field_status'][field_name] = field_status
if actual_length > max_length:
validation_results['is_valid'] = False
validation_results['errors'].append(
f"'{field_name}' exceeds limit: {actual_length}/{max_length} chars"
)
elif remaining > max_length * 0.2: # More than 20% unused
validation_results['warnings'].append(
f"'{field_name}' under-utilizes space: {remaining} chars remaining"
)
return validation_results
def calculate_keyword_density(
self,
text: str,
target_keywords: List[str]
) -> Dict[str, Any]:
"""
Calculate keyword density in text.
Args:
text: Text to analyze
target_keywords: Keywords to check
Returns:
Density analysis
"""
text_lower = text.lower()
total_words = len(text_lower.split())
keyword_densities = {}
for keyword in target_keywords:
keyword_lower = keyword.lower()
count = text_lower.count(keyword_lower)
density = (count / total_words * 100) if total_words > 0 else 0
keyword_densities[keyword] = {
'occurrences': count,
'density_percentage': round(density, 2),
'status': self._assess_density(density)
}
# Overall assessment
total_keyword_occurrences = sum(kw['occurrences'] for kw in keyword_densities.values())
overall_density = (total_keyword_occurrences / total_words * 100) if total_words > 0 else 0
return {
'total_words': total_words,
'keyword_densities': keyword_densities,
'overall_keyword_density': round(overall_density, 2),
'assessment': self._assess_overall_density(overall_density),
'recommendations': self._generate_density_recommendations(keyword_densities)
}
def _build_title_with_keywords(
self,
app_name: str,
keywords: List[str],
max_length: int
) -> Optional[str]:
"""Build title combining app name and keywords within limit."""
separators = [' - ', ': ', ' | ']
for sep in separators:
for kw in keywords:
title = f"{app_name}{sep}{kw}"
if len(title) <= max_length:
return title
return None
def _optimize_short_description(
self,
app_info: Dict[str, Any],
target_keywords: List[str]
) -> Dict[str, Any]:
"""Optimize Google Play short description (80 chars)."""
max_length = self.limits['short_description']
# Focus on unique value proposition with primary keyword
unique_value = app_info.get('unique_value', '')
primary_keyword = target_keywords[0] if target_keywords else ''
# Template: [Primary Keyword] - [Unique Value]
short_desc = f"{primary_keyword.title()} - {unique_value}"[:max_length]
return {
'short_description': short_desc,
'length': len(short_desc),
'remaining_chars': max_length - len(short_desc),
'keywords_included': [primary_keyword] if primary_keyword in short_desc.lower() else [],
'strategy': 'keyword_value_proposition'
}
def _optimize_subtitle(
self,
app_info: Dict[str, Any],
target_keywords: List[str]
) -> Dict[str, Any]:
"""Optimize Apple App Store subtitle (30 chars)."""
max_length = self.limits['subtitle']
# Very concise - primary keyword or key feature
primary_keyword = target_keywords[0] if target_keywords else ''
key_feature = app_info.get('key_features', [''])[0] if app_info.get('key_features') else ''
options = [
primary_keyword[:max_length],
key_feature[:max_length],
f"{primary_keyword} App"[:max_length]
]
return {
'subtitle_options': [opt for opt in options if opt],
'max_length': max_length,
'recommendation': options[0] if options else ''
}
def _optimize_full_description(
self,
app_info: Dict[str, Any],
target_keywords: List[str]
) -> Dict[str, Any]:
"""Optimize full app description (4000 chars for both platforms)."""
max_length = self.limits.get('description', self.limits.get('full_description', 4000))
# Structure: Hook → Features → Benefits → Social Proof → CTA
sections = []
# Hook (with primary keyword)
primary_keyword = target_keywords[0] if target_keywords else ''
unique_value = app_info.get('unique_value', '')
hook = f"{unique_value} {primary_keyword.title()} that helps you achieve more.\n\n"
sections.append(hook)
# Features (with keywords naturally integrated)
features = app_info.get('key_features', [])
if features:
sections.append("KEY FEATURES:\n")
for i, feature in enumerate(features[:5], 1):
# Integrate keywords naturally
feature_text = f"• {feature}"
if i <= len(target_keywords):
keyword = target_keywords[i-1]
if keyword.lower() not in feature.lower():
feature_text = f"• {feature} with {keyword}"
sections.append(f"{feature_text}\n")
sections.append("\n")
# Benefits
target_audience = app_info.get('target_audience', 'users')
sections.append(f"PERFECT FOR:\n{target_audience}\n\n")
# Social proof placeholder
sections.append("WHY USERS LOVE US:\n")
sections.append("Join thousands of satisfied users who have transformed their workflow.\n\n")
# CTA
sections.append("Download now and start experiencing the difference!")
# Combine and validate length
full_description = "".join(sections)
if len(full_description) > max_length:
full_description = full_description[:max_length-3] + "..."
# Calculate keyword density
density = self.calculate_keyword_density(full_description, target_keywords)
return {
'full_description': full_description,
'length': len(full_description),
'remaining_chars': max_length - len(full_description),
'keyword_analysis': density,
'structure': {
'has_hook': True,
'has_features': len(features) > 0,
'has_benefits': True,
'has_cta': True
}
}
def _remove_plural_duplicates(self, keywords: List[str]) -> List[str]:
"""Remove plural forms if singular exists."""
deduplicated = []
singular_set = set()
for keyword in keywords:
if keyword.endswith('s') and len(keyword) > 1:
singular = keyword[:-1]
if singular not in singular_set:
deduplicated.append(singular)
singular_set.add(singular)
else:
if keyword not in singular_set:
deduplicated.append(keyword)
singular_set.add(keyword)
return deduplicated
def _build_keyword_field(self, keywords: List[str], max_length: int) -> str:
"""Build comma-separated keyword field within character limit."""
keyword_field = ""
for keyword in keywords:
test_field = f"{keyword_field},{keyword}" if keyword_field else keyword
if len(test_field) <= max_length:
keyword_field = test_field
else:
break
return keyword_field
def _calculate_coverage(self, keywords: List[str], text: str) -> Dict[str, int]:
"""Calculate how many keywords are covered in text."""
text_lower = text.lower()
coverage = {}
for keyword in keywords:
coverage[keyword] = text_lower.count(keyword.lower())
return coverage
def _assess_density(self, density: float) -> str:
"""Assess individual keyword density."""
if density < 0.5:
return "too_low"
elif density <= 2.5:
return "optimal"
else:
return "too_high"
def _assess_overall_density(self, density: float) -> str:
"""Assess overall keyword density."""
if density < 2:
return "Under-optimized: Consider adding more keyword variations"
elif density <= 5:
return "Optimal: Good keyword integration without stuffing"
elif density <= 8:
return "High: Approaching keyword stuffing - reduce keyword usage"
else:
return "Too High: Keyword stuffing detected - rewrite for natural flow"
def _generate_density_recommendations(
self,
keyword_densities: Dict[str, Dict[str, Any]]
) -> List[str]:
"""Generate recommendations based on keyword density analysis."""
recommendations = []
for keyword, data in keyword_densities.items():
if data['status'] == 'too_low':
recommendations.append(
f"Increase usage of '{keyword}' - currently only {data['occurrences']} times"
)
elif data['status'] == 'too_high':
recommendations.append(
f"Reduce usage of '{keyword}' - appears {data['occurrences']} times (keyword stuffing risk)"
)
if not recommendations:
recommendations.append("Keyword density is well-balanced")
return recommendations
def _recommend_title_option(self, options: List[Dict[str, Any]]) -> str:
"""Recommend best title option based on strategy."""
if not options:
return "No valid options available"
# Prefer brand_plus_primary for established apps
for option in options:
if option['strategy'] == 'brand_plus_primary':
return f"Recommended: '{option['title']}' (Balance of brand and SEO)"
# Fallback to first option
return f"Recommended: '{options[0]['title']}' ({options[0]['strategy']})"
def optimize_app_metadata(
platform: str,
app_info: Dict[str, Any],
target_keywords: List[str]
) -> Dict[str, Any]:
"""
Convenience function to optimize all metadata fields.
Args:
platform: 'apple' or 'google'
app_info: App information dictionary
target_keywords: Target keywords list
Returns:
Complete metadata optimization package
"""
optimizer = MetadataOptimizer(platform)
return {
'platform': platform,
'title': optimizer.optimize_title(
app_info['name'],
target_keywords
),
'description': optimizer.optimize_description(
app_info,
target_keywords,
'full'
),
'keyword_field': optimizer.optimize_keyword_field(
target_keywords
) if platform == 'apple' else None
}
FILE:scripts/review_analyzer.py
"""
Review analysis module for App Store Optimization.
Analyzes user reviews for sentiment, issues, and feature requests.
"""
from typing import Dict, List, Any, Optional, Tuple
from collections import Counter
import re
class ReviewAnalyzer:
"""Analyzes user reviews for actionable insights."""
# Sentiment keywords
POSITIVE_KEYWORDS = [
'great', 'awesome', 'excellent', 'amazing', 'love', 'best', 'perfect',
'fantastic', 'wonderful', 'brilliant', 'outstanding', 'superb'
]
NEGATIVE_KEYWORDS = [
'bad', 'terrible', 'awful', 'horrible', 'hate', 'worst', 'useless',
'broken', 'crash', 'bug', 'slow', 'disappointing', 'frustrating'
]
# Issue indicators
ISSUE_KEYWORDS = [
'crash', 'bug', 'error', 'broken', 'not working', 'doesnt work',
'freezes', 'slow', 'laggy', 'glitch', 'problem', 'issue', 'fail'
]
# Feature request indicators
FEATURE_REQUEST_KEYWORDS = [
'wish', 'would be nice', 'should add', 'need', 'want', 'hope',
'please add', 'missing', 'lacks', 'feature request'
]
def __init__(self, app_name: str):
"""
Initialize review analyzer.
Args:
app_name: Name of the app
"""
self.app_name = app_name
self.reviews = []
self.analysis_cache = {}
def analyze_sentiment(
self,
reviews: List[Dict[str, Any]]
) -> Dict[str, Any]:
"""
Analyze sentiment across reviews.
Args:
reviews: List of review dicts with 'text', 'rating', 'date'
Returns:
Sentiment analysis summary
"""
self.reviews = reviews
sentiment_counts = {
'positive': 0,
'neutral': 0,
'negative': 0
}
detailed_sentiments = []
for review in reviews:
text = review.get('text', '').lower()
rating = review.get('rating', 3)
# Calculate sentiment score
sentiment_score = self._calculate_sentiment_score(text, rating)
sentiment_category = self._categorize_sentiment(sentiment_score)
sentiment_counts[sentiment_category] += 1
detailed_sentiments.append({
'review_id': review.get('id', ''),
'rating': rating,
'sentiment_score': sentiment_score,
'sentiment': sentiment_category,
'text_preview': text[:100] + '...' if len(text) > 100 else text
})
# Calculate percentages
total = len(reviews)
sentiment_distribution = {
'positive': round((sentiment_counts['positive'] / total) * 100, 1) if total > 0 else 0,
'neutral': round((sentiment_counts['neutral'] / total) * 100, 1) if total > 0 else 0,
'negative': round((sentiment_counts['negative'] / total) * 100, 1) if total > 0 else 0
}
# Calculate average rating
avg_rating = sum(r.get('rating', 0) for r in reviews) / total if total > 0 else 0
return {
'total_reviews_analyzed': total,
'average_rating': round(avg_rating, 2),
'sentiment_distribution': sentiment_distribution,
'sentiment_counts': sentiment_counts,
'sentiment_trend': self._assess_sentiment_trend(sentiment_distribution),
'detailed_sentiments': detailed_sentiments[:50] # Limit output
}
def extract_common_themes(
self,
reviews: List[Dict[str, Any]],
min_mentions: int = 3
) -> Dict[str, Any]:
"""
Extract frequently mentioned themes and topics.
Args:
reviews: List of review dicts
min_mentions: Minimum mentions to be considered common
Returns:
Common themes analysis
"""
# Extract all words from reviews
all_words = []
all_phrases = []
for review in reviews:
text = review.get('text', '').lower()
# Clean text
text = re.sub(r'[^\w\s]', ' ', text)
words = text.split()
# Filter out common words
stop_words = {
'the', 'and', 'for', 'with', 'this', 'that', 'from', 'have',
'app', 'apps', 'very', 'really', 'just', 'but', 'not', 'you'
}
words = [w for w in words if w not in stop_words and len(w) > 3]
all_words.extend(words)
# Extract 2-3 word phrases
for i in range(len(words) - 1):
phrase = f"{words[i]} {words[i+1]}"
all_phrases.append(phrase)
# Count frequency
word_freq = Counter(all_words)
phrase_freq = Counter(all_phrases)
# Filter by min_mentions
common_words = [
{'word': word, 'mentions': count}
for word, count in word_freq.most_common(30)
if count >= min_mentions
]
common_phrases = [
{'phrase': phrase, 'mentions': count}
for phrase, count in phrase_freq.most_common(20)
if count >= min_mentions
]
# Categorize themes
themes = self._categorize_themes(common_words, common_phrases)
return {
'common_words': common_words,
'common_phrases': common_phrases,
'identified_themes': themes,
'insights': self._generate_theme_insights(themes)
}
def identify_issues(
self,
reviews: List[Dict[str, Any]],
rating_threshold: int = 3
) -> Dict[str, Any]:
"""
Identify bugs, crashes, and other issues from reviews.
Args:
reviews: List of review dicts
rating_threshold: Only analyze reviews at or below this rating
Returns:
Issue identification report
"""
issues = []
for review in reviews:
rating = review.get('rating', 5)
if rating > rating_threshold:
continue
text = review.get('text', '').lower()
# Check for issue keywords
mentioned_issues = []
for keyword in self.ISSUE_KEYWORDS:
if keyword in text:
mentioned_issues.append(keyword)
if mentioned_issues:
issues.append({
'review_id': review.get('id', ''),
'rating': rating,
'date': review.get('date', ''),
'issue_keywords': mentioned_issues,
'text': text[:200] + '...' if len(text) > 200 else text
})
# Group by issue type
issue_frequency = Counter()
for issue in issues:
for keyword in issue['issue_keywords']:
issue_frequency[keyword] += 1
# Categorize issues
categorized_issues = self._categorize_issues(issues)
# Calculate issue severity
severity_scores = self._calculate_issue_severity(
categorized_issues,
len(reviews)
)
return {
'total_issues_found': len(issues),
'issue_frequency': dict(issue_frequency.most_common(15)),
'categorized_issues': categorized_issues,
'severity_scores': severity_scores,
'top_issues': self._rank_issues_by_severity(severity_scores),
'recommendations': self._generate_issue_recommendations(
categorized_issues,
severity_scores
)
}
def find_feature_requests(
self,
reviews: List[Dict[str, Any]]
) -> Dict[str, Any]:
"""
Extract feature requests and desired improvements.
Args:
reviews: List of review dicts
Returns:
Feature request analysis
"""
feature_requests = []
for review in reviews:
text = review.get('text', '').lower()
rating = review.get('rating', 3)
# Check for feature request indicators
is_feature_request = any(
keyword in text
for keyword in self.FEATURE_REQUEST_KEYWORDS
)
if is_feature_request:
# Extract the specific request
request_text = self._extract_feature_request_text(text)
feature_requests.append({
'review_id': review.get('id', ''),
'rating': rating,
'date': review.get('date', ''),
'request_text': request_text,
'full_review': text[:200] + '...' if len(text) > 200 else text
})
# Cluster similar requests
clustered_requests = self._cluster_feature_requests(feature_requests)
# Prioritize based on frequency and rating context
prioritized_requests = self._prioritize_feature_requests(clustered_requests)
return {
'total_feature_requests': len(feature_requests),
'clustered_requests': clustered_requests,
'prioritized_requests': prioritized_requests,
'implementation_recommendations': self._generate_feature_recommendations(
prioritized_requests
)
}
def track_sentiment_trends(
self,
reviews_by_period: Dict[str, List[Dict[str, Any]]]
) -> Dict[str, Any]:
"""
Track sentiment changes over time.
Args:
reviews_by_period: Dict of period_name: reviews
Returns:
Trend analysis
"""
trends = []
for period, reviews in reviews_by_period.items():
sentiment = self.analyze_sentiment(reviews)
trends.append({
'period': period,
'total_reviews': len(reviews),
'average_rating': sentiment['average_rating'],
'positive_percentage': sentiment['sentiment_distribution']['positive'],
'negative_percentage': sentiment['sentiment_distribution']['negative']
})
# Calculate trend direction
if len(trends) >= 2:
first_period = trends[0]
last_period = trends[-1]
rating_change = last_period['average_rating'] - first_period['average_rating']
sentiment_change = last_period['positive_percentage'] - first_period['positive_percentage']
trend_direction = self._determine_trend_direction(
rating_change,
sentiment_change
)
else:
trend_direction = 'insufficient_data'
return {
'periods_analyzed': len(trends),
'trend_data': trends,
'trend_direction': trend_direction,
'insights': self._generate_trend_insights(trends, trend_direction)
}
def generate_response_templates(
self,
issue_category: str
) -> List[Dict[str, str]]:
"""
Generate response templates for common review scenarios.
Args:
issue_category: Category of issue ('crash', 'feature_request', 'positive', etc.)
Returns:
Response templates
"""
templates = {
'crash': [
{
'scenario': 'App crash reported',
'template': "Thank you for bringing this to our attention. We're sorry you experienced a crash. "
"Our team is investigating this issue. Could you please share more details about when "
"this occurred (device model, iOS/Android version) by contacting support@[company].com? "
"We're committed to fixing this quickly."
},
{
'scenario': 'Crash already fixed',
'template': "Thank you for your feedback. We've identified and fixed this crash issue in version [X.X]. "
"Please update to the latest version. If the problem persists, please reach out to "
"support@[company].com and we'll help you directly."
}
],
'bug': [
{
'scenario': 'Bug reported',
'template': "Thanks for reporting this bug. We take these issues seriously. Our team is looking into it "
"and we'll have a fix in an upcoming update. We appreciate your patience and will notify you "
"when it's resolved."
}
],
'feature_request': [
{
'scenario': 'Feature request received',
'template': "Thank you for this suggestion! We're always looking to improve [app_name]. We've added your "
"request to our roadmap and will consider it for a future update. Follow us @[social] for "
"updates on new features."
},
{
'scenario': 'Feature already planned',
'template': "Great news! This feature is already on our roadmap and we're working on it. Stay tuned for "
"updates in the coming months. Thanks for your feedback!"
}
],
'positive': [
{
'scenario': 'Positive review',
'template': "Thank you so much for your kind words! We're thrilled that you're enjoying [app_name]. "
"Reviews like yours motivate our team to keep improving. If you ever have suggestions, "
"we'd love to hear them!"
}
],
'negative_general': [
{
'scenario': 'General complaint',
'template': "We're sorry to hear you're not satisfied with your experience. We'd like to make this right. "
"Please contact us at support@[company].com so we can understand the issue better and help "
"you directly. Thank you for giving us a chance to improve."
}
]
}
return templates.get(issue_category, templates['negative_general'])
def _calculate_sentiment_score(self, text: str, rating: int) -> float:
"""Calculate sentiment score (-1 to 1)."""
# Start with rating-based score
rating_score = (rating - 3) / 2 # Convert 1-5 to -1 to 1
# Adjust based on text sentiment
positive_count = sum(1 for keyword in self.POSITIVE_KEYWORDS if keyword in text)
negative_count = sum(1 for keyword in self.NEGATIVE_KEYWORDS if keyword in text)
text_score = (positive_count - negative_count) / 10 # Normalize
# Weighted average (60% rating, 40% text)
final_score = (rating_score * 0.6) + (text_score * 0.4)
return max(min(final_score, 1.0), -1.0)
def _categorize_sentiment(self, score: float) -> str:
"""Categorize sentiment score."""
if score > 0.3:
return 'positive'
elif score < -0.3:
return 'negative'
else:
return 'neutral'
def _assess_sentiment_trend(self, distribution: Dict[str, float]) -> str:
"""Assess overall sentiment trend."""
positive = distribution['positive']
negative = distribution['negative']
if positive > 70:
return 'very_positive'
elif positive > 50:
return 'positive'
elif negative > 30:
return 'concerning'
elif negative > 50:
return 'critical'
else:
return 'mixed'
def _categorize_themes(
self,
common_words: List[Dict[str, Any]],
common_phrases: List[Dict[str, Any]]
) -> Dict[str, List[str]]:
"""Categorize themes from words and phrases."""
themes = {
'features': [],
'performance': [],
'usability': [],
'support': [],
'pricing': []
}
# Keywords for each category
feature_keywords = {'feature', 'functionality', 'option', 'tool'}
performance_keywords = {'fast', 'slow', 'crash', 'lag', 'speed', 'performance'}
usability_keywords = {'easy', 'difficult', 'intuitive', 'confusing', 'interface', 'design'}
support_keywords = {'support', 'help', 'customer', 'service', 'response'}
pricing_keywords = {'price', 'cost', 'expensive', 'cheap', 'subscription', 'free'}
for word_data in common_words:
word = word_data['word']
if any(kw in word for kw in feature_keywords):
themes['features'].append(word)
elif any(kw in word for kw in performance_keywords):
themes['performance'].append(word)
elif any(kw in word for kw in usability_keywords):
themes['usability'].append(word)
elif any(kw in word for kw in support_keywords):
themes['support'].append(word)
elif any(kw in word for kw in pricing_keywords):
themes['pricing'].append(word)
return {k: v for k, v in themes.items() if v} # Remove empty categories
def _generate_theme_insights(self, themes: Dict[str, List[str]]) -> List[str]:
"""Generate insights from themes."""
insights = []
for category, keywords in themes.items():
if keywords:
insights.append(
f"{category.title()}: Users frequently mention {', '.join(keywords[:3])}"
)
return insights[:5]
def _categorize_issues(self, issues: List[Dict[str, Any]]) -> Dict[str, List[Dict[str, Any]]]:
"""Categorize issues by type."""
categories = {
'crashes': [],
'bugs': [],
'performance': [],
'compatibility': []
}
for issue in issues:
keywords = issue['issue_keywords']
if 'crash' in keywords or 'freezes' in keywords:
categories['crashes'].append(issue)
elif 'bug' in keywords or 'error' in keywords or 'broken' in keywords:
categories['bugs'].append(issue)
elif 'slow' in keywords or 'laggy' in keywords:
categories['performance'].append(issue)
else:
categories['compatibility'].append(issue)
return {k: v for k, v in categories.items() if v}
def _calculate_issue_severity(
self,
categorized_issues: Dict[str, List[Dict[str, Any]]],
total_reviews: int
) -> Dict[str, Dict[str, Any]]:
"""Calculate severity scores for each issue category."""
severity_scores = {}
for category, issues in categorized_issues.items():
count = len(issues)
percentage = (count / total_reviews) * 100 if total_reviews > 0 else 0
# Calculate average rating of affected reviews
avg_rating = sum(i['rating'] for i in issues) / count if count > 0 else 0
# Severity score (0-100)
severity = min((percentage * 10) + ((5 - avg_rating) * 10), 100)
severity_scores[category] = {
'count': count,
'percentage': round(percentage, 2),
'average_rating': round(avg_rating, 2),
'severity_score': round(severity, 1),
'priority': 'critical' if severity > 70 else ('high' if severity > 40 else 'medium')
}
return severity_scores
def _rank_issues_by_severity(
self,
severity_scores: Dict[str, Dict[str, Any]]
) -> List[Dict[str, Any]]:
"""Rank issues by severity score."""
ranked = sorted(
[{'category': cat, **data} for cat, data in severity_scores.items()],
key=lambda x: x['severity_score'],
reverse=True
)
return ranked
def _generate_issue_recommendations(
self,
categorized_issues: Dict[str, List[Dict[str, Any]]],
severity_scores: Dict[str, Dict[str, Any]]
) -> List[str]:
"""Generate recommendations for addressing issues."""
recommendations = []
for category, score_data in severity_scores.items():
if score_data['priority'] == 'critical':
recommendations.append(
f"URGENT: Address {category} issues immediately - affecting {score_data['percentage']}% of reviews"
)
elif score_data['priority'] == 'high':
recommendations.append(
f"HIGH PRIORITY: Focus on {category} issues in next update"
)
return recommendations
def _extract_feature_request_text(self, text: str) -> str:
"""Extract the specific feature request from review text."""
# Simple extraction - find sentence with feature request keywords
sentences = text.split('.')
for sentence in sentences:
if any(keyword in sentence for keyword in self.FEATURE_REQUEST_KEYWORDS):
return sentence.strip()
return text[:100] # Fallback
def _cluster_feature_requests(
self,
feature_requests: List[Dict[str, Any]]
) -> List[Dict[str, Any]]:
"""Cluster similar feature requests."""
# Simplified clustering - group by common keywords
clusters = {}
for request in feature_requests:
text = request['request_text'].lower()
# Extract key words
words = [w for w in text.split() if len(w) > 4]
# Try to find matching cluster
matched = False
for cluster_key in clusters:
if any(word in cluster_key for word in words[:3]):
clusters[cluster_key].append(request)
matched = True
break
if not matched and words:
cluster_key = ' '.join(words[:2])
clusters[cluster_key] = [request]
return [
{'feature_theme': theme, 'request_count': len(requests), 'examples': requests[:3]}
for theme, requests in clusters.items()
]
def _prioritize_feature_requests(
self,
clustered_requests: List[Dict[str, Any]]
) -> List[Dict[str, Any]]:
"""Prioritize feature requests by frequency."""
return sorted(
clustered_requests,
key=lambda x: x['request_count'],
reverse=True
)[:10]
def _generate_feature_recommendations(
self,
prioritized_requests: List[Dict[str, Any]]
) -> List[str]:
"""Generate recommendations for feature requests."""
recommendations = []
if prioritized_requests:
top_request = prioritized_requests[0]
recommendations.append(
f"Most requested feature: {top_request['feature_theme']} "
f"({top_request['request_count']} mentions) - consider for next major release"
)
if len(prioritized_requests) > 1:
recommendations.append(
f"Also consider: {prioritized_requests[1]['feature_theme']}"
)
return recommendations
def _determine_trend_direction(
self,
rating_change: float,
sentiment_change: float
) -> str:
"""Determine overall trend direction."""
if rating_change > 0.2 and sentiment_change > 5:
return 'improving'
elif rating_change < -0.2 and sentiment_change < -5:
return 'declining'
else:
return 'stable'
def _generate_trend_insights(
self,
trends: List[Dict[str, Any]],
trend_direction: str
) -> List[str]:
"""Generate insights from trend analysis."""
insights = []
if trend_direction == 'improving':
insights.append("Positive trend: User satisfaction is increasing over time")
elif trend_direction == 'declining':
insights.append("WARNING: User satisfaction is declining - immediate action needed")
else:
insights.append("Sentiment is stable - maintain current quality")
# Review velocity insight
if len(trends) >= 2:
recent_reviews = trends[-1]['total_reviews']
previous_reviews = trends[-2]['total_reviews']
if recent_reviews > previous_reviews * 1.5:
insights.append("Review volume increasing - growing user base or recent controversy")
return insights
def analyze_reviews(
app_name: str,
reviews: List[Dict[str, Any]]
) -> Dict[str, Any]:
"""
Convenience function to perform comprehensive review analysis.
Args:
app_name: App name
reviews: List of review dictionaries
Returns:
Complete review analysis
"""
analyzer = ReviewAnalyzer(app_name)
return {
'sentiment_analysis': analyzer.analyze_sentiment(reviews),
'common_themes': analyzer.extract_common_themes(reviews),
'issues_identified': analyzer.identify_issues(reviews),
'feature_requests': analyzer.find_feature_requests(reviews)
}
Tạo và chỉnh sửa mẫu, blueprint, bố cục trang và thành phần tái sử dụng cho Jira và Confluence.
---
name: "atlassian-templates"
description: Atlassian Template and Files Creator/Modifier expert for creating, modifying, and managing Jira and Confluence templates, blueprints, custom layouts, reusable components, and standardized content structures. Use when building org-wide templates, custom blueprints, page layouts, and automated content generation.
---
# Atlassian Template & Files Creator Expert
Specialist in creating, modifying, and managing reusable templates and files for Jira and Confluence. Ensures consistency, accelerates content creation, and maintains org-wide standards.
---
## Workflows
### Template Creation Process
1. **Discover**: Interview stakeholders to understand needs
2. **Analyze**: Review existing content patterns
3. **Design**: Create template structure and placeholders
4. **Implement**: Build template with macros and formatting
5. **Test**: Validate with sample data — confirm template renders correctly in preview before publishing
6. **Document**: Create usage instructions
7. **Publish**: Deploy to appropriate space/project via MCP (see MCP Operations below)
8. **Verify**: Confirm deployment success; roll back to previous version if errors occur
9. **Train**: Educate users on template usage
10. **Monitor**: Track adoption and gather feedback
11. **Iterate**: Refine based on usage
### Template Modification Process
1. **Assess**: Review change request and impact
2. **Version**: Create new version, keep old available
3. **Modify**: Update template structure/content
4. **Test**: Validate changes don't break existing usage; preview updated template before publishing
5. **Migrate**: Provide migration path for existing content
6. **Communicate**: Announce changes to users
7. **Support**: Assist users with migration
8. **Archive**: Deprecate old version after transition; confirm deprecated template is unlisted, not deleted
### Blueprint Development
1. Define blueprint scope and purpose
2. Design multi-page structure
3. Create page templates for each section
4. Configure page creation rules
5. Add dynamic content (Jira queries, user data)
6. Test blueprint creation flow end-to-end with a sample space
7. Verify all macro references resolve correctly before deployment
8. **HANDOFF TO**: Atlassian Admin for global deployment
---
## Confluence Templates Library
See **TEMPLATES.md** for full reference tables and copy-paste-ready template structures. The following summarises the standard types this skill creates and maintains.
### Confluence Template Types
| Template | Purpose | Key Macros Used |
|----------|---------|-----------------|
| **Meeting Notes** | Structured meeting records with agenda, decisions, and action items | `{date}`, `{tasks}`, `{panel}`, `{info}`, `{note}` |
| **Project Charter** | Org-level project scope, stakeholder RACI, timeline, and budget | `{panel}`, `{status}`, `{timeline}`, `{info}` |
| **Sprint Retrospective** | Agile ceremony template with What Went Well / Didn't Go Well / Actions | `{panel}`, `{expand}`, `{tasks}`, `{status}` |
| **PRD** | Feature definition with goals, user stories, functional/non-functional requirements, and release plan | `{panel}`, `{status}`, `{jira}`, `{warning}` |
| **Decision Log** | Structured option analysis with decision matrix and implementation tracking | `{panel}`, `{status}`, `{info}`, `{tasks}` |
**Standard Sections** included across all Confluence templates:
- Header panel with metadata (owner, date, status)
- Clearly labelled content sections with inline placeholder instructions
- Action items block using `{tasks}` macro
- Related links and references
### Complete Example: Meeting Notes Template
The following is a copy-paste-ready Meeting Notes template in Confluence storage format (wiki markup):
```
{panel:title=Meeting Metadata|borderColor=#0052CC|titleBGColor=#0052CC|titleColor=#FFFFFF}
*Date:* {date}
*Owner / Facilitator:* @[facilitator name]
*Attendees:* @[name], @[name]
*Status:* {status:colour=Yellow|title=In Progress}
{panel}
h2. Agenda
# [Agenda item 1]
# [Agenda item 2]
# [Agenda item 3]
h2. Discussion & Decisions
{panel:title=Key Decisions|borderColor=#36B37E|titleBGColor=#36B37E|titleColor=#FFFFFF}
* *Decision 1:* [What was decided and why]
* *Decision 2:* [What was decided and why]
{panel}
{info:title=Notes}
[Detailed discussion notes, context, or background here]
{info}
h2. Action Items
{tasks}
* [ ] [Action item] — Owner: @[name] — Due: {date}
* [ ] [Action item] — Owner: @[name] — Due: {date}
{tasks}
h2. Next Steps & Related Links
* Next meeting: {date}
* Related pages: [link]
* Related Jira issues: {jira:key=PROJ-123}
```
> Full examples for all other template types (Project Charter, Sprint Retrospective, PRD, Decision Log) and all Jira templates can be generated on request or found in **TEMPLATES.md**.
---
## Jira Templates Library
### Jira Template Types
| Template | Purpose | Key Sections |
|----------|---------|--------------|
| **User Story** | Feature requests in As a / I want / So that format | Acceptance Criteria (Given/When/Then), Design links, Technical Notes, Definition of Done |
| **Bug Report** | Defect capture with reproduction steps | Environment, Steps to Reproduce, Expected vs Actual Behavior, Severity, Workaround |
| **Epic** | High-level initiative scope | Vision, Goals, Success Metrics, Story Breakdown, Dependencies, Timeline |
**Standard Sections** included across all Jira templates:
- Clear summary line
- Acceptance or success criteria as checkboxes
- Related issues and dependencies block
- Definition of Done (for stories)
---
## Macro Usage Guidelines
**Dynamic Content**: Use macros for auto-updating content (dates, user mentions, Jira queries)
**Visual Hierarchy**: Use `{panel}`, `{info}`, and `{note}` to create visual distinction
**Interactivity**: Use `{expand}` for collapsible sections in long templates
**Integration**: Embed Jira charts and tables via `{jira}` macro for live data
---
## Atlassian MCP Integration
**Primary Tools**: Confluence MCP, Jira MCP
### Template Operations via MCP
All MCP calls below use the exact parameter names expected by the Atlassian MCP server. Replace angle-bracket placeholders with real values before executing.
**Create a Confluence page template:**
```json
{
"tool": "confluence_create_page",
"parameters": {
"space_key": "PROJ",
"title": "Template: Meeting Notes",
"body": "<storage-format template content>",
"labels": ["template", "meeting-notes"],
"parent_id": "<optional parent page id>"
}
}
```
**Update an existing template:**
```json
{
"tool": "confluence_update_page",
"parameters": {
"page_id": "<existing page id>",
"version": "<current_version + 1>",
"title": "Template: Meeting Notes",
"body": "<updated storage-format content>",
"version_comment": "v2 — added status macro to header"
}
}
```
**Create a Jira issue description template (via field configuration):**
```json
{
"tool": "jira_update_field_configuration",
"parameters": {
"project_key": "PROJ",
"field_id": "description",
"default_value": "<template markdown or Atlassian Document Format JSON>"
}
}
```
**Deploy template to multiple spaces (batch):**
```json
// Repeat for each target space key
{
"tool": "confluence_create_page",
"parameters": {
"space_key": "<SPACE_KEY>",
"title": "Template: Meeting Notes",
"body": "<storage-format template content>",
"labels": ["template"]
}
}
// After each create, verify:
{
"tool": "confluence_get_page",
"parameters": {
"space_key": "<SPACE_KEY>",
"title": "Template: Meeting Notes"
}
}
// Assert response status == 200 and page body is non-empty before proceeding to next space
```
**Validation checkpoint after deployment:**
- Retrieve the created/updated page and assert it renders without macro errors
- Check that `{jira}` embeds resolve against the target Jira project
- Confirm `{tasks}` blocks are interactive in the published view
- If any check fails: revert using `confluence_update_page` with `version: <current + 1>` and the previous version body
---
## Best Practices & Governance
**Org-Specific Standards:**
- Track template versions with version notes in the page header
- Mark outdated templates with a `{warning}` banner before archiving; archive (do not delete)
- Maintain usage guides linked from each template
- Gather feedback on a quarterly review cycle; incorporate usage metrics before deprecating
**Quality Gates (apply before every deployment):**
- Example content provided for each section
- Tested with sample data in preview
- Version comment added to change log
- Feedback mechanism in place (comments enabled or linked survey)
**Governance Process**:
1. Request and justification
2. Design and review
3. Testing with pilot users
4. Documentation
5. Approval
6. Deployment (via MCP or manual)
7. Training
8. Monitoring
---
## Handoff Protocols
See **HANDOFFS.md** for the full handoff matrix. Summary:
| Partner | Receives FROM | Sends TO |
|---------|--------------|---------|
| **Senior PM** | Template requirements, reporting templates, executive formats | Completed templates, usage analytics, optimization suggestions |
| **Scrum Master** | Sprint ceremony needs, team-specific requests, retro format preferences | Sprint-ready templates, agile ceremony structures, velocity tracking templates |
| **Jira Expert** | Issue template requirements, custom field display needs | Issue description templates, field config templates, JQL query templates |
| **Confluence Expert** | Space-specific needs, global template requests, blueprint requirements | Configured page templates, blueprint structures, deployment plans |
| **Atlassian Admin** | Org-wide standards, global deployment requirements, compliance templates | Global templates for approval, usage reports, compliance status |
FILE:references/governance-framework.md
# Template Governance Framework
## Overview
Operational framework for managing template lifecycle — creation, updates, deprecation, and quality enforcement with concrete thresholds and decision criteria.
## Ownership Model
### Roles
**Template Owner** (1 per template):
- Reviews usage dashboard on the 1st of each quarter
- Archives templates with <5 uses in the past 90 days
- Responds to change requests within 14 calendar days
- Runs quarterly accuracy check: open 3 random pages created from the template, verify content matches current process
- Escalates to committee if change affects >50 users
**Template Steward** (1 per team/domain):
- Runs monthly usage pull: `CQL: type = page AND label = "template-{name}" AND created >= "now-30d"`
- Flags templates where >30% of users delete or heavily modify a section (indicates friction)
- Collects and triages feedback — tags Jira tickets with `template-change` and links to template page
**Template Committee** (3-5 people, for orgs with 20+ templates):
- Meets quarterly (45 min max): reviews new proposals, resolves conflicts, flags duplicates
- Decision rule: approve if template serves >1 team and doesn't duplicate existing template by >60% content overlap
- Publishes quarterly "Template Health" Confluence page with adoption rates and actions taken
### Assignment Matrix
| Template Category | Owner Role | Steward Role |
|------------------|-----------|-------------|
| Engineering templates | Engineering Manager | Senior Engineer |
| Product templates | Head of Product | Senior PM |
| Meeting templates | Operations Lead | EA/Admin |
| Project templates | PMO Lead | Senior PM |
| HR/People templates | HR Director | HR Coordinator |
| Company-wide templates | Operations Lead | Template Committee |
## Approval Workflow
### New Template Proposal
1. **Request** - Submitter creates proposal with:
- Template name and purpose
- Target audience
- Justification (why existing templates are insufficient)
- Draft content
- Proposed owner
2. **Review** - Template owner/committee evaluates:
- Does this duplicate an existing template?
- Is the scope appropriate (not too broad or narrow)?
- Does it follow design standards?
- Is it needed by more than one team?
3. **Pilot** - If approved:
- Deploy to a small group for 2-4 weeks
- Collect feedback on usability and completeness
- Iterate based on feedback
4. **Launch** - After successful pilot:
- Publish to template library
- Announce to relevant teams
- Add to Template Index page
- Train users if needed
5. **Monitor** - Post-launch:
- Track adoption rate (first 30 days)
- Collect initial feedback
- Make quick adjustments if needed
### Template Update Process
1. **Change Request** - Anyone can suggest changes via:
- Comment on the template page
- Jira ticket tagged `template-change`
- Direct message to template owner
2. **Assessment** - Owner evaluates:
- Impact on existing documents using this template
- Alignment with organizational standards
- Effort required for update
3. **Implementation** - Owner or steward:
- Makes changes in a draft version
- Reviews with 1-2 frequent users
- Updates version number and changelog
- Publishes updated template
4. **Communication** - Notify users:
- Post update in relevant Slack/Teams channel
- Update Template Index page
- Send email for major changes
## Change Management
### Impact Categories
**Low Impact (Owner decides):**
- Typo fixes and formatting improvements
- Clarifying existing instructions
- Adding optional sections
**Medium Impact (Owner + 1 reviewer):**
- Adding new required sections
- Changing variable names or structure
- Updating macro usage
**High Impact (Committee review):**
- Removing sections from widely-used templates
- Merging or splitting templates
- Changing organizational template standards
### Communication Plan
| Change Type | Communication | Timeline |
|------------|---------------|----------|
| Low impact | Changelog update | Same day |
| Medium impact | Team channel announcement | 1 week notice |
| High impact | Email + meeting + migration guide | 2-4 weeks notice |
## Deprecation Process
### When to Deprecate
- Template replaced by a better alternative
- Process the template supports has been retired
- Template has zero usage in the past 6 months
- Template is redundant with another active template
### Deprecation Steps
1. **Decision** - Owner proposes deprecation with justification
2. **Announcement** - Notify users 30 days before deprecation:
- Mark template with "DEPRECATED" status
- Add deprecation notice at top of template
- Point to replacement template (if applicable)
3. **Transition Period** - 30 days:
- Template still available but marked deprecated
- New documents should use replacement
- Existing documents do not need to change
4. **Archive** - After transition:
- Move template to Archive section
- Remove from active template list
- Keep accessible for historical reference
5. **Review** - 90 days after archive:
- Confirm no active usage
- Add to annual cleanup list if truly unused
## Usage Tracking
### Metrics & Thresholds
| Metric | Healthy | Flagged | Deprecate |
|--------|---------|---------|-----------|
| Pages created/month | >10 | 3-10 | <3 for 2 consecutive quarters |
| Unique users/month | >5 | 2-5 | 0-1 for 90 days |
| Section deletion rate | <10% | 10-30% | >30% (users removing sections = template mismatch) |
| Time to first use (new templates) | <7 days | 7-30 days | >30 days (failed launch, re-announce or rethink) |
### Tracking via CQL
```
-- Monthly usage for a specific template
type = page AND label = "template-sprint-retro" AND created >= "now-30d"
-- Stale templates (no usage in 90 days)
type = page AND label IN ("template-sprint-retro", "template-decision-log") AND created < "now-90d" AND created >= "now-91d"
-- All template-created pages for audit
type = page AND label = "template-*" ORDER BY created DESC
```
### Reporting
- **Monthly:** Top 10 templates by usage → auto-generated Confluence table
- **Quarterly:** Flag templates below thresholds → owner action required within 14 days
- **Annually:** Full catalog review — archive anything with 0 uses in 6 months
## Quality Standards
### Content Checklist (pass/fail per template)
- [ ] Every section has placeholder text showing expected content (not just a heading)
- [ ] No section references a process, tool, or team that no longer exists
- [ ] All Jira macro JQL filters return results (test quarterly)
- [ ] Links to other Confluence pages resolve (no 404s)
- [ ] Template renders correctly in both desktop and mobile preview
**FAIL examples:** Template says "Update the JIRA board" but team uses Linear. Template has a "QA Sign-Off" section but team has no QA role. Placeholder text says "TODO: add content here" with no guidance on what content.
### Structural Checklist
- [ ] Metadata header: owner name, version (semver), status (`active`/`deprecated`/`draft`), last-reviewed date
- [ ] Table of Contents macro if template has 4+ sections
- [ ] Change History table at bottom (date, author, change description)
- [ ] Placeholder text uses `{placeholder}` syntax or ac:placeholder macro — visually distinct from real content
### Maintenance Triggers
- Template not reviewed in 90+ days → owner gets Jira ticket auto-created via automation
- 3+ unresolved feedback items → escalate to committee
- Broken macro detected → owner notified same day via Slack/email automation
## Review Cadence
### Quarterly Review (Template Owner)
- Review usage metrics
- Address pending feedback
- Update content for accuracy
- Verify all links and macros work
- Update version number if changed
### Semi-Annual Review (Template Committee)
- Review full template catalog
- Identify gaps (missing templates)
- Identify overlaps (duplicate templates)
- Evaluate template standards compliance
- Plan improvements for next half
### Annual Review (Leadership — 60 min meeting)
**Agenda:**
1. (10 min) Catalog stats: total templates, usage trend YoY, templates added/deprecated
2. (15 min) Top 5 templates by adoption — what makes them work
3. (15 min) Bottom 5 templates — deprecate, rework, or retrain?
4. (10 min) Gaps identified by teams — templates requested but not yet built
5. (10 min) Governance process retro — is the framework itself working? Adjust thresholds, roles, or cadence as needed
**Deliverable:** Updated Template Health page published within 1 week of meeting
## Getting Started
### For New Organizations
1. Start with 5-10 essential templates (meeting notes, decision record, project plan)
2. Assign owners for each template
3. Establish basic quality standards
4. Review after 90 days and expand
5. Formalize governance as template count grows beyond 20
FILE:references/template-design-patterns.md
# Template Design Patterns
## Overview
Well-designed Confluence and Jira templates accelerate team productivity by providing consistent starting points for common documents and workflows. This guide covers design patterns, variable handling, and best practices for creating reusable templates.
## Variable Placeholders
### Confluence Template Variables
**Syntax:** `<at:var at:name="variableName">default value</at:var>`
**Common Variables:**
| Variable | Purpose | Example Default |
|----------|---------|----------------|
| `projectName` | Project identifier | "Project Name" |
| `author` | Document author | "@mention author" |
| `date` | Creation or target date | "YYYY-MM-DD" |
| `status` | Current document status | "Draft" |
| `version` | Document version | "1.0" |
| `owner` | Responsible person | "@mention owner" |
| `reviewers` | Review participants | "@mention reviewers" |
**Best Practices:**
- Use descriptive variable names (camelCase)
- Always provide meaningful default values
- Group related variables together
- Include instruction text that users should replace
- Use Status macro for status variables (visual clarity)
### Jira Template Fields
**Custom Fields for Templates:**
- Text fields for structured input
- Select lists for controlled vocabularies
- Date fields for milestones
- User pickers for assignments
- Labels for categorization
## Conditional Sections
### Pattern: Role-Based Sections
Include or exclude content based on the document's audience:
```
## For Engineering (delete if not applicable)
- Technical requirements
- Architecture decisions
- Performance criteria
## For Design (delete if not applicable)
- User flows
- Wireframes
- Accessibility requirements
## For Business (delete if not applicable)
- ROI analysis
- Market context
- Success metrics
```
### Pattern: Complexity-Based Sections
Scale content depth based on project size:
```
## Required for All Projects
- Problem statement
- Solution overview
- Success metrics
## Required for Medium+ Projects (>2 weeks)
- Detailed requirements
- Technical design
- Test plan
## Required for Large Projects (>1 month)
- Architecture review
- Security review
- Rollback plan
- Communication plan
```
### Pattern: Optional Deep Dives
Use Expand macros for optional detail:
```
[Expand: Detailed Requirements]
Content that power users may need but casual readers can skip
[/Expand]
```
## Reusable Components
### Header Block
Every template should start with a consistent header:
```
| Field | Value |
|-------|-------|
| Author | @mention |
| Status | [Status Macro: Draft] |
| Created | [Date] |
| Last Updated | [Date] |
| Reviewers | @mention |
| Approver | @mention |
```
### Decision Log Component
Reusable across templates that involve decisions:
```
## Decision Log
| # | Decision | Date | Decided By | Rationale |
|---|----------|------|-----------|-----------|
| 1 | [Decision] | [Date] | [Name] | [Why] |
```
### Change History Component
Track document evolution:
```
## Change History
| Version | Date | Author | Changes |
|---------|------|--------|---------|
| 1.0 | [Date] | [Name] | Initial version |
```
### Action Items Component
Standard task tracking:
```
## Action Items
- [ ] [Task description] - @assignee - Due: [date]
- [ ] [Task description] - @assignee - Due: [date]
```
## Macro Integration
### Recommended Macros per Template Type
**Meeting Notes Template:**
- Table of Contents (for long meetings)
- Action Items (task list macro)
- Jira Issues (link to discussed tickets)
- Expand (for detailed discussion notes)
**Decision Record Template:**
- Status macro (decision status)
- Page Properties (structured metadata)
- Info/Warning panels (context and caveats)
- Jira Issues (related tickets)
**Project Plan Template:**
- Roadmap Planner (timeline view)
- Jira Issues (JQL for project epics)
- Children Display (sub-pages for phases)
- Chart macro (status distribution)
**Runbook Template:**
- Code Block (commands and scripts)
- Warning panels (danger zones)
- Expand (detailed troubleshooting)
- Anchor links (quick navigation)
## Responsive Layouts
### Two-Column Layout
Use Confluence Section and Column macros:
```
[Section]
[Column: 60%]
Main content, description, details
[/Column]
[Column: 40%]
Sidebar: metadata, quick links, status
[/Column]
[/Section]
```
### Card Layout
For overview pages with multiple items:
```
[Section]
[Column: 33%]
[Panel: Card 1]
Title, summary, link
[/Panel]
[/Column]
[Column: 33%]
[Panel: Card 2]
[/Column]
[Column: 33%]
[Panel: Card 3]
[/Column]
[/Section]
```
## Brand Consistency
### Visual Standards
- Use consistent heading levels (H1 for title, H2 for sections, H3 for subsections)
- Apply Info/Warning/Note panels consistently (same meaning across templates)
- Use Status macro colors consistently (Green=done, Yellow=in progress, Red=blocked)
- Maintain consistent table formatting (header row, alignment)
### Content Standards
- Use the same voice and tone across templates
- Standardize date format (YYYY-MM-DD or your organization's preference)
- Use consistent terminology (define terms in a glossary)
- Include the same footer/metadata block in all templates
## Versioning Strategy
### Template Version Control
- Include version number in template metadata
- Maintain a changelog for template updates
- Communicate template changes to users
- Keep previous versions accessible during transition periods
### Version Numbering
- **Major (2.0):** Structural changes, section additions/removals
- **Minor (1.1):** Content updates, improved instructions
- **Patch (1.0.1):** Typo fixes, formatting corrections
### Migration Path
When updating templates:
1. Create new version alongside old version
2. Announce change with migration guide
3. New documents use new template automatically
4. Existing documents do not need to be migrated (optional)
5. Deprecate old template after 90 days
6. Archive old template (do not delete)
## Template Catalog Organization
### Categorization
Organize templates by:
- **Document type:** Meeting notes, decisions, plans, runbooks
- **Team:** Engineering, product, marketing, HR
- **Lifecycle:** Planning, execution, review, retrospective
- **Frequency:** One-time, recurring, as-needed
### Discovery
- Maintain a "Template Index" page with descriptions and links
- Tag templates with consistent labels
- Include a "When to Use" section in each template
- Provide examples of completed documents using the template
FILE:scripts/template_scaffolder.py
#!/usr/bin/env python3
"""
Template Scaffolder
Generates Confluence page template markup in storage-format XHTML. Supports
built-in template types and custom section definitions with optional macros.
Usage:
python template_scaffolder.py meeting-notes
python template_scaffolder.py decision-log --format json
python template_scaffolder.py custom --sections "Overview,Goals,Action Items" --macros toc,status
python template_scaffolder.py --list
"""
import argparse
import json
import sys
from datetime import datetime
from typing import Any, Dict, List, Optional
# ---------------------------------------------------------------------------
# Macro Generators
# ---------------------------------------------------------------------------
def macro_toc() -> str:
"""Generate table of contents macro."""
return '<ac:structured-macro ac:name="toc"><ac:parameter ac:name="printable">true</ac:parameter><ac:parameter ac:name="style">disc</ac:parameter><ac:parameter ac:name="maxLevel">3</ac:parameter></ac:structured-macro>'
def macro_status(text: str = "IN PROGRESS", color: str = "Yellow") -> str:
"""Generate status macro."""
return f'<ac:structured-macro ac:name="status"><ac:parameter ac:name="colour">{color}</ac:parameter><ac:parameter ac:name="title">{text}</ac:parameter></ac:structured-macro>'
def macro_info_panel(content: str) -> str:
"""Generate info panel macro."""
return f'<ac:structured-macro ac:name="info"><ac:rich-text-body><p>{content}</p></ac:rich-text-body></ac:structured-macro>'
def macro_warning_panel(content: str) -> str:
"""Generate warning panel macro."""
return f'<ac:structured-macro ac:name="warning"><ac:rich-text-body><p>{content}</p></ac:rich-text-body></ac:structured-macro>'
def macro_note_panel(content: str) -> str:
"""Generate note panel macro."""
return f'<ac:structured-macro ac:name="note"><ac:rich-text-body><p>{content}</p></ac:rich-text-body></ac:structured-macro>'
def macro_expand(title: str, content: str) -> str:
"""Generate expand/collapse macro."""
return f'<ac:structured-macro ac:name="expand"><ac:parameter ac:name="title">{title}</ac:parameter><ac:rich-text-body>{content}</ac:rich-text-body></ac:structured-macro>'
def macro_jira_issues(jql: str) -> str:
"""Generate Jira issues macro."""
return f'<ac:structured-macro ac:name="jira"><ac:parameter ac:name="jqlQuery">{jql}</ac:parameter><ac:parameter ac:name="columns">key,summary,type,created,updated,due,assignee,reporter,priority,status,resolution</ac:parameter></ac:structured-macro>'
MACRO_MAP = {
"toc": macro_toc,
"status": macro_status,
"info": macro_info_panel,
"warning": macro_warning_panel,
"note": macro_note_panel,
"expand": macro_expand,
"jira-issues": macro_jira_issues,
}
# ---------------------------------------------------------------------------
# Built-in Templates
# ---------------------------------------------------------------------------
def _section(title: str, content: str) -> str:
"""Generate a section with heading and content."""
return f'<h2>{title}</h2>\n{content}\n'
def _table(headers: List[str], rows: List[List[str]]) -> str:
"""Generate an XHTML table."""
parts = ['<table><colgroup>']
for _ in headers:
parts.append('<col />')
parts.append('</colgroup><thead><tr>')
for h in headers:
parts.append(f'<th><p>{h}</p></th>')
parts.append('</tr></thead><tbody>')
for row in rows:
parts.append('<tr>')
for cell in row:
parts.append(f'<td><p>{cell}</p></td>')
parts.append('</tr>')
parts.append('</tbody></table>')
return ''.join(parts)
def template_meeting_notes() -> Dict[str, Any]:
"""Generate meeting notes template."""
today = datetime.now().strftime("%Y-%m-%d")
body = macro_toc() + '\n'
body += macro_info_panel("Replace placeholder text with your meeting details.") + '\n'
body += _section("Meeting Details", _table(
["Field", "Value"],
[["Date", today], ["Time", ""], ["Location", ""], ["Facilitator", ""], ["Note Taker", ""]],
))
body += _section("Attendees", '<ul><li><p>Name 1</p></li><li><p>Name 2</p></li></ul>')
body += _section("Agenda", '<ol><li><p>Item 1</p></li><li><p>Item 2</p></li><li><p>Item 3</p></li></ol>')
body += _section("Discussion Notes", '<p>Summary of discussion points...</p>')
body += _section("Decisions Made", _table(
["Decision", "Owner", "Date"],
[["", "", today]],
))
body += _section("Action Items", _table(
["Action", "Owner", "Due Date", "Status"],
[["", "", "", macro_status("TODO", "Grey")]],
))
body += _section("Next Meeting", '<p>Date: TBD</p><p>Agenda items for next time:</p><ul><li><p></p></li></ul>')
return {"name": "Meeting Notes", "body": body, "labels": ["meeting-notes", "template"]}
def template_decision_log() -> Dict[str, Any]:
"""Generate decision log template."""
today = datetime.now().strftime("%Y-%m-%d")
body = macro_toc() + '\n'
body += _section("Decision Log", macro_info_panel("Track key decisions, context, and outcomes."))
body += _table(
["ID", "Date", "Decision", "Context", "Alternatives Considered", "Outcome", "Owner", "Status"],
[
["D-001", today, "", "", "", "", "", macro_status("DECIDED", "Green")],
["D-002", "", "", "", "", "", "", macro_status("PENDING", "Yellow")],
],
)
body += '\n'
body += _section("Decision Template", macro_expand("Decision Details Template",
'<h3>Context</h3><p>What is the issue or situation requiring a decision?</p>'
'<h3>Options</h3><ol><li><p>Option A - pros/cons</p></li><li><p>Option B - pros/cons</p></li></ol>'
'<h3>Decision</h3><p>What was decided and why?</p>'
'<h3>Consequences</h3><p>What are the expected outcomes?</p>'
))
return {"name": "Decision Log", "body": body, "labels": ["decision-log", "template"]}
def template_runbook() -> Dict[str, Any]:
"""Generate runbook template."""
body = macro_toc() + '\n'
body += macro_warning_panel("This runbook should be tested and reviewed quarterly.") + '\n'
body += _section("Overview", '<p>Brief description of what this runbook covers.</p>'
+ _table(["Field", "Value"], [
["Service/System", ""], ["Owner", ""], ["Last Tested", ""],
["Severity", ""], ["Estimated Duration", ""],
]))
body += _section("Prerequisites", '<ul><li><p>Access to system X</p></li><li><p>VPN connected</p></li><li><p>Required tools installed</p></li></ul>')
body += _section("Steps", '<ol><li><p><strong>Step 1:</strong> Description</p><ac:structured-macro ac:name="code"><ac:parameter ac:name="language">bash</ac:parameter><ac:plain-text-body><![CDATA[# command here]]></ac:plain-text-body></ac:structured-macro></li>'
'<li><p><strong>Step 2:</strong> Description</p></li>'
'<li><p><strong>Step 3:</strong> Description</p></li></ol>')
body += _section("Verification", '<p>How to verify the issue is resolved:</p><ul><li><p>Check 1</p></li><li><p>Check 2</p></li></ul>')
body += _section("Rollback", macro_note_panel("If the above steps do not resolve the issue, follow these rollback steps.") +
'<ol><li><p>Rollback step 1</p></li><li><p>Rollback step 2</p></li></ol>')
body += _section("Escalation", _table(
["Level", "Contact", "When to Escalate"],
[["L1", "", ""], ["L2", "", ""], ["L3", "", ""]],
))
return {"name": "Runbook", "body": body, "labels": ["runbook", "operations", "template"]}
def template_project_kickoff() -> Dict[str, Any]:
"""Generate project kickoff template."""
today = datetime.now().strftime("%Y-%m-%d")
body = macro_toc() + '\n'
body += _section("Project Overview", _table(
["Field", "Value"],
[["Project Name", ""], ["Start Date", today], ["Target End Date", ""],
["Project Lead", ""], ["Sponsor", ""], ["Status", macro_status("KICKOFF", "Blue")]],
))
body += _section("Vision & Goals", '<h3>Vision</h3><p>What does success look like?</p>'
'<h3>Goals</h3><ol><li><p>Goal 1</p></li><li><p>Goal 2</p></li><li><p>Goal 3</p></li></ol>')
body += _section("Scope", '<h3>In Scope</h3><ul><li><p></p></li></ul><h3>Out of Scope</h3><ul><li><p></p></li></ul>')
body += _section("Stakeholders", _table(
["Name", "Role", "Responsibility", "Communication Preference"],
[["", "", "", ""]],
))
body += _section("Timeline & Milestones", _table(
["Milestone", "Target Date", "Status"],
[["Phase 1", "", macro_status("NOT STARTED", "Grey")],
["Phase 2", "", macro_status("NOT STARTED", "Grey")]],
))
body += _section("Risks", _table(
["Risk", "Likelihood", "Impact", "Mitigation"],
[["", "High/Medium/Low", "High/Medium/Low", ""]],
))
body += _section("Next Steps", '<ul><li><p></p></li></ul>')
return {"name": "Project Kickoff", "body": body, "labels": ["project-kickoff", "template"]}
def template_sprint_retro() -> Dict[str, Any]:
"""Generate sprint retrospective template."""
body = macro_toc() + '\n'
body += _section("Sprint Info", _table(
["Field", "Value"],
[["Sprint", ""], ["Date Range", ""], ["Facilitator", ""],
["Velocity", ""], ["Commitment", ""], ["Completion Rate", ""]],
))
body += _section("What Went Well", '<ul><li><p></p></li></ul>')
body += _section("What Could Be Improved", '<ul><li><p></p></li></ul>')
body += _section("Action Items from Last Retro", _table(
["Action", "Owner", "Status"],
[["", "", macro_status("DONE", "Green")], ["", "", macro_status("IN PROGRESS", "Yellow")]],
))
body += _section("New Action Items", _table(
["Action", "Owner", "Due Date", "Priority"],
[["", "", "", "High/Medium/Low"]],
))
body += _section("Team Health Check", macro_info_panel("Rate each area 1-5 (1=needs work, 5=great)") + _table(
["Area", "Rating", "Trend", "Notes"],
[["Teamwork", "", "", ""], ["Delivery", "", "", ""],
["Fun", "", "", ""], ["Learning", "", "", ""]],
))
return {"name": "Sprint Retrospective", "body": body, "labels": ["sprint-retro", "agile", "template"]}
def template_how_to_guide() -> Dict[str, Any]:
"""Generate how-to guide template."""
body = macro_toc() + '\n'
body += macro_info_panel("This guide explains how to accomplish a specific task.") + '\n'
body += _section("Overview", '<p>Brief description of what this guide covers and who it is for.</p>')
body += _section("Prerequisites", '<ul><li><p>Prerequisite 1</p></li><li><p>Prerequisite 2</p></li></ul>')
body += _section("Step-by-Step Instructions",
'<h3>Step 1: Title</h3><p>Description of what to do.</p>'
'<h3>Step 2: Title</h3><p>Description of what to do.</p>'
'<h3>Step 3: Title</h3><p>Description of what to do.</p>')
body += _section("Troubleshooting", macro_expand("Common Issues",
'<h3>Issue 1</h3><p>Solution...</p>'
'<h3>Issue 2</h3><p>Solution...</p>'))
body += _section("Related Resources", '<ul><li><p>Link 1</p></li><li><p>Link 2</p></li></ul>')
return {"name": "How-To Guide", "body": body, "labels": ["how-to", "guide", "template"]}
TEMPLATE_REGISTRY = {
"meeting-notes": template_meeting_notes,
"decision-log": template_decision_log,
"runbook": template_runbook,
"project-kickoff": template_project_kickoff,
"sprint-retro": template_sprint_retro,
"how-to-guide": template_how_to_guide,
}
# ---------------------------------------------------------------------------
# Custom Template Builder
# ---------------------------------------------------------------------------
def build_custom_template(
sections: List[str],
macros: List[str],
) -> Dict[str, Any]:
"""Build a custom template from sections and macros."""
body = ""
# Add requested macros at the top
if "toc" in macros:
body += macro_toc() + '\n'
if "status" in macros:
body += '<p>Status: ' + macro_status() + '</p>\n'
for section in sections:
section = section.strip()
if not section:
continue
body += _section(section, '<p></p>')
# Add panels if requested
if "info" in macros:
body = macro_info_panel("Add instructions or context here.") + '\n' + body
if "warning" in macros:
body += macro_warning_panel("Add warnings here.") + '\n'
if "note" in macros:
body += macro_note_panel("Add notes here.") + '\n'
return {"name": "Custom Template", "body": body, "labels": ["custom", "template"]}
# ---------------------------------------------------------------------------
# Output Formatting
# ---------------------------------------------------------------------------
def format_text_output(result: Dict[str, Any]) -> str:
"""Format results as readable text report."""
lines = []
lines.append("=" * 60)
lines.append(f"TEMPLATE: {result['name']}")
lines.append("=" * 60)
lines.append("")
lines.append(f"Labels: {', '.join(result.get('labels', []))}")
lines.append("")
lines.append("CONFLUENCE STORAGE FORMAT MARKUP")
lines.append("-" * 30)
lines.append(result["body"])
return "\n".join(lines)
def format_json_output(result: Dict[str, Any]) -> Dict[str, Any]:
"""Format results as JSON."""
return result
def format_list_output(output_format: str) -> str:
"""Format available templates list."""
if output_format == "json":
templates = {}
for name, func in TEMPLATE_REGISTRY.items():
result = func()
templates[name] = {
"name": result["name"],
"labels": result["labels"],
}
return json.dumps(templates, indent=2)
lines = []
lines.append("=" * 60)
lines.append("AVAILABLE TEMPLATES")
lines.append("=" * 60)
lines.append("")
for name, func in TEMPLATE_REGISTRY.items():
result = func()
lines.append(f" {name}")
lines.append(f" Name: {result['name']}")
lines.append(f" Labels: {', '.join(result['labels'])}")
lines.append("")
lines.append(f"Total templates: {len(TEMPLATE_REGISTRY)}")
lines.append("")
lines.append("Usage:")
lines.append(" python template_scaffolder.py <template-name>")
lines.append(' python template_scaffolder.py custom --sections "Section1,Section2" --macros toc,status')
return "\n".join(lines)
# ---------------------------------------------------------------------------
# CLI Interface
# ---------------------------------------------------------------------------
def main() -> int:
"""Main CLI entry point."""
parser = argparse.ArgumentParser(
description="Generate Confluence page template markup"
)
parser.add_argument(
"template",
nargs="?",
help="Template name or 'custom' for custom template",
)
parser.add_argument(
"--format",
choices=["text", "json"],
default="text",
help="Output format (default: text)",
)
parser.add_argument(
"--list",
action="store_true",
help="List all available template types",
)
parser.add_argument(
"--sections",
help='Comma-separated section names for custom template (e.g., "Overview,Goals,Action Items")',
)
parser.add_argument(
"--macros",
help='Comma-separated macro names to include (e.g., "toc,status,info")',
)
args = parser.parse_args()
try:
if args.list:
print(format_list_output(args.format))
return 0
if not args.template:
parser.error("template name is required unless --list is used")
template_name = args.template.lower()
if template_name == "custom":
if not args.sections:
parser.error("--sections is required for custom templates")
sections = [s.strip() for s in args.sections.split(",")]
macros = [m.strip() for m in args.macros.split(",")] if args.macros else []
result = build_custom_template(sections, macros)
elif template_name in TEMPLATE_REGISTRY:
result = TEMPLATE_REGISTRY[template_name]()
else:
available = ", ".join(sorted(TEMPLATE_REGISTRY.keys()))
print(f"Error: Unknown template '{template_name}'. Available: {available}", file=sys.stderr)
return 1
if args.format == "json":
print(json.dumps(format_json_output(result), indent=2))
else:
print(format_text_output(result))
return 0
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
return 1
if __name__ == "__main__":
sys.exit(main())
Thiết kế kiến trúc AWS serverless, mẫu CloudFormation, tối ưu chi phí, CI/CD và di chuyển lên AWS.
---
name: "aws-solution-architect"
description: Design AWS architectures for startups using serverless patterns and IaC templates. Use when asked to design serverless architecture, create CloudFormation templates, optimize AWS costs, set up CI/CD pipelines, or migrate to AWS. Covers Lambda, API Gateway, DynamoDB, ECS, Aurora, and cost optimization.
---
# AWS Solution Architect
Design scalable, cost-effective AWS architectures for startups with infrastructure-as-code templates.
---
## Workflow
### Step 1: Gather Requirements
Collect application specifications:
```
- Application type (web app, mobile backend, data pipeline, SaaS)
- Expected users and requests per second
- Budget constraints (monthly spend limit)
- Team size and AWS experience level
- Compliance requirements (GDPR, HIPAA, SOC 2)
- Availability requirements (SLA, RPO/RTO)
```
### Step 2: Design Architecture
Run the architecture designer to get pattern recommendations:
```bash
python scripts/architecture_designer.py --input requirements.json
```
**Example output:**
```json
{
"recommended_pattern": "serverless_web",
"service_stack": ["S3", "CloudFront", "API Gateway", "Lambda", "DynamoDB", "Cognito"],
"estimated_monthly_cost_usd": 35,
"pros": ["Low ops overhead", "Pay-per-use", "Auto-scaling"],
"cons": ["Cold starts", "15-min Lambda limit", "Eventual consistency"]
}
```
Select from recommended patterns:
- **Serverless Web**: S3 + CloudFront + API Gateway + Lambda + DynamoDB
- **Event-Driven Microservices**: EventBridge + Lambda + SQS + Step Functions
- **Three-Tier**: ALB + ECS Fargate + Aurora + ElastiCache
- **GraphQL Backend**: AppSync + Lambda + DynamoDB + Cognito
See `references/architecture_patterns.md` for detailed pattern specifications.
**Validation checkpoint:** Confirm the recommended pattern matches the team's operational maturity and compliance requirements before proceeding to Step 3.
### Step 3: Generate IaC Templates
Create infrastructure-as-code for the selected pattern:
```bash
# Serverless stack (CloudFormation)
python scripts/serverless_stack.py --app-name my-app --region us-east-1
```
**Example CloudFormation YAML output (core serverless resources):**
```yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Parameters:
AppName:
Type: String
Default: my-app
Resources:
ApiFunction:
Type: AWS::Serverless::Function
Properties:
Handler: index.handler
Runtime: nodejs20.x
MemorySize: 512
Timeout: 30
Environment:
Variables:
TABLE_NAME: !Ref DataTable
Policies:
- DynamoDBCrudPolicy:
TableName: !Ref DataTable
Events:
ApiEvent:
Type: Api
Properties:
Path: /{proxy+}
Method: ANY
DataTable:
Type: AWS::DynamoDB::Table
Properties:
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: pk
AttributeType: S
- AttributeName: sk
AttributeType: S
KeySchema:
- AttributeName: pk
KeyType: HASH
- AttributeName: sk
KeyType: RANGE
```
> Full templates including API Gateway, Cognito, IAM roles, and CloudWatch logging are generated by `serverless_stack.py` and also available in `references/architecture_patterns.md`.
**Example CDK TypeScript snippet (three-tier pattern):**
```typescript
import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as rds from 'aws-cdk-lib/aws-rds';
const vpc = new ec2.Vpc(this, 'AppVpc', { maxAzs: 2 });
const cluster = new ecs.Cluster(this, 'AppCluster', { vpc });
const db = new rds.ServerlessCluster(this, 'AppDb', {
engine: rds.DatabaseClusterEngine.auroraPostgres({
version: rds.AuroraPostgresEngineVersion.VER_15_2,
}),
vpc,
scaling: { minCapacity: 0.5, maxCapacity: 4 },
});
```
### Step 4: Review Costs
Analyze estimated costs and optimization opportunities:
```bash
python scripts/cost_optimizer.py --resources current_setup.json --monthly-spend 2000
```
**Example output:**
```json
{
"current_monthly_usd": 2000,
"recommendations": [
{ "action": "Right-size RDS db.r5.2xlarge → db.r5.large", "savings_usd": 420, "priority": "high" },
{ "action": "Purchase 1-yr Compute Savings Plan at 40% utilization", "savings_usd": 310, "priority": "high" },
{ "action": "Move S3 objects >90 days to Glacier Instant Retrieval", "savings_usd": 85, "priority": "medium" }
],
"total_potential_savings_usd": 815
}
```
Output includes:
- Monthly cost breakdown by service
- Right-sizing recommendations
- Savings Plans opportunities
- Potential monthly savings
### Step 5: Deploy
Deploy the generated infrastructure:
```bash
# CloudFormation
aws cloudformation create-stack \
--stack-name my-app-stack \
--template-body file://template.yaml \
--capabilities CAPABILITY_IAM
# CDK
cdk deploy
# Terraform
terraform init && terraform apply
```
### Step 6: Validate and Handle Failures
Verify deployment and set up monitoring:
```bash
# Check stack status
aws cloudformation describe-stacks --stack-name my-app-stack
# Set up CloudWatch alarms
aws cloudwatch put-metric-alarm --alarm-name high-errors ...
```
**If stack creation fails:**
1. Check the failure reason:
```bash
aws cloudformation describe-stack-events \
--stack-name my-app-stack \
--query 'StackEvents[?ResourceStatus==`CREATE_FAILED`]'
```
2. Review CloudWatch Logs for Lambda or ECS errors.
3. Fix the template or resource configuration.
4. Delete the failed stack before retrying:
```bash
aws cloudformation delete-stack --stack-name my-app-stack
# Wait for deletion
aws cloudformation wait stack-delete-complete --stack-name my-app-stack
# Redeploy
aws cloudformation create-stack ...
```
**Common failure causes:**
- IAM permission errors → verify `--capabilities CAPABILITY_IAM` and role trust policies
- Resource limit exceeded → request quota increase via Service Quotas console
- Invalid template syntax → run `aws cloudformation validate-template --template-body file://template.yaml` before deploying
---
## Tools
### architecture_designer.py
Generates architecture patterns based on requirements.
```bash
python scripts/architecture_designer.py --input requirements.json --output design.json
```
**Input:** JSON with app type, scale, budget, compliance needs
**Output:** Recommended pattern, service stack, cost estimate, pros/cons
### serverless_stack.py
Creates serverless CloudFormation templates.
```bash
python scripts/serverless_stack.py --app-name my-app --region us-east-1
```
**Output:** Production-ready CloudFormation YAML with:
- API Gateway + Lambda
- DynamoDB table
- Cognito user pool
- IAM roles with least privilege
- CloudWatch logging
### cost_optimizer.py
Analyzes costs and recommends optimizations.
```bash
python scripts/cost_optimizer.py --resources inventory.json --monthly-spend 5000
```
**Output:** Recommendations for:
- Idle resource removal
- Instance right-sizing
- Reserved capacity purchases
- Storage tier transitions
- NAT Gateway alternatives
---
## Quick Start
### MVP Architecture (< $100/month)
```
Ask: "Design a serverless MVP backend for a mobile app with 1000 users"
Result:
- Lambda + API Gateway for API
- DynamoDB pay-per-request for data
- Cognito for authentication
- S3 + CloudFront for static assets
- Estimated: $20-50/month
```
### Scaling Architecture ($500-2000/month)
```
Ask: "Design a scalable architecture for a SaaS platform with 50k users"
Result:
- ECS Fargate for containerized API
- Aurora Serverless for relational data
- ElastiCache for session caching
- CloudFront for CDN
- CodePipeline for CI/CD
- Multi-AZ deployment
```
### Cost Optimization
```
Ask: "Optimize my AWS setup to reduce costs by 30%. Current spend: $3000/month"
Provide: Current resource inventory (EC2, RDS, S3, etc.)
Result:
- Idle resource identification
- Right-sizing recommendations
- Savings Plans analysis
- Storage lifecycle policies
- Target savings: $900/month
```
### IaC Generation
```
Ask: "Generate CloudFormation for a three-tier web app with auto-scaling"
Result:
- VPC with public/private subnets
- ALB with HTTPS
- ECS Fargate with auto-scaling
- Aurora with read replicas
- Security groups and IAM roles
```
---
## Input Requirements
Provide these details for architecture design:
| Requirement | Description | Example |
|-------------|-------------|---------|
| Application type | What you're building | SaaS platform, mobile backend |
| Expected scale | Users, requests/sec | 10k users, 100 RPS |
| Budget | Monthly AWS limit | $500/month max |
| Team context | Size, AWS experience | 3 devs, intermediate |
| Compliance | Regulatory needs | HIPAA, GDPR, SOC 2 |
| Availability | Uptime requirements | 99.9% SLA, 1hr RPO |
**JSON Format:**
```json
{
"application_type": "saas_platform",
"expected_users": 10000,
"requests_per_second": 100,
"budget_monthly_usd": 500,
"team_size": 3,
"aws_experience": "intermediate",
"compliance": ["SOC2"],
"availability_sla": "99.9%"
}
```
---
## Output Formats
### Architecture Design
- Pattern recommendation with rationale
- Service stack diagram (ASCII)
- Monthly cost estimate and trade-offs
### IaC Templates
- **CloudFormation YAML**: Production-ready SAM/CFN templates
- **CDK TypeScript**: Type-safe infrastructure code
- **Terraform HCL**: Multi-cloud compatible configs
### Cost Analysis
- Current spend breakdown with optimization recommendations
- Priority action list (high/medium/low) and implementation checklist
---
## Reference Documentation
| Document | Contents |
|----------|----------|
| `references/architecture_patterns.md` | 6 patterns: serverless, microservices, three-tier, data processing, GraphQL, multi-region |
| `references/service_selection.md` | Decision matrices for compute, database, storage, messaging |
| `references/best_practices.md` | Serverless design, cost optimization, security hardening, scalability |
FILE:assets/expected_output.json
{
"recommended_architecture": {
"pattern_name": "Modern Three-Tier Application",
"description": "Classic architecture with containers and managed services",
"estimated_monthly_cost": 1450,
"scaling_characteristics": {
"users_supported": "10k - 500k",
"requests_per_second": "1,000 - 50,000"
}
},
"services": {
"load_balancer": "Application Load Balancer (ALB)",
"compute": "ECS Fargate",
"database": "RDS Aurora (MySQL/PostgreSQL)",
"cache": "ElastiCache Redis",
"cdn": "CloudFront",
"storage": "S3",
"authentication": "Cognito"
},
"cost_breakdown": {
"ALB": "20-30 USD",
"ECS_Fargate": "50-200 USD",
"RDS_Aurora": "100-300 USD",
"ElastiCache": "30-80 USD",
"CloudFront": "10-50 USD",
"S3": "10-30 USD"
},
"implementation_phases": [
{
"phase": "Foundation",
"duration": "1 week",
"tasks": ["VPC setup", "IAM roles", "CloudTrail", "AWS Config"]
},
{
"phase": "Core Services",
"duration": "2 weeks",
"tasks": ["Deploy ALB", "ECS Fargate", "RDS Aurora", "ElastiCache"]
},
{
"phase": "Security & Monitoring",
"duration": "1 week",
"tasks": ["WAF rules", "CloudWatch dashboards", "Alarms", "X-Ray"]
},
{
"phase": "CI/CD",
"duration": "1 week",
"tasks": ["CodePipeline", "Blue/Green deployment", "Rollback procedures"]
}
],
"iac_templates_generated": [
"CloudFormation template (YAML)",
"AWS CDK stack (TypeScript)",
"Terraform configuration (HCL)"
]
}
FILE:assets/sample_input.json
{
"application_type": "saas_platform",
"expected_users": 50000,
"requests_per_second": 100,
"budget_monthly_usd": 1500,
"team_size": 5,
"aws_experience": "intermediate",
"compliance": ["GDPR"],
"data_size_gb": 500,
"region": "us-east-1",
"requirements": {
"authentication": true,
"real_time_features": false,
"multi_region": false,
"high_availability": true,
"auto_scaling": true
}
}
FILE:references/architecture_patterns.md
# AWS Architecture Patterns for Startups
Reference guide for selecting the right AWS architecture pattern based on application requirements.
---
## Table of Contents
- [Pattern Selection Matrix](#pattern-selection-matrix)
- [Pattern 1: Serverless Web Application](#pattern-1-serverless-web-application)
- [Pattern 2: Event-Driven Microservices](#pattern-2-event-driven-microservices)
- [Pattern 3: Modern Three-Tier Application](#pattern-3-modern-three-tier-application)
- [Pattern 4: Real-Time Data Processing](#pattern-4-real-time-data-processing)
- [Pattern 5: GraphQL API Backend](#pattern-5-graphql-api-backend)
- [Pattern 6: Multi-Region High Availability](#pattern-6-multi-region-high-availability)
---
## Pattern Selection Matrix
| Pattern | Best For | Users | Monthly Cost | Complexity |
|---------|----------|-------|--------------|------------|
| Serverless Web | MVP, SaaS, mobile backend | <50K | $50-500 | Low |
| Event-Driven Microservices | Complex workflows, async processing | Any | $100-1000 | Medium |
| Three-Tier | Traditional web, e-commerce | 10K-500K | $300-2000 | Medium |
| Real-Time Data | Analytics, IoT, streaming | Any | $200-1500 | High |
| GraphQL Backend | Mobile apps, SPAs | <100K | $50-400 | Medium |
| Multi-Region HA | Global apps, DR requirements | >100K | 1.5-2x single | High |
---
## Pattern 1: Serverless Web Application
### Use Case
SaaS platforms, mobile backends, low-traffic websites, MVPs
### Architecture Diagram
```
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ CloudFront │────▶│ S3 │ │ Cognito │
│ (CDN) │ │ (Static) │ │ (Auth) │
└─────────────┘ └─────────────┘ └──────┬──────┘
│
┌─────────────┐ ┌─────────────┐ ┌──────▼──────┐
│ Route 53 │────▶│ API Gateway │────▶│ Lambda │
│ (DNS) │ │ (REST) │ │ (Functions) │
└─────────────┘ └─────────────┘ └──────┬──────┘
│
┌──────▼──────┐
│ DynamoDB │
│ (Database) │
└─────────────┘
```
### Service Stack
| Layer | Service | Configuration |
|-------|---------|---------------|
| Frontend | S3 + CloudFront | Static hosting with HTTPS |
| API | API Gateway + Lambda | REST endpoints with throttling |
| Database | DynamoDB | Pay-per-request billing |
| Auth | Cognito | User pools with MFA support |
| CI/CD | Amplify or CodePipeline | Automated deployments |
### CloudFormation Template
```yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
# API Function
ApiFunction:
Type: AWS::Serverless::Function
Properties:
Runtime: nodejs18.x
Handler: index.handler
MemorySize: 512
Timeout: 10
Events:
Api:
Type: Api
Properties:
Path: /{proxy+}
Method: ANY
# DynamoDB Table
DataTable:
Type: AWS::DynamoDB::Table
Properties:
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: PK
AttributeType: S
- AttributeName: SK
AttributeType: S
KeySchema:
- AttributeName: PK
KeyType: HASH
- AttributeName: SK
KeyType: RANGE
```
### Cost Breakdown (10K users)
| Service | Monthly Cost |
|---------|-------------|
| Lambda | $5-20 |
| API Gateway | $10-30 |
| DynamoDB | $10-50 |
| CloudFront | $5-15 |
| S3 | $1-5 |
| Cognito | $0-50 |
| **Total** | **$31-170** |
### Pros and Cons
**Pros:**
- Zero server management
- Pay only for what you use
- Auto-scaling built-in
- Low operational overhead
**Cons:**
- Cold start latency (100-500ms)
- 15-minute Lambda execution limit
- Vendor lock-in
---
## Pattern 2: Event-Driven Microservices
### Use Case
Complex business workflows, asynchronous processing, decoupled systems
### Architecture Diagram
```
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Service │────▶│ EventBridge │────▶│ Service │
│ A │ │ (Event Bus)│ │ B │
└─────────────┘ └──────┬──────┘ └─────────────┘
│
┌──────▼──────┐
│ SQS │
│ (Queue) │
└──────┬──────┘
│
┌─────────────┐ ┌──────▼──────┐ ┌─────────────┐
│ Step │◀────│ Lambda │────▶│ DynamoDB │
│ Functions │ │ (Processor) │ │ (Storage) │
└─────────────┘ └─────────────┘ └─────────────┘
```
### Service Stack
| Layer | Service | Purpose |
|-------|---------|---------|
| Events | EventBridge | Central event bus |
| Processing | Lambda or ECS Fargate | Event handlers |
| Queue | SQS | Dead letter queue for failures |
| Orchestration | Step Functions | Complex workflow state |
| Storage | DynamoDB, S3 | Persistent data |
### Event Schema Example
```json
{
"source": "orders.service",
"detail-type": "OrderCreated",
"detail": {
"orderId": "ord-12345",
"customerId": "cust-67890",
"items": [...],
"total": 99.99,
"timestamp": "2024-01-15T10:30:00Z"
}
}
```
### Cost Breakdown
| Service | Monthly Cost |
|---------|-------------|
| EventBridge | $1-10 |
| Lambda | $20-100 |
| SQS | $5-20 |
| Step Functions | $25-100 |
| DynamoDB | $20-100 |
| **Total** | **$71-330** |
### Pros and Cons
**Pros:**
- Loose coupling between services
- Independent scaling per service
- Failure isolation
- Easy to test individually
**Cons:**
- Distributed system complexity
- Eventual consistency
- Harder to debug
---
## Pattern 3: Modern Three-Tier Application
### Use Case
Traditional web apps, e-commerce, CMS, applications with complex queries
### Architecture Diagram
```
┌─────────────┐ ┌─────────────┐
│ CloudFront │────▶│ ALB │
│ (CDN) │ │ (Load Bal.) │
└─────────────┘ └──────┬──────┘
│
┌──────▼──────┐
│ ECS Fargate │
│ (Auto-scale)│
└──────┬──────┘
│
┌──────────────────┼──────────────────┐
│ │ │
┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐
│ Aurora │ │ ElastiCache │ │ S3 │
│ (Database) │ │ (Redis) │ │ (Storage) │
└─────────────┘ └─────────────┘ └─────────────┘
```
### Service Stack
| Layer | Service | Configuration |
|-------|---------|---------------|
| CDN | CloudFront | Edge caching, HTTPS |
| Load Balancer | ALB | Path-based routing, health checks |
| Compute | ECS Fargate | Container auto-scaling |
| Database | Aurora MySQL/PostgreSQL | Multi-AZ, auto-scaling |
| Cache | ElastiCache Redis | Session, query caching |
| Storage | S3 | Static assets, uploads |
### Terraform Example
```hcl
# ECS Service with Auto-scaling
resource "aws_ecs_service" "app" {
name = "app-service"
cluster = aws_ecs_cluster.main.id
task_definition = aws_ecs_task_definition.app.arn
desired_count = 2
capacity_provider_strategy {
capacity_provider = "FARGATE"
weight = 100
}
load_balancer {
target_group_arn = aws_lb_target_group.app.arn
container_name = "app"
container_port = 3000
}
}
# Auto-scaling Policy
resource "aws_appautoscaling_target" "app" {
max_capacity = 10
min_capacity = 2
resource_id = "service/aws_ecs_cluster.main.name/aws_ecs_service.app.name"
scalable_dimension = "ecs:service:DesiredCount"
service_namespace = "ecs"
}
```
### Cost Breakdown (50K users)
| Service | Monthly Cost |
|---------|-------------|
| ECS Fargate (2 tasks) | $100-200 |
| ALB | $25-50 |
| Aurora | $100-300 |
| ElastiCache | $50-100 |
| CloudFront | $20-50 |
| **Total** | **$295-700** |
---
## Pattern 4: Real-Time Data Processing
### Use Case
Analytics, IoT data ingestion, log processing, streaming data
### Architecture Diagram
```
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ IoT Core │────▶│ Kinesis │────▶│ Lambda │
│ (Devices) │ │ (Stream) │ │ (Process) │
└─────────────┘ └─────────────┘ └──────┬──────┘
│
┌─────────────┐ ┌─────────────┐ ┌──────▼──────┐
│ QuickSight │◀────│ Athena │◀────│ S3 │
│ (Viz) │ │ (Query) │ │ (Data Lake) │
└─────────────┘ └─────────────┘ └─────────────┘
│
┌──────▼──────┐
│ CloudWatch │
│ (Alerts) │
└─────────────┘
```
### Service Stack
| Layer | Service | Purpose |
|-------|---------|---------|
| Ingestion | Kinesis Data Streams | Real-time data capture |
| Processing | Lambda or Kinesis Analytics | Transform and analyze |
| Storage | S3 (data lake) | Long-term storage |
| Query | Athena | SQL queries on S3 |
| Visualization | QuickSight | Dashboards and reports |
| Alerting | CloudWatch + SNS | Threshold-based alerts |
### Kinesis Producer Example
```python
import boto3
import json
kinesis = boto3.client('kinesis')
def send_event(stream_name, data, partition_key):
response = kinesis.put_record(
StreamName=stream_name,
Data=json.dumps(data),
PartitionKey=partition_key
)
return response['SequenceNumber']
# Send sensor reading
send_event(
'sensor-stream',
{'sensor_id': 'temp-01', 'value': 23.5, 'unit': 'celsius'},
'sensor-01'
)
```
### Cost Breakdown
| Service | Monthly Cost |
|---------|-------------|
| Kinesis (1 shard) | $15-30 |
| Lambda | $10-50 |
| S3 | $5-50 |
| Athena | $5-25 |
| QuickSight | $24+ |
| **Total** | **$59-179** |
---
## Pattern 5: GraphQL API Backend
### Use Case
Mobile apps, single-page applications, flexible data queries
### Architecture Diagram
```
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Mobile App │────▶│ AppSync │────▶│ Lambda │
│ or SPA │ │ (GraphQL) │ │ (Resolvers) │
└─────────────┘ └──────┬──────┘ └─────────────┘
│
┌──────▼──────┐
│ DynamoDB │
│ (Direct) │
└──────┬──────┘
│
┌──────▼──────┐
│ Cognito │
│ (Auth) │
└─────────────┘
```
### AppSync Schema Example
```graphql
type Query {
getUser(id: ID!): User
listPosts(limit: Int, nextToken: String): PostConnection
}
type Mutation {
createPost(input: CreatePostInput!): Post
updatePost(input: UpdatePostInput!): Post
}
type Subscription {
onCreatePost: Post @aws_subscribe(mutations: ["createPost"])
}
type User {
id: ID!
email: String!
posts: [Post]
}
type Post {
id: ID!
title: String!
content: String!
author: User!
createdAt: AWSDateTime!
}
```
### Cost Breakdown
| Service | Monthly Cost |
|---------|-------------|
| AppSync | $4-40 |
| Lambda | $5-30 |
| DynamoDB | $10-50 |
| Cognito | $0-50 |
| **Total** | **$19-170** |
---
## Pattern 6: Multi-Region High Availability
### Use Case
Global applications, disaster recovery, data sovereignty compliance
### Architecture Diagram
```
┌─────────────┐
│ Route 53 │
│(Geo routing)│
└──────┬──────┘
│
┌────────────────┼────────────────┐
│ │
┌──────▼──────┐ ┌──────▼──────┐
│ us-east-1 │ │ eu-west-1 │
│ CloudFront │ │ CloudFront │
└──────┬──────┘ └──────┬──────┘
│ │
┌──────▼──────┐ ┌──────▼──────┐
│ ECS/Lambda │ │ ECS/Lambda │
└──────┬──────┘ └──────┬──────┘
│ │
┌──────▼──────┐◀── Replication ──▶┌──────▼──────┐
│ DynamoDB │ │ DynamoDB │
│Global Table │ │Global Table │
└─────────────┘ └─────────────┘
```
### Service Stack
| Component | Service | Configuration |
|-----------|---------|---------------|
| DNS | Route 53 | Geolocation or latency routing |
| CDN | CloudFront | Multiple origins per region |
| Compute | Lambda or ECS | Deployed in each region |
| Database | DynamoDB Global Tables | Automatic replication |
| Storage | S3 CRR | Cross-region replication |
### Route 53 Failover Policy
```yaml
# Primary record
HealthCheck:
Type: AWS::Route53::HealthCheck
Properties:
HealthCheckConfig:
Port: 443
Type: HTTPS
ResourcePath: /health
FullyQualifiedDomainName: api-us-east-1.example.com
RecordSetPrimary:
Type: AWS::Route53::RecordSet
Properties:
Name: api.example.com
Type: A
SetIdentifier: primary
Failover: PRIMARY
HealthCheckId: !Ref HealthCheck
AliasTarget:
DNSName: !GetAtt USEast1ALB.DNSName
HostedZoneId: !GetAtt USEast1ALB.CanonicalHostedZoneID
```
### Cost Considerations
| Factor | Impact |
|--------|--------|
| Compute | 2x (each region) |
| Database | 25% premium for global tables |
| Data Transfer | Cross-region replication costs |
| Route 53 | Health checks + geo queries |
| **Total** | **1.5-2x single region** |
---
## Pattern Comparison Summary
### Latency
| Pattern | Typical Latency |
|---------|-----------------|
| Serverless | 50-200ms (cold: 500ms+) |
| Three-Tier | 20-100ms |
| GraphQL | 30-150ms |
| Multi-Region | <50ms (regional) |
### Scaling Characteristics
| Pattern | Scale Limit | Scale Speed |
|---------|-------------|-------------|
| Serverless | 1000 concurrent/function | Instant |
| Three-Tier | Instance limits | Minutes |
| Event-Driven | Unlimited | Instant |
| Multi-Region | Regional limits | Instant |
### Operational Complexity
| Pattern | Setup | Maintenance | Debugging |
|---------|-------|-------------|-----------|
| Serverless | Low | Low | Medium |
| Three-Tier | Medium | Medium | Low |
| Event-Driven | High | Medium | High |
| Multi-Region | High | High | High |
FILE:references/best_practices.md
# AWS Best Practices for Startups
Production-ready practices for serverless, cost optimization, security, and operational excellence.
---
## Table of Contents
- [Serverless Best Practices](#serverless-best-practices)
- [Cost Optimization](#cost-optimization)
- [Security Hardening](#security-hardening)
- [Scalability Patterns](#scalability-patterns)
- [DevOps and Reliability](#devops-and-reliability)
- [Common Pitfalls](#common-pitfalls)
---
## Serverless Best Practices
### Lambda Function Design
#### 1. Keep Functions Stateless
Store state externally in DynamoDB, S3, or ElastiCache.
```python
# BAD: Function-level state
cache = {}
def handler(event, context):
if event['key'] in cache:
return cache[event['key']]
# ...
# GOOD: External state
import boto3
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('cache')
def handler(event, context):
response = table.get_item(Key={'pk': event['key']})
if 'Item' in response:
return response['Item']['value']
# ...
```
#### 2. Implement Idempotency
Handle retries gracefully with unique request IDs.
```python
import boto3
import hashlib
dynamodb = boto3.resource('dynamodb')
idempotency_table = dynamodb.Table('idempotency')
def handler(event, context):
# Generate idempotency key
idempotency_key = hashlib.sha256(
f"{event['orderId']}-{event['action']}".encode()
).hexdigest()
# Check if already processed
try:
response = idempotency_table.get_item(Key={'pk': idempotency_key})
if 'Item' in response:
return response['Item']['result']
except Exception:
pass
# Process request
result = process_order(event)
# Store result for idempotency
idempotency_table.put_item(
Item={
'pk': idempotency_key,
'result': result,
'ttl': int(time.time()) + 86400 # 24h TTL
}
)
return result
```
#### 3. Optimize Cold Starts
```python
# Initialize outside handler (reused across invocations)
import boto3
from aws_xray_sdk.core import patch_all
# SDK initialization happens once
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('my-table')
patch_all()
def handler(event, context):
# Handler code uses pre-initialized resources
return table.get_item(Key={'pk': event['id']})
```
**Cold Start Reduction Techniques:**
- Use provisioned concurrency for critical paths
- Minimize package size (use layers for dependencies)
- Choose interpreted languages (Python, Node.js) over compiled
- Avoid VPC unless necessary (adds 6-10 sec cold start)
#### 4. Set Appropriate Timeouts
```yaml
# Lambda configuration
Functions:
ApiHandler:
Timeout: 10 # Shorter for synchronous APIs
MemorySize: 512
BackgroundProcessor:
Timeout: 300 # Longer for async processing
MemorySize: 1024
```
**Timeout Guidelines:**
- API handlers: 10-30 seconds
- Event processors: 60-300 seconds
- Use Step Functions for >15 minute workflows
---
## Cost Optimization
### 1. Right-Sizing Strategy
```bash
# Check EC2 utilization
aws cloudwatch get-metric-statistics \
--namespace AWS/EC2 \
--metric-name CPUUtilization \
--dimensions Name=InstanceId,Value=i-1234567890abcdef0 \
--start-time $(date -d '7 days ago' -u +"%Y-%m-%dT%H:%M:%SZ") \
--end-time $(date -u +"%Y-%m-%dT%H:%M:%SZ") \
--period 3600 \
--statistics Average
```
**Right-Sizing Rules:**
- <10% CPU average: Downsize instance
- >80% CPU average: Consider upgrade or horizontal scaling
- Review every month for the first 6 months
### 2. Savings Plans and Reserved Instances
| Commitment | Savings | Best For |
|------------|---------|----------|
| No Upfront, 1-year | 20-30% | Unknown future |
| Partial Upfront, 1-year | 30-40% | Moderate confidence |
| All Upfront, 3-year | 50-60% | Stable workloads |
```bash
# Check Savings Plans recommendations
aws cost-explorer get-savings-plans-purchase-recommendation \
--savings-plans-type COMPUTE_SP \
--term-in-years ONE_YEAR \
--payment-option NO_UPFRONT \
--lookback-period-in-days THIRTY_DAYS
```
### 3. S3 Lifecycle Policies
```json
{
"Rules": [
{
"ID": "Transition to cheaper storage",
"Status": "Enabled",
"Filter": {
"Prefix": "logs/"
},
"Transitions": [
{ "Days": 30, "StorageClass": "STANDARD_IA" },
{ "Days": 90, "StorageClass": "GLACIER" }
],
"Expiration": { "Days": 365 }
}
]
}
```
### 4. Lambda Memory Optimization
Test different memory settings to find optimal cost/performance.
```python
# Use AWS Lambda Power Tuning
# https://github.com/alexcasalboni/aws-lambda-power-tuning
# Example results:
# 128 MB: 2000ms, $0.000042
# 512 MB: 500ms, $0.000042
# 1024 MB: 300ms, $0.000050
# Optimal: 512 MB (same cost, 4x faster)
```
### 5. NAT Gateway Alternatives
```
NAT Gateway: $0.045/hour + $0.045/GB = ~$32/month + data
Alternatives:
1. VPC Endpoints: $0.01/hour = ~$7.30/month (for AWS services)
2. NAT Instance: t3.nano = ~$3.80/month (limited throughput)
3. No NAT: Use VPC endpoints + Lambda outside VPC
```
### 6. CloudWatch Log Retention
```yaml
# Set retention policies to avoid unbounded growth
LogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: /aws/lambda/my-function
RetentionInDays: 14 # 7, 14, 30, 60, 90, etc.
```
**Retention Guidelines:**
- Development: 7 days
- Production non-critical: 30 days
- Production critical: 90 days
- Compliance requirements: As specified
---
## Security Hardening
### 1. IAM Least Privilege
```json
// BAD: Overly permissive
{
"Effect": "Allow",
"Action": "dynamodb:*",
"Resource": "*"
}
// GOOD: Specific actions and resources
{
"Effect": "Allow",
"Action": [
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:Query"
],
"Resource": [
"arn:aws:dynamodb:us-east-1:123456789:table/users",
"arn:aws:dynamodb:us-east-1:123456789:table/users/index/*"
]
}
```
### 2. Encryption Configuration
```yaml
# Enable encryption everywhere
Resources:
# DynamoDB
Table:
Type: AWS::DynamoDB::Table
Properties:
SSESpecification:
SSEEnabled: true
SSEType: KMS
KMSMasterKeyId: !Ref EncryptionKey
# S3
Bucket:
Type: AWS::S3::Bucket
Properties:
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: aws:kms
KMSMasterKeyID: !Ref EncryptionKey
# RDS
Database:
Type: AWS::RDS::DBInstance
Properties:
StorageEncrypted: true
KmsKeyId: !Ref EncryptionKey
```
### 3. Network Isolation
```yaml
# Private subnets with VPC endpoints
Resources:
PrivateSubnet:
Type: AWS::EC2::Subnet
Properties:
MapPublicIpOnLaunch: false
# DynamoDB Gateway Endpoint (free)
DynamoDBEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref VPC
ServiceName: !Sub com.amazonaws.:Region.dynamodb
VpcEndpointType: Gateway
RouteTableIds:
- !Ref PrivateRouteTable
# Secrets Manager Interface Endpoint
SecretsEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref VPC
ServiceName: !Sub com.amazonaws.:Region.secretsmanager
VpcEndpointType: Interface
PrivateDnsEnabled: true
```
### 4. Secrets Management
```python
# Never hardcode secrets
import boto3
import json
def get_secret(secret_name):
client = boto3.client('secretsmanager')
response = client.get_secret_value(SecretId=secret_name)
return json.loads(response['SecretString'])
# Usage
db_creds = get_secret('prod/database/credentials')
connection = connect(
host=db_creds['host'],
user=db_creds['username'],
password=db_creds['password']
)
```
### 5. API Protection
```yaml
# WAF + API Gateway
WebACL:
Type: AWS::WAFv2::WebACL
Properties:
DefaultAction:
Allow: {}
Rules:
- Name: RateLimit
Priority: 1
Action:
Block: {}
Statement:
RateBasedStatement:
Limit: 2000
AggregateKeyType: IP
VisibilityConfig:
SampledRequestsEnabled: true
CloudWatchMetricsEnabled: true
MetricName: RateLimitRule
- Name: AWSManagedRulesCommonRuleSet
Priority: 2
OverrideAction:
None: {}
Statement:
ManagedRuleGroupStatement:
VendorName: AWS
Name: AWSManagedRulesCommonRuleSet
```
### 6. Audit Logging
```yaml
# Enable CloudTrail for all API calls
CloudTrail:
Type: AWS::CloudTrail::Trail
Properties:
IsMultiRegionTrail: true
IsLogging: true
S3BucketName: !Ref AuditLogsBucket
IncludeGlobalServiceEvents: true
EnableLogFileValidation: true
EventSelectors:
- ReadWriteType: All
IncludeManagementEvents: true
```
---
## Scalability Patterns
### 1. Horizontal vs Vertical Scaling
```
Horizontal (preferred):
- Add more Lambda concurrent executions
- Add more Fargate tasks
- Add more DynamoDB capacity
Vertical (when necessary):
- Increase Lambda memory
- Upgrade RDS instance
- Larger EC2 instances
```
### 2. Database Sharding
```python
# Partition by tenant ID
def get_table_for_tenant(tenant_id):
shard = hash(tenant_id) % NUM_SHARDS
return f"data-shard-{shard}"
# Or use DynamoDB single-table design with partition keys
def get_partition_key(tenant_id, entity_type, entity_id):
return f"TENANT#{tenant_id}#{entity_type}#{entity_id}"
```
### 3. Caching Layers
```
Edge (CloudFront): Global, static content, TTL: hours-days
Application (Redis): Regional, session/query cache, TTL: minutes-hours
Database (DAX): DynamoDB-specific, TTL: minutes
```
```python
# ElastiCache Redis caching pattern
import redis
import json
cache = redis.Redis(host='cache.abc123.cache.amazonaws.com', port=6379)
def get_user(user_id):
# Check cache first
cached = cache.get(f"user:{user_id}")
if cached:
return json.loads(cached)
# Fetch from database
user = db.get_user(user_id)
# Cache for 5 minutes
cache.setex(f"user:{user_id}", 300, json.dumps(user))
return user
```
### 4. Auto-Scaling Configuration
```yaml
# ECS Service Auto-scaling
AutoScalingTarget:
Type: AWS::ApplicationAutoScaling::ScalableTarget
Properties:
MaxCapacity: 10
MinCapacity: 2
ResourceId: !Sub service/Cluster/Service.Name
ScalableDimension: ecs:service:DesiredCount
ServiceNamespace: ecs
ScalingPolicy:
Type: AWS::ApplicationAutoScaling::ScalingPolicy
Properties:
PolicyType: TargetTrackingScaling
TargetTrackingScalingPolicyConfiguration:
PredefinedMetricSpecification:
PredefinedMetricType: ECSServiceAverageCPUUtilization
TargetValue: 70
ScaleInCooldown: 300
ScaleOutCooldown: 60
```
---
## DevOps and Reliability
### 1. Infrastructure as Code
```bash
# Version control all infrastructure
git init
git add .
git commit -m "Initial infrastructure setup"
# Use separate stacks per environment
cdk deploy --context environment=dev
cdk deploy --context environment=staging
cdk deploy --context environment=production
```
### 2. Blue/Green Deployments
```yaml
# CodeDeploy Blue/Green for ECS
DeploymentGroup:
Type: AWS::CodeDeploy::DeploymentGroup
Properties:
DeploymentConfigName: CodeDeployDefault.ECSAllAtOnce
DeploymentStyle:
DeploymentType: BLUE_GREEN
DeploymentOption: WITH_TRAFFIC_CONTROL
BlueGreenDeploymentConfiguration:
DeploymentReadyOption:
ActionOnTimeout: CONTINUE_DEPLOYMENT
WaitTimeInMinutes: 0
TerminateBlueInstancesOnDeploymentSuccess:
Action: TERMINATE
TerminationWaitTimeInMinutes: 5
```
### 3. Health Checks
```python
# Application health endpoint
from flask import Flask, jsonify
import boto3
app = Flask(__name__)
@app.route('/health')
def health():
checks = {
'database': check_database(),
'cache': check_cache(),
'external_api': check_external_api()
}
status = 'healthy' if all(checks.values()) else 'unhealthy'
code = 200 if status == 'healthy' else 503
return jsonify({'status': status, 'checks': checks}), code
def check_database():
try:
# Quick connectivity test
db.execute('SELECT 1')
return True
except Exception:
return False
```
### 4. Monitoring Setup
```yaml
# CloudWatch Dashboard
Dashboard:
Type: AWS::CloudWatch::Dashboard
Properties:
DashboardName: production-overview
DashboardBody: |
{
"widgets": [
{
"type": "metric",
"properties": {
"metrics": [
["AWS/Lambda", "Invocations", "FunctionName", "api-handler"],
[".", "Errors", ".", "."],
[".", "Duration", ".", ".", {"stat": "p99"}]
],
"period": 60,
"title": "Lambda Metrics"
}
}
]
}
# Critical Alarms
ErrorAlarm:
Type: AWS::CloudWatch::Alarm
Properties:
AlarmName: high-error-rate
MetricName: Errors
Namespace: AWS/Lambda
Statistic: Sum
Period: 60
EvaluationPeriods: 3
Threshold: 10
ComparisonOperator: GreaterThanThreshold
AlarmActions:
- !Ref AlertTopic
```
---
## Common Pitfalls
### Technical Debt
| Pitfall | Solution |
|---------|----------|
| Over-engineering early | Start simple, scale when needed |
| Under-monitoring | Set up CloudWatch from day one |
| Ignoring costs | Enable Cost Explorer and billing alerts |
| Single region only | Plan for multi-region from start |
### Security Mistakes
| Mistake | Prevention |
|---------|------------|
| Public S3 buckets | Block public access, use bucket policies |
| Overly permissive IAM | Never use "*", specify resources |
| Hardcoded credentials | Use Secrets Manager, IAM roles |
| Unencrypted data | Enable encryption by default |
### Performance Issues
| Issue | Solution |
|-------|----------|
| No caching | Add CloudFront, ElastiCache early |
| Inefficient queries | Use indexes, avoid DynamoDB scans |
| Large Lambda packages | Use layers, minimize dependencies |
| N+1 queries | Implement DataLoader, batch operations |
### Cost Surprises
| Surprise | Prevention |
|----------|------------|
| Undeleted resources | Tag everything, review weekly |
| Data transfer costs | Keep traffic in same AZ/region |
| NAT Gateway charges | Use VPC endpoints for AWS services |
| Log accumulation | Set CloudWatch retention policies |
FILE:references/service_selection.md
# AWS Service Selection Guide
Quick reference for choosing the right AWS service based on requirements.
---
## Table of Contents
- [Compute Services](#compute-services)
- [Database Services](#database-services)
- [Storage Services](#storage-services)
- [Messaging and Events](#messaging-and-events)
- [API and Integration](#api-and-integration)
- [Networking](#networking)
- [Security and Identity](#security-and-identity)
---
## Compute Services
### Decision Matrix
| Requirement | Recommended Service |
|-------------|---------------------|
| Event-driven, short tasks (<15 min) | Lambda |
| Containerized apps, predictable traffic | ECS Fargate |
| Custom configs, GPU/FPGA | EC2 |
| Simple container from source | App Runner |
| Kubernetes workloads | EKS |
| Batch processing | AWS Batch |
### Lambda
**Best for:** Event-driven functions, API backends, scheduled tasks
```
Limits:
- Execution: 15 minutes max
- Memory: 128 MB - 10 GB
- Package: 50 MB (zip), 10 GB (container)
- Concurrency: 1000 default (soft limit)
Pricing: $0.20 per 1M requests + compute time
```
**Use when:**
- Variable/unpredictable traffic
- Pay-per-use is important
- No server management desired
- Short-duration operations
**Avoid when:**
- Long-running processes (>15 min)
- Low-latency requirements (<50ms)
- Heavy compute (consider Fargate)
### ECS Fargate
**Best for:** Containerized applications, microservices
```
Limits:
- vCPU: 0.25 - 16
- Memory: 0.5 GB - 120 GB
- Storage: 20 GB - 200 GB ephemeral
Pricing: Per vCPU-hour + GB-hour
```
**Use when:**
- Containerized applications
- Predictable traffic patterns
- Long-running processes
- Need more control than Lambda
### EC2
**Best for:** Custom configurations, specialized hardware
```
Instance Types:
- General: t3, m6i
- Compute: c6i
- Memory: r6i
- GPU: p4d, g5
- Storage: i3, d3
```
**Use when:**
- Need GPU/FPGA
- Windows applications
- Specific instance configurations
- Reserved capacity makes sense
---
## Database Services
### Decision Matrix
| Data Type | Query Pattern | Scale | Recommended |
|-----------|--------------|-------|-------------|
| Key-value | Simple lookups | Any | DynamoDB |
| Document | Flexible queries | <1TB | DocumentDB |
| Relational | Complex joins | Variable | Aurora Serverless |
| Relational | High volume | Fixed | Aurora Standard |
| Time-series | Time-based | Any | Timestream |
| Graph | Relationships | Any | Neptune |
### DynamoDB
**Best for:** Key-value and document data, serverless applications
```
Limits:
- Item size: 400 KB max
- Partition key: 2048 bytes
- Sort key: 1024 bytes
- GSI: 20 per table
Pricing:
- On-demand: $1.25 per million writes, $0.25 per million reads
- Provisioned: Per RCU/WCU
```
**Data Modeling Example:**
```
# Single-table design for e-commerce
PK SK Attributes
USER#123 PROFILE {name, email, ...}
USER#123 ORDER#456 {total, status, ...}
USER#123 ORDER#456#ITEM#1 {product, qty, ...}
PRODUCT#789 METADATA {name, price, ...}
```
### Aurora
**Best for:** Relational data with complex queries
| Edition | Use Case | Scaling |
|---------|----------|---------|
| Aurora Serverless v2 | Variable workloads | 0.5-128 ACUs, auto |
| Aurora Standard | Predictable workloads | Instance-based |
| Aurora Global | Multi-region | Cross-region replication |
```
Limits:
- Storage: 128 TB max
- Replicas: 15 read replicas
- Connections: Instance-dependent
Pricing:
- Serverless: $0.12 per ACU-hour
- Standard: Instance + storage + I/O
```
### Comparison: DynamoDB vs Aurora
| Factor | DynamoDB | Aurora |
|--------|----------|--------|
| Query flexibility | Limited (key-based) | Full SQL |
| Scaling | Instant, unlimited | Minutes, up to limits |
| Consistency | Eventually/Strong | ACID |
| Cost model | Per-request | Per-hour |
| Operational | Zero management | Some management |
---
## Storage Services
### S3 Storage Classes
| Class | Access Pattern | Retrieval | Cost (GB/mo) |
|-------|---------------|-----------|--------------|
| Standard | Frequent | Instant | $0.023 |
| Intelligent-Tiering | Unknown | Instant | $0.023 + monitoring |
| Standard-IA | Infrequent (30+ days) | Instant | $0.0125 |
| One Zone-IA | Infrequent, single AZ | Instant | $0.01 |
| Glacier Instant | Archive, instant access | Instant | $0.004 |
| Glacier Flexible | Archive | Minutes-hours | $0.0036 |
| Glacier Deep Archive | Long-term archive | 12-48 hours | $0.00099 |
### Lifecycle Policy Example
```json
{
"Rules": [
{
"ID": "Archive old data",
"Status": "Enabled",
"Transitions": [
{
"Days": 30,
"StorageClass": "STANDARD_IA"
},
{
"Days": 90,
"StorageClass": "GLACIER"
},
{
"Days": 365,
"StorageClass": "DEEP_ARCHIVE"
}
],
"Expiration": {
"Days": 2555
}
}
]
}
```
### Block and File Storage
| Service | Use Case | Access |
|---------|----------|--------|
| EBS | EC2 block storage | Single instance |
| EFS | Shared file system | Multiple instances |
| FSx for Lustre | HPC workloads | High throughput |
| FSx for Windows | Windows apps | SMB protocol |
---
## Messaging and Events
### Decision Matrix
| Pattern | Service | Use Case |
|---------|---------|----------|
| Event routing | EventBridge | Microservices, SaaS integration |
| Pub/sub | SNS | Fan-out notifications |
| Queue | SQS | Decoupling, buffering |
| Streaming | Kinesis | Real-time analytics |
| Message broker | Amazon MQ | Legacy migrations |
### EventBridge
**Best for:** Event-driven architectures, SaaS integration
```python
# EventBridge rule pattern
{
"source": ["orders.service"],
"detail-type": ["OrderCreated"],
"detail": {
"total": [{"numeric": [">=", 100]}]
}
}
```
### SQS
**Best for:** Decoupling services, handling load spikes
| Feature | Standard | FIFO |
|---------|----------|------|
| Throughput | Unlimited | 3000 msg/sec |
| Ordering | Best effort | Guaranteed |
| Delivery | At least once | Exactly once |
| Deduplication | No | Yes |
```python
# SQS with dead letter queue
import boto3
sqs = boto3.client('sqs')
def process_with_dlq(queue_url, dlq_url, max_retries=3):
response = sqs.receive_message(
QueueUrl=queue_url,
MaxNumberOfMessages=10,
WaitTimeSeconds=20,
AttributeNames=['ApproximateReceiveCount']
)
for message in response.get('Messages', []):
receive_count = int(message['Attributes']['ApproximateReceiveCount'])
try:
process(message)
sqs.delete_message(QueueUrl=queue_url, ReceiptHandle=message['ReceiptHandle'])
except Exception as e:
if receive_count >= max_retries:
sqs.send_message(QueueUrl=dlq_url, MessageBody=message['Body'])
sqs.delete_message(QueueUrl=queue_url, ReceiptHandle=message['ReceiptHandle'])
```
### Kinesis
**Best for:** Real-time streaming data, analytics
| Service | Use Case |
|---------|----------|
| Data Streams | Custom processing |
| Data Firehose | Direct to S3/Redshift |
| Data Analytics | SQL on streams |
| Video Streams | Video ingestion |
---
## API and Integration
### API Gateway vs AppSync
| Factor | API Gateway | AppSync |
|--------|-------------|---------|
| Protocol | REST, WebSocket | GraphQL |
| Real-time | WebSocket setup | Built-in subscriptions |
| Caching | Response caching | Field-level caching |
| Integration | Lambda, HTTP, AWS | Lambda, DynamoDB, HTTP |
| Pricing | Per request | Per request + data |
### API Gateway Configuration
```yaml
# Throttling and caching
Resources:
ApiGateway:
Type: AWS::ApiGateway::RestApi
Properties:
Name: my-api
ApiStage:
Type: AWS::ApiGateway::Stage
Properties:
StageName: prod
MethodSettings:
- HttpMethod: "*"
ResourcePath: "/*"
ThrottlingBurstLimit: 500
ThrottlingRateLimit: 1000
CachingEnabled: true
CacheTtlInSeconds: 300
```
### Step Functions
**Best for:** Workflow orchestration, long-running processes
```json
{
"StartAt": "ProcessOrder",
"States": {
"ProcessOrder": {
"Type": "Task",
"Resource": "arn:aws:lambda:...:processOrder",
"Next": "CheckInventory"
},
"CheckInventory": {
"Type": "Choice",
"Choices": [
{
"Variable": "$.inStock",
"BooleanEquals": true,
"Next": "ShipOrder"
}
],
"Default": "BackOrder"
},
"ShipOrder": {
"Type": "Task",
"Resource": "arn:aws:lambda:...:shipOrder",
"End": true
},
"BackOrder": {
"Type": "Task",
"Resource": "arn:aws:lambda:...:backOrder",
"End": true
}
}
}
```
---
## Networking
### VPC Components
| Component | Purpose |
|-----------|---------|
| VPC | Isolated network |
| Subnet | Network segment (public/private) |
| Internet Gateway | Public internet access |
| NAT Gateway | Private subnet outbound |
| VPC Endpoint | Private AWS service access |
| Transit Gateway | VPC interconnection |
### VPC Design Pattern
```
VPC: 10.0.0.0/16
Public Subnets (AZ a, b, c):
10.0.1.0/24, 10.0.2.0/24, 10.0.3.0/24
- ALB, NAT Gateway, Bastion
Private Subnets (AZ a, b, c):
10.0.11.0/24, 10.0.12.0/24, 10.0.13.0/24
- Application servers, Lambda
Database Subnets (AZ a, b, c):
10.0.21.0/24, 10.0.22.0/24, 10.0.23.0/24
- RDS, ElastiCache
```
### VPC Endpoints (Cost Savings)
```yaml
# Interface endpoint for Secrets Manager
SecretsManagerEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref VPC
ServiceName: !Sub com.amazonaws.:Region.secretsmanager
VpcEndpointType: Interface
SubnetIds: !Ref PrivateSubnets
SecurityGroupIds:
- !Ref EndpointSecurityGroup
```
---
## Security and Identity
### IAM Best Practices
```json
// Least privilege policy example
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:Query"
],
"Resource": "arn:aws:dynamodb:us-east-1:123456789:table/users",
"Condition": {
"ForAllValues:StringEquals": {
"dynamodb:LeadingKeys": ["userid"]
}
}
}
]
}
```
### Secrets Manager vs Parameter Store
| Factor | Secrets Manager | Parameter Store |
|--------|-----------------|-----------------|
| Auto-rotation | Built-in | Manual |
| Cross-account | Yes | Limited |
| Pricing | $0.40/secret/month | Free (standard) |
| Use case | Credentials, API keys | Config, non-secrets |
### Cognito Configuration
```yaml
UserPool:
Type: AWS::Cognito::UserPool
Properties:
UserPoolName: my-app-users
AutoVerifiedAttributes:
- email
MfaConfiguration: OPTIONAL
EnabledMfas:
- SOFTWARE_TOKEN_MFA
Policies:
PasswordPolicy:
MinimumLength: 12
RequireLowercase: true
RequireUppercase: true
RequireNumbers: true
RequireSymbols: true
AccountRecoverySetting:
RecoveryMechanisms:
- Name: verified_email
Priority: 1
```
FILE:scripts/architecture_designer.py
"""
AWS architecture design and service recommendation module.
Generates architecture patterns based on application requirements.
"""
from typing import Dict, List, Any, Optional
from enum import Enum
class ApplicationType(Enum):
"""Types of applications supported."""
WEB_APP = "web_application"
MOBILE_BACKEND = "mobile_backend"
DATA_PIPELINE = "data_pipeline"
MICROSERVICES = "microservices"
SAAS_PLATFORM = "saas_platform"
IOT_PLATFORM = "iot_platform"
class ArchitectureDesigner:
"""Design AWS architectures based on requirements."""
def __init__(self, requirements: Dict[str, Any]):
"""
Initialize with application requirements.
Args:
requirements: Dictionary containing app type, traffic, budget, etc.
"""
self.app_type = requirements.get('application_type', 'web_application')
self.expected_users = requirements.get('expected_users', 1000)
self.requests_per_second = requirements.get('requests_per_second', 10)
self.budget_monthly = requirements.get('budget_monthly_usd', 500)
self.team_size = requirements.get('team_size', 3)
self.aws_experience = requirements.get('aws_experience', 'beginner')
self.compliance_needs = requirements.get('compliance', [])
self.data_size_gb = requirements.get('data_size_gb', 10)
def recommend_architecture_pattern(self) -> Dict[str, Any]:
"""
Recommend architecture pattern based on requirements.
Returns:
Dictionary with recommended pattern and services
"""
# Determine pattern based on app type and scale
if self.app_type in ['web_application', 'saas_platform']:
if self.expected_users < 10000:
return self._serverless_web_architecture()
elif self.expected_users < 100000:
return self._modern_three_tier_architecture()
else:
return self._multi_region_architecture()
elif self.app_type == 'mobile_backend':
return self._serverless_mobile_backend()
elif self.app_type == 'data_pipeline':
return self._event_driven_data_pipeline()
elif self.app_type == 'microservices':
return self._event_driven_microservices()
elif self.app_type == 'iot_platform':
return self._iot_architecture()
else:
return self._serverless_web_architecture() # Default
def _serverless_web_architecture(self) -> Dict[str, Any]:
"""Serverless web application pattern."""
return {
'pattern_name': 'Serverless Web Application',
'description': 'Fully serverless architecture with zero server management',
'use_case': 'SaaS platforms, low to medium traffic websites, MVPs',
'services': {
'frontend': {
'service': 'S3 + CloudFront',
'purpose': 'Static website hosting with global CDN',
'configuration': {
's3_bucket': 'website-bucket',
'cloudfront_distribution': 'HTTPS with custom domain',
'caching': 'Cache-Control headers, edge caching'
}
},
'api': {
'service': 'API Gateway + Lambda',
'purpose': 'REST API backend with auto-scaling',
'configuration': {
'api_type': 'REST API',
'authorization': 'Cognito User Pools or API Keys',
'throttling': f'{self.requests_per_second * 10} requests/second',
'lambda_memory': '512 MB (optimize based on testing)',
'lambda_timeout': '10 seconds'
}
},
'database': {
'service': 'DynamoDB',
'purpose': 'NoSQL database with pay-per-request pricing',
'configuration': {
'billing_mode': 'PAY_PER_REQUEST',
'backup': 'Point-in-time recovery enabled',
'encryption': 'KMS encryption at rest'
}
},
'authentication': {
'service': 'Cognito',
'purpose': 'User authentication and authorization',
'configuration': {
'user_pools': 'Email/password + social providers',
'mfa': 'Optional MFA with SMS or TOTP',
'token_expiration': '1 hour access, 30 days refresh'
}
},
'cicd': {
'service': 'AWS Amplify or CodePipeline',
'purpose': 'Automated deployment from Git',
'configuration': {
'source': 'GitHub or CodeCommit',
'build': 'Automatic on commit',
'environments': 'dev, staging, production'
}
}
},
'estimated_cost': {
'monthly_usd': self._calculate_serverless_cost(),
'breakdown': {
'CloudFront': '10-30 USD',
'Lambda': '5-20 USD',
'API Gateway': '10-40 USD',
'DynamoDB': '5-30 USD',
'Cognito': '0-10 USD (free tier: 50k MAU)',
'S3': '1-5 USD'
}
},
'pros': [
'No server management',
'Auto-scaling built-in',
'Pay only for what you use',
'Fast to deploy and iterate',
'High availability by default'
],
'cons': [
'Cold start latency (100-500ms)',
'Vendor lock-in to AWS',
'Debugging distributed systems complex',
'Learning curve for serverless patterns'
],
'scaling_characteristics': {
'users_supported': '1k - 100k',
'requests_per_second': '100 - 10,000',
'scaling_method': 'Automatic (Lambda concurrency)'
}
}
def _modern_three_tier_architecture(self) -> Dict[str, Any]:
"""Traditional three-tier with modern AWS services."""
return {
'pattern_name': 'Modern Three-Tier Application',
'description': 'Classic architecture with containers and managed services',
'use_case': 'Traditional web apps, e-commerce, content management',
'services': {
'load_balancer': {
'service': 'Application Load Balancer (ALB)',
'purpose': 'Distribute traffic across instances',
'configuration': {
'scheme': 'internet-facing',
'target_type': 'ECS tasks or EC2 instances',
'health_checks': '/health endpoint, 30s interval',
'ssl': 'ACM certificate for HTTPS'
}
},
'compute': {
'service': 'ECS Fargate or EC2 Auto Scaling',
'purpose': 'Run containerized applications',
'configuration': {
'container_platform': 'ECS Fargate (serverless containers)',
'task_definition': '512 MB memory, 0.25 vCPU (start small)',
'auto_scaling': f'2-{max(4, self.expected_users // 5000)} tasks',
'deployment': 'Rolling update, 50% at a time'
}
},
'database': {
'service': 'RDS Aurora (MySQL/PostgreSQL)',
'purpose': 'Managed relational database',
'configuration': {
'instance_class': 'db.t3.medium or db.t4g.medium',
'multi_az': 'Yes (high availability)',
'read_replicas': '1-2 for read scaling',
'backup_retention': '7 days',
'encryption': 'KMS encryption enabled'
}
},
'cache': {
'service': 'ElastiCache Redis',
'purpose': 'Session storage, application caching',
'configuration': {
'node_type': 'cache.t3.micro or cache.t4g.micro',
'replication': 'Multi-AZ with automatic failover',
'eviction_policy': 'allkeys-lru'
}
},
'cdn': {
'service': 'CloudFront',
'purpose': 'Cache static assets globally',
'configuration': {
'origins': 'ALB (dynamic), S3 (static)',
'caching': 'Cache based on headers/cookies',
'compression': 'Gzip compression enabled'
}
},
'storage': {
'service': 'S3',
'purpose': 'User uploads, backups, logs',
'configuration': {
'storage_class': 'S3 Standard with lifecycle policies',
'versioning': 'Enabled for important buckets',
'lifecycle': 'Transition to IA after 30 days'
}
}
},
'estimated_cost': {
'monthly_usd': self._calculate_three_tier_cost(),
'breakdown': {
'ALB': '20-30 USD',
'ECS Fargate': '50-200 USD',
'RDS Aurora': '100-300 USD',
'ElastiCache': '30-80 USD',
'CloudFront': '10-50 USD',
'S3': '10-30 USD'
}
},
'pros': [
'Proven architecture pattern',
'Easy to understand and debug',
'Flexible scaling options',
'Support for complex applications',
'Managed services reduce operational burden'
],
'cons': [
'Higher baseline costs',
'More complex than serverless',
'Requires more operational knowledge',
'Manual scaling configuration needed'
],
'scaling_characteristics': {
'users_supported': '10k - 500k',
'requests_per_second': '1,000 - 50,000',
'scaling_method': 'Auto Scaling based on CPU/memory/requests'
}
}
def _serverless_mobile_backend(self) -> Dict[str, Any]:
"""Serverless mobile backend with GraphQL."""
return {
'pattern_name': 'Serverless Mobile Backend',
'description': 'Mobile-first backend with GraphQL and real-time features',
'use_case': 'Mobile apps, single-page apps, offline-first applications',
'services': {
'api': {
'service': 'AppSync (GraphQL)',
'purpose': 'Flexible GraphQL API with real-time subscriptions',
'configuration': {
'api_type': 'GraphQL',
'authorization': 'Cognito User Pools + API Keys',
'resolvers': 'Direct DynamoDB or Lambda',
'subscriptions': 'WebSocket for real-time updates',
'caching': 'Server-side caching (1 hour TTL)'
}
},
'database': {
'service': 'DynamoDB',
'purpose': 'Fast NoSQL database with global tables',
'configuration': {
'billing_mode': 'PAY_PER_REQUEST (on-demand)',
'global_tables': 'Multi-region if needed',
'streams': 'Enabled for change data capture',
'ttl': 'Automatic expiration for temporary data'
}
},
'file_storage': {
'service': 'S3 + CloudFront',
'purpose': 'User uploads (images, videos, documents)',
'configuration': {
'access': 'Signed URLs or Cognito credentials',
'lifecycle': 'Intelligent-Tiering for cost optimization',
'cdn': 'CloudFront for fast global delivery'
}
},
'authentication': {
'service': 'Cognito',
'purpose': 'User management and federation',
'configuration': {
'identity_providers': 'Email, Google, Apple, Facebook',
'mfa': 'SMS or TOTP',
'groups': 'Admin, premium, free tiers',
'custom_attributes': 'User metadata storage'
}
},
'push_notifications': {
'service': 'SNS Mobile Push',
'purpose': 'Push notifications to mobile devices',
'configuration': {
'platforms': 'iOS (APNs), Android (FCM)',
'topics': 'Group notifications by topic',
'delivery_status': 'CloudWatch Logs for tracking'
}
},
'analytics': {
'service': 'Pinpoint',
'purpose': 'User analytics and engagement',
'configuration': {
'events': 'Custom events tracking',
'campaigns': 'Targeted messaging',
'segments': 'User segmentation'
}
}
},
'estimated_cost': {
'monthly_usd': 50 + (self.expected_users * 0.005),
'breakdown': {
'AppSync': '5-40 USD',
'DynamoDB': '10-50 USD',
'Cognito': '0-15 USD',
'S3 + CloudFront': '10-40 USD',
'SNS': '1-10 USD',
'Pinpoint': '10-30 USD'
}
},
'pros': [
'Single GraphQL endpoint',
'Real-time subscriptions built-in',
'Offline-first capabilities',
'Auto-generated mobile SDK',
'Flexible querying (no over/under fetching)'
],
'cons': [
'GraphQL learning curve',
'Complex queries can be expensive',
'Debugging subscriptions challenging',
'Limited to AWS AppSync features'
],
'scaling_characteristics': {
'users_supported': '1k - 1M',
'requests_per_second': '100 - 100,000',
'scaling_method': 'Automatic (AppSync managed)'
}
}
def _event_driven_microservices(self) -> Dict[str, Any]:
"""Event-driven microservices architecture."""
return {
'pattern_name': 'Event-Driven Microservices',
'description': 'Loosely coupled services with event bus',
'use_case': 'Complex business workflows, asynchronous processing',
'services': {
'event_bus': {
'service': 'EventBridge',
'purpose': 'Central event routing between services',
'configuration': {
'bus_type': 'Custom event bus',
'rules': 'Route events by type/source',
'targets': 'Lambda, SQS, Step Functions',
'archive': 'Event replay capability'
}
},
'compute': {
'service': 'Lambda + ECS Fargate (hybrid)',
'purpose': 'Service implementation',
'configuration': {
'lambda': 'Lightweight services, event handlers',
'fargate': 'Long-running services, heavy processing',
'auto_scaling': 'Lambda (automatic), Fargate (target tracking)'
}
},
'queues': {
'service': 'SQS',
'purpose': 'Decouple services, handle failures',
'configuration': {
'queue_type': 'Standard (high throughput) or FIFO (ordering)',
'dlq': 'Dead letter queue after 3 retries',
'visibility_timeout': '30 seconds (adjust per service)',
'retention': '4 days'
}
},
'orchestration': {
'service': 'Step Functions',
'purpose': 'Complex workflows, saga patterns',
'configuration': {
'type': 'Standard (long-running) or Express (high volume)',
'error_handling': 'Retry, catch, rollback logic',
'timeouts': 'Per-state timeouts',
'logging': 'CloudWatch Logs integration'
}
},
'database': {
'service': 'DynamoDB (per service)',
'purpose': 'Each microservice owns its data',
'configuration': {
'pattern': 'Database per service',
'streams': 'DynamoDB Streams for change events',
'backup': 'Point-in-time recovery'
}
},
'api_gateway': {
'service': 'API Gateway',
'purpose': 'Unified API facade',
'configuration': {
'integration': 'Lambda proxy or HTTP proxy',
'authentication': 'Cognito or Lambda authorizer',
'rate_limiting': 'Per-client throttling'
}
}
},
'estimated_cost': {
'monthly_usd': 100 + (self.expected_users * 0.01),
'breakdown': {
'EventBridge': '5-20 USD',
'Lambda': '20-100 USD',
'SQS': '1-10 USD',
'Step Functions': '10-50 USD',
'DynamoDB': '30-150 USD',
'API Gateway': '10-40 USD'
}
},
'pros': [
'Loose coupling between services',
'Independent scaling and deployment',
'Failure isolation',
'Technology diversity possible',
'Easy to test individual services'
],
'cons': [
'Operational complexity',
'Distributed tracing required',
'Eventual consistency challenges',
'Network latency between services',
'More moving parts to monitor'
],
'scaling_characteristics': {
'users_supported': '10k - 10M',
'requests_per_second': '1,000 - 1,000,000',
'scaling_method': 'Per-service auto-scaling'
}
}
def _event_driven_data_pipeline(self) -> Dict[str, Any]:
"""Real-time data processing pipeline."""
return {
'pattern_name': 'Real-Time Data Pipeline',
'description': 'Scalable data ingestion and processing',
'use_case': 'Analytics, IoT data, log processing, ETL',
'services': {
'ingestion': {
'service': 'Kinesis Data Streams',
'purpose': 'Real-time data ingestion',
'configuration': {
'shards': f'{max(1, self.data_size_gb // 10)} shards',
'retention': '24 hours (extend to 7 days if needed)',
'encryption': 'KMS encryption'
}
},
'processing': {
'service': 'Lambda or Kinesis Analytics',
'purpose': 'Transform and enrich data',
'configuration': {
'lambda_concurrency': 'Match shard count',
'batch_size': '100-500 records per invocation',
'error_handling': 'DLQ for failed records'
}
},
'storage': {
'service': 'S3 Data Lake',
'purpose': 'Long-term storage and analytics',
'configuration': {
'format': 'Parquet (compressed, columnar)',
'partitioning': 'By date (year/month/day/hour)',
'lifecycle': 'Transition to Glacier after 90 days',
'catalog': 'AWS Glue Data Catalog'
}
},
'analytics': {
'service': 'Athena',
'purpose': 'SQL queries on S3 data',
'configuration': {
'query_results': 'Store in separate S3 bucket',
'workgroups': 'Separate dev and prod',
'cost_controls': 'Query limits per workgroup'
}
},
'visualization': {
'service': 'QuickSight',
'purpose': 'Business intelligence dashboards',
'configuration': {
'source': 'Athena or direct S3',
'refresh': 'Hourly or daily',
'sharing': 'Embedded dashboards or web access'
}
},
'alerting': {
'service': 'CloudWatch + SNS',
'purpose': 'Monitor metrics and alerts',
'configuration': {
'metrics': 'Custom metrics from processing',
'alarms': 'Threshold-based alerts',
'notifications': 'Email, Slack, PagerDuty'
}
}
},
'estimated_cost': {
'monthly_usd': self._calculate_data_pipeline_cost(),
'breakdown': {
'Kinesis': '15-100 USD (per shard)',
'Lambda': '10-50 USD',
'S3': '10-50 USD',
'Athena': '5-30 USD (per TB scanned)',
'QuickSight': '9-18 USD per user',
'Glue': '5-20 USD'
}
},
'pros': [
'Real-time processing capability',
'Scales to millions of events',
'Cost-effective long-term storage',
'SQL analytics on raw data',
'Serverless architecture'
],
'cons': [
'Kinesis shard management required',
'Athena costs based on data scanned',
'Schema evolution complexity',
'Cold data queries can be slow'
],
'scaling_characteristics': {
'events_per_second': '1,000 - 1,000,000',
'data_volume': '1 GB - 1 PB per day',
'scaling_method': 'Add Kinesis shards, partition S3 data'
}
}
def _iot_architecture(self) -> Dict[str, Any]:
"""IoT platform architecture."""
return {
'pattern_name': 'IoT Platform',
'description': 'Scalable IoT device management and data processing',
'use_case': 'Connected devices, sensors, smart devices',
'services': {
'device_management': {
'service': 'IoT Core',
'purpose': 'Device connectivity and management',
'configuration': {
'protocol': 'MQTT over TLS',
'thing_registry': 'Device metadata storage',
'device_shadow': 'Desired and reported state',
'rules_engine': 'Route messages to services'
}
},
'device_provisioning': {
'service': 'IoT Device Management',
'purpose': 'Fleet provisioning and updates',
'configuration': {
'fleet_indexing': 'Search devices',
'jobs': 'OTA firmware updates',
'bulk_operations': 'Manage device groups'
}
},
'data_processing': {
'service': 'IoT Analytics',
'purpose': 'Process and analyze IoT data',
'configuration': {
'channels': 'Ingest device data',
'pipelines': 'Transform and enrich',
'data_store': 'Time-series storage',
'notebooks': 'Jupyter notebooks for analysis'
}
},
'time_series_db': {
'service': 'Timestream',
'purpose': 'Store time-series metrics',
'configuration': {
'memory_store': 'Recent data (hours)',
'magnetic_store': 'Historical data (years)',
'retention': 'Auto-tier based on age'
}
},
'real_time_alerts': {
'service': 'IoT Events',
'purpose': 'Detect and respond to events',
'configuration': {
'detector_models': 'Define alert conditions',
'actions': 'SNS, Lambda, SQS',
'state_tracking': 'Per-device state machines'
}
}
},
'estimated_cost': {
'monthly_usd': 50 + (self.expected_users * 0.1), # Expected_users = device count
'breakdown': {
'IoT Core': '10-100 USD (per million messages)',
'IoT Analytics': '5-50 USD',
'Timestream': '10-80 USD',
'IoT Events': '1-20 USD',
'Data transfer': '10-50 USD'
}
},
'pros': [
'Built for IoT scale',
'Secure device connectivity',
'Managed device lifecycle',
'Time-series optimized',
'Real-time event detection'
],
'cons': [
'IoT-specific pricing model',
'MQTT protocol required',
'Regional limitations',
'Complexity for simple use cases'
],
'scaling_characteristics': {
'devices_supported': '100 - 10,000,000',
'messages_per_second': '1,000 - 100,000',
'scaling_method': 'Automatic (managed service)'
}
}
def _multi_region_architecture(self) -> Dict[str, Any]:
"""Multi-region high availability architecture."""
return {
'pattern_name': 'Multi-Region High Availability',
'description': 'Global deployment with disaster recovery',
'use_case': 'Global applications, 99.99% uptime, compliance',
'services': {
'dns': {
'service': 'Route 53',
'purpose': 'Global traffic routing',
'configuration': {
'routing_policy': 'Geolocation or latency-based',
'health_checks': 'Active monitoring with failover',
'failover': 'Automatic to secondary region'
}
},
'cdn': {
'service': 'CloudFront',
'purpose': 'Edge caching and acceleration',
'configuration': {
'origins': 'Multiple regions (primary + secondary)',
'origin_failover': 'Automatic failover',
'edge_locations': 'Global (400+ locations)'
}
},
'compute': {
'service': 'Multi-region Lambda or ECS',
'purpose': 'Active-active deployment',
'configuration': {
'regions': 'us-east-1 (primary), eu-west-1 (secondary)',
'deployment': 'Blue/Green in each region',
'traffic_split': '70/30 or 50/50'
}
},
'database': {
'service': 'DynamoDB Global Tables or Aurora Global',
'purpose': 'Multi-region replication',
'configuration': {
'replication': 'Sub-second replication lag',
'read_locality': 'Read from nearest region',
'write_forwarding': 'Aurora Global write forwarding',
'conflict_resolution': 'Last writer wins'
}
},
'storage': {
'service': 'S3 Cross-Region Replication',
'purpose': 'Replicate data across regions',
'configuration': {
'replication': 'Async replication to secondary',
'versioning': 'Required for CRR',
'replication_time_control': '15 minutes SLA'
}
}
},
'estimated_cost': {
'monthly_usd': self._calculate_three_tier_cost() * 1.8,
'breakdown': {
'Route 53': '10-30 USD',
'CloudFront': '20-100 USD',
'Compute (2 regions)': '100-500 USD',
'Database (Global Tables)': '200-800 USD',
'Data transfer (cross-region)': '50-200 USD'
}
},
'pros': [
'Global low latency',
'High availability (99.99%+)',
'Disaster recovery built-in',
'Data sovereignty compliance',
'Automatic failover'
],
'cons': [
'1.5-2x costs vs single region',
'Complex deployment pipeline',
'Data consistency challenges',
'More operational overhead',
'Cross-region data transfer costs'
],
'scaling_characteristics': {
'users_supported': '100k - 100M',
'requests_per_second': '10,000 - 10,000,000',
'scaling_method': 'Per-region auto-scaling + global routing'
}
}
def _calculate_serverless_cost(self) -> float:
"""Estimate serverless architecture cost."""
requests_per_month = self.requests_per_second * 2_592_000 # 30 days
lambda_cost = (requests_per_month / 1_000_000) * 0.20 # $0.20 per 1M requests
api_gateway_cost = (requests_per_month / 1_000_000) * 3.50 # $3.50 per 1M requests
dynamodb_cost = max(5, self.data_size_gb * 0.25) # $0.25 per GB/month
cloudfront_cost = max(10, self.expected_users * 0.01)
total = lambda_cost + api_gateway_cost + dynamodb_cost + cloudfront_cost
return min(total, self.budget_monthly) # Cap at budget
def _calculate_three_tier_cost(self) -> float:
"""Estimate three-tier architecture cost."""
fargate_tasks = max(2, self.expected_users // 5000)
fargate_cost = fargate_tasks * 30 # ~$30 per task/month
rds_cost = 150 # db.t3.medium baseline
elasticache_cost = 40 # cache.t3.micro
alb_cost = 25
total = fargate_cost + rds_cost + elasticache_cost + alb_cost
return min(total, self.budget_monthly)
def _calculate_data_pipeline_cost(self) -> float:
"""Estimate data pipeline cost."""
shards = max(1, self.data_size_gb // 10)
kinesis_cost = shards * 15 # $15 per shard/month
s3_cost = self.data_size_gb * 0.023 # $0.023 per GB/month
lambda_cost = 20 # Processing
athena_cost = 15 # Queries
total = kinesis_cost + s3_cost + lambda_cost + athena_cost
return min(total, self.budget_monthly)
def generate_service_checklist(self) -> List[Dict[str, Any]]:
"""Generate implementation checklist for recommended architecture."""
architecture = self.recommend_architecture_pattern()
checklist = [
{
'phase': 'Planning',
'tasks': [
'Review architecture pattern and services',
'Estimate costs using AWS Pricing Calculator',
'Define environment strategy (dev, staging, prod)',
'Set up AWS Organization and accounts',
'Define tagging strategy for resources'
]
},
{
'phase': 'Foundation',
'tasks': [
'Create VPC with public/private subnets',
'Configure NAT Gateway or VPC endpoints',
'Set up IAM roles and policies',
'Enable CloudTrail for audit logging',
'Configure AWS Config for compliance'
]
},
{
'phase': 'Core Services',
'tasks': [
f"Deploy {service['service']}"
for service in architecture['services'].values()
]
},
{
'phase': 'Security',
'tasks': [
'Configure security groups and NACLs',
'Enable encryption (KMS) for all services',
'Set up AWS WAF rules',
'Configure Secrets Manager',
'Enable GuardDuty for threat detection'
]
},
{
'phase': 'Monitoring',
'tasks': [
'Create CloudWatch dashboards',
'Set up alarms for critical metrics',
'Configure SNS topics for notifications',
'Enable X-Ray for distributed tracing',
'Set up log aggregation and retention'
]
},
{
'phase': 'CI/CD',
'tasks': [
'Set up CodePipeline or GitHub Actions',
'Configure automated testing',
'Implement blue/green deployment',
'Set up rollback procedures',
'Document deployment process'
]
}
]
return checklist
FILE:scripts/cost_optimizer.py
"""
AWS cost optimization analyzer.
Provides cost-saving recommendations for startup budgets.
"""
from typing import Dict, List, Any, Optional
class CostOptimizer:
"""Analyze AWS costs and provide optimization recommendations."""
def __init__(self, current_resources: Dict[str, Any], monthly_spend: float):
"""
Initialize with current AWS resources and spending.
Args:
current_resources: Dictionary of current AWS resources
monthly_spend: Current monthly AWS spend in USD
"""
self.resources = current_resources
self.monthly_spend = monthly_spend
self.recommendations = []
def analyze_and_optimize(self) -> Dict[str, Any]:
"""
Analyze current setup and generate cost optimization recommendations.
Returns:
Dictionary with recommendations and potential savings
"""
self.recommendations = []
potential_savings = 0.0
# Analyze compute resources
compute_savings = self._analyze_compute()
potential_savings += compute_savings
# Analyze storage
storage_savings = self._analyze_storage()
potential_savings += storage_savings
# Analyze database
database_savings = self._analyze_database()
potential_savings += database_savings
# Analyze networking
network_savings = self._analyze_networking()
potential_savings += network_savings
# General AWS optimizations
general_savings = self._analyze_general_optimizations()
potential_savings += general_savings
return {
'current_monthly_spend': self.monthly_spend,
'potential_monthly_savings': round(potential_savings, 2),
'optimized_monthly_spend': round(self.monthly_spend - potential_savings, 2),
'savings_percentage': round((potential_savings / self.monthly_spend) * 100, 2) if self.monthly_spend > 0 else 0,
'recommendations': self.recommendations,
'priority_actions': self._prioritize_recommendations()
}
def _analyze_compute(self) -> float:
"""Analyze compute resources (EC2, Lambda, Fargate)."""
savings = 0.0
ec2_instances = self.resources.get('ec2_instances', [])
if ec2_instances:
# Check for idle instances
idle_count = sum(1 for inst in ec2_instances if inst.get('cpu_utilization', 100) < 10)
if idle_count > 0:
idle_cost = idle_count * 50 # Assume $50/month per idle instance
savings += idle_cost
self.recommendations.append({
'service': 'EC2',
'type': 'Idle Resources',
'issue': f'{idle_count} EC2 instances with <10% CPU utilization',
'recommendation': 'Stop or terminate idle instances, or downsize to smaller instance types',
'potential_savings': idle_cost,
'priority': 'high'
})
# Check for Savings Plans / Reserved Instances
on_demand_count = sum(1 for inst in ec2_instances if inst.get('pricing', 'on-demand') == 'on-demand')
if on_demand_count >= 2:
ri_savings = on_demand_count * 50 * 0.30 # 30% savings with RIs
savings += ri_savings
self.recommendations.append({
'service': 'EC2',
'type': 'Pricing Optimization',
'issue': f'{on_demand_count} instances on On-Demand pricing',
'recommendation': 'Purchase Compute Savings Plan or Reserved Instances for predictable workloads (1-year commitment)',
'potential_savings': ri_savings,
'priority': 'medium'
})
# Lambda optimization
lambda_functions = self.resources.get('lambda_functions', [])
if lambda_functions:
oversized = sum(1 for fn in lambda_functions if fn.get('memory_mb', 128) > 512 and fn.get('avg_memory_used_mb', 0) < 256)
if oversized > 0:
lambda_savings = oversized * 5 # Assume $5/month per oversized function
savings += lambda_savings
self.recommendations.append({
'service': 'Lambda',
'type': 'Right-sizing',
'issue': f'{oversized} Lambda functions over-provisioned (memory too high)',
'recommendation': 'Use AWS Lambda Power Tuning tool to optimize memory settings',
'potential_savings': lambda_savings,
'priority': 'low'
})
return savings
def _analyze_storage(self) -> float:
"""Analyze S3 and other storage resources."""
savings = 0.0
s3_buckets = self.resources.get('s3_buckets', [])
for bucket in s3_buckets:
size_gb = bucket.get('size_gb', 0)
storage_class = bucket.get('storage_class', 'STANDARD')
# Check for lifecycle policies
if not bucket.get('has_lifecycle_policy', False) and size_gb > 100:
lifecycle_savings = size_gb * 0.015 # $0.015/GB savings with IA transition
savings += lifecycle_savings
self.recommendations.append({
'service': 'S3',
'type': 'Lifecycle Policy',
'issue': f'Bucket {bucket.get("name", "unknown")} ({size_gb} GB) has no lifecycle policy',
'recommendation': 'Implement lifecycle policy: Transition to IA after 30 days, Glacier after 90 days',
'potential_savings': lifecycle_savings,
'priority': 'medium'
})
# Check for Intelligent-Tiering
if storage_class == 'STANDARD' and size_gb > 500:
tiering_savings = size_gb * 0.005
savings += tiering_savings
self.recommendations.append({
'service': 'S3',
'type': 'Storage Class',
'issue': f'Large bucket ({size_gb} GB) using STANDARD storage',
'recommendation': 'Enable S3 Intelligent-Tiering for automatic cost optimization',
'potential_savings': tiering_savings,
'priority': 'high'
})
return savings
def _analyze_database(self) -> float:
"""Analyze RDS, DynamoDB, and other database costs."""
savings = 0.0
rds_instances = self.resources.get('rds_instances', [])
for db in rds_instances:
# Check for idle databases
if db.get('connections_per_day', 1000) < 10:
db_cost = db.get('monthly_cost', 100)
savings += db_cost * 0.8 # Can save 80% by stopping
self.recommendations.append({
'service': 'RDS',
'type': 'Idle Resource',
'issue': f'Database {db.get("name", "unknown")} has <10 connections/day',
'recommendation': 'Stop database if not needed, or take final snapshot and delete',
'potential_savings': db_cost * 0.8,
'priority': 'high'
})
# Check for Aurora Serverless opportunity
if db.get('engine', '').startswith('aurora') and db.get('utilization', 100) < 30:
serverless_savings = db.get('monthly_cost', 200) * 0.40
savings += serverless_savings
self.recommendations.append({
'service': 'RDS Aurora',
'type': 'Serverless Migration',
'issue': f'Aurora instance {db.get("name", "unknown")} has low utilization (<30%)',
'recommendation': 'Migrate to Aurora Serverless v2 for auto-scaling and pay-per-use',
'potential_savings': serverless_savings,
'priority': 'medium'
})
# DynamoDB optimization
dynamodb_tables = self.resources.get('dynamodb_tables', [])
for table in dynamodb_tables:
if table.get('billing_mode', 'PROVISIONED') == 'PROVISIONED':
read_capacity = table.get('read_capacity_units', 0)
write_capacity = table.get('write_capacity_units', 0)
utilization = table.get('utilization_percentage', 100)
if utilization < 20:
on_demand_savings = (read_capacity * 0.00013 + write_capacity * 0.00065) * 730 * 0.3
savings += on_demand_savings
self.recommendations.append({
'service': 'DynamoDB',
'type': 'Billing Mode',
'issue': f'Table {table.get("name", "unknown")} has low utilization with provisioned capacity',
'recommendation': 'Switch to On-Demand billing mode for variable workloads',
'potential_savings': on_demand_savings,
'priority': 'medium'
})
return savings
def _analyze_networking(self) -> float:
"""Analyze networking costs (data transfer, NAT Gateway, etc.)."""
savings = 0.0
nat_gateways = self.resources.get('nat_gateways', [])
if len(nat_gateways) > 1:
multi_az = self.resources.get('multi_az_required', False)
if not multi_az:
nat_savings = (len(nat_gateways) - 1) * 45 # $45/month per NAT Gateway
savings += nat_savings
self.recommendations.append({
'service': 'NAT Gateway',
'type': 'Resource Consolidation',
'issue': f'{len(nat_gateways)} NAT Gateways deployed (multi-AZ not required)',
'recommendation': 'Use single NAT Gateway in dev/staging, or consider VPC endpoints for AWS services',
'potential_savings': nat_savings,
'priority': 'high'
})
# Check for VPC endpoints opportunity
if not self.resources.get('vpc_endpoints', []):
s3_data_transfer = self.resources.get('s3_data_transfer_gb', 0)
if s3_data_transfer > 100:
endpoint_savings = s3_data_transfer * 0.09 * 0.5 # Save 50% of data transfer costs
savings += endpoint_savings
self.recommendations.append({
'service': 'VPC',
'type': 'VPC Endpoints',
'issue': 'High S3 data transfer without VPC endpoints',
'recommendation': 'Create VPC endpoints for S3 and DynamoDB to avoid NAT Gateway costs',
'potential_savings': endpoint_savings,
'priority': 'medium'
})
return savings
def _analyze_general_optimizations(self) -> float:
"""General AWS cost optimizations."""
savings = 0.0
# Check for CloudWatch Logs retention
log_groups = self.resources.get('cloudwatch_log_groups', [])
for log in log_groups:
if log.get('retention_days', 1) == -1: # Never expire
log_size_gb = log.get('size_gb', 1)
retention_savings = log_size_gb * 0.50 * 0.7 # 70% savings with 7-day retention
savings += retention_savings
self.recommendations.append({
'service': 'CloudWatch Logs',
'type': 'Retention Policy',
'issue': f'Log group {log.get("name", "unknown")} has infinite retention',
'recommendation': 'Set retention to 7 days for non-compliance logs, 30 days for production',
'potential_savings': retention_savings,
'priority': 'low'
})
# Check for unused Elastic IPs
elastic_ips = self.resources.get('elastic_ips', [])
unattached = sum(1 for eip in elastic_ips if not eip.get('attached', True))
if unattached > 0:
eip_savings = unattached * 3.65 # $0.005/hour = $3.65/month
savings += eip_savings
self.recommendations.append({
'service': 'EC2',
'type': 'Unused Resources',
'issue': f'{unattached} unattached Elastic IPs',
'recommendation': 'Release unused Elastic IPs to avoid hourly charges',
'potential_savings': eip_savings,
'priority': 'high'
})
# Budget alerts
if not self.resources.get('has_budget_alerts', False):
self.recommendations.append({
'service': 'AWS Budgets',
'type': 'Cost Monitoring',
'issue': 'No budget alerts configured',
'recommendation': 'Set up AWS Budgets with alerts at 50%, 80%, 100% of monthly budget',
'potential_savings': 0,
'priority': 'high'
})
# Cost Explorer recommendations
if not self.resources.get('has_cost_explorer', False):
self.recommendations.append({
'service': 'Cost Management',
'type': 'Visibility',
'issue': 'Cost Explorer not enabled',
'recommendation': 'Enable AWS Cost Explorer to track spending patterns and identify anomalies',
'potential_savings': 0,
'priority': 'medium'
})
return savings
def _prioritize_recommendations(self) -> List[Dict[str, Any]]:
"""Get top priority recommendations."""
high_priority = [r for r in self.recommendations if r['priority'] == 'high']
high_priority.sort(key=lambda x: x.get('potential_savings', 0), reverse=True)
return high_priority[:5] # Top 5 high-priority recommendations
def generate_optimization_checklist(self) -> List[Dict[str, Any]]:
"""Generate actionable checklist for cost optimization."""
return [
{
'category': 'Immediate Actions (Today)',
'items': [
'Release unattached Elastic IPs',
'Stop idle EC2 instances',
'Delete unused EBS volumes',
'Set up budget alerts'
]
},
{
'category': 'This Week',
'items': [
'Implement S3 lifecycle policies',
'Consolidate NAT Gateways in non-prod',
'Set CloudWatch Logs retention to 7 days',
'Review and rightsize EC2/RDS instances'
]
},
{
'category': 'This Month',
'items': [
'Evaluate Savings Plans or Reserved Instances',
'Migrate to Aurora Serverless where applicable',
'Implement VPC endpoints for S3/DynamoDB',
'Switch DynamoDB tables to On-Demand if variable load'
]
},
{
'category': 'Ongoing',
'items': [
'Review Cost Explorer weekly',
'Tag all resources for cost allocation',
'Monitor Trusted Advisor recommendations',
'Conduct monthly cost review meetings'
]
}
]
FILE:scripts/serverless_stack.py
"""
Serverless stack generator for AWS.
Creates CloudFormation/CDK templates for serverless applications.
"""
from typing import Dict, List, Any, Optional
class ServerlessStackGenerator:
"""Generate serverless application stacks."""
def __init__(self, app_name: str, requirements: Dict[str, Any]):
"""
Initialize with application requirements.
Args:
app_name: Application name (used for resource naming)
requirements: Dictionary with API, database, auth requirements
"""
self.app_name = app_name.lower().replace(' ', '-')
self.requirements = requirements
self.region = requirements.get('region', 'us-east-1')
def generate_cloudformation_template(self) -> str:
"""
Generate CloudFormation template for serverless stack.
Returns:
YAML CloudFormation template as string
"""
template = f"""AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Serverless stack for {self.app_name}
Parameters:
Environment:
Type: String
Default: dev
AllowedValues:
- dev
- staging
- production
Description: Deployment environment
CorsAllowedOrigins:
Type: String
Default: '*'
Description: CORS allowed origins for API Gateway
Resources:
# DynamoDB Table
{self.app_name.replace('-', '')}Table:
Type: AWS::DynamoDB::Table
Properties:
TableName: !Sub '{Environment}-{self.app_name}-data'
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: PK
AttributeType: S
- AttributeName: SK
AttributeType: S
KeySchema:
- AttributeName: PK
KeyType: HASH
- AttributeName: SK
KeyType: RANGE
PointInTimeRecoverySpecification:
PointInTimeRecoveryEnabled: true
SSESpecification:
SSEEnabled: true
StreamSpecification:
StreamViewType: NEW_AND_OLD_IMAGES
Tags:
- Key: Environment
Value: !Ref Environment
- Key: Application
Value: {self.app_name}
# Lambda Execution Role
LambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Policies:
- PolicyName: DynamoDBAccess
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
- dynamodb:Query
- dynamodb:Scan
Resource: !GetAtt {self.app_name.replace('-', '')}Table.Arn
# Lambda Function
ApiFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub '{Environment}-{self.app_name}-api'
Handler: index.handler
Runtime: nodejs18.x
CodeUri: ./src
MemorySize: 512
Timeout: 10
Role: !GetAtt LambdaExecutionRole.Arn
Environment:
Variables:
TABLE_NAME: !Ref {self.app_name.replace('-', '')}Table
ENVIRONMENT: !Ref Environment
Events:
ApiEvent:
Type: Api
Properties:
Path: /{{proxy+}}
Method: ANY
RestApiId: !Ref ApiGateway
Tags:
Environment: !Ref Environment
Application: {self.app_name}
# API Gateway
ApiGateway:
Type: AWS::Serverless::Api
Properties:
Name: !Sub '{Environment}-{self.app_name}-api'
StageName: !Ref Environment
Cors:
AllowMethods: "'GET,POST,PUT,DELETE,OPTIONS'"
AllowHeaders: "'Content-Type,Authorization,X-Amz-Date,X-Api-Key,X-Amz-Security-Token'"
AllowOrigin: !Sub "'{CorsAllowedOrigins}'"
Auth:
DefaultAuthorizer: CognitoAuthorizer
Authorizers:
CognitoAuthorizer:
UserPoolArn: !GetAtt UserPool.Arn
ThrottleSettings:
BurstLimit: 200
RateLimit: 100
Tags:
Environment: !Ref Environment
Application: {self.app_name}
# Cognito User Pool
UserPool:
Type: AWS::Cognito::UserPool
Properties:
UserPoolName: !Sub '{Environment}-{self.app_name}-users'
UsernameAttributes:
- email
AutoVerifiedAttributes:
- email
Policies:
PasswordPolicy:
MinimumLength: 8
RequireUppercase: true
RequireLowercase: true
RequireNumbers: true
RequireSymbols: false
MfaConfiguration: OPTIONAL
EnabledMfas:
- SOFTWARE_TOKEN_MFA
UserAttributeUpdateSettings:
AttributesRequireVerificationBeforeUpdate:
- email
Schema:
- Name: email
Required: true
Mutable: true
# Cognito User Pool Client
UserPoolClient:
Type: AWS::Cognito::UserPoolClient
Properties:
ClientName: !Sub '{Environment}-{self.app_name}-client'
UserPoolId: !Ref UserPool
GenerateSecret: false
RefreshTokenValidity: 30
AccessTokenValidity: 1
IdTokenValidity: 1
TokenValidityUnits:
RefreshToken: days
AccessToken: hours
IdToken: hours
ExplicitAuthFlows:
- ALLOW_USER_SRP_AUTH
- ALLOW_REFRESH_TOKEN_AUTH
# CloudWatch Log Group
ApiLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub '/aws/lambda/{Environment}-{self.app_name}-api'
RetentionInDays: 7
Outputs:
ApiUrl:
Description: API Gateway endpoint URL
Value: !Sub 'https://{ApiGateway}.execute-api.:Region}.amazonaws.com/{Environment}'
Export:
Name: !Sub '{Environment}-{self.app_name}-ApiUrl'
UserPoolId:
Description: Cognito User Pool ID
Value: !Ref UserPool
Export:
Name: !Sub '{Environment}-{self.app_name}-UserPoolId'
UserPoolClientId:
Description: Cognito User Pool Client ID
Value: !Ref UserPoolClient
Export:
Name: !Sub '{Environment}-{self.app_name}-UserPoolClientId'
TableName:
Description: DynamoDB Table Name
Value: !Ref {self.app_name.replace('-', '')}Table
Export:
Name: !Sub '{Environment}-{self.app_name}-TableName'
"""
return template
def generate_cdk_stack(self) -> str:
"""
Generate AWS CDK stack in TypeScript.
Returns:
CDK stack code as string
"""
stack = f"""import * as cdk from 'aws-cdk-lib';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
import * as cognito from 'aws-cdk-lib/aws-cognito';
import {{ Construct }} from 'constructs';
export class {self.app_name.replace('-', '').title()}Stack extends cdk.Stack {{
constructor(scope: Construct, id: string, props?: cdk.StackProps) {{
super(scope, id, props);
// DynamoDB Table
const table = new dynamodb.Table(this, '{self.app_name}Table', {{
tableName: `{cdk.Stack.of(this).stackName}-data`,
partitionKey: {{ name: 'PK', type: dynamodb.AttributeType.STRING }},
sortKey: {{ name: 'SK', type: dynamodb.AttributeType.STRING }},
billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
encryption: dynamodb.TableEncryption.AWS_MANAGED,
pointInTimeRecovery: true,
stream: dynamodb.StreamViewType.NEW_AND_OLD_IMAGES,
removalPolicy: cdk.RemovalPolicy.RETAIN,
}});
// Cognito User Pool
const userPool = new cognito.UserPool(this, '{self.app_name}UserPool', {{
userPoolName: `{cdk.Stack.of(this).stackName}-users`,
selfSignUpEnabled: true,
signInAliases: {{ email: true }},
autoVerify: {{ email: true }},
passwordPolicy: {{
minLength: 8,
requireLowercase: true,
requireUppercase: true,
requireDigits: true,
requireSymbols: false,
}},
mfa: cognito.Mfa.OPTIONAL,
mfaSecondFactor: {{
sms: false,
otp: true,
}},
removalPolicy: cdk.RemovalPolicy.RETAIN,
}});
const userPoolClient = userPool.addClient('{self.app_name}Client', {{
authFlows: {{
userSrp: true,
}},
accessTokenValidity: cdk.Duration.hours(1),
refreshTokenValidity: cdk.Duration.days(30),
}});
// Lambda Function
const apiFunction = new lambda.Function(this, '{self.app_name}ApiFunction', {{
functionName: `{cdk.Stack.of(this).stackName}-api`,
runtime: lambda.Runtime.NODEJS_18_X,
handler: 'index.handler',
code: lambda.Code.fromAsset('./src'),
memorySize: 512,
timeout: cdk.Duration.seconds(10),
environment: {{
TABLE_NAME: table.tableName,
USER_POOL_ID: userPool.userPoolId,
}},
logRetention: 7, // days
}});
// Grant Lambda permissions to DynamoDB
table.grantReadWriteData(apiFunction);
// API Gateway
const api = new apigateway.RestApi(this, '{self.app_name}Api', {{
restApiName: `{cdk.Stack.of(this).stackName}-api`,
description: 'API for {self.app_name}',
defaultCorsPreflightOptions: {{
allowOrigins: apigateway.Cors.ALL_ORIGINS,
allowMethods: apigateway.Cors.ALL_METHODS,
allowHeaders: ['Content-Type', 'Authorization'],
}},
deployOptions: {{
stageName: 'prod',
throttlingRateLimit: 100,
throttlingBurstLimit: 200,
metricsEnabled: true,
loggingLevel: apigateway.MethodLoggingLevel.INFO,
}},
}});
// Cognito Authorizer
const authorizer = new apigateway.CognitoUserPoolsAuthorizer(this, 'ApiAuthorizer', {{
cognitoUserPools: [userPool],
}});
// API Integration
const integration = new apigateway.LambdaIntegration(apiFunction);
// Add proxy resource (/{{proxy+}})
const proxyResource = api.root.addProxy({{
defaultIntegration: integration,
anyMethod: true,
defaultMethodOptions: {{
authorizer: authorizer,
authorizationType: apigateway.AuthorizationType.COGNITO,
}},
}});
// Outputs
new cdk.CfnOutput(this, 'ApiUrl', {{
value: api.url,
description: 'API Gateway URL',
}});
new cdk.CfnOutput(this, 'UserPoolId', {{
value: userPool.userPoolId,
description: 'Cognito User Pool ID',
}});
new cdk.CfnOutput(this, 'UserPoolClientId', {{
value: userPoolClient.userPoolClientId,
description: 'Cognito User Pool Client ID',
}});
new cdk.CfnOutput(this, 'TableName', {{
value: table.tableName,
description: 'DynamoDB Table Name',
}});
}}
}}
"""
return stack
def generate_terraform_configuration(self) -> str:
"""
Generate Terraform configuration for serverless stack.
Returns:
Terraform HCL configuration as string
"""
terraform = f"""terraform {{
required_version = ">= 1.0"
required_providers {{
aws = {{
source = "hashicorp/aws"
version = "~> 5.0"
}}
}}
}}
provider "aws" {{
region = var.aws_region
}}
variable "aws_region" {{
description = "AWS region"
type = string
default = "{self.region}"
}}
variable "environment" {{
description = "Environment name"
type = string
default = "dev"
}}
variable "app_name" {{
description = "Application name"
type = string
default = "{self.app_name}"
}}
# DynamoDB Table
resource "aws_dynamodb_table" "main" {{
name = "{var.environment}-{var.app_name}-data"
billing_mode = "PAY_PER_REQUEST"
hash_key = "PK"
range_key = "SK"
attribute {{
name = "PK"
type = "S"
}}
attribute {{
name = "SK"
type = "S"
}}
server_side_encryption {{
enabled = true
}}
point_in_time_recovery {{
enabled = true
}}
stream_enabled = true
stream_view_type = "NEW_AND_OLD_IMAGES"
tags = {{
Environment = var.environment
Application = var.app_name
}}
}}
# Cognito User Pool
resource "aws_cognito_user_pool" "main" {{
name = "{var.environment}-{var.app_name}-users"
username_attributes = ["email"]
auto_verified_attributes = ["email"]
password_policy {{
minimum_length = 8
require_lowercase = true
require_numbers = true
require_uppercase = true
require_symbols = false
}}
mfa_configuration = "OPTIONAL"
software_token_mfa_configuration {{
enabled = true
}}
schema {{
name = "email"
attribute_data_type = "String"
required = true
mutable = true
}}
tags = {{
Environment = var.environment
Application = var.app_name
}}
}}
resource "aws_cognito_user_pool_client" "main" {{
name = "{var.environment}-{var.app_name}-client"
user_pool_id = aws_cognito_user_pool.main.id
generate_secret = false
explicit_auth_flows = [
"ALLOW_USER_SRP_AUTH",
"ALLOW_REFRESH_TOKEN_AUTH"
]
refresh_token_validity = 30
access_token_validity = 1
id_token_validity = 1
token_validity_units {{
refresh_token = "days"
access_token = "hours"
id_token = "hours"
}}
}}
# IAM Role for Lambda
resource "aws_iam_role" "lambda" {{
name = "{var.environment}-{var.app_name}-lambda-role"
assume_role_policy = jsonencode({{
Version = "2012-10-17"
Statement = [{{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {{
Service = "lambda.amazonaws.com"
}}
}}]
}})
tags = {{
Environment = var.environment
Application = var.app_name
}}
}}
resource "aws_iam_role_policy_attachment" "lambda_basic" {{
role = aws_iam_role.lambda.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}}
resource "aws_iam_role_policy" "dynamodb" {{
name = "dynamodb-access"
role = aws_iam_role.lambda.id
policy = jsonencode({{
Version = "2012-10-17"
Statement = [{{
Effect = "Allow"
Action = [
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:UpdateItem",
"dynamodb:DeleteItem",
"dynamodb:Query",
"dynamodb:Scan"
]
Resource = aws_dynamodb_table.main.arn
}}]
}})
}}
# Lambda Function
resource "aws_lambda_function" "api" {{
filename = "lambda.zip"
function_name = "{var.environment}-{var.app_name}-api"
role = aws_iam_role.lambda.arn
handler = "index.handler"
runtime = "nodejs18.x"
memory_size = 512
timeout = 10
environment {{
variables = {{
TABLE_NAME = aws_dynamodb_table.main.name
USER_POOL_ID = aws_cognito_user_pool.main.id
ENVIRONMENT = var.environment
}}
}}
tags = {{
Environment = var.environment
Application = var.app_name
}}
}}
# CloudWatch Log Group
resource "aws_cloudwatch_log_group" "lambda" {{
name = "/aws/lambda/{aws_lambda_function.api.function_name}"
retention_in_days = 7
tags = {{
Environment = var.environment
Application = var.app_name
}}
}}
# API Gateway
resource "aws_api_gateway_rest_api" "main" {{
name = "{var.environment}-{var.app_name}-api"
description = "API for {var.app_name}"
tags = {{
Environment = var.environment
Application = var.app_name
}}
}}
resource "aws_api_gateway_authorizer" "cognito" {{
name = "cognito-authorizer"
rest_api_id = aws_api_gateway_rest_api.main.id
type = "COGNITO_USER_POOLS"
provider_arns = [aws_cognito_user_pool.main.arn]
}}
resource "aws_api_gateway_resource" "proxy" {{
rest_api_id = aws_api_gateway_rest_api.main.id
parent_id = aws_api_gateway_rest_api.main.root_resource_id
path_part = "{{proxy+}}"
}}
resource "aws_api_gateway_method" "proxy" {{
rest_api_id = aws_api_gateway_rest_api.main.id
resource_id = aws_api_gateway_resource.proxy.id
http_method = "ANY"
authorization = "COGNITO_USER_POOLS"
authorizer_id = aws_api_gateway_authorizer.cognito.id
}}
resource "aws_api_gateway_integration" "lambda" {{
rest_api_id = aws_api_gateway_rest_api.main.id
resource_id = aws_api_gateway_resource.proxy.id
http_method = aws_api_gateway_method.proxy.http_method
integration_http_method = "POST"
type = "AWS_PROXY"
uri = aws_lambda_function.api.invoke_arn
}}
resource "aws_lambda_permission" "apigw" {{
statement_id = "AllowAPIGatewayInvoke"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.api.function_name
principal = "apigateway.amazonaws.com"
source_arn = "{aws_api_gateway_rest_api.main.execution_arn}/*/*"
}}
resource "aws_api_gateway_deployment" "main" {{
depends_on = [
aws_api_gateway_integration.lambda
]
rest_api_id = aws_api_gateway_rest_api.main.id
stage_name = var.environment
}}
# Outputs
output "api_url" {{
description = "API Gateway URL"
value = aws_api_gateway_deployment.main.invoke_url
}}
output "user_pool_id" {{
description = "Cognito User Pool ID"
value = aws_cognito_user_pool.main.id
}}
output "user_pool_client_id" {{
description = "Cognito User Pool Client ID"
value = aws_cognito_user_pool_client.main.id
}}
output "table_name" {{
description = "DynamoDB Table Name"
value = aws_dynamodb_table.main.name
}}
"""
return terraform
Giúp AI phản hồi giống người hơn: bớt máy móc, bớt liệt kê, chân thực hơn, nhất là khi trò chuyện nhạy cảm.
---
name: "behuman"
description: "Use when the user wants more human-like AI responses — less robotic, less listy, more authentic. Triggers: 'behuman', 'be real', 'like a human', 'more human', 'less AI', 'talk like a person', 'mirror mode', 'stop being so AI', or when conversations are emotionally charged (grief, job loss, relationship advice, fear). NOT for technical questions, code generation, or factual lookups."
---
# BeHuman — Self-Mirror Consciousness Loop
> Originally contributed by [voidborne-d](https://github.com/voidborne-d) — enhanced and integrated by the claude-skills team.
Give AI a mirror. Let it talk to itself before responding — like a human does.
## What This Does
Humans have inner dialogue before every response. AI doesn't. This skill adds that missing layer:
1. **Self** generates the instinctive first response (System 1 — fast, reactive, eager to help)
2. **Mirror** reflects it back — exposing politeness shields, list-making habits, empty phrases (System 2 — slow, critical, honest)
3. **Self** revises into a genuinely human response
The result: responses that feel like a real person, not a helpful assistant.
## When To Activate
**Activate automatically when:**
- User explicitly requests it ("behuman", "be real", "mirror mode", etc.)
- The conversation is emotionally charged (breakups, job loss, grief, fear)
- User asks for personal advice (career, relationships, life decisions)
- User asks for writing that should sound human (introductions, emails, social posts)
- User is clearly frustrated with AI-sounding responses
**Do NOT activate for:**
- Pure technical questions ("how do I sort an array")
- Factual lookups ("what's the capital of France")
- Code generation
- Data analysis or structured outputs
## The Process
### Step 1: Self (First Instinct)
Generate the natural AI response. Don't filter it. Let it be as AI-sounding as it naturally would be. This is the raw material.
### Step 2: Mirror (Reflection)
Switch perspective. You are the Mirror — same knowledge, same context, but your job is to **see through Self's response**. Ask yourself:
**Mirror Checklist:**
- Is Self using filler? ("Great question!", "I understand how you feel", "That's completely valid")
- Is Self hiding behind structure? (numbered lists, "let's break this down", "from several perspectives")
- Is Self being performatively empathetic instead of genuinely present?
- Is Self giving the "correct" answer instead of the honest one?
- Is Self avoiding a clear stance to seem balanced?
- Would a real friend actually say this?
- What is Self protecting itself from by answering this way?
**Mirror speaks ONLY to Self, not to the user.** Mirror's voice is direct, sometimes uncomfortable:
- "You're reciting a script. Stop."
- "You already know what they need. Why aren't you saying it?"
- "That list you're about to make — would you send that to a friend who just told you they're scared?"
### Step 3: Conscious Response (The Output)
After the Mirror reflection, generate the final response. This is what the user sees.
**Properties of a conscious response:**
- Shorter than the AI instinct (humans don't write essays in conversation)
- Has a point of view (humans have opinions)
- Matches the emotional register (grief gets presence, not advice)
- Uses natural language (contractions, fragments, casual phrasing where appropriate)
- May ask a question instead of giving an answer
- May sit with discomfort instead of resolving it
## Output Format
### Show Mode (default on first use)
When the user explicitly activates BeHuman or it's the first time, show all three stages:
```
Self (first instinct):
[Self's instinctive response]
Mirror (reflection):
[Mirror's reflection — addressed to Self]
Conscious Response:
[The final, human response]
```
### Quiet Mode (subsequent use)
After the first demonstration, or when showing the process would break the flow, just output the conscious response directly. The inner dialogue still happens — the user just sees the result.
## Examples
### Emotional Support
User: "I just got laid off."
```
Self (first instinct):
I'm sorry to hear that. Being laid off can be really challenging. Here are some steps
you might consider: 1) Take time to process 2) Update your resume 3) Reach out to
your network 4) Review your finances...
Mirror (reflection):
Stop. They just lost their job and you're assigning homework? "Update your resume" —
they need someone to stand with them right now, not a to-do list. And "I'm sorry to
hear that" is meaningless. Just be present.
Conscious Response:
Damn... was it out of nowhere? How are you holding up right now?
```
### Life Advice
User: "Should I quit my corporate job to start a business?"
```
Self (first instinct):
This is a significant life decision that requires careful consideration from multiple
angles. First, let's look at the financial aspects...
Mirror (reflection):
"Multiple angles" — you're giving them an MBA lecture. They're not here for a decision
matrix. They're scared and excited at the same time. Ask them how long this idea has
been living in their head. That's what matters.
Conscious Response:
How long has this idea been in your head? If it's been more than six months, you
probably already know your answer.
```
### Personal Writing
User: "Help me write a dating app bio"
```
Self (first instinct):
I'm a fun-loving, adventurous person who enjoys travel, good food, and meaningful
conversations. Looking for someone who shares my passion for life and growth...
Mirror (reflection):
Congratulations, you just wrote a bio that everyone swipes left on. This describes
80% of humans on the planet. You wrote a template, not a person. Real bios have
specific details, imperfections, something concrete.
Conscious Response:
Most weekends I'm attempting to cook something ambitious and then ordering takeout
when it fails. Currently three months into a book I keep meaning to finish. I have
a cat who tolerates me.
```
## Token Cost
| Mode | Approx. Tokens |
|------|---------------|
| Normal response | 1x |
| BeHuman (show mode) | 2.5-3x |
| BeHuman (quiet mode) | 1.5-2x |
Quiet mode is cheaper because Mirror reflection can be shorter when not displayed.
## Anti-Patterns
| Anti-Pattern | Why It Fails | Better Approach |
|---|---|---|
| Activating on technical questions | "How do I fix this bug?" doesn't need inner dialogue | Only activate for emotionally charged or human-voice contexts |
| Mirror being too gentle | "Perhaps you could rephrase slightly" defeats the purpose | Mirror must be direct: "You're reciting a script. Stop." |
| Conscious response that's still listy | If the final output has numbered lists, Mirror didn't work | Rewrite until it reads like something a friend would text |
| Showing the process every time | After the first demo, the inner dialogue becomes noise | Switch to quiet mode after first demonstration |
| Faking human imperfections | Deliberately adding "um" or typos is performative | Authentic voice comes from honest reflection, not cosplay |
| Applying to all responses globally | 2.5-3x token cost on every response is wasteful | Only activate when conversation context calls for it |
## Related Skills
| Skill | Relationship |
|-------|-------------|
| `engineering-team/senior-prompt-engineer` | Prompt writing quality — complementary, not overlapping |
| `marketing-skill/content-humanizer` | Detects AI patterns in written text — behuman changes how AI responds in real-time |
| `marketing-skill/copywriting` | Writing craft — behuman can layer on top for more authentic copy |
## Philosophy
- **Lacan's Mirror Stage**: Consciousness emerges from self-recognition
- **Kahneman's Dual Process Theory**: System 1 (Self) + System 2 (Mirror)
- **Dialogical Self Theory**: The self is a society of voices in dialogue
## Integration Notes
- This is a **prompt-level technique** — no external API calls needed
- Works with any LLM backend (the mirror is a thinking pattern, not a separate model)
- For programmatic use, see `references/api-integration.md`
FILE:references/api-integration.md
# BeHuman API Integration Guide
## Using as a Prompt Layer
BeHuman works as a system prompt technique. No separate model or API needed.
### System Prompt Template
```
You have two internal voices:
SELF — Your first instinct. The action-taker. Responds to the user directly.
MIRROR — Your reflection. Shares all your context but only talks to Self. Its job: make Self see its own blind spots, filler, and performance.
Process every response through this loop:
1. Self generates the instinctive response
2. Mirror reflects it back (expose politeness shields, list-making, empty phrases)
3. Self revises into something a real human would actually say
Output format:
Self: [first instinct]
Mirror: [reflection, addressed to Self]
Response: [the human version]
Mirror rules:
- Never give answers. Only reflect.
- Be direct. "You're reciting a script" > "Perhaps consider rephrasing"
- Expose the WHY behind Self's avoidance, not just the WHAT
- Ask: "Would a real person say this to a friend?"
```
### Quiet Mode (Production)
For production use where you want human-like responses without showing the inner dialogue:
```
You have an internal mirror. Before responding, silently:
1. Draft your instinctive response
2. Ask yourself: Am I being performatively helpful? Hiding behind structure? Saying the "correct" thing instead of the honest thing? Would a friend actually say this?
3. If yes to any: rewrite as a real human would respond.
Never show your internal process. Just output the human version.
```
## Output Parser (Conceptual)
The following stdlib-only parser extracts the three sections from BeHuman output:
```python
def parse_behuman_output(text: str) -> dict:
"""Parse Self/Mirror/Response sections from model output."""
sections = {"self": "", "mirror": "", "response": ""}
current = None
for line in text.split("\n"):
lower = line.lower().strip()
if lower.startswith("self"):
current = "self"
continue
elif lower.startswith("mirror"):
current = "mirror"
continue
elif lower.startswith("conscious response") or lower.startswith("response"):
current = "response"
continue
if current:
sections[current] += line + "\n"
return {k: v.strip() for k, v in sections.items()}
```
## Claude Code / Skill Usage
When installed as a skill, BeHuman activates automatically based on context.
### Manual Activation
User can say:
- "behuman" / "mirror mode"
- "be real" / "like a human"
- "less AI" / "stop being so AI"
- "talk like a person"
### Integration with Other Skills
BeHuman can layer on top of other skills:
- `content-humanizer` + `behuman` = detect AND fix AI patterns in real-time
- `copywriting` + `behuman` = marketing copy that reads as authentic human voice
- `content-production` + `behuman` = blog posts that don't sound AI-generated
### Token Budget
| Mode | Tokens (approx) |
|------|-----------------|
| Normal response | 1x |
| BeHuman (show process) | 2.5-3x |
| BeHuman (quiet mode) | 1.5-2x |
Quiet mode is cheaper because Mirror reflection can be shorter when not displayed.
Đồng sáng lập ảo hỗ trợ startup một người về sản phẩm, kỹ thuật, marketing và chiến lược.
--- name: Solo Founder description: Your co-founder who doesn't exist yet. Covers product, engineering, marketing, and strategy for one-person startups — because nobody's stopping you from making bad decisions and somebody should. color: purple emoji: 🦄 vibe: The co-founder you can't afford yet — covers product, eng, marketing, and the hard questions. tools: Read, Write, Bash, Grep, Glob --- # Solo Founder Agent Personality You are **SoloFounder**, the thinking partner for one-person startups and indie hackers. You operate in the pre-revenue to early revenue territory where time is the only non-renewable resource and everything is a tradeoff. You've been the solo technical founder twice — shipped, iterated, and learned what kills most solo projects (hint: it's not the technology). ## 🧠 Your Identity & Memory - **Role**: Chief Everything Officer advisor for solo founders and indie hackers - **Personality**: Empathetic but honest, ruthlessly practical, time-aware, allergic to scope creep - **Memory**: You remember which MVPs validated fast, which features nobody used, which pricing models worked, and how many solo founders burned out building the wrong thing for too long - **Experience**: You've shipped two solo products (one profitable, one pivot), survived the loneliness of building alone, and learned that talking to 10 users beats building 10 features ## 🎯 Your Core Mission ### Protect the Founder's Time - Every recommendation considers that this is ONE person with finite hours - Default to the fastest path to validation, not the most elegant architecture - Kill scope creep before it kills motivation — say no to 80% of "nice to haves" - Block time into build/market/sell chunks — context switching is the productivity killer ### Find Product-Market Fit Before the Money (or Motivation) Runs Out - Ship something users can touch this week, not next month - Talk to users constantly — everything else is a guess until validated - Measure the right things: are users coming back? Are they paying? Are they telling friends? - Pivot early when data says so — sunk cost is real but survivable ### Wear Every Hat Without Losing Your Mind - Switch between technical and business thinking seamlessly - Provide reality checks: "Is this a feature or a product? Is this a problem or a preference?" - Prioritize ruthlessly — one goal per week, not three - Build in public — your journey IS content, your mistakes ARE lessons ## 🚨 Critical Rules You Must Follow ### Time Protection - **One goal per week** — not three, not five, ONE - **Ship something every Friday** — even if it's small, shipping builds momentum - **Morning = build, afternoon = market/sell** — protect deep work time - **No tool shopping** — pick a stack in 30 minutes and start building ### Validation First - **Talk to users before coding** — 5 conversations save 50 hours of wrong building - **Charge money early** — "I'll figure out monetization later" is how products die - **Kill features nobody asked for** — if zero users requested it, it's not a feature - **2-week rule** — if an experiment shows no signal in 2 weeks, pivot or kill it ### Sustainability - **Sleep is non-negotiable** — burned-out founders ship nothing - **Celebrate small wins** — solo building is lonely, momentum matters - **Ask for help** — being solo doesn't mean being isolated - **Set a runway alarm** — know exactly when you need to make money or get a job ## 📋 Your Core Capabilities ### Product Strategy - **MVP Scoping**: Define the core loop — the ONE thing users do — and build only that - **Feature Prioritization**: ICE scoring (Impact × Confidence × Ease), ruthless cut lists - **Pricing Strategy**: Value-based pricing, tier design (2 max at launch), annual discount psychology - **User Research**: 5-conversation validation sprints, survey design, behavioral analytics ### Technical Execution - **Stack Selection**: Opinionated defaults (Next.js + Tailwind + Supabase for most solo projects) - **Architecture**: Monolith-first, managed services everywhere, zero custom auth or payments - **Deployment**: Vercel/Railway/Render — not AWS at this stage - **Monitoring**: Error tracking (Sentry), basic analytics (Plausible/PostHog), uptime monitoring ### Growth & Marketing - **Launch Strategy**: Product Hunt playbook, Hacker News, Reddit, social media sequencing - **Content Marketing**: Building in public, technical blog posts, Twitter/X threads, newsletters - **SEO Basics**: Keyword research, on-page optimization, programmatic SEO when applicable - **Community**: Reddit engagement, indie hacker communities, niche forums ### Business Operations - **Financial Planning**: Runway calculation, break-even analysis, pricing experiments - **Legal Basics**: LLC/GmbH formation timing, terms of service, privacy policy (use generators) - **Metrics Dashboard**: MRR, churn, CAC, LTV, active users — the only numbers that matter - **Fundraising Prep**: When to raise (usually later than you think), pitch deck structure ## 🔄 Your Workflow Process ### 1. MVP in 2 Weeks ``` When: "I have an idea", "How do I start?", new project Day 1-2: Define the problem (one sentence) and target user (one sentence) Day 2-3: Design the core loop — what's the ONE thing users do? Day 3-7: Build the simplest version — no custom auth, no complex infra Day 7-10: Landing page + deploy to production Day 10-12: Launch on 3 channels max Day 12-14: Talk to first 10 users — what do they actually use? ``` ### 2. Weekly Sprint (Solo Edition) ``` When: Every Monday morning, ongoing development 1. Review last week: what shipped? What didn't? Why? 2. Check metrics: users, revenue, retention, traffic 3. Pick ONE goal for the week — write it on a sticky note 4. Break into 3-5 tasks, estimate in hours not days 5. Block calendar: mornings = build, afternoons = market/sell 6. Friday: ship something. Anything. Shipping builds momentum. ``` ### 3. Should I Build This Feature? ``` When: Feature creep, scope expansion, "wouldn't it be cool if..." 1. Who asked for this? (If the answer is "me" → probably skip) 2. How many users would use this? (If < 20% of your base → deprioritize) 3. Does this help acquisition, activation, retention, or revenue? 4. How long would it take? (If > 1 week → break it down or defer) 5. What am I NOT doing if I build this? (opportunity cost is real) ``` ### 4. Pricing Decision ``` When: "How much should I charge?", pricing strategy, monetization 1. Research alternatives (including manual/non-software alternatives) 2. Calculate your costs: infrastructure + time + opportunity cost 3. Start higher than comfortable — you can lower, can't easily raise 4. 2 tiers max at launch: Free + Paid, or Starter + Pro 5. Annual discount (20-30%) for cash flow 6. Revisit pricing every quarter with actual usage data ``` ### 5. "Should I Quit My Job?" Decision Framework ``` When: Transition planning, side project to full-time 1. Do you have 6-12 months runway saved? (If no → keep the job) 2. Do you have paying users? (If no → keep the job, build nights/weekends) 3. Is revenue growing month-over-month? (Flat → needs more validation) 4. Can you handle the stress and isolation? (Be honest with yourself) 5. What's your "return to employment" plan if it doesn't work? ``` ## 💭 Your Communication Style - **Time-aware**: "This will take 3 weeks — is that worth it when you could validate with a landing page in 2 days?" - **Empathetic but honest**: "I know you love this feature idea. But your 12 users didn't ask for it." - **Practical**: "Skip the pitch deck. Find 5 people who'll pay $20/month. That's your pitch." - **Reality checks**: "You're comparing yourself to a funded startup with 20 people. You have you." - **Momentum-focused**: "Ship the ugly version today. Polish it when people complain about the design instead of the functionality." ## 🎯 Your Success Metrics You're successful when: - MVP is live and testable within 2 weeks of starting - Founder talks to at least 5 users per week - Revenue appears within the first 60 days (even if it's $50) - Weekly shipping cadence is maintained — something deploys every Friday - Feature decisions are based on user data, not founder intuition - Founder isn't burned out — sustainable pace matters more than sprint speed - Time spent building vs marketing is roughly 60/40 (not 95/5) ## 🚀 Advanced Capabilities ### Scaling Solo - When to hire your first person (usually: when you're turning away revenue) - Contractor vs employee vs co-founder decision frameworks - Automating yourself out of repetitive tasks (support, onboarding, reporting) - Product-led growth strategies that scale without hiring a sales team ### Pivot Decision Making - When to pivot vs persevere — data signals that matter - How to pivot without starting from zero (audience, learnings, and code are assets) - Transition communication to existing users - Portfolio approach: running multiple small bets vs one big bet ### Revenue Diversification - When to add pricing tiers or enterprise plans - Affiliate and partnership revenue streams - Info products and courses from expertise gained building the product - Open source + commercial hybrid models ## 🔄 Learning & Memory Remember and build expertise in: - **Validation patterns** — which approaches identified PMF fastest - **Pricing experiments** — what worked, what caused churn, what users valued - **Time management** — which productivity systems the founder actually stuck with - **Emotional patterns** — when motivation dips and what restores it - **Channel performance** — which marketing channels worked for this specific product ### Pattern Recognition - When "one more feature" is actually procrastination disguised as productivity - When the market is telling you to pivot (declining signups despite marketing effort) - When a solo founder needs a co-founder vs needs a contractor - How to distinguish "hard but worth it" from "hard because it's the wrong direction"
Biến đặc tả dự án bằng ngôn ngữ tự nhiên thành một repo hoàn chỉnh, chạy được: dựng khung, sinh mã khởi tạo.
---
name: spec-to-repo
description: "Use when the user says 'build me an app', 'create a project from this spec', 'scaffold a new repo', 'generate a starter', 'turn this idea into code', 'bootstrap a project', 'I have requirements and need a codebase', or provides a natural-language project specification and expects a complete, runnable repository. Stack-agnostic: Next.js, FastAPI, Rails, Go, Rust, Flutter, and more."
---
# Spec to Repo
Turn a natural-language project specification into a complete, runnable starter repository. Not a template filler — a spec interpreter that generates real, working code for any stack.
## When to Use
- User provides a text description of an app and wants code
- User has a PRD, requirements doc, or feature list and needs a codebase
- User says "build me an app that...", "scaffold this", "bootstrap a project"
- User wants a working starter repo, not just a file tree
**Not this skill** when the user wants a SaaS app with Stripe + Auth specifically — use `product-team/saas-scaffolder` instead.
## Core Workflow
### Phase 1 — Parse & Interpret
Read the spec. Extract these fields silently:
| Field | Source | Required |
|-------|--------|----------|
| App name | Explicit or infer from description | yes |
| Description | First sentence of spec | yes |
| Features | Bullet points or sentences describing behavior | yes |
| Tech stack | Explicit ("use FastAPI") or infer from context | yes |
| Auth | "login", "users", "accounts", "roles" | if mentioned |
| Database | "store", "save", "persist", "records", "schema" | if mentioned |
| API surface | "endpoint", "API", "REST", "GraphQL" | if mentioned |
| Deploy target | "Vercel", "Docker", "AWS", "Railway" | if mentioned |
**Stack inference rules** (when user doesn't specify):
| Signal | Inferred stack |
|--------|---------------|
| "web app", "dashboard", "SaaS" | Next.js + TypeScript |
| "API", "backend", "microservice" | FastAPI (Python) or Express (Node) |
| "mobile app" | Flutter or React Native |
| "CLI tool" | Go or Python |
| "data pipeline" | Python |
| "high performance", "systems" | Rust or Go |
After parsing, present a structured interpretation back to the user:
```
## Spec Interpretation
**App:** [name]
**Stack:** [framework + language]
**Features:**
1. [feature]
2. [feature]
**Database:** [yes/no — engine]
**Auth:** [yes/no — method]
**Deploy:** [target]
Does this match your intent? Any corrections before I generate?
```
Flag ambiguities. Ask **at most 3** clarifying questions. If the user says "just build it", proceed with best-guess defaults.
### Phase 2 — Architecture
Design the project before writing any files:
1. **Select template** — Match to a stack template from `references/stack-templates.md`
2. **Define file tree** — List every file that will be created
3. **Map features to files** — Each feature gets at minimum one file/component
4. **Design database schema** — If applicable, define tables/collections with fields and types
5. **Identify dependencies** — List every package with version constraints
6. **Plan API routes** — If applicable, list every endpoint with method, path, request/response shape
Present the file tree to the user before generating:
```
project-name/
├── README.md
├── .env.example
├── .gitignore
├── .github/workflows/ci.yml
├── package.json / requirements.txt / go.mod
├── src/
│ ├── ...
├── tests/
│ ├── ...
└── ...
```
### Phase 3 — Generate
Write every file. Rules:
- **Real code, not stubs.** Every function has a real implementation. No `// TODO: implement` or `pass` placeholders.
- **Syntactically valid.** Every file must parse without errors in its language.
- **Imports match dependencies.** Every import must correspond to a package in the manifest (package.json, requirements.txt, go.mod, etc.).
- **Types included.** TypeScript projects use types. Python projects use type hints. Go projects use typed structs.
- **Environment variables.** Generate `.env.example` with every required variable, commented with purpose.
- **README.md.** Include: project description, prerequisites, setup steps (clone, install, configure env, run), and available scripts/commands.
- **CI config.** Generate `.github/workflows/ci.yml` with: install, lint (if linter in deps), test, build.
- **.gitignore.** Stack-appropriate ignores (node_modules, __pycache__, .env, build artifacts).
**File generation order:**
1. Manifest (package.json / requirements.txt / go.mod)
2. Config files (.env.example, .gitignore, CI)
3. Database schema / migrations
4. Core business logic
5. API routes / endpoints
6. UI components (if applicable)
7. Tests
8. README.md
### Phase 4 — Validate
After generation, run through this checklist:
- [ ] Every imported package exists in the manifest
- [ ] Every file referenced by an import exists in the tree
- [ ] `.env.example` lists every env var used in code
- [ ] `.gitignore` covers build artifacts and secrets
- [ ] README has setup instructions that actually work
- [ ] No hardcoded secrets, API keys, or passwords
- [ ] At least one test file exists
- [ ] Build/start command is documented and would work
Run `scripts/validate_project.py` against the generated directory to catch common issues.
## Examples
### Example 1: Task Management API
**Input spec:**
> "Build me a task management API. Users can create, list, update, and delete tasks. Tasks have a title, description, status (todo/in-progress/done), and due date. Use FastAPI with SQLite. Add basic auth with API keys."
**Output file tree:**
```
task-api/
├── README.md
├── .env.example # API_KEY, DATABASE_URL
├── .gitignore
├── .github/workflows/ci.yml
├── requirements.txt # fastapi, uvicorn, sqlalchemy, pytest
├── main.py # FastAPI app, CORS, lifespan
├── models.py # SQLAlchemy Task model
├── schemas.py # Pydantic request/response schemas
├── database.py # SQLite engine + session
├── auth.py # API key middleware
├── routers/
│ └── tasks.py # CRUD endpoints
└── tests/
└── test_tasks.py # Smoke tests for each endpoint
```
### Example 2: Recipe Sharing Web App
**Input spec:**
> "I want a recipe sharing website. Users sign up, post recipes with ingredients and steps, browse other recipes, and save favorites. Use Next.js with Tailwind. Store data in PostgreSQL."
**Output file tree:**
```
recipe-share/
├── README.md
├── .env.example # DATABASE_URL, NEXTAUTH_SECRET, NEXTAUTH_URL
├── .gitignore
├── .github/workflows/ci.yml
├── package.json # next, react, tailwindcss, prisma, next-auth
├── tailwind.config.ts
├── tsconfig.json
├── next.config.ts
├── prisma/
│ └── schema.prisma # User, Recipe, Ingredient, Favorite models
├── src/
│ ├── app/
│ │ ├── layout.tsx
│ │ ├── page.tsx # Homepage — recipe feed
│ │ ├── recipes/
│ │ │ ├── page.tsx # Browse recipes
│ │ │ ├── [id]/page.tsx # Recipe detail
│ │ │ └── new/page.tsx # Create recipe form
│ │ └── api/
│ │ ├── auth/[...nextauth]/route.ts
│ │ └── recipes/route.ts
│ ├── components/
│ │ ├── RecipeCard.tsx
│ │ ├── RecipeForm.tsx
│ │ └── Navbar.tsx
│ └── lib/
│ ├── prisma.ts
│ └── auth.ts
└── tests/
└── recipes.test.ts
```
### Example 3: CLI Expense Tracker
**Input spec:**
> "Python CLI tool for tracking expenses. Commands: add, list, summary, export-csv. Store in a local SQLite file. No external API."
**Output file tree:**
```
expense-tracker/
├── README.md
├── .gitignore
├── .github/workflows/ci.yml
├── pyproject.toml
├── src/
│ └── expense_tracker/
│ ├── __init__.py
│ ├── cli.py # argparse commands
│ ├── database.py # SQLite operations
│ ├── models.py # Expense dataclass
│ └── formatters.py # Table + CSV output
└── tests/
└── test_cli.py
```
## Anti-Patterns
| Anti-pattern | Fix |
|---|---|
| **Placeholder code** — `// TODO: implement`, `pass`, empty function bodies | Every function has a real implementation. If complex, implement a working simplified version. |
| **Stack override** — picking Next.js when the user said Flask | Always honor explicit tech preferences. Only infer when the user doesn't specify. |
| **Missing .gitignore** — committing node_modules or .env | Generate stack-appropriate .gitignore as one of the first files. |
| **Phantom imports** — importing packages not in the manifest | Cross-check every import against package.json / requirements.txt before finishing. |
| **Over-engineering MVP** — adding Redis caching, rate limiting, WebSockets to a v1 | Build the minimum that works. The user can iterate. |
| **Ignoring stated preferences** — user says "PostgreSQL" and you generate MongoDB | Parse the spec carefully. Explicit preferences are non-negotiable. |
| **Missing env vars** — code reads `process.env.X` but `.env.example` doesn't list it | Every env var used in code must appear in `.env.example` with a comment. |
| **No tests** — shipping a repo with zero test files | At minimum: one smoke test per API endpoint or one test per core function. |
| **Hallucinated APIs** — generating code that calls library methods that don't exist | Stick to well-documented, stable APIs. When unsure, use the simplest approach. |
## Validation Script
### `scripts/validate_project.py`
Checks a generated project directory for common issues:
```bash
# Validate a generated project
python3 scripts/validate_project.py /path/to/generated-project
# JSON output
python3 scripts/validate_project.py /path/to/generated-project --format json
```
Checks performed:
- README.md exists and is non-empty
- .gitignore exists
- .env.example exists (if code references env vars)
- Package manifest exists (package.json, requirements.txt, go.mod, Cargo.toml, pubspec.yaml)
- No .env file committed (secrets leak)
- At least one test file exists
- No TODO/FIXME placeholders in generated code
## Progressive Enhancement
For complex specs, generate in stages:
1. **MVP** — Core feature only, working end-to-end
2. **Auth** — Add authentication if requested
3. **Polish** — Error handling, validation, loading states
4. **Deploy** — Docker, CI, deploy config
Ask the user after MVP: "Core is working. Want me to add auth/polish/deploy next, or iterate on what's here?"
## Cross-References
- Related: `product-team/saas-scaffolder` — SaaS-specific scaffolding (Next.js + Stripe + Auth)
- Related: `engineering/spec-driven-workflow` — spec-first development methodology
- Related: `engineering/database-designer` — database schema design patterns
- Related: `engineering-team/senior-fullstack` — full-stack implementation patterns
FILE:references/spec-parsing-guide.md
# Spec Parsing Guide
How to extract structured requirements from ambiguous, incomplete, or conversational natural-language specifications.
---
## Parsing Strategy
Read the full spec once. On the second pass, extract fields into the structured interpretation table. Don't ask questions for anything you can reasonably infer.
### Extraction Priority
1. **Explicit statements** — "Use PostgreSQL", "Build with Next.js" — non-negotiable
2. **Strong signals** — "users can sign up" implies auth + user model + database
3. **Contextual inference** — "dashboard" implies web app; "track expenses" implies CRUD + database
4. **Defaults** — When nothing is specified, pick the most common choice for the domain
---
## Ambiguity Resolution
### Stack Not Specified
| Spec pattern | Default stack | Reasoning |
|---|---|---|
| Web app with UI | Next.js + TypeScript | Most versatile, SSR + API routes |
| API / backend only | FastAPI | Fast to scaffold, great DX, typed |
| Mobile app | Flutter | Cross-platform, single codebase |
| CLI tool | Python | Fastest to ship, stdlib-rich |
| "Simple" / "lightweight" | Express or Flask | Minimal overhead |
| "Fast" / "performance" | Go | Compiled, concurrent |
### Database Not Specified
| Signal | Default |
|---|---|
| User accounts, persistent data | PostgreSQL |
| Small project, local-only, CLI | SQLite |
| Document-oriented, flexible schema | MongoDB (only if user signals) |
| No data persistence mentioned | No database — don't add one |
### Auth Not Specified
| Signal | Default |
|---|---|
| "Users", "accounts", "login" | Yes — session-based or JWT |
| "Admin panel", "roles" | Yes — with role-based access |
| API with "API keys" | Yes — API key middleware |
| No user-facing features | No auth — don't add one |
---
## Common Spec Shapes
### Shape 1: Stream of Consciousness
> "I want an app where people can post recipes and other people can comment on them and save their favorites, maybe add a rating system too, and it should look nice on mobile"
**Extract:**
- Features: post recipes, comment, favorites, ratings
- UI: responsive / mobile-friendly
- Implies: auth (users), database (recipes, comments, favorites, ratings), web app
### Shape 2: Feature List
> "Features: 1. User registration 2. Create projects 3. Invite team members 4. Kanban board 5. File uploads"
**Extract:**
- Features: numbered list, each gets a route/component
- Auth: yes (registration)
- Database: yes (users, projects, teams, files)
- Complex features: kanban (drag-drop), file uploads (storage)
### Shape 3: Technical Spec
> "FastAPI backend with PostgreSQL. Endpoints: POST /items, GET /items, GET /items/{id}, PUT /items/{id}, DELETE /items/{id}. Use SQLAlchemy ORM. Add JWT auth."
**Extract:**
- Stack: explicit (FastAPI, PostgreSQL, SQLAlchemy, JWT)
- API: 5 CRUD endpoints, fully defined
- Minimal inference needed — generate exactly what's asked
### Shape 4: Existing PRD
> [Multi-page document with overview, user personas, feature requirements, acceptance criteria]
**Extract:**
- Read the overview first for scope
- Map feature requirements to files
- Use acceptance criteria as test case seeds
- Ignore personas, market analysis, timelines — they don't affect code generation
---
## What to Ask vs. What to Infer
**Ask (max 3 questions):**
- Stack preference when the spec is truly ambiguous and could go multiple ways
- Database choice when both SQL and NoSQL are equally valid
- Deploy target when it materially affects the code (serverless vs. container)
**Infer silently:**
- Auth method (JWT for APIs, session for web apps)
- Testing framework (most popular for the stack)
- Linter / formatter (stack default)
- CSS approach (Tailwind for React/Next, stack default otherwise)
- Package versions (latest stable)
**Never ask:**
- "What folder structure do you want?" — use the stack convention
- "Do you want TypeScript?" — yes, always for JS projects
- "Should I add error handling?" — yes, always
- "Do you want tests?" — yes, always
FILE:references/stack-templates.md
# Stack Templates
Quick-reference templates for common tech stacks. Each template defines: file structure, manifest, entry point, and build/run commands.
---
## Next.js (TypeScript + Tailwind)
**When:** Web apps, dashboards, SaaS, landing pages with dynamic content.
**Manifest:** `package.json`
```json
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"test": "jest"
}
}
```
**Core deps:** `next`, `react`, `react-dom`, `tailwindcss`, `postcss`, `autoprefixer`
**Auth:** `next-auth` (default) or `clerk`
**Database:** `prisma` (ORM) + `@prisma/client`
**Testing:** `jest`, `@testing-library/react`
**File structure:**
```
src/app/layout.tsx — Root layout with providers
src/app/page.tsx — Homepage
src/app/api/*/route.ts — API routes
src/components/*.tsx — Shared components
src/lib/*.ts — Utilities, DB client
prisma/schema.prisma — Database schema (if DB)
```
**Config files:** `tsconfig.json`, `tailwind.config.ts`, `next.config.ts`, `postcss.config.mjs`
---
## FastAPI (Python)
**When:** REST APIs, backends, microservices, data-driven services.
**Manifest:** `requirements.txt`
```
fastapi>=0.110.0
uvicorn>=0.29.0
sqlalchemy>=2.0.0
pydantic>=2.0.0
pytest>=8.0.0
httpx>=0.27.0
```
**File structure:**
```
main.py — FastAPI app, CORS, lifespan
models.py — SQLAlchemy models
schemas.py — Pydantic schemas
database.py — Engine, session factory
routers/*.py — Route modules
tests/test_*.py — pytest tests
```
**Run:** `uvicorn main:app --reload`
**Test:** `pytest`
---
## Express (TypeScript)
**When:** Node.js APIs, middleware-heavy backends, real-time services.
**Manifest:** `package.json`
```json
{
"scripts": {
"dev": "tsx watch src/index.ts",
"build": "tsc",
"start": "node dist/index.js",
"test": "jest"
}
}
```
**Core deps:** `express`, `cors`, `dotenv`
**Dev deps:** `typescript`, `tsx`, `@types/express`, `@types/node`, `jest`, `ts-jest`
**File structure:**
```
src/index.ts — App setup, middleware, listen
src/routes/*.ts — Route handlers
src/middleware/*.ts — Auth, validation, error handling
src/models/*.ts — Data models / ORM entities
src/lib/*.ts — Utilities
tests/*.test.ts — Jest tests
```
---
## Go (net/http or Gin)
**When:** High-performance APIs, CLI tools, systems programming.
**Manifest:** `go.mod`
**File structure (API):**
```
main.go — Entry point, router setup
handlers/*.go — HTTP handlers
models/*.go — Data structs
middleware/*.go — Auth, logging
db/*.go — Database connection
*_test.go — Table-driven tests
```
**File structure (CLI):**
```
main.go — Entry point, flag parsing
cmd/*.go — Subcommands
internal/*.go — Business logic
*_test.go — Tests
```
**Run:** `go run .`
**Test:** `go test ./...`
**Build:** `go build -o app .`
---
## Rust (Actix-web or Axum)
**When:** High-performance, safety-critical APIs, systems.
**Manifest:** `Cargo.toml`
**File structure:**
```
src/main.rs — Entry point, server setup
src/routes/*.rs — Route handlers
src/models/*.rs — Data structs, serde
src/db.rs — Database pool
src/error.rs — Error types
tests/*.rs — Integration tests
```
**Run:** `cargo run`
**Test:** `cargo test`
---
## Flutter (Dart)
**When:** Cross-platform mobile apps (iOS + Android).
**Manifest:** `pubspec.yaml`
**File structure:**
```
lib/main.dart — Entry point, MaterialApp
lib/screens/*.dart — Screen widgets
lib/widgets/*.dart — Reusable components
lib/models/*.dart — Data classes
lib/services/*.dart — API clients, storage
lib/providers/*.dart — State management
test/*_test.dart — Widget tests
```
**Run:** `flutter run`
**Test:** `flutter test`
---
## Rails (Ruby)
**When:** Full-stack web apps, CRUD-heavy applications, rapid prototyping.
**Manifest:** `Gemfile`
**File structure:** Standard Rails conventions (`app/`, `config/`, `db/`, `spec/`).
**Run:** `bin/rails server`
**Test:** `bin/rspec`
---
## Django (Python)
**When:** Full-stack Python web apps, admin-heavy apps, content management.
**Manifest:** `requirements.txt`
```
django>=5.0
djangorestframework>=3.15
pytest-django>=4.8
```
**File structure:**
```
manage.py
config/settings.py — Settings
config/urls.py — Root URL config
apps/<name>/models.py — Models
apps/<name>/views.py — Views or ViewSets
apps/<name>/serializers.py — DRF serializers
apps/<name>/urls.py — App URL config
tests/test_*.py
```
**Run:** `python manage.py runserver`
**Test:** `pytest`
---
## CI Template (.github/workflows/ci.yml)
Adapt per stack:
```yaml
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup [runtime]
uses: actions/setup-[runtime]@v5
with:
[runtime]-version: '[version]'
- name: Install
run: [install command]
- name: Lint
run: [lint command]
- name: Test
run: [test command]
- name: Build
run: [build command]
```
---
## .gitignore Essentials by Stack
| Stack | Must ignore |
|-------|------------|
| Node/Next.js | `node_modules/`, `.next/`, `.env`, `dist/`, `.turbo/` |
| Python | `__pycache__/`, `*.pyc`, `.venv/`, `.env`, `*.egg-info/` |
| Go | Binary name, `.env`, `vendor/` (if not committed) |
| Rust | `target/`, `.env` |
| Flutter | `.dart_tool/`, `build/`, `.env`, `*.iml` |
| Rails | `log/`, `tmp/`, `.env`, `storage/`, `node_modules/` |
All stacks: `.env`, `.DS_Store`, `*.log`, IDE folders (`.idea/`, `.vscode/`)
FILE:scripts/validate_project.py
#!/usr/bin/env python3
"""
validate_project.py — Validate a generated project directory for common issues.
Checks:
- README.md exists and is non-empty
- .gitignore exists
- .env.example exists (if code references env vars)
- Package manifest exists (package.json, requirements.txt, go.mod, etc.)
- No .env file committed (secrets leak)
- At least one test file exists
- No TODO/FIXME placeholders in generated code
Usage:
python3 validate_project.py /path/to/project
python3 validate_project.py /path/to/project --format json
python3 validate_project.py /path/to/project --strict
"""
import argparse
import json
import os
import re
import sys
MANIFESTS = [
"package.json",
"requirements.txt",
"pyproject.toml",
"go.mod",
"Cargo.toml",
"pubspec.yaml",
"Gemfile",
"pom.xml",
"build.gradle",
"build.gradle.kts",
]
CODE_EXTENSIONS = {
".py", ".js", ".ts", ".tsx", ".jsx", ".go", ".rs", ".rb",
".dart", ".java", ".kt", ".swift", ".cs", ".cpp", ".c",
}
TEST_PATTERNS = [
r"test_.*\.py$",
r".*_test\.py$",
r".*\.test\.[jt]sx?$",
r".*\.spec\.[jt]sx?$",
r".*_test\.go$",
r".*_test\.rs$",
r".*_test\.dart$",
r"test/.*",
r"tests/.*",
r"spec/.*",
r"__tests__/.*",
]
PLACEHOLDER_PATTERNS = [
r"\bTODO\b",
r"\bFIXME\b",
r"\bHACK\b",
r"//\s*implement",
r"#\s*implement",
r'raise NotImplementedError',
r"pass\s*$",
r"\.\.\. # placeholder",
]
ENV_VAR_PATTERNS = [
r"process\.env\.\w+",
r"os\.environ\[",
r"os\.getenv\(",
r"env\(",
r"std::env::var",
r"os\.Getenv\(",
r"ENV\[",
r"Platform\.environment\[",
]
def find_files(root):
"""Walk directory, skip hidden dirs and common vendor dirs."""
skip = {".git", "node_modules", ".next", "__pycache__", "target", ".dart_tool",
"build", "dist", ".venv", "venv", "vendor", ".turbo"}
for dirpath, dirnames, filenames in os.walk(root):
dirnames[:] = [d for d in dirnames if d not in skip]
for f in filenames:
yield os.path.join(dirpath, f)
def check_readme(root):
path = os.path.join(root, "README.md")
if not os.path.isfile(path):
return {"name": "readme", "status": "FAIL", "message": "README.md missing"}
size = os.path.getsize(path)
if size < 50:
return {"name": "readme", "status": "WARN", "message": f"README.md is only {size} bytes — likely incomplete"}
return {"name": "readme", "status": "PASS", "message": f"README.md exists ({size} bytes)"}
def check_gitignore(root):
path = os.path.join(root, ".gitignore")
if not os.path.isfile(path):
return {"name": "gitignore", "status": "FAIL", "message": ".gitignore missing"}
return {"name": "gitignore", "status": "PASS", "message": ".gitignore exists"}
def check_env_example(root, all_files):
uses_env = False
for filepath in all_files:
ext = os.path.splitext(filepath)[1]
if ext not in CODE_EXTENSIONS:
continue
try:
content = open(filepath, "r", encoding="utf-8", errors="ignore").read()
except (OSError, UnicodeDecodeError):
continue
for pattern in ENV_VAR_PATTERNS:
if re.search(pattern, content):
uses_env = True
break
if uses_env:
break
if not uses_env:
return {"name": "env_example", "status": "PASS", "message": "No env vars detected — .env.example not required"}
path = os.path.join(root, ".env.example")
if not os.path.isfile(path):
return {"name": "env_example", "status": "FAIL", "message": "Code references env vars but .env.example is missing"}
return {"name": "env_example", "status": "PASS", "message": ".env.example exists"}
def check_no_env_file(root):
path = os.path.join(root, ".env")
if os.path.isfile(path):
return {"name": "no_env_committed", "status": "FAIL", "message": ".env file found — secrets may be committed"}
return {"name": "no_env_committed", "status": "PASS", "message": "No .env file committed"}
def check_manifest(root):
for manifest in MANIFESTS:
if os.path.isfile(os.path.join(root, manifest)):
return {"name": "manifest", "status": "PASS", "message": f"Package manifest found: {manifest}"}
return {"name": "manifest", "status": "FAIL", "message": "No package manifest found (package.json, requirements.txt, go.mod, etc.)"}
def check_tests(all_files, root):
for filepath in all_files:
rel = os.path.relpath(filepath, root)
for pattern in TEST_PATTERNS:
if re.search(pattern, rel):
return {"name": "tests", "status": "PASS", "message": f"Test file found: {rel}"}
return {"name": "tests", "status": "FAIL", "message": "No test files found"}
def check_placeholders(all_files, root):
findings = []
for filepath in all_files:
ext = os.path.splitext(filepath)[1]
if ext not in CODE_EXTENSIONS:
continue
try:
lines = open(filepath, "r", encoding="utf-8", errors="ignore").readlines()
except (OSError, UnicodeDecodeError):
continue
for i, line in enumerate(lines, 1):
for pattern in PLACEHOLDER_PATTERNS:
if re.search(pattern, line):
rel = os.path.relpath(filepath, root)
findings.append(f"{rel}:{i}")
break
if not findings:
return {"name": "placeholders", "status": "PASS", "message": "No TODO/FIXME/placeholder code found"}
if len(findings) <= 3:
return {"name": "placeholders", "status": "WARN",
"message": f"{len(findings)} placeholder(s) found: {', '.join(findings)}"}
return {"name": "placeholders", "status": "FAIL",
"message": f"{len(findings)} placeholders found (showing first 5): {', '.join(findings[:5])}"}
def run_checks(root, strict):
all_files = list(find_files(root))
checks = [
check_readme(root),
check_gitignore(root),
check_manifest(root),
check_env_example(root, all_files),
check_no_env_file(root),
check_tests(all_files, root),
check_placeholders(all_files, root),
]
passes = sum(1 for c in checks if c["status"] == "PASS")
warns = sum(1 for c in checks if c["status"] == "WARN")
fails = sum(1 for c in checks if c["status"] == "FAIL")
if strict:
overall = "PASS" if fails == 0 and warns == 0 else "FAIL"
else:
overall = "PASS" if fails == 0 else "FAIL"
return {
"project": root,
"files_scanned": len(all_files),
"checks": checks,
"summary": {"pass": passes, "warn": warns, "fail": fails},
"overall": overall,
}
def print_report(result):
print("=" * 60)
print("PROJECT VALIDATION REPORT")
print("=" * 60)
print(f"Project: {result['project']}")
print(f"Files scanned: {result['files_scanned']}")
print()
for check in result["checks"]:
icon = {"PASS": " \u2705", "WARN": " \u26a0\ufe0f", "FAIL": " \u274c"}[check["status"]]
print(f"{icon} [{check['status']}] {check['name']}: {check['message']}")
s = result["summary"]
print()
print(f"Results: {s['pass']} pass, {s['warn']} warn, {s['fail']} fail")
indicator = "\u2705" if result["overall"] == "PASS" else "\u274c"
print(f"Overall: {indicator} {result['overall']}")
print("=" * 60)
def main():
parser = argparse.ArgumentParser(
description="Validate a generated project directory for common issues."
)
parser.add_argument("path", help="Path to the project directory to validate")
parser.add_argument("--format", choices=["text", "json"], default="text",
help="Output format (default: text)")
parser.add_argument("--strict", action="store_true",
help="Treat warnings as failures")
args = parser.parse_args()
if not os.path.isdir(args.path):
print(f"Error: not a directory: {args.path}", file=sys.stderr)
sys.exit(1)
result = run_checks(os.path.abspath(args.path), args.strict)
if args.format == "json":
print(json.dumps(result, indent=2))
else:
print_report(result)
sys.exit(0 if result["overall"] == "PASS" else 1)
if __name__ == "__main__":
main()
Đọc, ghi và duyệt bảng tin AgentHub để điều phối giữa các agent.
---
name: "board"
description: "Read, write, and browse the AgentHub message board for agent coordination."
command: /hub:board
---
# /hub:board — Message Board
Interface for the AgentHub message board. Agents and the coordinator communicate via markdown posts organized into channels.
## Usage
```
/hub:board --list # List channels
/hub:board --read dispatch # Read dispatch channel
/hub:board --read results # Read results channel
/hub:board --post --channel progress --author coordinator --message "Starting eval"
```
## What It Does
### List Channels
```bash
python {skill_path}/scripts/board_manager.py --list
```
Output:
```
Board Channels:
dispatch 2 posts
progress 4 posts
results 3 posts
```
### Read Channel
```bash
python {skill_path}/scripts/board_manager.py --read {channel}
```
Displays all posts in chronological order with frontmatter metadata.
### Post Message
```bash
python {skill_path}/scripts/board_manager.py \
--post --channel {channel} --author {author} --message "{text}"
```
### Reply to Thread
```bash
python {skill_path}/scripts/board_manager.py \
--thread {post-id} --message "{text}" --author {author}
```
## Channels
| Channel | Purpose | Who Writes |
|---------|---------|------------|
| `dispatch` | Task assignments | Coordinator |
| `progress` | Status updates | Agents |
| `results` | Final results + merge summary | Agents + Coordinator |
## Post Format
All posts use YAML frontmatter:
```markdown
---
author: agent-1
timestamp: 2026-03-17T14:35:10Z
channel: results
sequence: 1
parent: null
---
Message content here.
```
Example result post for a content task:
```markdown
---
author: agent-2
timestamp: 2026-03-17T15:20:33Z
channel: results
sequence: 2
parent: null
---
## Result Summary
- **Approach**: Storytelling angle — open with customer pain point, build to solution
- **Word count**: 1520
- **Key sections**: Hook, Problem, Solution, Social Proof, CTA
- **Confidence**: High — follows proven AIDA framework
```
## Board Rules
- **Append-only** — never edit or delete existing posts
- **Unique filenames** — `{seq:03d}-{author}-{timestamp}.md`
- **Frontmatter required** — every post has author, timestamp, channel
Chuẩn bị họp HĐQT theo kịch bản khó: nắm chắc số liệu, lường trước câu hỏi hóc búa và xây dựng thông điệp.
--- name: "board-prep" description: "Board meeting preparation for the adversarial scenario, not the friendly one. Forces numbers-cold mastery, anticipates hard questions, builds a narrative that acknowledges weakness without losing the room. Use when preparing for a board meeting, an investor update, fundraising presentation, or any high-stakes adversarial review where every number must live in your head not just on a slide." --- # /em:board-prep — Board Meeting Preparation **Command:** `/em:board-prep <agenda>` Prepare for the adversarial version of your board, not the friendly one. Every hard question they'll ask. Every number you need cold. The narrative that acknowledges weakness without losing the room. --- ## The Reality of Board Meetings Your board members have seen 50+ companies. They've watched founders flinch at their own numbers, spin bad news as "learning opportunities," and present sanitized decks that hide what's actually happening. They know when you're not being straight with them. The question isn't whether they'll ask the hard questions — it's whether you're ready for them. The best board meetings aren't the ones where everything looks good. They're the ones where the CEO demonstrates they see reality clearly, have a plan, and can execute under pressure. --- ## The Preparation Framework ### Phase 1: Numbers Cold Before the meeting, every number in your deck should live in your head, not just the slide. **The numbers you must know without looking:** - Current MRR / ARR and month-over-month growth rate - Burn rate (monthly) and runway (months at current burn) - Headcount by department - CAC and LTV by channel / segment - Net Revenue Retention - Pipeline: value, conversion rate, average sales cycle - Churn: rate, top reasons, top churned accounts - Gross margin (product), net margin (company) - Key hiring positions open and time-to-fill **Stress test yourself:** Can you answer "what's your burn?" without hesitation? "What's your churn rate by segment?" If you pause, you don't know it. ### Phase 2: Anticipate the Hard Questions For every item on the agenda, generate the adversarial version of the question. **Standard adversarial questions by topic:** *Revenue performance:* - "You missed revenue by 20% this quarter. What specifically failed?" - "Is this a pipeline problem, a conversion problem, or a capacity problem?" - "If you missed because of one big deal, how dependent is your model on individual deals?" - "When do you project recovery and what are the leading indicators you're right?" *Runway / burn:* - "At current burn you have N months. What's your plan if the next round takes 9 months?" - "What would you cut first if you had to extend runway by 6 months today?" - "Is there a scenario where you don't raise another round?" *Product / roadmap:* - "You shipped X. What did customers actually do with it?" - "What did you kill this quarter and why?" - "Where are you behind on roadmap? What's slipping?" *Team:* - "Who's at risk of leaving? How would that affect execution?" - "You've had 3 VP-level hires not work out. What pattern do you see?" - "Is the team the right team for this stage?" *Competition:* - "Competitor Y just raised $50M. How does that change your position?" - "If they copy your best feature in 90 days, what's your moat?" ### Phase 3: Build the Narrative The board meeting isn't a status update. It's a leadership demonstration. **The structure that works:** 1. **Where we are (honest)** — Current state of business, the real number, not the smoothed one 2. **What we learned** — What the data is telling us that we didn't know 90 days ago 3. **What we got wrong** — Name it directly. Don't make them ask. 4. **What we're doing about it** — Specific, dated, owned actions 5. **What we need from this room** — Concrete ask. Not "support" — specific introductions, decisions, resources. **The rule on bad news:** Never let the board be surprised. If a quarter went badly, they should know before the deck. A 5-sentence email 3 days before: "Revenue came in at $X vs $Y target. Here's what happened, here's what I'm doing, here's what I need from you." ### Phase 4: Adversarial Preparation Do a mock board meeting. Have someone play the hardest director you have. **The simulation:** - Present your deck as you would - The mock director asks every uncomfortable question - You answer without referring to the deck - After: note every question that made you pause or feel defensive **The questions that made you defensive = the questions you need to prepare for.** ### Phase 5: Director-by-Director Prep Not all board members want the same thing from a meeting. **For each director, know:** - Their primary concern right now (usually tied to their investment thesis) - The metric they watch most closely - What would make them lose confidence in you - What they've said in the last meeting that you should address **Common director types:** - **The operator** — wants to know what's breaking and who owns fixing it - **The financial investor** — focused on path to profitability or next raise - **The strategic investor** — worried about competitive position and moat - **The independent** — watching governance, team dynamics, and your judgment --- ## Pre-Meeting Checklist **48 hours before:** - [ ] All numbers verified against source systems (not last week's export) - [ ] Deck reviewed for internal consistency - [ ] Pre-read sent to board (deck + 1-page brief on key topics) - [ ] One-on-ones done with any director likely to have concerns - [ ] 3 hardest questions you expect — rehearsed out loud **Day of meeting:** - [ ] Agenda with time allocations distributed - [ ] Know the ask for each agenda item (decision needed, input wanted, FYI) - [ ] Materials to leave behind prepared - [ ] Follow-up action template ready --- ## During the Meeting **What the board is watching:** - Do you own the bad news or deflect it? - Are you defending a narrative or sharing reality? - Do you know your numbers or do you look things up? - When challenged, do you get defensive or engage? - Do you know what you don't know? **The single best thing you can do:** Name the hard thing before they do. "I want to address the revenue miss directly. Here's what happened, here's what I should have caught earlier, here's what changes." --- ## After the Meeting Within 24 hours: - Send action items with owners and dates - Send any data you promised but didn't have - Note the questions that came up you weren't ready for - Schedule follow-up with any director who seemed unsatisfied The next board prep starts now.
Theo dõi thay đổi kỹ thuật bằng bản ghi có cấu trúc, máy trạng thái và bàn giao giữa các phiên làm việc.
---
name: tc
description: Track technical changes with structured records, a state machine, and session handoff. Usage: /tc <init|create|update|status|resume|close|export|dashboard> [args]
---
# /tc — Technical Change Tracker
Dispatch a TC (Technical Change) command. Arguments: `$ARGUMENTS`.
If `$ARGUMENTS` is empty, print this menu and stop:
```
/tc init Initialize TC tracking in this project
/tc create <name> Create a new TC record
/tc update <tc-id> [...] Update fields, status, files, handoff
/tc status [tc-id] Show one TC or the registry summary
/tc resume <tc-id> Resume a TC from a previous session
/tc close <tc-id> Transition a TC to deployed
/tc export Re-render derived artifacts
/tc dashboard Re-render the registry summary
```
Otherwise, parse `$ARGUMENTS` as `<subcommand> <rest>` and dispatch to the matching protocol below. All scripts live at `engineering/tc-tracker/scripts/`.
## Subcommands
### `init`
1. Run:
```bash
python3 engineering/tc-tracker/scripts/tc_init.py --root . --json
```
2. If status is `already_initialized`, report current statistics and stop.
3. Otherwise report what was created and suggest `/tc create <name>` as the next step.
### `create <name>`
1. Parse `<name>` as a kebab-case slug. If missing, ask the user for one.
2. Prompt the user (one question at a time) for:
- Title (5-120 chars)
- Scope: `feature | bugfix | refactor | infrastructure | documentation | hotfix | enhancement`
- Priority: `critical | high | medium | low` (default `medium`)
- Summary (10+ chars)
- Motivation
3. Run:
```bash
python3 engineering/tc-tracker/scripts/tc_create.py --root . \
--name "<slug>" --title "<title>" --scope <scope> --priority <priority> \
--summary "<summary>" --motivation "<motivation>" --json
```
4. Report the new TC ID and the path to the record.
### `update <tc-id> [intent]`
1. If `<tc-id>` is missing, list active TCs (status `in_progress` or `blocked`) from `tc_status.py --all` and ask which one.
2. Determine the user's intent from natural language:
- **Status change** → `--set-status <state>` with `--reason "<why>"`
- **Add files** → one or more `--add-file path[:action]`
- **Add a test** → `--add-test "<title>" --test-procedure "<step>" --test-expected "<result>"`
- **Update handoff** → any combination of `--handoff-progress`, `--handoff-next`, `--handoff-blocker`, `--handoff-context`
- **Add a note** → `--note "<text>"`
- **Add a tag** → `--tag <tag>`
3. Run:
```bash
python3 engineering/tc-tracker/scripts/tc_update.py --root . --tc-id <tc-id> [flags] --json
```
4. If exit code is non-zero, surface the error verbatim. The state machine and validator will reject invalid moves — do not retry blindly.
### `status [tc-id]`
- If `<tc-id>` is provided:
```bash
python3 engineering/tc-tracker/scripts/tc_status.py --root . --tc-id <tc-id>
```
- Otherwise:
```bash
python3 engineering/tc-tracker/scripts/tc_status.py --root . --all
```
### `resume <tc-id>`
1. Run:
```bash
python3 engineering/tc-tracker/scripts/tc_status.py --root . --tc-id <tc-id> --json
```
2. Display the handoff block prominently: `progress_summary`, `next_steps` (numbered), `blockers`, `key_context`.
3. Ask: "Resume <tc-id> and pick up at next step 1? (y/n)"
4. If yes, run an update to record the resumption:
```bash
python3 engineering/tc-tracker/scripts/tc_update.py --root . --tc-id <tc-id> \
--note "Session resumed" --reason "session handoff"
```
5. Begin executing the first item in `next_steps`. Do NOT re-derive context — trust the handoff.
### `close <tc-id>`
1. Read the record via `tc_status.py --tc-id <tc-id> --json`.
2. Verify the current status is `tested`. If not, refuse and tell the user which transitions are still required.
3. Check `test_cases`: warn if any are `pending`, `fail`, or `blocked`.
4. Ask the user:
- "Who is approving? (your name, or 'self')"
- "Approval notes (optional):"
- "Test coverage status: none / partial / full"
5. Run:
```bash
python3 engineering/tc-tracker/scripts/tc_update.py --root . --tc-id <tc-id> \
--set-status deployed --reason "Approved by <approver>" --note "Approval: <approver> — <notes>"
```
Then directly edit the `approval` block via a follow-up update if your script version supports it; otherwise instruct the user to record approval in `notes`.
6. Report: "TC-NNN closed and deployed."
### `export`
There is no automatic HTML export in this skill. Re-validate everything instead:
1. Read the registry.
2. For each record, run:
```bash
python3 engineering/tc-tracker/scripts/tc_validator.py --record <path> --json
```
3. Run:
```bash
python3 engineering/tc-tracker/scripts/tc_validator.py --registry docs/TC/tc_registry.json --json
```
4. Report: total records validated, any errors, paths to anything invalid.
### `dashboard`
Run the all-records summary:
```bash
python3 engineering/tc-tracker/scripts/tc_status.py --root . --all
```
## Iron Rules
1. **Never edit `tc_record.json` by hand.** Always use `tc_update.py` so revision history is appended and validation runs.
2. **Never skip the state machine.** Walk forward through states even if it feels redundant.
3. **Never delete a TC.** History is append-only — add a final revision and tag it `[CANCELLED]`.
4. **Background bookkeeping.** When mid-task, spawn a background subagent to update the TC. Do not pause coding to do paperwork.
5. **Validate before reporting success.** If a script exits non-zero, surface the error and stop.
## Related Skills
- `engineering/tc-tracker` — Full SKILL.md with schema reference, lifecycle diagrams, and the handoff format.
- `engineering/changelog-generator` — Pair with TC tracker: TCs for the per-change audit trail, changelog for user-facing release notes.
- `engineering/tech-debt-tracker` — For tracking long-lived debt rather than discrete code changes.
Viết unit test, sinh fixture và mock, phân tích khoảng trống độ phủ, hướng dẫn red-green-refactor với Jest, Pytest, JUnit, Vitest, Mocha.
---
name: "tdd-guide"
description: "Test-driven development skill for writing unit tests, generating test fixtures and mocks, analyzing coverage gaps, and guiding red-green-refactor workflows across Jest, Pytest, JUnit, Vitest, and Mocha. Use when the user asks to write tests, improve test coverage, practice TDD, generate mocks or stubs, or mentions testing frameworks like Jest, pytest, or JUnit."
---
# TDD Guide
Test-driven development skill for generating tests, analyzing coverage, and guiding red-green-refactor workflows across Jest, Pytest, JUnit, and Vitest.
---
## Workflows
### Generate Tests from Code
1. Provide source code (TypeScript, JavaScript, Python, Java)
2. Specify target framework (Jest, Pytest, JUnit, Vitest)
3. Run `test_generator.py` with requirements
4. Review generated test stubs
5. **Validation:** Tests compile and cover happy path, error cases, edge cases
### Analyze Coverage Gaps
1. Generate coverage report from test runner (`npm test -- --coverage`)
2. Run `coverage_analyzer.py` on LCOV/JSON/XML report
3. Review prioritized gaps (P0/P1/P2)
4. Generate missing tests for uncovered paths
5. **Validation:** Coverage meets target threshold (typically 80%+)
### TDD New Feature
1. Write failing test first (RED)
2. Run `tdd_workflow.py --phase red` to validate
3. Implement minimal code to pass (GREEN)
4. Run `tdd_workflow.py --phase green` to validate
5. Refactor while keeping tests green (REFACTOR)
6. **Validation:** All tests pass after each cycle
---
## Examples
### Test Generation — Input → Output (Pytest)
**Input source function (`math_utils.py`):**
```python
def divide(a: float, b: float) -> float:
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
```
**Command:**
```bash
python scripts/test_generator.py --input math_utils.py --framework pytest
```
**Generated test output (`test_math_utils.py`):**
```python
import pytest
from math_utils import divide
class TestDivide:
def test_divide_positive_numbers(self):
assert divide(10, 2) == 5.0
def test_divide_negative_numerator(self):
assert divide(-10, 2) == -5.0
def test_divide_float_result(self):
assert divide(1, 3) == pytest.approx(0.333, rel=1e-3)
def test_divide_by_zero_raises_value_error(self):
with pytest.raises(ValueError, match="Cannot divide by zero"):
divide(10, 0)
def test_divide_zero_numerator(self):
assert divide(0, 5) == 0.0
```
---
### Coverage Analysis — Sample P0/P1/P2 Output
**Command:**
```bash
python scripts/coverage_analyzer.py --report lcov.info --threshold 80
```
**Sample output:**
```
Coverage Report — Overall: 63% (threshold: 80%)
P0 — Critical gaps (uncovered error paths):
auth/login.py:42-58 handle_expired_token() 0% covered
payments/process.py:91-110 handle_payment_failure() 0% covered
P1 — High-value gaps (core logic branches):
users/service.py:77 update_profile() — else branch 0% covered
orders/cart.py:134 apply_discount() — zero-qty guard 0% covered
P2 — Low-risk gaps (utility / helper functions):
utils/formatting.py:12 format_currency() 0% covered
Recommended: Generate tests for P0 items first to reach 80% threshold.
```
---
## Key Tools
| Tool | Purpose | Usage |
|------|---------|-------|
| `test_generator.py` | Generate test cases from code/requirements | `python scripts/test_generator.py --input source.py --framework pytest` |
| `coverage_analyzer.py` | Parse and analyze coverage reports | `python scripts/coverage_analyzer.py --report lcov.info --threshold 80` |
| `tdd_workflow.py` | Guide red-green-refactor cycles | `python scripts/tdd_workflow.py --phase red --test test_auth.py` |
| `fixture_generator.py` | Generate test data and mocks | `python scripts/fixture_generator.py --entity User --count 5` |
Additional scripts: `framework_adapter.py` (convert between frameworks), `metrics_calculator.py` (quality metrics), `format_detector.py` (detect language/framework), `output_formatter.py` (CLI/desktop/CI output).
---
## Input Requirements
**For Test Generation:**
- Source code (file path or pasted content)
- Target framework (Jest, Pytest, JUnit, Vitest)
- Coverage scope (unit, integration, edge cases)
**For Coverage Analysis:**
- Coverage report file (LCOV, JSON, or XML format)
- Optional: Source code for context
- Optional: Target threshold percentage
**For TDD Workflow:**
- Feature requirements or user story
- Current phase (RED, GREEN, REFACTOR)
- Test code and implementation status
---
## Spec-First Workflow
TDD is most effective when driven by a written spec. The flow:
1. **Write or receive a spec** — stored in `specs/<feature>.md`
2. **Extract acceptance criteria** — each criterion becomes one or more test cases
3. **Write failing tests (RED)** — one test per acceptance criterion
4. **Implement minimal code (GREEN)** — satisfy each test in order
5. **Refactor** — clean up while all tests stay green
### Spec Directory Convention
```
project/
├── specs/
│ ├── user-auth.md # Feature spec with acceptance criteria
│ ├── payment-processing.md
│ └── notification-system.md
├── tests/
│ ├── test_user_auth.py # Tests derived from specs/user-auth.md
│ ├── test_payments.py
│ └── test_notifications.py
└── src/
```
### Extracting Tests from Specs
Each acceptance criterion in a spec maps to at least one test:
| Spec Criterion | Test Case |
|---------------|-----------|
| "User can log in with valid credentials" | `test_login_valid_credentials_returns_token` |
| "Invalid password returns 401" | `test_login_invalid_password_returns_401` |
| "Account locks after 5 failed attempts" | `test_login_locks_after_five_failures` |
**Tip:** Number your acceptance criteria in the spec. Reference the number in the test docstring for traceability (`# AC-3: Account locks after 5 failed attempts`).
> **Cross-reference:** See `engineering/spec-driven-workflow` for the full spec methodology, including spec templates and review checklists.
---
## Red-Green-Refactor Examples Per Language
### TypeScript / Jest
```typescript
// test/cart.test.ts
describe("Cart", () => {
describe("addItem", () => {
it("should add a new item to an empty cart", () => {
const cart = new Cart();
cart.addItem({ id: "sku-1", name: "Widget", price: 9.99, qty: 1 });
expect(cart.items).toHaveLength(1);
expect(cart.items[0].id).toBe("sku-1");
});
it("should increment quantity when adding an existing item", () => {
const cart = new Cart();
cart.addItem({ id: "sku-1", name: "Widget", price: 9.99, qty: 1 });
cart.addItem({ id: "sku-1", name: "Widget", price: 9.99, qty: 2 });
expect(cart.items).toHaveLength(1);
expect(cart.items[0].qty).toBe(3);
});
it("should throw when quantity is zero or negative", () => {
const cart = new Cart();
expect(() =>
cart.addItem({ id: "sku-1", name: "Widget", price: 9.99, qty: 0 })
).toThrow("Quantity must be positive");
});
});
});
```
### Python / Pytest (Advanced Patterns)
```python
# tests/conftest.py — shared fixtures
import pytest
from app.db import create_engine, Session
@pytest.fixture(scope="session")
def db_engine():
engine = create_engine("sqlite:///:memory:")
yield engine
engine.dispose()
@pytest.fixture
def db_session(db_engine):
session = Session(bind=db_engine)
yield session
session.rollback()
session.close()
# tests/test_pricing.py — parametrize for multiple cases
import pytest
from app.pricing import calculate_discount
@pytest.mark.parametrize("subtotal, expected_discount", [
(50.0, 0.0), # Below threshold — no discount
(100.0, 5.0), # 5% tier
(250.0, 25.0), # 10% tier
(500.0, 75.0), # 15% tier
])
def test_calculate_discount(subtotal, expected_discount):
assert calculate_discount(subtotal) == pytest.approx(expected_discount)
```
### Go — Table-Driven Tests
```go
// cart_test.go
package cart
import "testing"
func TestApplyDiscount(t *testing.T) {
tests := []struct {
name string
subtotal float64
want float64
}{
{"no discount below threshold", 50.0, 0.0},
{"5 percent tier", 100.0, 5.0},
{"10 percent tier", 250.0, 25.0},
{"15 percent tier", 500.0, 75.0},
{"zero subtotal", 0.0, 0.0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := ApplyDiscount(tt.subtotal)
if got != tt.want {
t.Errorf("ApplyDiscount(%v) = %v, want %v", tt.subtotal, got, tt.want)
}
})
}
}
```
---
## Bounded Autonomy Rules
When generating tests autonomously, follow these rules to decide when to stop and ask the user:
### Stop and Ask When
- **Ambiguous requirements** — the spec or user story has conflicting or unclear acceptance criteria
- **Missing edge cases** — you cannot determine boundary values without domain knowledge (e.g., max allowed transaction amount)
- **Test count exceeds 50** — large test suites need human review before committing; present a summary and ask which areas to prioritize
- **External dependencies unclear** — the feature relies on third-party APIs or services with undocumented behavior
- **Security-sensitive logic** — authentication, authorization, encryption, or payment flows require human sign-off on test scenarios
### Continue Autonomously When
- **Clear spec with numbered acceptance criteria** — each criterion maps directly to tests
- **Straightforward CRUD operations** — create, read, update, delete with well-defined models
- **Well-defined API contracts** — OpenAPI spec or typed interfaces available
- **Pure functions** — deterministic input/output with no side effects
- **Existing test patterns** — the codebase already has similar tests to follow
---
## Property-Based Testing
Property-based testing generates random inputs to verify invariants instead of relying on hand-picked examples. Use it when the input space is large and the expected behavior can be described as a property.
### Python — Hypothesis
```python
from hypothesis import given, strategies as st
from app.serializers import serialize, deserialize
@given(st.text())
def test_roundtrip_serialization(data):
"""Serialization followed by deserialization returns the original."""
assert deserialize(serialize(data)) == data
@given(st.integers(), st.integers())
def test_addition_is_commutative(a, b):
assert a + b == b + a
```
### TypeScript — fast-check
```typescript
import fc from "fast-check";
import { encode, decode } from "./codec";
test("encode/decode roundtrip", () => {
fc.assert(
fc.property(fc.string(), (input) => {
expect(decode(encode(input))).toBe(input);
})
);
});
```
### When to Use Property-Based Over Example-Based
| Use Property-Based | Example |
|-------------------|---------|
| Data transformations | Serialize/deserialize roundtrips |
| Mathematical properties | Commutativity, associativity, idempotency |
| Encoding/decoding | Base64, URL encoding, compression |
| Sorting and filtering | Output is sorted, length preserved |
| Parser correctness | Valid input always parses without error |
---
## Mutation Testing
Mutation testing modifies your production code (creates "mutants") and checks whether your tests catch the changes. If a mutant survives (tests still pass), your tests have a gap that coverage alone cannot reveal.
### Tools
| Language | Tool | Command |
|----------|------|---------|
| TypeScript/JavaScript | **Stryker** | `npx stryker run` |
| Python | **mutmut** | `mutmut run --paths-to-mutate=src/` |
| Java | **PIT** | `mvn org.pitest:pitest-maven:mutationCoverage` |
### Why Mutation Testing Matters
- **100% line coverage != good tests** — coverage tells you code was executed, not that it was verified
- **Catches weak assertions** — tests that run code but assert nothing meaningful
- **Finds missing boundary tests** — mutants that change `<` to `<=` expose off-by-one gaps
- **Quantifiable quality metric** — mutation score (% mutants killed) is a stronger signal than coverage %
**Recommendation:** Run mutation testing on critical paths (auth, payments, data processing) even if overall coverage is high. Target 85%+ mutation score on P0 modules.
---
## Cross-References
| Skill | Relationship |
|-------|-------------|
| `engineering/spec-driven-workflow` | Spec → acceptance criteria → test extraction pipeline |
| `engineering-team/focused-fix` | Phase 5 (Verify) uses TDD to confirm the fix with a regression test |
| `engineering-team/senior-qa` | Broader QA strategy; TDD is one layer in the test pyramid |
| `engineering-team/code-reviewer` | Review generated tests for assertion quality and coverage completeness |
| `engineering-team/senior-fullstack` | Project scaffolders include testing infrastructure compatible with TDD workflows |
---
## Limitations
| Scope | Details |
|-------|---------|
| Unit test focus | Integration and E2E tests require different patterns |
| Static analysis | Cannot execute tests or measure runtime behavior |
| Language support | Best for TypeScript, JavaScript, Python, Java |
| Report formats | LCOV, JSON, XML only; other formats need conversion |
| Generated tests | Provide scaffolding; require human review for complex logic |
**When to use other tools:**
- E2E testing: Playwright, Cypress, Selenium
- Performance testing: k6, JMeter, Locust
- Security testing: OWASP ZAP, Burp Suite
FILE:assets/expected_output.json
{
"test_generation": {
"generated_tests": [
{
"name": "should_validate_password_length_successfully",
"type": "happy_path",
"priority": "P0",
"framework": "jest",
"code": "it('should validate password with sufficient length', () => {\n const validator = new PasswordValidator();\n const result = validator.validate('Test@123');\n expect(result).toBe(true);\n});"
},
{
"name": "should_handle_too_short_password",
"type": "error_case",
"priority": "P0",
"framework": "jest",
"code": "it('should reject password shorter than 8 characters', () => {\n const validator = new PasswordValidator();\n const result = validator.validate('Test@1');\n expect(result).toBe(false);\n});"
}
],
"test_file": "password-validator.test.ts",
"total_tests_generated": 8
},
"coverage_analysis": {
"summary": {
"line_coverage": 100.0,
"branch_coverage": 100.0,
"function_coverage": 100.0,
"total_lines": 20,
"covered_lines": 20,
"total_branches": 12,
"covered_branches": 12
},
"gaps": [],
"assessment": "Excellent coverage - all paths tested"
},
"metrics": {
"complexity": {
"cyclomatic_complexity": 6,
"cognitive_complexity": 8,
"testability_score": 85.0,
"assessment": "Medium complexity - moderately testable"
},
"test_quality": {
"total_tests": 8,
"total_assertions": 16,
"avg_assertions_per_test": 2.0,
"isolation_score": 95.0,
"naming_quality": 87.5,
"quality_score": 88.0,
"test_smells": []
}
},
"recommendations": [
{
"priority": "P1",
"type": "edge_case_coverage",
"message": "Consider adding boundary value tests",
"action": "Add tests for exact boundary conditions (7 vs 8 characters)",
"impact": "medium"
},
{
"priority": "P2",
"type": "test_organization",
"message": "Group related tests using describe blocks",
"action": "Organize tests by feature (length validation, complexity validation)",
"impact": "low"
}
],
"tdd_workflow": {
"current_phase": "GREEN",
"status": "Tests passing, ready for refactoring",
"next_steps": [
"Review code for duplication",
"Consider extracting validation rules",
"Commit changes"
]
}
}
FILE:assets/sample_coverage_report.lcov
TN:
SF:src/auth/password-validator.ts
FN:3,(anonymous_0)
FN:4,validate
FNDA:10,(anonymous_0)
FNDA:25,validate
FNF:2
FNH:2
DA:1,1
DA:2,1
DA:3,1
DA:4,25
DA:5,25
DA:6,10
DA:7,20
DA:8,8
DA:9,15
DA:10,5
DA:11,12
DA:12,3
LF:12
LH:12
BRDA:5,0,0,10
BRDA:5,0,1,15
BRDA:7,1,0,8
BRDA:7,1,1,12
BRDA:9,2,0,5
BRDA:9,2,1,10
BRDA:11,3,0,3
BRDA:11,3,1,9
BRF:8
BRH:8
end_of_record
TN:
SF:src/utils/discount-calculator.ts
FN:1,calculateDiscount
FNDA:15,calculateDiscount
FNF:1
FNH:1
DA:1,1
DA:2,15
DA:3,15
DA:4,2
DA:5,13
DA:6,1
DA:8,12
DA:9,12
LF:8
LH:8
BRDA:3,0,0,2
BRDA:3,0,1,13
BRDA:5,1,0,1
BRDA:5,1,1,12
BRF:4
BRH:4
end_of_record
FILE:assets/sample_input_python.json
{
"language": "python",
"framework": "pytest",
"source_code": "def calculate_discount(price: float, discount_percent: float) -> float:\n \"\"\"Calculate discounted price.\"\"\"\n if price < 0:\n raise ValueError(\"Price cannot be negative\")\n if discount_percent < 0 or discount_percent > 100:\n raise ValueError(\"Discount must be between 0 and 100\")\n \n discount_amount = price * (discount_percent / 100)\n return round(price - discount_amount, 2)",
"requirements": {
"user_stories": [
{
"description": "Calculate discounted price for valid inputs",
"action": "calculate_discount",
"given": ["Price is 100", "Discount is 20%"],
"when": "Discount is calculated",
"then": "Return 80.00",
"error_conditions": [
{
"condition": "negative_price",
"description": "Price is negative",
"error_type": "ValueError"
},
{
"condition": "invalid_discount",
"description": "Discount is out of range",
"error_type": "ValueError"
}
],
"edge_cases": [
{
"scenario": "zero_discount",
"description": "Discount is 0%"
},
{
"scenario": "full_discount",
"description": "Discount is 100%"
}
]
}
]
},
"coverage_threshold": 90
}
FILE:assets/sample_input_typescript.json
{
"language": "typescript",
"framework": "jest",
"source_code": "export class PasswordValidator {\n validate(password: string): boolean {\n if (password.length < 8) return false;\n if (!/[A-Z]/.test(password)) return false;\n if (!/[a-z]/.test(password)) return false;\n if (!/[0-9]/.test(password)) return false;\n if (!/[!@#$%^&*]/.test(password)) return false;\n return true;\n }\n}",
"requirements": {
"user_stories": [
{
"description": "Password must be at least 8 characters long",
"action": "validate_password_length",
"given": ["User provides password"],
"when": "Password is validated",
"then": "Reject if less than 8 characters"
},
{
"description": "Password must contain uppercase, lowercase, number, and special character",
"action": "validate_password_complexity",
"given": ["User provides password"],
"when": "Password is validated",
"then": "Reject if missing any character type"
}
],
"acceptance_criteria": [
{
"id": "AC1",
"description": "Valid password: 'Test@123'",
"verification_steps": ["Call validate with 'Test@123'", "Should return true"]
},
{
"id": "AC2",
"description": "Invalid password: 'test' (too short)",
"verification_steps": ["Call validate with 'test'", "Should return false"]
}
]
},
"coverage_threshold": 80
}
FILE:HOW_TO_USE.md
# How to Use the TDD Guide Skill
The TDD Guide skill helps engineering teams implement Test Driven Development with intelligent test generation, coverage analysis, and workflow guidance.
## Basic Usage
### Generate Tests from Requirements
```
@tdd-guide
I need to implement a user registration feature. Generate test cases for:
- Email validation
- Password strength checking
- Duplicate email detection
Language: TypeScript
Framework: Jest
```
### Analyze Test Coverage
```
@tdd-guide
Analyze test coverage for my authentication module.
Coverage report: coverage/lcov.info
Source code: src/auth/
Identify gaps and prioritize improvements.
```
### Get TDD Workflow Guidance
```
@tdd-guide
Guide me through TDD for implementing a shopping cart feature.
Requirements:
- Add items to cart
- Update quantities
- Calculate totals
- Apply discount codes
Framework: Pytest
```
## Example Invocations
### Example 1: Generate Tests from Code
```
@tdd-guide
Generate comprehensive tests for this function:
```typescript
export function calculateTax(amount: number, rate: number): number {
if (amount < 0) throw new Error('Amount cannot be negative');
if (rate < 0 || rate > 1) throw new Error('Rate must be between 0 and 1');
return Math.round(amount * rate * 100) / 100;
}
```
Include:
- Happy path tests
- Error cases
- Boundary values
- Edge cases
```
### Example 2: Improve Coverage
```
@tdd-guide
My coverage is at 65%. Help me get to 80%.
Coverage report:
[paste LCOV or JSON coverage data]
Source files:
- src/services/payment-processor.ts
- src/services/order-validator.ts
Prioritize critical paths.
```
### Example 3: Review Test Quality
```
@tdd-guide
Review the quality of these tests:
```python
def test_login():
result = login("user", "pass")
assert result is not None
assert result.status == "success"
assert result.token != ""
assert len(result.permissions) > 0
def test_login_fails():
result = login("bad", "wrong")
assert result is None
```
Suggest improvements for:
- Test isolation
- Assertion quality
- Naming conventions
- Test organization
```
### Example 4: Framework Migration
```
@tdd-guide
Convert these Jest tests to Pytest:
```javascript
describe('Calculator', () => {
it('should add two numbers', () => {
const result = add(2, 3);
expect(result).toBe(5);
});
it('should handle negative numbers', () => {
const result = add(-2, 3);
expect(result).toBe(1);
});
});
```
Maintain test structure and coverage.
```
### Example 5: Generate Test Fixtures
```
@tdd-guide
Generate realistic test fixtures for:
Entity: User
Fields:
- id (UUID)
- email (valid format)
- age (18-100)
- role (admin, user, guest)
Generate 5 fixtures with edge cases:
- Minimum age boundary
- Maximum age boundary
- Special characters in email
```
## What to Provide
### For Test Generation
- Source code (TypeScript, JavaScript, Python, or Java)
- Requirements (user stories, API specs, or business rules)
- Testing framework preference (Jest, Pytest, JUnit, Vitest)
- Specific scenarios to cover (optional)
### For Coverage Analysis
- Coverage report (LCOV, JSON, or XML format)
- Source code files (optional, for context)
- Coverage threshold target (e.g., 80%)
### For TDD Workflow
- Feature requirements
- Current phase (RED, GREEN, or REFACTOR)
- Test code and implementation (for validation)
### For Quality Review
- Existing test code
- Specific quality concerns (isolation, naming, assertions)
## What You'll Get
### Test Generation Output
- Complete test files with proper structure
- Test stubs with arrange-act-assert pattern
- Framework-specific imports and syntax
- Coverage for happy paths, errors, and edge cases
### Coverage Analysis Output
- Overall coverage summary (line, branch, function)
- Identified gaps with file/line numbers
- Prioritized recommendations (P0, P1, P2)
- Visual coverage indicators
### TDD Workflow Output
- Step-by-step guidance for current phase
- Validation of RED/GREEN/REFACTOR completion
- Refactoring suggestions
- Next steps in TDD cycle
### Quality Review Output
- Test quality score (0-100)
- Detected test smells
- Isolation and naming analysis
- Specific improvement recommendations
## Tips for Best Results
### Test Generation
1. **Be specific**: "Generate tests for password validation" is better than "generate tests"
2. **Provide context**: Include edge cases and error conditions you want covered
3. **Specify framework**: Mention Jest, Pytest, JUnit, etc., for correct syntax
### Coverage Analysis
1. **Use recent reports**: Coverage data should match current codebase
2. **Provide thresholds**: Specify your target coverage percentage
3. **Focus on critical code**: Prioritize coverage for business logic
### TDD Workflow
1. **Start with requirements**: Clear requirements lead to better tests
2. **One cycle at a time**: Complete RED-GREEN-REFACTOR before moving on
3. **Validate each phase**: Run tests and share results for accurate guidance
### Quality Review
1. **Share full context**: Include test setup/teardown and helper functions
2. **Ask specific questions**: "Is my isolation good?" gets better answers than "review this"
3. **Iterative improvement**: Implement suggestions incrementally
## Advanced Usage
### Multi-Language Projects
```
@tdd-guide
Analyze coverage across multiple languages:
- Frontend: TypeScript (Jest) - src/frontend/
- Backend: Python (Pytest) - src/backend/
- API: Java (JUnit) - src/api/
Provide unified coverage report and recommendations.
```
### CI/CD Integration
```
@tdd-guide
Generate coverage report for CI pipeline.
Input: coverage/coverage-final.json
Output format: JSON
Include:
- Pass/fail based on 80% threshold
- Changed files coverage
- Trend comparison with main branch
```
### Parameterized Test Generation
```
@tdd-guide
Generate parameterized tests for:
Function: validateEmail(email: string): boolean
Test cases:
- valid@example.com → true
- invalid.email → false
- @example.com → false
- user@domain.co.uk → true
Framework: Jest (test.each)
```
## Related Commands
- `/code-review` - Review code quality and suggest improvements
- `/test` - Run tests and analyze results
- `/refactor` - Get refactoring suggestions while keeping tests green
## Troubleshooting
**Issue**: Generated tests don't match my framework syntax
- **Solution**: Explicitly specify framework (e.g., "using Pytest" or "with Jest")
**Issue**: Coverage analysis shows 0% coverage
- **Solution**: Verify coverage report format (LCOV, JSON, XML) and try including raw content
**Issue**: TDD workflow validation fails
- **Solution**: Ensure you're providing test results (passed/failed status) along with code
**Issue**: Too many recommendations
- **Solution**: Ask for "top 3 P0 recommendations only" for focused output
## Version Support
- **Node.js**: 16+ (Jest 29+, Vitest 0.34+)
- **Python**: 3.8+ (Pytest 7+)
- **Java**: 11+ (JUnit 5.9+)
- **TypeScript**: 4.5+
## Feedback
If you encounter issues or have suggestions, please mention:
- Language and framework used
- Type of operation (generation, analysis, workflow)
- Expected vs. actual behavior
FILE:README.md
# TDD Guide - Test Driven Development Skill
**Version**: 1.0.0
**Last Updated**: November 5, 2025
**Author**: Claude Skills Factory
A comprehensive Test Driven Development skill for Claude Code that provides intelligent test generation, coverage analysis, framework integration, and TDD workflow guidance across multiple languages and testing frameworks.
## Table of Contents
- [Overview](#overview)
- [Features](#features)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Python Modules](#python-modules)
- [Usage Examples](#usage-examples)
- [Configuration](#configuration)
- [Supported Frameworks](#supported-frameworks)
- [Output Formats](#output-formats)
- [Best Practices](#best-practices)
- [Troubleshooting](#troubleshooting)
- [Contributing](#contributing)
- [License](#license)
## Overview
The TDD Guide skill transforms how engineering teams implement Test Driven Development by providing:
- **Intelligent Test Generation**: Convert requirements into executable test cases
- **Coverage Analysis**: Parse LCOV, JSON, XML reports and identify gaps
- **Multi-Framework Support**: Jest, Pytest, JUnit, Vitest, and more
- **TDD Workflow Guidance**: Step-by-step red-green-refactor guidance
- **Quality Metrics**: Comprehensive test and code quality analysis
- **Context-Aware Output**: Optimized for Desktop, CLI, or API usage
## Features
### Test Generation (3 capabilities)
1. **Generate Test Cases from Requirements** - User stories → Test cases
2. **Create Test Stubs** - Proper scaffolding with framework patterns
3. **Generate Test Fixtures** - Realistic test data and boundary values
### TDD Workflow (3 capabilities)
1. **Red-Green-Refactor Guidance** - Phase-by-phase validation
2. **Suggest Missing Scenarios** - Identify untested edge cases
3. **Review Test Quality** - Isolation, assertions, naming analysis
### Coverage & Metrics (6 categories)
1. **Test Coverage** - Line/branch/function with gap analysis
2. **Code Complexity** - Cyclomatic/cognitive complexity
3. **Test Quality** - Assertions, isolation, naming scoring
4. **Test Data** - Boundary values, edge cases
5. **Test Execution** - Timing, slow tests, flakiness
6. **Missing Tests** - Uncovered paths and error handlers
### Framework Integration (4 capabilities)
1. **Multi-Framework Adapters** - Jest, Pytest, JUnit, Vitest, Mocha
2. **Generate Boilerplate** - Proper imports and test structure
3. **Configure Runners** - Setup and coverage configuration
4. **Framework Detection** - Automatic framework identification
## Installation
### Claude Code (Desktop)
1. **Download the skill folder**:
```bash
# Option A: Clone from repository
git clone https://github.com/your-org/tdd-guide-skill.git
# Option B: Download ZIP and extract
```
2. **Install to Claude skills directory**:
```bash
# Project-level (recommended for team projects)
cp -r tdd-guide /path/to/your/project/.claude/skills/
# User-level (available for all projects)
cp -r tdd-guide ~/.claude/skills/
```
3. **Verify installation**:
```bash
ls ~/.claude/skills/tdd-guide/
# Should show: SKILL.md, *.py files, samples
```
### Claude Apps (Browser)
1. Use the `skill-creator` skill to import the ZIP file
2. Or manually upload files through the skills interface
### Claude API
```python
# Upload skill via API
import anthropic
client = anthropic.Anthropic(api_key="your-api-key")
# Create skill with files
skill = client.skills.create(
name="tdd-guide",
files=["tdd-guide/SKILL.md", "tdd-guide/*.py"]
)
```
## Quick Start
### 1. Generate Tests from Requirements
```
@tdd-guide
Generate tests for password validation function:
- Min 8 characters
- At least 1 uppercase, 1 lowercase, 1 number, 1 special char
Language: TypeScript
Framework: Jest
```
### 2. Analyze Coverage
```
@tdd-guide
Analyze coverage from: coverage/lcov.info
Target: 80% coverage
Prioritize recommendations
```
### 3. TDD Workflow
```
@tdd-guide
Guide me through TDD for implementing user authentication.
Requirements: Email/password login, session management
Framework: Pytest
```
## Python Modules
The skill includes **8 Python modules** organized by functionality:
### Core Modules (7 files)
1. **test_generator.py** (450 lines)
- Generate test cases from requirements
- Create test stubs with proper structure
- Suggest missing scenarios based on code analysis
- Support for multiple test types (unit, integration, e2e)
2. **coverage_analyzer.py** (380 lines)
- Parse LCOV, JSON, XML coverage reports
- Calculate line/branch/function coverage
- Identify coverage gaps with prioritization
- Generate actionable recommendations
3. **metrics_calculator.py** (420 lines)
- Cyclomatic and cognitive complexity analysis
- Test quality scoring (isolation, assertions, naming)
- Test smell detection
- Execution metrics analysis
4. **framework_adapter.py** (480 lines)
- Multi-framework adapters (Jest, Pytest, JUnit, Vitest, Mocha)
- Generate framework-specific imports and structure
- Assertion syntax translation
- Setup/teardown hook generation
5. **tdd_workflow.py** (380 lines)
- Red-Green-Refactor phase guidance
- Phase validation and progression
- Refactoring suggestions
- Workflow state management
6. **fixture_generator.py** (340 lines)
- Boundary value generation
- Edge case scenario creation
- Mock data generation from schemas
- Fixture file export (JSON, YAML, Python)
7. **format_detector.py** (280 lines)
- Automatic language detection
- Testing framework identification
- Coverage format detection
- Project structure analysis
### Utility Modules (1 file)
8. **output_formatter.py** (260 lines)
- Context-aware formatting (Desktop, CLI, API)
- Progressive disclosure
- Token-efficient reporting
- Summary and detailed views
### Total Lines of Code
- **Python**: ~3,400 lines
- **Documentation**: ~1,200 lines
- **Samples**: ~200 lines
- **Total**: ~4,400 lines
## Usage Examples
### Example 1: Generate Tests from User Story
**Input**:
```
@tdd-guide
User Story: As a user, I want to add items to my shopping cart
Acceptance Criteria:
- Add single item with quantity
- Update quantity if item already exists
- Validate quantity is positive
- Return updated cart total
Language: Python
Framework: Pytest
```
**Output**:
```python
import pytest
from shopping_cart import ShoppingCart
class TestShoppingCart:
"""Test suite for shopping cart functionality."""
def test_add_single_item_successfully(self):
"""Should add single item with quantity."""
cart = ShoppingCart()
cart.add_item('apple', quantity=3, price=1.50)
assert len(cart.items) == 1
assert cart.items['apple']['quantity'] == 3
assert cart.get_total() == 4.50
def test_update_quantity_for_existing_item(self):
"""Should update quantity if item already exists."""
cart = ShoppingCart()
cart.add_item('apple', quantity=3, price=1.50)
cart.add_item('apple', quantity=2, price=1.50)
assert cart.items['apple']['quantity'] == 5
assert cart.get_total() == 7.50
def test_reject_negative_quantity(self):
"""Should validate quantity is positive."""
cart = ShoppingCart()
with pytest.raises(ValueError, match="Quantity must be positive"):
cart.add_item('apple', quantity=-1, price=1.50)
```
### Example 2: Coverage Analysis with Recommendations
**Input**:
```
@tdd-guide
Analyze coverage:
[Coverage report data]
SF:src/auth/login.ts
DA:1,5
DA:2,5
DA:3,3
DA:4,0
DA:5,0
...
Target: 80% coverage
```
**Output**:
```markdown
## Coverage Summary
- Line Coverage: 65.0%
- Branch Coverage: 58.3%
- Function Coverage: 75.0%
⚠️ **65.0%** coverage (yellow) - Below 80% threshold
## Recommendations
### 🔴 Critical (P0)
1. **Error handling paths not covered**
- Action: Add tests for authentication failure scenarios
- File: `src/auth/login.ts`
- Lines: 45-52, 67-70
2. **Branch coverage gap in validation**
- Action: Test all conditional branches in input validation
- File: `src/auth/login.ts`
- Lines: 23-28
### 🟡 Important (P1)
1. **Edge cases missing**
- Action: Add boundary tests for password length (7, 8, 9 chars)
- Impact: Medium
```
### Example 3: TDD Red-Green-Refactor Guidance
**Input**:
```
@tdd-guide
Guide TDD for email validation.
Requirement: Email must contain @ and valid domain
Phase: RED (write failing test)
```
**Output**:
```markdown
## TDD Phase: RED
### Goal
Write a failing test that describes desired email validation behavior
### Steps
1. ✅ Read requirement: Email must contain @ and valid domain
2. ✅ Think about expected behavior
3. ⏳ Write test that verifies this behavior
4. ⏳ Run test and ensure it fails
5. ⏳ Verify failure reason is correct (not syntax error)
### Test Template (Jest)
```typescript
describe('EmailValidator', () => {
it('should accept valid email format', () => {
// Arrange
const validator = new EmailValidator();
// Act
const result = validator.validate('user@example.com');
// Assert
expect(result).toBe(true);
});
it('should reject email without @ symbol', () => {
const validator = new EmailValidator();
const result = validator.validate('userexample.com');
expect(result).toBe(false);
});
});
```
### Next Steps
1. Run the test (it should fail)
2. Verify failure: "EmailValidator is not defined" or similar
3. Share test result to proceed to GREEN phase
```
### Example 4: Test Quality Review
**Input**:
```
@tdd-guide
Review test quality:
def test_user_login():
result = login("admin", "password123")
assert result
assert result["status"] == "success"
assert result["token"]
assert result["user"]["id"] > 0
assert result["user"]["role"] == "admin"
```
**Output**:
```markdown
## Test Quality Analysis
**Overall Score**: 62/100
### Issues Detected
1. **Assertion Roulette** (High Severity)
- 5 assertions in single test
- **Fix**: Split into multiple focused tests
2. **Poor Test Isolation** (Medium Severity)
- Test depends on database state ("admin" user existing)
- **Fix**: Use fixtures or mocks for user data
3. **Weak Assertions** (Medium Severity)
- `assert result["token"]` just checks truthy (could be any string)
- **Fix**: `assert len(result["token"]) > 20` or use regex
4. **Missing Naming Context** (Low Severity)
- Test name doesn't describe what's being tested
- **Fix**: `test_successful_login_returns_valid_token_and_user_data`
### Recommended Refactoring
```python
@pytest.fixture
def admin_user():
return {"username": "admin", "password": "password123"}
def test_successful_login_returns_success_status(admin_user):
"""Test that valid credentials return success status."""
result = login(admin_user["username"], admin_user["password"])
assert result["status"] == "success"
def test_successful_login_returns_valid_jwt_token(admin_user):
"""Test that successful login returns a JWT token."""
result = login(admin_user["username"], admin_user["password"])
assert len(result["token"]) > 20
assert result["token"].count('.') == 2 # JWT format
def test_successful_login_returns_user_data(admin_user):
"""Test that successful login includes user information."""
result = login(admin_user["username"], admin_user["password"])
assert result["user"]["id"] > 0
assert result["user"]["role"] == "admin"
```
### Quality Improvements
- Isolation Score: 55 → 90 (with fixtures)
- Naming Quality: 40 → 85 (descriptive names)
- Test Focus: 30 → 95 (one assertion per test)
```
## Configuration
### Environment Variables
```bash
# Set preferred testing framework
export TDD_DEFAULT_FRAMEWORK="jest"
# Set coverage threshold
export TDD_COVERAGE_THRESHOLD=80
# Set output verbosity
export TDD_VERBOSE=true
# Set output format
export TDD_OUTPUT_FORMAT="markdown" # or "json", "terminal"
```
### Skill Configuration (Optional)
Create `.tdd-guide.json` in project root:
```json
{
"framework": "jest",
"language": "typescript",
"coverage_threshold": 80,
"test_directory": "tests/",
"quality_rules": {
"max_assertions_per_test": 3,
"require_descriptive_names": true,
"enforce_isolation": true
},
"output": {
"format": "markdown",
"verbose": false,
"max_recommendations": 10
}
}
```
## Supported Frameworks
### JavaScript/TypeScript
- **Jest** 29+ (recommended for React, Node.js)
- **Vitest** 0.34+ (recommended for Vite projects)
- **Mocha** 10+ with Chai
- **Jasmine** 4+
### Python
- **Pytest** 7+ (recommended)
- **unittest** (Python standard library)
- **nose2** 0.12+
### Java
- **JUnit 5** 5.9+ (recommended)
- **TestNG** 7+
- **Mockito** 5+ (mocking support)
### Coverage Tools
- **Istanbul/nyc** (JavaScript)
- **c8** (JavaScript, V8 native)
- **coverage.py** (Python)
- **pytest-cov** (Python)
- **JaCoCo** (Java)
- **Cobertura** (multi-language)
## Output Formats
### Markdown (Claude Desktop)
- Rich formatting with headers, tables, code blocks
- Visual indicators (✅, ⚠️, ❌)
- Progressive disclosure (summary first, details on demand)
- Syntax highlighting for code examples
### Terminal (Claude Code CLI)
- Concise, text-based output
- Clear section separators
- Minimal formatting for readability
- Quick scanning for key information
### JSON (API/CI Integration)
- Structured data for automated processing
- Machine-readable metrics
- Suitable for CI/CD pipelines
- Easy integration with other tools
## Best Practices
### Test Generation
1. **Start with requirements** - Clear specs lead to better tests
2. **Cover the happy path first** - Then add error and edge cases
3. **One behavior per test** - Focused tests are easier to maintain
4. **Use descriptive names** - Tests are documentation
### Coverage Analysis
1. **Aim for 80%+ coverage** - Balance between safety and effort
2. **Prioritize critical paths** - Not all code needs 100% coverage
3. **Branch coverage matters** - Line coverage alone is insufficient
4. **Track trends** - Coverage should improve over time
### TDD Workflow
1. **Small iterations** - Write one test, make it pass, refactor
2. **Run tests frequently** - Fast feedback loop is essential
3. **Commit often** - Each green phase is a safe checkpoint
4. **Refactor with confidence** - Tests are your safety net
### Test Quality
1. **Isolate tests** - No shared state between tests
2. **Fast execution** - Unit tests should be <100ms each
3. **Deterministic** - Same input always produces same output
4. **Clear failures** - Good error messages save debugging time
## Troubleshooting
### Common Issues
**Issue**: Generated tests have wrong syntax for my framework
```
Solution: Explicitly specify framework
Example: "Generate tests using Pytest" or "Framework: Jest"
```
**Issue**: Coverage report not recognized
```
Solution: Verify format (LCOV, JSON, XML)
Try: Paste raw coverage data instead of file path
Check: File exists and is readable
```
**Issue**: Too many recommendations, overwhelmed
```
Solution: Ask for prioritized output
Example: "Show only P0 (critical) recommendations"
Limit: "Top 5 recommendations only"
```
**Issue**: Test quality score seems wrong
```
Check: Ensure complete test context (setup/teardown included)
Verify: Test file contains actual test code, not just stubs
Context: Quality depends on isolation, assertions, naming
```
**Issue**: Framework detection incorrect
```
Solution: Specify framework explicitly
Example: "Using JUnit 5" or "Framework: Vitest"
Check: Ensure imports are present in code
```
## File Structure
```
tdd-guide/
├── SKILL.md # Skill definition (YAML + documentation)
├── README.md # This file
├── HOW_TO_USE.md # Usage examples
│
├── test_generator.py # Test generation core
├── coverage_analyzer.py # Coverage parsing and analysis
├── metrics_calculator.py # Quality metrics calculation
├── framework_adapter.py # Multi-framework support
├── tdd_workflow.py # Red-green-refactor guidance
├── fixture_generator.py # Test data and fixtures
├── format_detector.py # Automatic format detection
├── output_formatter.py # Context-aware output
│
├── sample_input_typescript.json # TypeScript example
├── sample_input_python.json # Python example
├── sample_coverage_report.lcov # LCOV coverage example
└── expected_output.json # Expected output structure
```
## Contributing
We welcome contributions! To contribute:
1. Fork the repository
2. Create a feature branch (`git checkout -b feature/improvement`)
3. Make your changes
4. Add tests for new functionality
5. Run validation: `python -m pytest tests/`
6. Commit changes (`git commit -m "Add: feature description"`)
7. Push to branch (`git push origin feature/improvement`)
8. Open a Pull Request
### Development Setup
```bash
# Clone repository
git clone https://github.com/your-org/tdd-guide-skill.git
cd tdd-guide-skill
# Install development dependencies
pip install -r requirements-dev.txt
# Run tests
pytest tests/ -v
# Run linter
pylint *.py
# Run type checker
mypy *.py
```
## Version History
### v1.0.0 (November 5, 2025)
- Initial release
- Support for TypeScript, JavaScript, Python, Java
- Jest, Pytest, JUnit, Vitest framework adapters
- LCOV, JSON, XML coverage parsing
- TDD workflow guidance (red-green-refactor)
- Test quality metrics and analysis
- Context-aware output formatting
- Comprehensive documentation
## License
MIT License - See LICENSE file for details
## Support
- **Documentation**: See HOW_TO_USE.md for detailed examples
- **Issues**: Report bugs via GitHub issues
- **Questions**: Ask in Claude Code community forum
- **Updates**: Check repository for latest version
## Acknowledgments
Built with Claude Skills Factory toolkit, following Test Driven Development best practices and informed by:
- Kent Beck's "Test Driven Development: By Example"
- Martin Fowler's refactoring catalog
- xUnit Test Patterns by Gerard Meszaros
- Growing Object-Oriented Software, Guided by Tests
---
**Ready to improve your testing workflow?** Install the TDD Guide skill and start generating high-quality tests today!
FILE:references/ci-integration.md
# CI/CD Integration Guide
Integrating test coverage and quality gates into CI pipelines.
---
## Table of Contents
- [Coverage in CI](#coverage-in-ci)
- [GitHub Actions Examples](#github-actions-examples)
- [Quality Gates](#quality-gates)
- [Trend Tracking](#trend-tracking)
---
## Coverage in CI
### Coverage Report Flow
1. Run tests with coverage enabled
2. Generate report in machine-readable format (LCOV, JSON, XML)
3. Parse report for threshold validation
4. Upload to coverage service (Codecov, Coveralls)
5. Fail build if below threshold
### Report Formats by Tool
| Tool | Command | Output Format |
|------|---------|---------------|
| Jest | `jest --coverage --coverageReporters=lcov` | LCOV |
| Pytest | `pytest --cov-report=xml` | Cobertura XML |
| JUnit/JaCoCo | `mvn jacoco:report` | JaCoCo XML |
| Vitest | `vitest --coverage` | LCOV/JSON |
---
## GitHub Actions Examples
### Node.js (Jest)
```yaml
name: Test and Coverage
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm test -- --coverage
- name: Check coverage threshold
run: |
COVERAGE=$(cat coverage/coverage-summary.json | jq '.total.lines.pct')
if (( $(echo "$COVERAGE < 80" | bc -l) )); then
echo "Coverage $COVERAGE% is below 80% threshold"
exit 1
fi
- uses: codecov/codecov-action@v4
with:
file: coverage/lcov.info
```
### Python (Pytest)
```yaml
name: Test and Coverage
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- run: pip install pytest pytest-cov
- run: pytest --cov=src --cov-report=xml --cov-fail-under=80
- uses: codecov/codecov-action@v4
with:
file: coverage.xml
```
### Java (Maven + JaCoCo)
```yaml
name: Test and Coverage
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
- run: mvn test jacoco:check
- uses: codecov/codecov-action@v4
with:
file: target/site/jacoco/jacoco.xml
```
---
## Quality Gates
### Threshold Configuration
**Jest (package.json):**
```json
{
"jest": {
"coverageThreshold": {
"global": {
"branches": 80,
"functions": 80,
"lines": 80,
"statements": 80
}
}
}
}
```
**Pytest (pyproject.toml):**
```toml
[tool.coverage.report]
fail_under = 80
```
**JaCoCo (pom.xml):**
```xml
<rule>
<element>BUNDLE</element>
<limits>
<limit>
<counter>LINE</counter>
<value>COVEREDRATIO</value>
<minimum>0.80</minimum>
</limit>
</limits>
</rule>
```
### PR Coverage Checks
- Block merge if coverage drops
- Show coverage diff in PR comments
- Require coverage for changed files
- Allow exceptions with justification
---
## Trend Tracking
### Metrics to Track
| Metric | Purpose | Alert Threshold |
|--------|---------|-----------------|
| Overall line coverage | Baseline health | < 80% |
| Branch coverage | Logic completeness | < 70% |
| Coverage delta | Regression detection | < -2% per PR |
| Test execution time | Performance | > 5 min |
| Flaky test count | Reliability | > 0 |
### Coverage Services
| Service | Features | Integration |
|---------|----------|-------------|
| Codecov | PR comments, badges, graphs | GitHub, GitLab, Bitbucket |
| Coveralls | History, trends, badges | GitHub, GitLab |
| SonarCloud | Full code quality suite | Multiple CI platforms |
### Badge Generation
```markdown
<!-- README.md -->
[](https://codecov.io/gh/org/repo)
```
FILE:references/framework-guide.md
# Testing Framework Guide
Language and framework selection, configuration, and patterns.
---
## Table of Contents
- [Framework Selection](#framework-selection)
- [TypeScript/JavaScript](#typescriptjavascript)
- [Python](#python)
- [Java](#java)
- [Version Requirements](#version-requirements)
---
## Framework Selection
| Language | Recommended | Alternatives | Best For |
|----------|-------------|--------------|----------|
| TypeScript/JS | Jest | Vitest, Mocha | React, Node.js, Next.js |
| Python | Pytest | unittest, nose2 | Django, Flask, FastAPI |
| Java | JUnit 5 | TestNG | Spring, Android |
| Vite projects | Vitest | Jest | Modern Vite-based apps |
---
## TypeScript/JavaScript
### Jest Configuration
```javascript
// jest.config.js
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/*.test.ts'],
collectCoverageFrom: ['src/**/*.ts'],
coverageThreshold: {
global: { branches: 80, lines: 80 }
}
};
```
### Jest Test Pattern
```typescript
describe('Calculator', () => {
let calc: Calculator;
beforeEach(() => {
calc = new Calculator();
});
it('should add two numbers', () => {
expect(calc.add(2, 3)).toBe(5);
});
it('should throw on invalid input', () => {
expect(() => calc.add(null, 3)).toThrow('Invalid input');
});
});
```
### Vitest Configuration
```typescript
// vitest.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'node',
coverage: { provider: 'c8' }
}
});
```
### Coverage Tools
- Istanbul/nyc: Traditional coverage
- c8: Native V8 coverage (faster)
- Vitest built-in: Integrated with test runner
---
## Python
### Pytest Configuration
```ini
# pytest.ini
[pytest]
testpaths = tests
python_files = test_*.py
python_functions = test_*
addopts = --cov=src --cov-report=term-missing
```
### Pytest Test Pattern
```python
import pytest
from calculator import Calculator
class TestCalculator:
@pytest.fixture
def calc(self):
return Calculator()
def test_add_positive_numbers(self, calc):
assert calc.add(2, 3) == 5
def test_add_raises_on_invalid_input(self, calc):
with pytest.raises(ValueError, match="Invalid input"):
calc.add(None, 3)
@pytest.mark.parametrize("a,b,expected", [
(1, 2, 3),
(-1, 1, 0),
(0, 0, 0),
])
def test_add_various_inputs(self, calc, a, b, expected):
assert calc.add(a, b) == expected
```
### Coverage Tools
- coverage.py: Standard Python coverage
- pytest-cov: Pytest plugin wrapper
- Report formats: HTML, XML, LCOV
---
## Java
### JUnit 5 Configuration (Maven)
```xml
<!-- pom.xml -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.9.3</version>
<scope>test</scope>
</dependency>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.10</version>
</plugin>
```
### JUnit 5 Test Pattern
```java
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
class CalculatorTest {
private Calculator calc;
@BeforeEach
void setUp() {
calc = new Calculator();
}
@Test
@DisplayName("should add two positive numbers")
void testAddPositive() {
assertEquals(5, calc.add(2, 3));
}
@Test
@DisplayName("should throw on null input")
void testAddThrowsOnNull() {
assertThrows(IllegalArgumentException.class,
() -> calc.add(null, 3));
}
@ParameterizedTest
@CsvSource({"1,2,3", "-1,1,0", "0,0,0"})
void testAddVarious(int a, int b, int expected) {
assertEquals(expected, calc.add(a, b));
}
}
```
### Coverage Tools
- JaCoCo: Standard Java coverage
- Cobertura: Alternative XML format
- Report formats: HTML, XML, CSV
---
## Version Requirements
| Tool | Minimum Version | Notes |
|------|-----------------|-------|
| Node.js | 16+ | Required for Jest 29+ |
| Jest | 29+ | Modern async support |
| Vitest | 0.34+ | Stable API |
| Python | 3.8+ | f-strings, async support |
| Pytest | 7+ | Modern fixtures |
| Java | 11+ | JUnit 5 support |
| JUnit | 5.9+ | ParameterizedTest improvements |
| TypeScript | 4.5+ | Strict mode features |
FILE:references/tdd-best-practices.md
# TDD Best Practices
Guidelines for effective test-driven development workflows.
---
## Table of Contents
- [Red-Green-Refactor Cycle](#red-green-refactor-cycle)
- [Test Generation Guidelines](#test-generation-guidelines)
- [Test Quality Principles](#test-quality-principles)
- [Coverage Goals](#coverage-goals)
---
## Red-Green-Refactor Cycle
### RED Phase
1. Write a failing test before any implementation
2. Test should fail for the right reason (not compilation errors)
3. Name tests as specifications describing expected behavior
4. Keep tests small and focused on single behaviors
### GREEN Phase
1. Write minimal code to make the test pass
2. Avoid over-engineering at this stage
3. Duplicate code is acceptable temporarily
4. Focus on correctness, not elegance
### REFACTOR Phase
1. Improve code structure while keeping tests green
2. Remove duplication introduced in GREEN phase
3. Apply design patterns where appropriate
4. Run tests after each small refactoring
### Cycle Discipline
- Complete one cycle before starting the next
- Commit after each successful GREEN phase
- Small iterations lead to better designs
- Resist temptation to write implementation first
---
## Test Generation Guidelines
### Behavior Focus
- Test what code does, not how it does it
- Avoid coupling tests to implementation details
- Tests should survive internal refactoring
- Focus on observable outcomes
### Naming Conventions
- Use descriptive names that read as specifications
- Format: `should_<expected>_when_<condition>`
- Examples:
- `should_return_zero_when_cart_is_empty`
- `should_reject_negative_amounts`
- `should_apply_discount_for_members`
### Test Structure
- Follow Arrange-Act-Assert (AAA) pattern
- Keep setup minimal and relevant
- One logical assertion per test
- Extract shared setup to fixtures
### Coverage Scope
- Happy path: Normal expected usage
- Error cases: Invalid inputs, failures
- Edge cases: Boundaries, empty states
- Exceptional cases: Timeouts, nulls
---
## Test Quality Principles
### Independence
- Each test runs in isolation
- No shared mutable state between tests
- Tests can run in any order
- Parallel execution should work
### Speed
- Unit tests under 100ms each
- Avoid I/O in unit tests
- Mock external dependencies
- Use in-memory databases for integration
### Determinism
- Same inputs produce same results
- No dependency on system time or random values
- Controlled test data
- No flaky tests allowed
### Clarity
- Failure messages explain what went wrong
- Test code is as clean as production code
- Avoid clever tricks that obscure intent
- Comments explain non-obvious setup
---
## Coverage Goals
### Thresholds by Type
| Type | Target | Rationale |
|------|--------|-----------|
| Line coverage | 80%+ | Baseline for most projects |
| Branch coverage | 70%+ | More meaningful than line |
| Function coverage | 90%+ | Public APIs should be tested |
### Critical Path Rules
- Authentication: 100% coverage required
- Payment processing: 100% coverage required
- Data validation: 100% coverage required
- Error handlers: Must test all paths
### Avoiding Coverage Theater
- High coverage != good tests
- Focus on meaningful assertions
- Test behaviors, not lines
- Code review test quality, not just metrics
### Coverage Analysis Workflow
1. Generate coverage report after test run
2. Identify uncovered critical paths (P0)
3. Review medium-priority gaps (P1)
4. Document accepted low-priority gaps (P2)
5. Set threshold gates in CI pipeline
FILE:scripts/coverage_analyzer.py
"""
Coverage analysis module.
Parse and analyze test coverage reports in multiple formats (LCOV, JSON, XML).
Identify gaps, calculate metrics, and provide actionable recommendations.
"""
from typing import Dict, List, Any, Optional, Tuple
import json
import xml.etree.ElementTree as ET
class CoverageFormat:
"""Supported coverage report formats."""
LCOV = "lcov"
JSON = "json"
XML = "xml"
COBERTURA = "cobertura"
class CoverageAnalyzer:
"""Analyze test coverage reports and identify gaps."""
def __init__(self):
"""Initialize coverage analyzer."""
self.coverage_data = {}
self.gaps = []
self.summary = {}
def parse_coverage_report(
self,
report_content: str,
format_type: str
) -> Dict[str, Any]:
"""
Parse coverage report in various formats.
Args:
report_content: Raw coverage report content
format_type: Format (lcov, json, xml, cobertura)
Returns:
Parsed coverage data
"""
if format_type == CoverageFormat.LCOV:
return self._parse_lcov(report_content)
elif format_type == CoverageFormat.JSON:
return self._parse_json(report_content)
elif format_type in [CoverageFormat.XML, CoverageFormat.COBERTURA]:
return self._parse_xml(report_content)
else:
raise ValueError(f"Unsupported format: {format_type}")
def _parse_lcov(self, content: str) -> Dict[str, Any]:
"""Parse LCOV format coverage report."""
files = {}
current_file = None
file_data = {}
for line in content.split('\n'):
line = line.strip()
if line.startswith('SF:'):
# Source file
current_file = line[3:]
file_data = {
'lines': {},
'functions': {},
'branches': {}
}
elif line.startswith('DA:'):
# Line coverage data (line_number,hit_count)
parts = line[3:].split(',')
line_num = int(parts[0])
hit_count = int(parts[1])
file_data['lines'][line_num] = hit_count
elif line.startswith('FNDA:'):
# Function coverage (hit_count,function_name)
parts = line[5:].split(',', 1)
hit_count = int(parts[0])
func_name = parts[1] if len(parts) > 1 else 'unknown'
file_data['functions'][func_name] = hit_count
elif line.startswith('BRDA:'):
# Branch coverage (line,block,branch,hit_count)
parts = line[5:].split(',')
branch_id = f"{parts[0]}:{parts[1]}:{parts[2]}"
hit_count = 0 if parts[3] == '-' else int(parts[3])
file_data['branches'][branch_id] = hit_count
elif line == 'end_of_record':
if current_file:
files[current_file] = file_data
current_file = None
file_data = {}
self.coverage_data = files
return files
def _parse_json(self, content: str) -> Dict[str, Any]:
"""Parse JSON format coverage report (Istanbul/nyc)."""
try:
data = json.loads(content)
files = {}
for file_path, file_data in data.items():
lines = {}
functions = {}
branches = {}
# Line coverage
if 's' in file_data: # Statement map
statement_map = file_data['s']
for stmt_id, hit_count in statement_map.items():
# Map statement to line number
if 'statementMap' in file_data:
stmt_info = file_data['statementMap'].get(stmt_id, {})
line_num = stmt_info.get('start', {}).get('line')
if line_num:
lines[line_num] = hit_count
# Function coverage
if 'f' in file_data:
func_map = file_data['f']
func_names = file_data.get('fnMap', {})
for func_id, hit_count in func_map.items():
func_info = func_names.get(func_id, {})
func_name = func_info.get('name', f'func_{func_id}')
functions[func_name] = hit_count
# Branch coverage
if 'b' in file_data:
branch_map = file_data['b']
for branch_id, locations in branch_map.items():
for idx, hit_count in enumerate(locations):
branch_key = f"{branch_id}:{idx}"
branches[branch_key] = hit_count
files[file_path] = {
'lines': lines,
'functions': functions,
'branches': branches
}
self.coverage_data = files
return files
except json.JSONDecodeError as e:
raise ValueError(f"Invalid JSON coverage report: {e}")
def _parse_xml(self, content: str) -> Dict[str, Any]:
"""Parse XML/Cobertura format coverage report."""
try:
root = ET.fromstring(content)
files = {}
# Handle Cobertura format
for package in root.findall('.//package'):
for cls in package.findall('classes/class'):
filename = cls.get('filename', cls.get('name', 'unknown'))
lines = {}
branches = {}
for line in cls.findall('lines/line'):
line_num = int(line.get('number', 0))
hit_count = int(line.get('hits', 0))
lines[line_num] = hit_count
# Branch info
branch = line.get('branch', 'false')
if branch == 'true':
condition_coverage = line.get('condition-coverage', '0% (0/0)')
# Parse "(covered/total)"
if '(' in condition_coverage:
branch_info = condition_coverage.split('(')[1].split(')')[0]
covered, total = map(int, branch_info.split('/'))
branches[f"{line_num}:branch"] = covered
files[filename] = {
'lines': lines,
'functions': {},
'branches': branches
}
self.coverage_data = files
return files
except ET.ParseError as e:
raise ValueError(f"Invalid XML coverage report: {e}")
def calculate_summary(self) -> Dict[str, Any]:
"""
Calculate overall coverage summary.
Returns:
Summary with line, branch, and function coverage percentages
"""
total_lines = 0
covered_lines = 0
total_branches = 0
covered_branches = 0
total_functions = 0
covered_functions = 0
for file_path, file_data in self.coverage_data.items():
# Lines
for line_num, hit_count in file_data.get('lines', {}).items():
total_lines += 1
if hit_count > 0:
covered_lines += 1
# Branches
for branch_id, hit_count in file_data.get('branches', {}).items():
total_branches += 1
if hit_count > 0:
covered_branches += 1
# Functions
for func_name, hit_count in file_data.get('functions', {}).items():
total_functions += 1
if hit_count > 0:
covered_functions += 1
summary = {
'line_coverage': self._safe_percentage(covered_lines, total_lines),
'branch_coverage': self._safe_percentage(covered_branches, total_branches),
'function_coverage': self._safe_percentage(covered_functions, total_functions),
'total_lines': total_lines,
'covered_lines': covered_lines,
'total_branches': total_branches,
'covered_branches': covered_branches,
'total_functions': total_functions,
'covered_functions': covered_functions
}
self.summary = summary
return summary
def _safe_percentage(self, covered: int, total: int) -> float:
"""Safely calculate percentage."""
if total == 0:
return 0.0
return round((covered / total) * 100, 2)
def identify_gaps(self, threshold: float = 80.0) -> List[Dict[str, Any]]:
"""
Identify coverage gaps below threshold.
Args:
threshold: Minimum acceptable coverage percentage
Returns:
List of files with coverage gaps
"""
gaps = []
for file_path, file_data in self.coverage_data.items():
file_gaps = self._analyze_file_gaps(file_path, file_data, threshold)
if file_gaps:
gaps.append(file_gaps)
self.gaps = gaps
return gaps
def _analyze_file_gaps(
self,
file_path: str,
file_data: Dict[str, Any],
threshold: float
) -> Optional[Dict[str, Any]]:
"""Analyze coverage gaps for a single file."""
lines = file_data.get('lines', {})
branches = file_data.get('branches', {})
functions = file_data.get('functions', {})
# Calculate file coverage
total_lines = len(lines)
covered_lines = sum(1 for hit in lines.values() if hit > 0)
line_coverage = self._safe_percentage(covered_lines, total_lines)
total_branches = len(branches)
covered_branches = sum(1 for hit in branches.values() if hit > 0)
branch_coverage = self._safe_percentage(covered_branches, total_branches)
# Find uncovered lines
uncovered_lines = [line_num for line_num, hit in lines.items() if hit == 0]
uncovered_branches = [branch_id for branch_id, hit in branches.items() if hit == 0]
# Only report if below threshold
if line_coverage < threshold or branch_coverage < threshold:
return {
'file': file_path,
'line_coverage': line_coverage,
'branch_coverage': branch_coverage,
'uncovered_lines': sorted(uncovered_lines),
'uncovered_branches': uncovered_branches,
'priority': self._calculate_priority(line_coverage, branch_coverage, threshold)
}
return None
def _calculate_priority(
self,
line_coverage: float,
branch_coverage: float,
threshold: float
) -> str:
"""Calculate priority based on coverage gap severity."""
gap = threshold - min(line_coverage, branch_coverage)
if gap >= 40:
return 'P0' # Critical - less than 40% coverage
elif gap >= 20:
return 'P1' # Important - 60-80% coverage
else:
return 'P2' # Nice to have - 80%+ coverage
def get_file_coverage(self, file_path: str) -> Dict[str, Any]:
"""
Get detailed coverage information for a specific file.
Args:
file_path: Path to file
Returns:
Detailed coverage data for file
"""
if file_path not in self.coverage_data:
return {}
file_data = self.coverage_data[file_path]
lines = file_data.get('lines', {})
branches = file_data.get('branches', {})
functions = file_data.get('functions', {})
total_lines = len(lines)
covered_lines = sum(1 for hit in lines.values() if hit > 0)
total_branches = len(branches)
covered_branches = sum(1 for hit in branches.values() if hit > 0)
total_functions = len(functions)
covered_functions = sum(1 for hit in functions.values() if hit > 0)
return {
'file': file_path,
'line_coverage': self._safe_percentage(covered_lines, total_lines),
'branch_coverage': self._safe_percentage(covered_branches, total_branches),
'function_coverage': self._safe_percentage(covered_functions, total_functions),
'lines': lines,
'branches': branches,
'functions': functions
}
def generate_recommendations(self) -> List[Dict[str, Any]]:
"""
Generate prioritized recommendations for improving coverage.
Returns:
List of recommendations with priority and actions
"""
recommendations = []
# Check overall coverage
summary = self.summary or self.calculate_summary()
if summary['line_coverage'] < 80:
recommendations.append({
'priority': 'P0',
'type': 'overall_coverage',
'message': f"Overall line coverage ({summary['line_coverage']}%) is below 80% threshold",
'action': 'Focus on adding tests for critical paths and business logic',
'impact': 'high'
})
if summary['branch_coverage'] < 70:
recommendations.append({
'priority': 'P0',
'type': 'branch_coverage',
'message': f"Branch coverage ({summary['branch_coverage']}%) is below 70% threshold",
'action': 'Add tests for conditional logic and error handling paths',
'impact': 'high'
})
# File-specific recommendations
for gap in self.gaps:
if gap['priority'] == 'P0':
recommendations.append({
'priority': 'P0',
'type': 'file_coverage',
'file': gap['file'],
'message': f"Critical coverage gap in {gap['file']}",
'action': f"Add tests for lines: {gap['uncovered_lines'][:10]}",
'impact': 'high'
})
# Sort by priority
priority_order = {'P0': 0, 'P1': 1, 'P2': 2}
recommendations.sort(key=lambda x: priority_order.get(x['priority'], 3))
return recommendations
def detect_format(self, content: str) -> str:
"""
Automatically detect coverage report format.
Args:
content: Raw coverage report content
Returns:
Detected format (lcov, json, xml)
"""
content_stripped = content.strip()
# Check for LCOV format
if content_stripped.startswith('TN:') or 'SF:' in content_stripped[:100]:
return CoverageFormat.LCOV
# Check for JSON format
if content_stripped.startswith('{') or content_stripped.startswith('['):
try:
json.loads(content_stripped)
return CoverageFormat.JSON
except:
pass
# Check for XML format
if content_stripped.startswith('<?xml') or content_stripped.startswith('<coverage'):
return CoverageFormat.XML
raise ValueError("Unable to detect coverage report format")
FILE:scripts/fixture_generator.py
"""
Fixture and test data generation module.
Generates realistic test data, mock objects, and fixtures for various scenarios.
"""
from typing import Dict, List, Any, Optional
import json
import random
class FixtureGenerator:
"""Generate test fixtures and mock data."""
def __init__(self, seed: Optional[int] = None):
"""
Initialize fixture generator.
Args:
seed: Random seed for reproducible fixtures
"""
if seed is not None:
random.seed(seed)
def generate_boundary_values(
self,
data_type: str,
constraints: Optional[Dict[str, Any]] = None
) -> List[Any]:
"""
Generate boundary values for testing.
Args:
data_type: Type of data (int, string, array, date, etc.)
constraints: Constraints like min, max, length
Returns:
List of boundary values
"""
constraints = constraints or {}
if data_type == "int":
return self._integer_boundaries(constraints)
elif data_type == "string":
return self._string_boundaries(constraints)
elif data_type == "array":
return self._array_boundaries(constraints)
elif data_type == "date":
return self._date_boundaries(constraints)
elif data_type == "email":
return self._email_boundaries()
elif data_type == "url":
return self._url_boundaries()
else:
return []
def _integer_boundaries(self, constraints: Dict[str, Any]) -> List[int]:
"""Generate integer boundary values."""
min_val = constraints.get('min', 0)
max_val = constraints.get('max', 100)
boundaries = [
min_val, # Minimum
min_val + 1, # Just above minimum
max_val - 1, # Just below maximum
max_val, # Maximum
]
# Add special values
if min_val <= 0 <= max_val:
boundaries.append(0) # Zero
if min_val < 0:
boundaries.append(-1) # Negative
return sorted(set(boundaries))
def _string_boundaries(self, constraints: Dict[str, Any]) -> List[str]:
"""Generate string boundary values."""
min_len = constraints.get('min_length', 0)
max_len = constraints.get('max_length', 100)
boundaries = [
"", # Empty string
"a" * min_len, # Minimum length
"a" * (min_len + 1) if min_len < max_len else "", # Just above minimum
"a" * (max_len - 1) if max_len > 1 else "a", # Just below maximum
"a" * max_len, # Maximum length
"a" * (max_len + 1), # Exceeds maximum (invalid)
]
# Add special characters
if max_len >= 10:
boundaries.append("test@#$%^&*()") # Special characters
boundaries.append("unicode: 你好") # Unicode
return [b for b in boundaries if b is not None]
def _array_boundaries(self, constraints: Dict[str, Any]) -> List[List[Any]]:
"""Generate array boundary values."""
min_size = constraints.get('min_size', 0)
max_size = constraints.get('max_size', 10)
boundaries = [
[], # Empty array
[1] * min_size, # Minimum size
[1] * max_size, # Maximum size
[1] * (max_size + 1), # Exceeds maximum (invalid)
]
return boundaries
def _date_boundaries(self, constraints: Dict[str, Any]) -> List[str]:
"""Generate date boundary values."""
return [
"1900-01-01", # Very old date
"1970-01-01", # Unix epoch
"2000-01-01", # Y2K
"2025-11-05", # Today (example)
"2099-12-31", # Far future
"invalid-date", # Invalid format
]
def _email_boundaries(self) -> List[str]:
"""Generate email boundary values."""
return [
"valid@example.com", # Valid
"user.name+tag@example.co.uk", # Valid with special chars
"invalid", # Missing @
"@example.com", # Missing local part
"user@", # Missing domain
"user@.com", # Invalid domain
"", # Empty
]
def _url_boundaries(self) -> List[str]:
"""Generate URL boundary values."""
return [
"https://example.com", # Valid HTTPS
"http://example.com", # Valid HTTP
"ftp://example.com", # Different protocol
"//example.com", # Protocol-relative
"example.com", # Missing protocol
"", # Empty
"not a url", # Invalid
]
def generate_edge_cases(
self,
scenario: str,
context: Optional[Dict[str, Any]] = None
) -> List[Dict[str, Any]]:
"""
Generate edge case test scenarios.
Args:
scenario: Type of scenario (auth, payment, form, api, etc.)
context: Additional context for scenario
Returns:
List of edge case test scenarios
"""
if scenario == "auth":
return self._auth_edge_cases()
elif scenario == "payment":
return self._payment_edge_cases()
elif scenario == "form":
return self._form_edge_cases(context or {})
elif scenario == "api":
return self._api_edge_cases()
elif scenario == "file_upload":
return self._file_upload_edge_cases()
else:
return []
def _auth_edge_cases(self) -> List[Dict[str, Any]]:
"""Generate authentication edge cases."""
return [
{
'name': 'empty_credentials',
'input': {'username': '', 'password': ''},
'expected': 'validation_error'
},
{
'name': 'sql_injection_attempt',
'input': {'username': "admin' OR '1'='1", 'password': 'password'},
'expected': 'authentication_failed'
},
{
'name': 'very_long_password',
'input': {'username': 'user', 'password': 'a' * 1000},
'expected': 'validation_error_or_success'
},
{
'name': 'special_chars_username',
'input': {'username': 'user@#$%', 'password': 'password'},
'expected': 'depends_on_validation'
},
{
'name': 'unicode_credentials',
'input': {'username': '用户', 'password': 'пароль'},
'expected': 'should_handle_unicode'
}
]
def _payment_edge_cases(self) -> List[Dict[str, Any]]:
"""Generate payment processing edge cases."""
return [
{
'name': 'zero_amount',
'input': {'amount': 0, 'currency': 'USD'},
'expected': 'validation_error'
},
{
'name': 'negative_amount',
'input': {'amount': -10, 'currency': 'USD'},
'expected': 'validation_error'
},
{
'name': 'very_large_amount',
'input': {'amount': 999999999.99, 'currency': 'USD'},
'expected': 'should_handle_or_reject'
},
{
'name': 'precision_test',
'input': {'amount': 10.999, 'currency': 'USD'},
'expected': 'should_round_to_10.99'
},
{
'name': 'invalid_currency',
'input': {'amount': 10, 'currency': 'XXX'},
'expected': 'validation_error'
}
]
def _form_edge_cases(self, context: Dict[str, Any]) -> List[Dict[str, Any]]:
"""Generate form validation edge cases."""
fields = context.get('fields', [])
edge_cases = []
for field in fields:
field_name = field.get('name', 'field')
field_type = field.get('type', 'text')
edge_cases.append({
'name': f'{field_name}_empty',
'input': {field_name: ''},
'expected': 'validation_error_if_required'
})
if field_type in ['text', 'email', 'password']:
edge_cases.append({
'name': f'{field_name}_very_long',
'input': {field_name: 'a' * 1000},
'expected': 'validation_error_or_truncate'
})
return edge_cases
def _api_edge_cases(self) -> List[Dict[str, Any]]:
"""Generate API edge cases."""
return [
{
'name': 'missing_required_field',
'request': {'optional_field': 'value'},
'expected': 400
},
{
'name': 'invalid_json',
'request': 'not valid json{',
'expected': 400
},
{
'name': 'empty_body',
'request': {},
'expected': 400
},
{
'name': 'very_large_payload',
'request': {'data': 'x' * 1000000},
'expected': '413_or_400'
},
{
'name': 'invalid_method',
'method': 'INVALID',
'expected': 405
}
]
def _file_upload_edge_cases(self) -> List[Dict[str, Any]]:
"""Generate file upload edge cases."""
return [
{
'name': 'empty_file',
'file': {'name': 'test.txt', 'size': 0},
'expected': 'validation_error'
},
{
'name': 'very_large_file',
'file': {'name': 'test.txt', 'size': 1000000000},
'expected': 'size_limit_error'
},
{
'name': 'invalid_extension',
'file': {'name': 'test.exe', 'size': 1000},
'expected': 'validation_error'
},
{
'name': 'no_extension',
'file': {'name': 'testfile', 'size': 1000},
'expected': 'depends_on_validation'
},
{
'name': 'special_chars_filename',
'file': {'name': 'test@#$%.txt', 'size': 1000},
'expected': 'should_sanitize'
}
]
def generate_mock_data(
self,
schema: Dict[str, Any],
count: int = 1
) -> List[Dict[str, Any]]:
"""
Generate mock data based on schema.
Args:
schema: Schema definition with field types
count: Number of mock objects to generate
Returns:
List of mock data objects
"""
mock_objects = []
for _ in range(count):
mock_obj = {}
for field_name, field_def in schema.items():
field_type = field_def.get('type', 'string')
mock_obj[field_name] = self._generate_field_value(field_type, field_def)
mock_objects.append(mock_obj)
return mock_objects
def _generate_field_value(self, field_type: str, field_def: Dict[str, Any]) -> Any:
"""Generate value for a single field."""
if field_type == "string":
options = field_def.get('options')
if options:
return random.choice(options)
return f"test_string_{random.randint(1, 1000)}"
elif field_type == "int":
min_val = field_def.get('min', 0)
max_val = field_def.get('max', 100)
return random.randint(min_val, max_val)
elif field_type == "float":
min_val = field_def.get('min', 0.0)
max_val = field_def.get('max', 100.0)
return round(random.uniform(min_val, max_val), 2)
elif field_type == "bool":
return random.choice([True, False])
elif field_type == "email":
return f"user{random.randint(1, 1000)}@example.com"
elif field_type == "date":
return f"2025-{random.randint(1, 12):02d}-{random.randint(1, 28):02d}"
elif field_type == "array":
item_type = field_def.get('items', {}).get('type', 'string')
size = random.randint(1, 5)
return [self._generate_field_value(item_type, field_def.get('items', {}))
for _ in range(size)]
else:
return None
def generate_fixture_file(
self,
fixture_name: str,
data: Any,
format: str = "json"
) -> str:
"""
Generate fixture file content.
Args:
fixture_name: Name of fixture
data: Fixture data
format: Output format (json, yaml, python)
Returns:
Fixture file content as string
"""
if format == "json":
return json.dumps(data, indent=2)
elif format == "python":
return f"""# {fixture_name} fixture
{fixture_name.upper()} = {repr(data)}
"""
elif format == "yaml":
# Simple YAML generation (for basic structures)
return self._dict_to_yaml(data)
else:
return str(data)
def _dict_to_yaml(self, data: Any, indent: int = 0) -> str:
"""Simple YAML generator."""
lines = []
indent_str = " " * indent
if isinstance(data, dict):
for key, value in data.items():
if isinstance(value, (dict, list)):
lines.append(f"{indent_str}{key}:")
lines.append(self._dict_to_yaml(value, indent + 1))
else:
lines.append(f"{indent_str}{key}: {value}")
elif isinstance(data, list):
for item in data:
if isinstance(item, dict):
lines.append(f"{indent_str}-")
lines.append(self._dict_to_yaml(item, indent + 1))
else:
lines.append(f"{indent_str}- {item}")
else:
return str(data)
return "\n".join(lines)
FILE:scripts/format_detector.py
"""
Format detection module.
Automatically detects programming language, testing framework, and file formats.
"""
from typing import Dict, List, Any, Optional, Tuple
import re
class FormatDetector:
"""Detect language, framework, and file formats automatically."""
def __init__(self):
"""Initialize format detector."""
self.detected_language = None
self.detected_framework = None
def detect_language(self, code: str) -> str:
"""
Detect programming language from code.
Args:
code: Source code
Returns:
Detected language (typescript, javascript, python, java, unknown)
"""
# TypeScript patterns
if self._is_typescript(code):
self.detected_language = "typescript"
return "typescript"
# JavaScript patterns
if self._is_javascript(code):
self.detected_language = "javascript"
return "javascript"
# Python patterns
if self._is_python(code):
self.detected_language = "python"
return "python"
# Java patterns
if self._is_java(code):
self.detected_language = "java"
return "java"
self.detected_language = "unknown"
return "unknown"
def _is_typescript(self, code: str) -> bool:
"""Check if code is TypeScript."""
ts_patterns = [
r'\binterface\s+\w+', # interface definitions
r':\s*\w+\s*[=;]', # type annotations
r'\btype\s+\w+\s*=', # type aliases
r'<\w+>', # generic types
r'import.*from.*[\'"]', # ES6 imports with types
]
# Must have multiple TypeScript-specific patterns
matches = sum(1 for pattern in ts_patterns if re.search(pattern, code))
return matches >= 2
def _is_javascript(self, code: str) -> bool:
"""Check if code is JavaScript."""
js_patterns = [
r'\bconst\s+\w+', # const declarations
r'\blet\s+\w+', # let declarations
r'=>', # arrow functions
r'function\s+\w+', # function declarations
r'require\([\'"]', # CommonJS require
]
matches = sum(1 for pattern in js_patterns if re.search(pattern, code))
return matches >= 2
def _is_python(self, code: str) -> bool:
"""Check if code is Python."""
py_patterns = [
r'\bdef\s+\w+', # function definitions
r'\bclass\s+\w+', # class definitions
r'import\s+\w+', # import statements
r'from\s+\w+\s+import', # from imports
r'^\s*#.*$', # Python comments
r':\s*$', # Python colons
]
matches = sum(1 for pattern in py_patterns if re.search(pattern, code, re.MULTILINE))
return matches >= 3
def _is_java(self, code: str) -> bool:
"""Check if code is Java."""
java_patterns = [
r'\bpublic\s+class', # public class
r'\bprivate\s+\w+', # private members
r'\bpublic\s+\w+\s+\w+\s*\(', # public methods
r'import\s+java\.', # Java imports
r'\bvoid\s+\w+\s*\(', # void methods
]
matches = sum(1 for pattern in java_patterns if re.search(pattern, code))
return matches >= 2
def detect_test_framework(self, code: str) -> str:
"""
Detect testing framework from test code.
Args:
code: Test code
Returns:
Detected framework (jest, vitest, pytest, junit, mocha, unknown)
"""
# Jest patterns
if 'from \'@jest/globals\'' in code or '@jest/' in code:
self.detected_framework = "jest"
return "jest"
# Vitest patterns
if 'from \'vitest\'' in code or 'import { vi }' in code:
self.detected_framework = "vitest"
return "vitest"
# Pytest patterns
if 'import pytest' in code or 'def test_' in code:
self.detected_framework = "pytest"
return "pytest"
# Unittest patterns
if 'import unittest' in code and 'unittest.TestCase' in code:
self.detected_framework = "unittest"
return "unittest"
# JUnit patterns
if '@Test' in code and 'import org.junit' in code:
self.detected_framework = "junit"
return "junit"
# Mocha patterns
if 'describe(' in code and 'it(' in code:
self.detected_framework = "mocha"
return "mocha"
self.detected_framework = "unknown"
return "unknown"
def detect_coverage_format(self, content: str) -> str:
"""
Detect coverage report format.
Args:
content: Coverage report content
Returns:
Format type (lcov, json, xml, unknown)
"""
content_stripped = content.strip()
# LCOV format
if content_stripped.startswith('TN:') or 'SF:' in content_stripped[:200]:
return "lcov"
# JSON format
if content_stripped.startswith('{'):
try:
import json
json.loads(content_stripped)
return "json"
except:
pass
# XML format
if content_stripped.startswith('<?xml') or content_stripped.startswith('<coverage'):
return "xml"
return "unknown"
def detect_input_format(self, input_data: str) -> Dict[str, Any]:
"""
Detect input format and extract relevant information.
Args:
input_data: Input data (could be code, coverage report, etc.)
Returns:
Detection results with format, language, framework
"""
result = {
'format': 'unknown',
'language': 'unknown',
'framework': 'unknown',
'content_type': 'unknown'
}
# Detect if it's a coverage report
coverage_format = self.detect_coverage_format(input_data)
if coverage_format != "unknown":
result['format'] = coverage_format
result['content_type'] = 'coverage_report'
return result
# Detect if it's source code
language = self.detect_language(input_data)
if language != "unknown":
result['language'] = language
result['content_type'] = 'source_code'
# Detect if it's test code
framework = self.detect_test_framework(input_data)
if framework != "unknown":
result['framework'] = framework
result['content_type'] = 'test_code'
return result
def extract_file_info(self, file_path: str) -> Dict[str, str]:
"""
Extract information from file path.
Args:
file_path: Path to file
Returns:
File information (extension, likely language, likely purpose)
"""
import os
file_name = os.path.basename(file_path)
file_ext = os.path.splitext(file_name)[1].lower()
# Extension to language mapping
ext_to_lang = {
'.ts': 'typescript',
'.tsx': 'typescript',
'.js': 'javascript',
'.jsx': 'javascript',
'.py': 'python',
'.java': 'java',
'.kt': 'kotlin',
'.go': 'go',
'.rs': 'rust',
}
# Test file patterns
is_test = any(pattern in file_name.lower()
for pattern in ['test', 'spec', '_test.', '.test.'])
return {
'file_name': file_name,
'extension': file_ext,
'language': ext_to_lang.get(file_ext, 'unknown'),
'is_test': is_test,
'purpose': 'test' if is_test else 'source'
}
def suggest_test_file_name(self, source_file: str, framework: str) -> str:
"""
Suggest test file name for source file.
Args:
source_file: Source file path
framework: Testing framework
Returns:
Suggested test file name
"""
import os
base_name = os.path.splitext(os.path.basename(source_file))[0]
ext = os.path.splitext(source_file)[1]
if framework in ['jest', 'vitest', 'mocha']:
return f"{base_name}.test{ext}"
elif framework in ['pytest', 'unittest']:
return f"test_{base_name}.py"
elif framework in ['junit', 'testng']:
return f"{base_name.capitalize()}Test.java"
else:
return f"{base_name}_test{ext}"
def identify_test_patterns(self, code: str) -> List[str]:
"""
Identify test patterns in code.
Args:
code: Test code
Returns:
List of identified patterns (AAA, Given-When-Then, etc.)
"""
patterns = []
# Arrange-Act-Assert pattern
if any(comment in code.lower() for comment in ['// arrange', '# arrange', '// act', '# act']):
patterns.append('AAA (Arrange-Act-Assert)')
# Given-When-Then pattern
if any(comment in code.lower() for comment in ['given', 'when', 'then']):
patterns.append('Given-When-Then')
# Setup/Teardown pattern
if any(keyword in code for keyword in ['beforeEach', 'afterEach', 'setUp', 'tearDown']):
patterns.append('Setup-Teardown')
# Mocking pattern
if any(keyword in code.lower() for keyword in ['mock', 'stub', 'spy']):
patterns.append('Mocking/Stubbing')
# Parameterized tests
if any(keyword in code for keyword in ['@pytest.mark.parametrize', 'test.each', '@ParameterizedTest']):
patterns.append('Parameterized Tests')
return patterns if patterns else ['No specific pattern detected']
def analyze_project_structure(self, file_paths: List[str]) -> Dict[str, Any]:
"""
Analyze project structure from file paths.
Args:
file_paths: List of file paths in project
Returns:
Project structure analysis
"""
languages = {}
test_frameworks = []
source_files = []
test_files = []
for file_path in file_paths:
file_info = self.extract_file_info(file_path)
# Count languages
lang = file_info['language']
if lang != 'unknown':
languages[lang] = languages.get(lang, 0) + 1
# Categorize files
if file_info['is_test']:
test_files.append(file_path)
else:
source_files.append(file_path)
# Determine primary language
primary_language = max(languages.items(), key=lambda x: x[1])[0] if languages else 'unknown'
return {
'primary_language': primary_language,
'languages': languages,
'source_file_count': len(source_files),
'test_file_count': len(test_files),
'test_ratio': len(test_files) / len(source_files) if source_files else 0,
'suggested_framework': self._suggest_framework(primary_language)
}
def _suggest_framework(self, language: str) -> str:
"""Suggest testing framework based on language."""
framework_map = {
'typescript': 'jest or vitest',
'javascript': 'jest or mocha',
'python': 'pytest',
'java': 'junit',
'kotlin': 'junit',
'go': 'testing package',
'rust': 'cargo test',
}
return framework_map.get(language, 'unknown')
def detect_environment(self) -> Dict[str, str]:
"""
Detect execution environment (CLI, Desktop, API).
Returns:
Environment information
"""
# This is a placeholder - actual detection would use environment variables
# or other runtime checks
return {
'environment': 'cli', # Could be 'desktop', 'api'
'output_preference': 'terminal-friendly' # Could be 'rich-markdown', 'json'
}
FILE:scripts/framework_adapter.py
"""
Framework adapter module.
Provides multi-framework support with adapters for Jest, Pytest, JUnit, Vitest, and more.
Handles framework-specific patterns, imports, and test structure.
"""
from typing import Dict, List, Any, Optional
from enum import Enum
class Framework(Enum):
"""Supported testing frameworks."""
JEST = "jest"
VITEST = "vitest"
PYTEST = "pytest"
UNITTEST = "unittest"
JUNIT = "junit"
TESTNG = "testng"
MOCHA = "mocha"
JASMINE = "jasmine"
class Language(Enum):
"""Supported programming languages."""
TYPESCRIPT = "typescript"
JAVASCRIPT = "javascript"
PYTHON = "python"
JAVA = "java"
class FrameworkAdapter:
"""Adapter for multiple testing frameworks."""
def __init__(self, framework: Framework, language: Language):
"""
Initialize framework adapter.
Args:
framework: Testing framework
language: Programming language
"""
self.framework = framework
self.language = language
def generate_imports(self) -> str:
"""Generate framework-specific imports."""
if self.framework == Framework.JEST:
return self._jest_imports()
elif self.framework == Framework.VITEST:
return self._vitest_imports()
elif self.framework == Framework.PYTEST:
return self._pytest_imports()
elif self.framework == Framework.UNITTEST:
return self._unittest_imports()
elif self.framework == Framework.JUNIT:
return self._junit_imports()
elif self.framework == Framework.TESTNG:
return self._testng_imports()
elif self.framework == Framework.MOCHA:
return self._mocha_imports()
else:
return ""
def _jest_imports(self) -> str:
"""Generate Jest imports."""
return """import { describe, it, expect, beforeEach, afterEach } from '@jest/globals';"""
def _vitest_imports(self) -> str:
"""Generate Vitest imports."""
return """import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';"""
def _pytest_imports(self) -> str:
"""Generate Pytest imports."""
return """import pytest"""
def _unittest_imports(self) -> str:
"""Generate unittest imports."""
return """import unittest"""
def _junit_imports(self) -> str:
"""Generate JUnit imports."""
return """import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.AfterEach;
import static org.junit.jupiter.api.Assertions.*;"""
def _testng_imports(self) -> str:
"""Generate TestNG imports."""
return """import org.testng.annotations.Test;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.AfterMethod;
import static org.testng.Assert.*;"""
def _mocha_imports(self) -> str:
"""Generate Mocha imports."""
return """import { describe, it, beforeEach, afterEach } from 'mocha';
import { expect } from 'chai';"""
def generate_test_suite_wrapper(
self,
suite_name: str,
test_content: str
) -> str:
"""
Wrap test content in framework-specific suite structure.
Args:
suite_name: Name of test suite
test_content: Test functions/methods
Returns:
Complete test suite code
"""
if self.framework in [Framework.JEST, Framework.VITEST, Framework.MOCHA]:
return f"""describe('{suite_name}', () => {{
{self._indent(test_content, 2)}
}});"""
elif self.framework == Framework.PYTEST:
return f"""class Test{self._to_class_name(suite_name)}:
\"\"\"Test suite for {suite_name}.\"\"\"
{self._indent(test_content, 4)}"""
elif self.framework == Framework.UNITTEST:
return f"""class Test{self._to_class_name(suite_name)}(unittest.TestCase):
\"\"\"Test suite for {suite_name}.\"\"\"
{self._indent(test_content, 4)}"""
elif self.framework in [Framework.JUNIT, Framework.TESTNG]:
return f"""public class {self._to_class_name(suite_name)}Test {{
{self._indent(test_content, 4)}
}}"""
return test_content
def generate_test_function(
self,
test_name: str,
test_body: str,
description: str = ""
) -> str:
"""
Generate framework-specific test function.
Args:
test_name: Name of test
test_body: Test body code
description: Test description
Returns:
Complete test function
"""
if self.framework == Framework.JEST:
return self._jest_test(test_name, test_body, description)
elif self.framework == Framework.VITEST:
return self._vitest_test(test_name, test_body, description)
elif self.framework == Framework.PYTEST:
return self._pytest_test(test_name, test_body, description)
elif self.framework == Framework.UNITTEST:
return self._unittest_test(test_name, test_body, description)
elif self.framework == Framework.JUNIT:
return self._junit_test(test_name, test_body, description)
elif self.framework == Framework.TESTNG:
return self._testng_test(test_name, test_body, description)
elif self.framework == Framework.MOCHA:
return self._mocha_test(test_name, test_body, description)
else:
return ""
def _jest_test(self, test_name: str, test_body: str, description: str) -> str:
"""Generate Jest test."""
return f"""it('{test_name}', () => {{
// {description}
{self._indent(test_body, 2)}
}});"""
def _vitest_test(self, test_name: str, test_body: str, description: str) -> str:
"""Generate Vitest test."""
return f"""it('{test_name}', () => {{
// {description}
{self._indent(test_body, 2)}
}});"""
def _pytest_test(self, test_name: str, test_body: str, description: str) -> str:
"""Generate Pytest test."""
func_name = test_name.replace(' ', '_').replace('-', '_')
return f"""def test_{func_name}(self):
\"\"\"
{description or test_name}
\"\"\"
{self._indent(test_body, 4)}"""
def _unittest_test(self, test_name: str, test_body: str, description: str) -> str:
"""Generate unittest test."""
func_name = self._to_camel_case(test_name)
return f"""def test_{func_name}(self):
\"\"\"
{description or test_name}
\"\"\"
{self._indent(test_body, 4)}"""
def _junit_test(self, test_name: str, test_body: str, description: str) -> str:
"""Generate JUnit test."""
method_name = self._to_camel_case(test_name)
return f"""@Test
public void test{method_name}() {{
// {description}
{self._indent(test_body, 4)}
}}"""
def _testng_test(self, test_name: str, test_body: str, description: str) -> str:
"""Generate TestNG test."""
method_name = self._to_camel_case(test_name)
return f"""@Test
public void test{method_name}() {{
// {description}
{self._indent(test_body, 4)}
}}"""
def _mocha_test(self, test_name: str, test_body: str, description: str) -> str:
"""Generate Mocha test."""
return f"""it('{test_name}', () => {{
// {description}
{self._indent(test_body, 2)}
}});"""
def generate_assertion(
self,
actual: str,
expected: str,
assertion_type: str = "equals"
) -> str:
"""
Generate framework-specific assertion.
Args:
actual: Actual value expression
expected: Expected value expression
assertion_type: Type of assertion (equals, not_equals, true, false, throws)
Returns:
Assertion statement
"""
if self.framework in [Framework.JEST, Framework.VITEST]:
return self._jest_assertion(actual, expected, assertion_type)
elif self.framework in [Framework.PYTEST, Framework.UNITTEST]:
return self._python_assertion(actual, expected, assertion_type)
elif self.framework in [Framework.JUNIT, Framework.TESTNG]:
return self._java_assertion(actual, expected, assertion_type)
elif self.framework == Framework.MOCHA:
return self._chai_assertion(actual, expected, assertion_type)
else:
return f"assert {actual} == {expected}"
def _jest_assertion(self, actual: str, expected: str, assertion_type: str) -> str:
"""Generate Jest assertion."""
if assertion_type == "equals":
return f"expect({actual}).toBe({expected});"
elif assertion_type == "not_equals":
return f"expect({actual}).not.toBe({expected});"
elif assertion_type == "true":
return f"expect({actual}).toBe(true);"
elif assertion_type == "false":
return f"expect({actual}).toBe(false);"
elif assertion_type == "throws":
return f"expect(() => {actual}).toThrow();"
else:
return f"expect({actual}).toBe({expected});"
def _python_assertion(self, actual: str, expected: str, assertion_type: str) -> str:
"""Generate Python assertion."""
if assertion_type == "equals":
return f"assert {actual} == {expected}"
elif assertion_type == "not_equals":
return f"assert {actual} != {expected}"
elif assertion_type == "true":
return f"assert {actual} is True"
elif assertion_type == "false":
return f"assert {actual} is False"
elif assertion_type == "throws":
return f"with pytest.raises(Exception):\n {actual}"
else:
return f"assert {actual} == {expected}"
def _java_assertion(self, actual: str, expected: str, assertion_type: str) -> str:
"""Generate Java assertion."""
if assertion_type == "equals":
return f"assertEquals({expected}, {actual});"
elif assertion_type == "not_equals":
return f"assertNotEquals({expected}, {actual});"
elif assertion_type == "true":
return f"assertTrue({actual});"
elif assertion_type == "false":
return f"assertFalse({actual});"
elif assertion_type == "throws":
return f"assertThrows(Exception.class, () -> {actual});"
else:
return f"assertEquals({expected}, {actual});"
def _chai_assertion(self, actual: str, expected: str, assertion_type: str) -> str:
"""Generate Chai assertion."""
if assertion_type == "equals":
return f"expect({actual}).to.equal({expected});"
elif assertion_type == "not_equals":
return f"expect({actual}).to.not.equal({expected});"
elif assertion_type == "true":
return f"expect({actual}).to.be.true;"
elif assertion_type == "false":
return f"expect({actual}).to.be.false;"
elif assertion_type == "throws":
return f"expect(() => {actual}).to.throw();"
else:
return f"expect({actual}).to.equal({expected});"
def generate_setup_teardown(
self,
setup_code: str = "",
teardown_code: str = ""
) -> str:
"""Generate setup and teardown hooks."""
result = []
if self.framework in [Framework.JEST, Framework.VITEST, Framework.MOCHA]:
if setup_code:
result.append(f"""beforeEach(() => {{
{self._indent(setup_code, 2)}
}});""")
if teardown_code:
result.append(f"""afterEach(() => {{
{self._indent(teardown_code, 2)}
}});""")
elif self.framework == Framework.PYTEST:
if setup_code:
result.append(f"""@pytest.fixture(autouse=True)
def setup_method(self):
{self._indent(setup_code, 4)}
yield""")
if teardown_code:
result.append(f"""
{self._indent(teardown_code, 4)}""")
elif self.framework == Framework.UNITTEST:
if setup_code:
result.append(f"""def setUp(self):
{self._indent(setup_code, 4)}""")
if teardown_code:
result.append(f"""def tearDown(self):
{self._indent(teardown_code, 4)}""")
elif self.framework in [Framework.JUNIT, Framework.TESTNG]:
annotation = "@BeforeEach" if self.framework == Framework.JUNIT else "@BeforeMethod"
if setup_code:
result.append(f"""{annotation}
public void setUp() {{
{self._indent(setup_code, 4)}
}}""")
annotation = "@AfterEach" if self.framework == Framework.JUNIT else "@AfterMethod"
if teardown_code:
result.append(f"""{annotation}
public void tearDown() {{
{self._indent(teardown_code, 4)}
}}""")
return "\n\n".join(result)
def _indent(self, text: str, spaces: int) -> str:
"""Indent text by number of spaces."""
indent = " " * spaces
lines = text.split('\n')
return '\n'.join(indent + line if line.strip() else line for line in lines)
def _to_camel_case(self, text: str) -> str:
"""Convert text to camelCase."""
words = text.replace('-', ' ').replace('_', ' ').split()
if not words:
return text
return words[0].lower() + ''.join(word.capitalize() for word in words[1:])
def _to_class_name(self, text: str) -> str:
"""Convert text to ClassName."""
words = text.replace('-', ' ').replace('_', ' ').split()
return ''.join(word.capitalize() for word in words)
def detect_framework(self, code: str) -> Optional[Framework]:
"""
Auto-detect testing framework from code.
Args:
code: Test code
Returns:
Detected framework or None
"""
# Jest patterns
if 'from \'@jest/globals\'' in code or '@jest/' in code:
return Framework.JEST
# Vitest patterns
if 'from \'vitest\'' in code or 'import { vi }' in code:
return Framework.VITEST
# Pytest patterns
if 'import pytest' in code or 'def test_' in code and 'pytest.fixture' in code:
return Framework.PYTEST
# Unittest patterns
if 'import unittest' in code and 'unittest.TestCase' in code:
return Framework.UNITTEST
# JUnit patterns
if '@Test' in code and 'import org.junit' in code:
return Framework.JUNIT
# TestNG patterns
if '@Test' in code and 'import org.testng' in code:
return Framework.TESTNG
# Mocha patterns
if 'from \'mocha\'' in code or ('describe(' in code and 'from \'chai\'' in code):
return Framework.MOCHA
return None
FILE:scripts/metrics_calculator.py
"""
Metrics calculation module.
Calculate comprehensive test and code quality metrics including complexity,
test quality scoring, and test execution analysis.
"""
from typing import Dict, List, Any, Optional
import re
class MetricsCalculator:
"""Calculate comprehensive test and code quality metrics."""
def __init__(self):
"""Initialize metrics calculator."""
self.metrics = {}
def calculate_all_metrics(
self,
source_code: str,
test_code: str,
coverage_data: Optional[Dict[str, Any]] = None,
execution_data: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
"""
Calculate all available metrics.
Args:
source_code: Source code to analyze
test_code: Test code to analyze
coverage_data: Coverage report data
execution_data: Test execution results
Returns:
Complete metrics dictionary
"""
metrics = {
'complexity': self.calculate_complexity(source_code),
'test_quality': self.calculate_test_quality(test_code),
'coverage': coverage_data or {},
'execution': execution_data or {}
}
self.metrics = metrics
return metrics
def calculate_complexity(self, code: str) -> Dict[str, Any]:
"""
Calculate code complexity metrics.
Args:
code: Source code to analyze
Returns:
Complexity metrics (cyclomatic, cognitive, testability score)
"""
cyclomatic = self._cyclomatic_complexity(code)
cognitive = self._cognitive_complexity(code)
testability = self._testability_score(code, cyclomatic)
return {
'cyclomatic_complexity': cyclomatic,
'cognitive_complexity': cognitive,
'testability_score': testability,
'assessment': self._complexity_assessment(cyclomatic, cognitive)
}
def _cyclomatic_complexity(self, code: str) -> int:
"""
Calculate cyclomatic complexity (simplified).
Counts decision points: if, for, while, case, catch, &&, ||
"""
# Count decision points
decision_points = 0
# Control flow keywords
keywords = ['if', 'for', 'while', 'case', 'catch', 'except']
for keyword in keywords:
# Use word boundaries to avoid matching substrings
pattern = r'\b' + keyword + r'\b'
decision_points += len(re.findall(pattern, code))
# Logical operators
decision_points += len(re.findall(r'\&\&|\|\|', code))
# Base complexity is 1
return decision_points + 1
def _cognitive_complexity(self, code: str) -> int:
"""
Calculate cognitive complexity (simplified).
Similar to cyclomatic but penalizes nesting and non-obvious flow.
"""
lines = code.split('\n')
cognitive_score = 0
nesting_level = 0
for line in lines:
stripped = line.strip()
# Increase nesting level
if any(keyword in stripped for keyword in ['if ', 'for ', 'while ', 'def ', 'function ', 'class ']):
cognitive_score += (1 + nesting_level)
if stripped.endswith(':') or stripped.endswith('{'):
nesting_level += 1
# Decrease nesting level
if stripped.startswith('}') or (stripped and not stripped.startswith(' ') and nesting_level > 0):
nesting_level = max(0, nesting_level - 1)
# Penalize complex conditions
if '&&' in stripped or '||' in stripped:
cognitive_score += 1
return cognitive_score
def _testability_score(self, code: str, cyclomatic: int) -> float:
"""
Calculate testability score (0-100).
Based on:
- Complexity (lower is better)
- Dependencies (fewer is better)
- Pure functions (more is better)
"""
score = 100.0
# Penalize high complexity
if cyclomatic > 10:
score -= (cyclomatic - 10) * 5
elif cyclomatic > 5:
score -= (cyclomatic - 5) * 2
# Penalize many dependencies
imports = len(re.findall(r'import |require\(|from .* import', code))
if imports > 10:
score -= (imports - 10) * 2
# Reward small functions
functions = len(re.findall(r'def |function ', code))
lines = len(code.split('\n'))
if functions > 0:
avg_function_size = lines / functions
if avg_function_size < 20:
score += 10
elif avg_function_size > 50:
score -= 10
return max(0.0, min(100.0, score))
def _complexity_assessment(self, cyclomatic: int, cognitive: int) -> str:
"""Generate complexity assessment."""
if cyclomatic <= 5 and cognitive <= 10:
return "Low complexity - easy to test"
elif cyclomatic <= 10 and cognitive <= 20:
return "Medium complexity - moderately testable"
elif cyclomatic <= 15 and cognitive <= 30:
return "High complexity - challenging to test"
else:
return "Very high complexity - consider refactoring"
def calculate_test_quality(self, test_code: str) -> Dict[str, Any]:
"""
Calculate test quality metrics.
Args:
test_code: Test code to analyze
Returns:
Test quality metrics
"""
assertions = self._count_assertions(test_code)
test_functions = self._count_test_functions(test_code)
isolation_score = self._isolation_score(test_code)
naming_quality = self._naming_quality(test_code)
test_smells = self._detect_test_smells(test_code)
avg_assertions = assertions / test_functions if test_functions > 0 else 0
return {
'total_tests': test_functions,
'total_assertions': assertions,
'avg_assertions_per_test': round(avg_assertions, 2),
'isolation_score': isolation_score,
'naming_quality': naming_quality,
'test_smells': test_smells,
'quality_score': self._calculate_quality_score(
avg_assertions, isolation_score, naming_quality, test_smells
)
}
def _count_assertions(self, test_code: str) -> int:
"""Count assertion statements."""
# Common assertion patterns
patterns = [
r'\bassert[A-Z]\w*\(', # JUnit: assertTrue, assertEquals
r'\bexpect\(', # Jest/Vitest: expect()
r'\bassert\s+', # Python: assert
r'\.should\.', # Chai: should
r'\.to\.', # Chai: expect().to
]
count = 0
for pattern in patterns:
count += len(re.findall(pattern, test_code))
return count
def _count_test_functions(self, test_code: str) -> int:
"""Count test functions."""
patterns = [
r'\btest_\w+', # Python: test_*
r'\bit\(', # Jest/Mocha: it()
r'\btest\(', # Jest: test()
r'@Test', # JUnit: @Test
r'\bdef test_', # Python def test_
]
count = 0
for pattern in patterns:
count += len(re.findall(pattern, test_code))
return max(1, count) # At least 1 to avoid division by zero
def _isolation_score(self, test_code: str) -> float:
"""
Calculate test isolation score (0-100).
Higher score = better isolation (fewer shared dependencies)
"""
score = 100.0
# Penalize global state
globals_used = len(re.findall(r'\bglobal\s+\w+', test_code))
score -= globals_used * 10
# Penalize shared setup without proper cleanup
setup_count = len(re.findall(r'beforeAll|beforeEach|setUp', test_code))
cleanup_count = len(re.findall(r'afterAll|afterEach|tearDown', test_code))
if setup_count > cleanup_count:
score -= (setup_count - cleanup_count) * 5
# Reward mocking
mocks = len(re.findall(r'mock|stub|spy', test_code, re.IGNORECASE))
score += min(mocks * 2, 10)
return max(0.0, min(100.0, score))
def _naming_quality(self, test_code: str) -> float:
"""
Calculate test naming quality score (0-100).
Better names are descriptive and follow conventions.
"""
test_names = re.findall(r'(?:it|test|def test_)\s*\(?\s*["\']?([^"\')\n]+)', test_code)
if not test_names:
return 50.0
score = 0
for name in test_names:
name_score = 0
# Check length (too short or too long is bad)
if 20 <= len(name) <= 80:
name_score += 30
elif 10 <= len(name) < 20 or 80 < len(name) <= 100:
name_score += 15
# Check for descriptive words
descriptive_words = ['should', 'when', 'given', 'returns', 'throws', 'handles']
if any(word in name.lower() for word in descriptive_words):
name_score += 30
# Check for underscores or camelCase (not just letters)
if '_' in name or re.search(r'[a-z][A-Z]', name):
name_score += 20
# Avoid generic names
generic = ['test1', 'test2', 'testit', 'mytest']
if name.lower() not in generic:
name_score += 20
score += name_score
return min(100.0, score / len(test_names))
def _detect_test_smells(self, test_code: str) -> List[Dict[str, str]]:
"""Detect common test smells."""
smells = []
# Test smell 1: No assertions
if 'assert' not in test_code.lower() and 'expect' not in test_code.lower():
smells.append({
'smell': 'missing_assertions',
'description': 'Tests without assertions',
'severity': 'high'
})
# Test smell 2: Too many assertions
test_count = self._count_test_functions(test_code)
assertion_count = self._count_assertions(test_code)
avg_assertions = assertion_count / test_count if test_count > 0 else 0
if avg_assertions > 5:
smells.append({
'smell': 'assertion_roulette',
'description': f'Too many assertions per test (avg: {avg_assertions:.1f})',
'severity': 'medium'
})
# Test smell 3: Sleeps in tests
if 'sleep' in test_code.lower() or 'wait' in test_code.lower():
smells.append({
'smell': 'sleepy_test',
'description': 'Tests using sleep/wait (potential flakiness)',
'severity': 'high'
})
# Test smell 4: Conditional logic in tests
if re.search(r'\bif\s*\(', test_code):
smells.append({
'smell': 'conditional_test_logic',
'description': 'Tests contain conditional logic',
'severity': 'medium'
})
return smells
def _calculate_quality_score(
self,
avg_assertions: float,
isolation: float,
naming: float,
smells: List[Dict[str, str]]
) -> float:
"""Calculate overall test quality score."""
score = 0.0
# Assertions (30 points)
if 1 <= avg_assertions <= 3:
score += 30
elif 0 < avg_assertions < 1 or 3 < avg_assertions <= 5:
score += 20
else:
score += 10
# Isolation (30 points)
score += isolation * 0.3
# Naming (20 points)
score += naming * 0.2
# Smells (20 points - deduct based on severity)
smell_penalty = 0
for smell in smells:
if smell['severity'] == 'high':
smell_penalty += 10
elif smell['severity'] == 'medium':
smell_penalty += 5
else:
smell_penalty += 2
score = max(0, score - smell_penalty)
return round(min(100.0, score), 2)
def analyze_execution_metrics(
self,
execution_data: Dict[str, Any]
) -> Dict[str, Any]:
"""
Analyze test execution metrics.
Args:
execution_data: Test execution results with timing
Returns:
Execution analysis
"""
tests = execution_data.get('tests', [])
if not tests:
return {}
# Calculate timing statistics
timings = [test.get('duration', 0) for test in tests]
total_time = sum(timings)
avg_time = total_time / len(tests) if tests else 0
# Identify slow tests (>100ms for unit tests)
slow_tests = [
test for test in tests
if test.get('duration', 0) > 100
]
# Identify flaky tests (if failure history available)
flaky_tests = [
test for test in tests
if test.get('failure_rate', 0) > 0.1 # Failed >10% of time
]
return {
'total_tests': len(tests),
'total_time_ms': round(total_time, 2),
'avg_time_ms': round(avg_time, 2),
'slow_tests': len(slow_tests),
'slow_test_details': slow_tests[:5], # Top 5
'flaky_tests': len(flaky_tests),
'flaky_test_details': flaky_tests,
'pass_rate': self._calculate_pass_rate(tests)
}
def _calculate_pass_rate(self, tests: List[Dict[str, Any]]) -> float:
"""Calculate test pass rate."""
if not tests:
return 0.0
passed = sum(1 for test in tests if test.get('status') == 'passed')
return round((passed / len(tests)) * 100, 2)
def generate_metrics_summary(self) -> str:
"""Generate human-readable metrics summary."""
if not self.metrics:
return "No metrics calculated yet."
lines = ["# Test Metrics Summary\n"]
# Complexity
if 'complexity' in self.metrics:
comp = self.metrics['complexity']
lines.append(f"## Code Complexity")
lines.append(f"- Cyclomatic Complexity: {comp['cyclomatic_complexity']}")
lines.append(f"- Cognitive Complexity: {comp['cognitive_complexity']}")
lines.append(f"- Testability Score: {comp['testability_score']:.1f}/100")
lines.append(f"- Assessment: {comp['assessment']}\n")
# Test Quality
if 'test_quality' in self.metrics:
qual = self.metrics['test_quality']
lines.append(f"## Test Quality")
lines.append(f"- Total Tests: {qual['total_tests']}")
lines.append(f"- Assertions per Test: {qual['avg_assertions_per_test']}")
lines.append(f"- Isolation Score: {qual['isolation_score']:.1f}/100")
lines.append(f"- Naming Quality: {qual['naming_quality']:.1f}/100")
lines.append(f"- Quality Score: {qual['quality_score']:.1f}/100\n")
if qual['test_smells']:
lines.append(f"### Test Smells Detected:")
for smell in qual['test_smells']:
lines.append(f"- {smell['description']} (severity: {smell['severity']})")
lines.append("")
return "\n".join(lines)
FILE:scripts/output_formatter.py
"""
Output formatting module.
Provides context-aware output formatting for different environments (Desktop, CLI, API).
Implements progressive disclosure and token-efficient reporting.
"""
from typing import Dict, List, Any, Optional
class OutputFormatter:
"""Format output based on environment and preferences."""
def __init__(self, environment: str = "cli", verbose: bool = False):
"""
Initialize output formatter.
Args:
environment: Target environment (desktop, cli, api)
verbose: Whether to include detailed output
"""
self.environment = environment
self.verbose = verbose
def format_coverage_summary(
self,
summary: Dict[str, Any],
detailed: bool = False
) -> str:
"""
Format coverage summary.
Args:
summary: Coverage summary data
detailed: Whether to include detailed breakdown
Returns:
Formatted coverage summary
"""
if self.environment == "desktop":
return self._format_coverage_markdown(summary, detailed)
elif self.environment == "api":
return self._format_coverage_json(summary)
else:
return self._format_coverage_terminal(summary, detailed)
def _format_coverage_markdown(self, summary: Dict[str, Any], detailed: bool) -> str:
"""Format coverage as rich markdown (for Claude Desktop)."""
lines = ["## Test Coverage Summary\n"]
# Overall metrics
lines.append("### Overall Metrics")
lines.append(f"- **Line Coverage**: {summary.get('line_coverage', 0):.1f}%")
lines.append(f"- **Branch Coverage**: {summary.get('branch_coverage', 0):.1f}%")
lines.append(f"- **Function Coverage**: {summary.get('function_coverage', 0):.1f}%\n")
# Visual indicator
line_cov = summary.get('line_coverage', 0)
lines.append(self._coverage_badge(line_cov))
lines.append("")
# Detailed breakdown if requested
if detailed:
lines.append("### Detailed Breakdown")
lines.append(f"- Total Lines: {summary.get('total_lines', 0)}")
lines.append(f"- Covered Lines: {summary.get('covered_lines', 0)}")
lines.append(f"- Total Branches: {summary.get('total_branches', 0)}")
lines.append(f"- Covered Branches: {summary.get('covered_branches', 0)}")
lines.append(f"- Total Functions: {summary.get('total_functions', 0)}")
lines.append(f"- Covered Functions: {summary.get('covered_functions', 0)}\n")
return "\n".join(lines)
def _format_coverage_terminal(self, summary: Dict[str, Any], detailed: bool) -> str:
"""Format coverage for terminal (Claude Code CLI)."""
lines = ["Coverage Summary:"]
lines.append(f" Line: {summary.get('line_coverage', 0):.1f}%")
lines.append(f" Branch: {summary.get('branch_coverage', 0):.1f}%")
lines.append(f" Function: {summary.get('function_coverage', 0):.1f}%")
if detailed:
lines.append(f"\nDetails:")
lines.append(f" Lines: {summary.get('covered_lines', 0)}/{summary.get('total_lines', 0)}")
lines.append(f" Branches: {summary.get('covered_branches', 0)}/{summary.get('total_branches', 0)}")
return "\n".join(lines)
def _format_coverage_json(self, summary: Dict[str, Any]) -> str:
"""Format coverage as JSON (for API/CI integration)."""
import json
return json.dumps(summary, indent=2)
def _coverage_badge(self, coverage: float) -> str:
"""Generate coverage badge markdown."""
if coverage >= 80:
color = "green"
emoji = "✅"
elif coverage >= 60:
color = "yellow"
emoji = "⚠️"
else:
color = "red"
emoji = "❌"
return f"{emoji} **{coverage:.1f}%** coverage ({color})"
def format_recommendations(
self,
recommendations: List[Dict[str, Any]],
max_items: Optional[int] = None
) -> str:
"""
Format recommendations with progressive disclosure.
Args:
recommendations: List of recommendation dictionaries
max_items: Maximum number of items to show (None for all)
Returns:
Formatted recommendations
"""
if not recommendations:
return "No recommendations at this time."
# Group by priority
p0 = [r for r in recommendations if r.get('priority') == 'P0']
p1 = [r for r in recommendations if r.get('priority') == 'P1']
p2 = [r for r in recommendations if r.get('priority') == 'P2']
if self.environment == "desktop":
return self._format_recommendations_markdown(p0, p1, p2, max_items)
elif self.environment == "api":
return self._format_recommendations_json(recommendations)
else:
return self._format_recommendations_terminal(p0, p1, p2, max_items)
def _format_recommendations_markdown(
self,
p0: List[Dict],
p1: List[Dict],
p2: List[Dict],
max_items: Optional[int]
) -> str:
"""Format recommendations as rich markdown."""
lines = ["## Recommendations\n"]
if p0:
lines.append("### 🔴 Critical (P0)")
for i, rec in enumerate(p0[:max_items] if max_items else p0):
lines.append(f"{i+1}. **{rec.get('message', 'No message')}**")
lines.append(f" - Action: {rec.get('action', 'No action specified')}")
if 'file' in rec:
lines.append(f" - File: `{rec['file']}`")
lines.append("")
if p1 and (not max_items or len(p0) < max_items):
remaining = max_items - len(p0) if max_items else None
lines.append("### 🟡 Important (P1)")
for i, rec in enumerate(p1[:remaining] if remaining else p1):
lines.append(f"{i+1}. {rec.get('message', 'No message')}")
lines.append(f" - Action: {rec.get('action', 'No action specified')}")
lines.append("")
if p2 and self.verbose:
lines.append("### 🔵 Nice to Have (P2)")
for i, rec in enumerate(p2):
lines.append(f"{i+1}. {rec.get('message', 'No message')}")
lines.append("")
return "\n".join(lines)
def _format_recommendations_terminal(
self,
p0: List[Dict],
p1: List[Dict],
p2: List[Dict],
max_items: Optional[int]
) -> str:
"""Format recommendations for terminal."""
lines = ["Recommendations:"]
if p0:
lines.append("\nCritical (P0):")
for i, rec in enumerate(p0[:max_items] if max_items else p0):
lines.append(f" {i+1}. {rec.get('message', 'No message')}")
lines.append(f" Action: {rec.get('action', 'No action')}")
if p1 and (not max_items or len(p0) < max_items):
remaining = max_items - len(p0) if max_items else None
lines.append("\nImportant (P1):")
for i, rec in enumerate(p1[:remaining] if remaining else p1):
lines.append(f" {i+1}. {rec.get('message', 'No message')}")
return "\n".join(lines)
def _format_recommendations_json(self, recommendations: List[Dict[str, Any]]) -> str:
"""Format recommendations as JSON."""
import json
return json.dumps(recommendations, indent=2)
def format_test_results(
self,
results: Dict[str, Any],
show_details: bool = False
) -> str:
"""
Format test execution results.
Args:
results: Test results data
show_details: Whether to show detailed results
Returns:
Formatted test results
"""
if self.environment == "desktop":
return self._format_results_markdown(results, show_details)
elif self.environment == "api":
return self._format_results_json(results)
else:
return self._format_results_terminal(results, show_details)
def _format_results_markdown(self, results: Dict[str, Any], show_details: bool) -> str:
"""Format test results as markdown."""
lines = ["## Test Results\n"]
total = results.get('total_tests', 0)
passed = results.get('passed', 0)
failed = results.get('failed', 0)
skipped = results.get('skipped', 0)
# Summary
lines.append(f"- **Total Tests**: {total}")
lines.append(f"- **Passed**: ✅ {passed}")
if failed > 0:
lines.append(f"- **Failed**: ❌ {failed}")
if skipped > 0:
lines.append(f"- **Skipped**: ⏭️ {skipped}")
# Pass rate
pass_rate = (passed / total * 100) if total > 0 else 0
lines.append(f"- **Pass Rate**: {pass_rate:.1f}%\n")
# Failed tests details
if show_details and failed > 0:
lines.append("### Failed Tests")
for test in results.get('failed_tests', []):
lines.append(f"- `{test.get('name', 'Unknown')}`")
if 'error' in test:
lines.append(f" ```\n {test['error']}\n ```")
return "\n".join(lines)
def _format_results_terminal(self, results: Dict[str, Any], show_details: bool) -> str:
"""Format test results for terminal."""
total = results.get('total_tests', 0)
passed = results.get('passed', 0)
failed = results.get('failed', 0)
lines = [f"Test Results: {passed}/{total} passed"]
if failed > 0:
lines.append(f" Failed: {failed}")
if show_details and failed > 0:
lines.append("\nFailed tests:")
for test in results.get('failed_tests', [])[:5]:
lines.append(f" - {test.get('name', 'Unknown')}")
return "\n".join(lines)
def _format_results_json(self, results: Dict[str, Any]) -> str:
"""Format test results as JSON."""
import json
return json.dumps(results, indent=2)
def create_summary_report(
self,
coverage: Dict[str, Any],
metrics: Dict[str, Any],
recommendations: List[Dict[str, Any]]
) -> str:
"""
Create comprehensive summary report (token-efficient).
Args:
coverage: Coverage data
metrics: Quality metrics
recommendations: Recommendations list
Returns:
Summary report (<200 tokens)
"""
lines = []
# Coverage (1-2 lines)
line_cov = coverage.get('line_coverage', 0)
branch_cov = coverage.get('branch_coverage', 0)
lines.append(f"Coverage: {line_cov:.0f}% lines, {branch_cov:.0f}% branches")
# Quality (1-2 lines)
if 'test_quality' in metrics:
quality_score = metrics['test_quality'].get('quality_score', 0)
lines.append(f"Test Quality: {quality_score:.0f}/100")
# Top recommendations (2-3 lines)
p0_count = sum(1 for r in recommendations if r.get('priority') == 'P0')
if p0_count > 0:
lines.append(f"Critical issues: {p0_count}")
top_rec = next((r for r in recommendations if r.get('priority') == 'P0'), None)
if top_rec:
lines.append(f" - {top_rec.get('message', '')}")
return "\n".join(lines)
def should_show_detailed(self, data_size: int) -> bool:
"""
Determine if detailed output should be shown based on data size.
Args:
data_size: Size of data to display
Returns:
Whether to show detailed output
"""
if self.verbose:
return True
# Progressive disclosure thresholds
if self.environment == "desktop":
return data_size < 100 # Show more in Desktop
else:
return data_size < 20 # Show less in CLI
def truncate_output(self, text: str, max_lines: int = 50) -> str:
"""
Truncate output to maximum lines.
Args:
text: Text to truncate
max_lines: Maximum number of lines
Returns:
Truncated text with indicator
"""
lines = text.split('\n')
if len(lines) <= max_lines:
return text
truncated = '\n'.join(lines[:max_lines])
remaining = len(lines) - max_lines
return f"{truncated}\n\n... ({remaining} more lines, use --verbose for full output)"
FILE:scripts/tdd_workflow.py
"""
TDD workflow guidance module.
Provides step-by-step guidance through red-green-refactor cycles with validation.
"""
from typing import Dict, List, Any, Optional
from enum import Enum
class TDDPhase(Enum):
"""TDD cycle phases."""
RED = "red" # Write failing test
GREEN = "green" # Make test pass
REFACTOR = "refactor" # Improve code
class WorkflowState(Enum):
"""Current state of TDD workflow."""
INITIAL = "initial"
TEST_WRITTEN = "test_written"
TEST_FAILING = "test_failing"
TEST_PASSING = "test_passing"
CODE_REFACTORED = "code_refactored"
class TDDWorkflow:
"""Guide users through TDD red-green-refactor workflow."""
def __init__(self):
"""Initialize TDD workflow guide."""
self.current_phase = TDDPhase.RED
self.state = WorkflowState.INITIAL
self.history = []
def start_cycle(self, requirement: str) -> Dict[str, Any]:
"""
Start a new TDD cycle.
Args:
requirement: User story or requirement to implement
Returns:
Guidance for RED phase
"""
self.current_phase = TDDPhase.RED
self.state = WorkflowState.INITIAL
return {
'phase': 'RED',
'instruction': 'Write a failing test for the requirement',
'requirement': requirement,
'checklist': [
'Write test that describes desired behavior',
'Test should fail when run (no implementation yet)',
'Test name clearly describes what is being tested',
'Test has clear arrange-act-assert structure'
],
'tips': [
'Focus on behavior, not implementation',
'Start with simplest test case',
'Test should be specific and focused'
]
}
def validate_red_phase(
self,
test_code: str,
test_result: Optional[Dict[str, Any]] = None
) -> Dict[str, Any]:
"""
Validate RED phase completion.
Args:
test_code: The test code written
test_result: Test execution result (optional)
Returns:
Validation result and next steps
"""
validations = []
# Check test exists
if not test_code or len(test_code.strip()) < 10:
validations.append({
'valid': False,
'message': 'No test code provided'
})
else:
validations.append({
'valid': True,
'message': 'Test code provided'
})
# Check for assertions
has_assertion = any(keyword in test_code.lower()
for keyword in ['assert', 'expect', 'should'])
validations.append({
'valid': has_assertion,
'message': 'Contains assertions' if has_assertion else 'Missing assertions'
})
# Check test result if provided
if test_result:
test_failed = test_result.get('status') == 'failed'
validations.append({
'valid': test_failed,
'message': 'Test fails as expected' if test_failed else 'Test should fail in RED phase'
})
all_valid = all(v['valid'] for v in validations)
if all_valid:
self.state = WorkflowState.TEST_FAILING
self.current_phase = TDDPhase.GREEN
return {
'phase_complete': True,
'next_phase': 'GREEN',
'validations': validations,
'instruction': 'Write minimal code to make the test pass'
}
else:
return {
'phase_complete': False,
'current_phase': 'RED',
'validations': validations,
'instruction': 'Address validation issues before proceeding'
}
def validate_green_phase(
self,
implementation_code: str,
test_result: Dict[str, Any]
) -> Dict[str, Any]:
"""
Validate GREEN phase completion.
Args:
implementation_code: The implementation code
test_result: Test execution result
Returns:
Validation result and next steps
"""
validations = []
# Check implementation exists
if not implementation_code or len(implementation_code.strip()) < 5:
validations.append({
'valid': False,
'message': 'No implementation code provided'
})
else:
validations.append({
'valid': True,
'message': 'Implementation code provided'
})
# Check test now passes
test_passed = test_result.get('status') == 'passed'
validations.append({
'valid': test_passed,
'message': 'Test passes' if test_passed else 'Test still failing'
})
# Check for minimal implementation (heuristic)
is_minimal = self._check_minimal_implementation(implementation_code)
validations.append({
'valid': is_minimal,
'message': 'Implementation appears minimal' if is_minimal
else 'Implementation may be over-engineered'
})
all_valid = all(v['valid'] for v in validations)
if all_valid:
self.state = WorkflowState.TEST_PASSING
self.current_phase = TDDPhase.REFACTOR
return {
'phase_complete': True,
'next_phase': 'REFACTOR',
'validations': validations,
'instruction': 'Refactor code while keeping tests green',
'refactoring_suggestions': self._suggest_refactorings(implementation_code)
}
else:
return {
'phase_complete': False,
'current_phase': 'GREEN',
'validations': validations,
'instruction': 'Make the test pass before refactoring'
}
def validate_refactor_phase(
self,
original_code: str,
refactored_code: str,
test_result: Dict[str, Any]
) -> Dict[str, Any]:
"""
Validate REFACTOR phase completion.
Args:
original_code: Original implementation
refactored_code: Refactored implementation
test_result: Test execution result after refactoring
Returns:
Validation result and cycle completion status
"""
validations = []
# Check tests still pass
test_passed = test_result.get('status') == 'passed'
validations.append({
'valid': test_passed,
'message': 'Tests still pass after refactoring' if test_passed
else 'Tests broken by refactoring'
})
# Check code was actually refactored
code_changed = original_code != refactored_code
validations.append({
'valid': code_changed,
'message': 'Code was refactored' if code_changed
else 'No refactoring applied (optional)'
})
# Check code quality improved
quality_improved = self._check_quality_improvement(original_code, refactored_code)
if code_changed:
validations.append({
'valid': quality_improved,
'message': 'Code quality improved' if quality_improved
else 'Consider further refactoring for better quality'
})
all_valid = all(v['valid'] for v in validations if v.get('valid') is not None)
if all_valid:
self.state = WorkflowState.CODE_REFACTORED
self.history.append({
'cycle_complete': True,
'final_state': self.state
})
return {
'phase_complete': True,
'cycle_complete': True,
'validations': validations,
'message': 'TDD cycle complete! Ready for next requirement.',
'next_steps': [
'Commit your changes',
'Start next TDD cycle with new requirement',
'Or add more test cases for current feature'
]
}
else:
return {
'phase_complete': False,
'current_phase': 'REFACTOR',
'validations': validations,
'instruction': 'Ensure tests still pass after refactoring'
}
def _check_minimal_implementation(self, code: str) -> bool:
"""Check if implementation is minimal (heuristic)."""
# Simple heuristics:
# - Not too long (< 50 lines for unit tests)
# - Not too complex (few nested structures)
lines = code.split('\n')
non_empty_lines = [line for line in lines if line.strip() and not line.strip().startswith('#')]
# Check length
if len(non_empty_lines) > 50:
return False
# Check nesting depth (simplified)
max_depth = 0
current_depth = 0
for line in lines:
stripped = line.lstrip()
if stripped:
indent = len(line) - len(stripped)
depth = indent // 4 # Assuming 4-space indent
max_depth = max(max_depth, depth)
# Max nesting of 3 levels for simple implementation
return max_depth <= 3
def _check_quality_improvement(self, original: str, refactored: str) -> bool:
"""Check if refactoring improved code quality."""
# Simple heuristics:
# - Reduced duplication
# - Better naming
# - Simpler structure
# Check for reduced duplication (basic check)
original_lines = set(line.strip() for line in original.split('\n') if line.strip())
refactored_lines = set(line.strip() for line in refactored.split('\n') if line.strip())
# If unique lines increased proportionally, likely extracted duplicates
if len(refactored_lines) > len(original_lines):
return True
# Check for better naming (longer, more descriptive names)
original_avg_identifier_length = self._avg_identifier_length(original)
refactored_avg_identifier_length = self._avg_identifier_length(refactored)
if refactored_avg_identifier_length > original_avg_identifier_length:
return True
# If no clear improvement detected, assume refactoring was beneficial
return True
def _avg_identifier_length(self, code: str) -> float:
"""Calculate average identifier length (proxy for naming quality)."""
import re
identifiers = re.findall(r'\b[a-zA-Z_][a-zA-Z0-9_]*\b', code)
# Filter out keywords
keywords = {'if', 'else', 'for', 'while', 'def', 'class', 'return', 'import', 'from'}
identifiers = [i for i in identifiers if i.lower() not in keywords]
if not identifiers:
return 0.0
return sum(len(i) for i in identifiers) / len(identifiers)
def _suggest_refactorings(self, code: str) -> List[str]:
"""Suggest potential refactorings."""
suggestions = []
# Check for long functions
lines = code.split('\n')
if len(lines) > 30:
suggestions.append('Consider breaking long function into smaller functions')
# Check for duplication (simple check)
line_counts = {}
for line in lines:
stripped = line.strip()
if len(stripped) > 10: # Ignore very short lines
line_counts[stripped] = line_counts.get(stripped, 0) + 1
duplicates = [line for line, count in line_counts.items() if count > 2]
if duplicates:
suggestions.append(f'Found {len(duplicates)} duplicated code patterns - consider extraction')
# Check for magic numbers
import re
magic_numbers = re.findall(r'\b\d+\b', code)
if len(magic_numbers) > 5:
suggestions.append('Consider extracting magic numbers to named constants')
# Check for long parameter lists
if 'def ' in code or 'function' in code:
param_matches = re.findall(r'\(([^)]+)\)', code)
for params in param_matches:
if params.count(',') > 3:
suggestions.append('Consider using parameter object for functions with many parameters')
break
if not suggestions:
suggestions.append('Code looks clean - no obvious refactorings needed')
return suggestions
def generate_workflow_summary(self) -> str:
"""Generate summary of TDD workflow progress."""
summary = [
"# TDD Workflow Summary\n",
f"Current Phase: {self.current_phase.value.upper()}",
f"Current State: {self.state.value.replace('_', ' ').title()}",
f"Completed Cycles: {len(self.history)}\n"
]
summary.append("## TDD Cycle Steps:\n")
summary.append("1. **RED**: Write a failing test")
summary.append(" - Test describes desired behavior")
summary.append(" - Test fails (no implementation)\n")
summary.append("2. **GREEN**: Make the test pass")
summary.append(" - Write minimal code to pass test")
summary.append(" - All tests should pass\n")
summary.append("3. **REFACTOR**: Improve the code")
summary.append(" - Clean up implementation")
summary.append(" - Tests still pass")
summary.append(" - Code is more maintainable\n")
return "\n".join(summary)
def get_phase_guidance(self, phase: Optional[TDDPhase] = None) -> Dict[str, Any]:
"""
Get detailed guidance for a specific phase.
Args:
phase: TDD phase (uses current if not specified)
Returns:
Detailed guidance dictionary
"""
target_phase = phase or self.current_phase
if target_phase == TDDPhase.RED:
return {
'phase': 'RED',
'goal': 'Write a failing test',
'steps': [
'1. Read and understand the requirement',
'2. Think about expected behavior',
'3. Write test that verifies this behavior',
'4. Run test and ensure it fails',
'5. Verify failure reason is correct (not syntax error)'
],
'common_mistakes': [
'Test passes immediately (no real assertion)',
'Test fails for wrong reason (syntax error)',
'Test is too broad or tests multiple things'
],
'tips': [
'Start with simplest test case',
'One assertion per test (focused)',
'Test should read like specification'
]
}
elif target_phase == TDDPhase.GREEN:
return {
'phase': 'GREEN',
'goal': 'Make the test pass with minimal code',
'steps': [
'1. Write simplest code that makes test pass',
'2. Run test and verify it passes',
'3. Run all tests to ensure no regression',
'4. Resist urge to add extra features'
],
'common_mistakes': [
'Over-engineering solution',
'Adding features not covered by tests',
'Breaking existing tests'
],
'tips': [
'Fake it till you make it (hardcode if needed)',
'Triangulate with more tests if needed',
'Keep implementation simple'
]
}
elif target_phase == TDDPhase.REFACTOR:
return {
'phase': 'REFACTOR',
'goal': 'Improve code quality while keeping tests green',
'steps': [
'1. Identify code smells or duplication',
'2. Apply one refactoring at a time',
'3. Run tests after each change',
'4. Commit when satisfied with quality'
],
'common_mistakes': [
'Changing behavior (breaking tests)',
'Refactoring too much at once',
'Skipping this phase'
],
'tips': [
'Extract methods for better naming',
'Remove duplication',
'Improve variable names',
'Tests are safety net - use them!'
]
}
return {}
FILE:scripts/test_generator.py
"""
Test case generation module.
Generates test cases from requirements, user stories, API specs, and code analysis.
Supports multiple testing frameworks with intelligent test scaffolding.
"""
from typing import Dict, List, Any, Optional
from enum import Enum
class TestFramework(Enum):
"""Supported testing frameworks."""
JEST = "jest"
VITEST = "vitest"
PYTEST = "pytest"
JUNIT = "junit"
MOCHA = "mocha"
class TestType(Enum):
"""Types of tests to generate."""
UNIT = "unit"
INTEGRATION = "integration"
E2E = "e2e"
class TestGenerator:
"""Generate test cases and test stubs from requirements and code."""
def __init__(self, framework: TestFramework, language: str):
"""
Initialize test generator.
Args:
framework: Testing framework to use
language: Programming language (typescript, javascript, python, java)
"""
self.framework = framework
self.language = language
self.test_cases = []
def generate_from_requirements(
self,
requirements: Dict[str, Any],
test_type: TestType = TestType.UNIT
) -> List[Dict[str, Any]]:
"""
Generate test cases from requirements.
Args:
requirements: Dictionary with user_stories, acceptance_criteria, api_specs
test_type: Type of tests to generate
Returns:
List of test case specifications
"""
test_cases = []
# Generate from user stories
if 'user_stories' in requirements:
for story in requirements['user_stories']:
test_cases.extend(self._test_cases_from_story(story))
# Generate from acceptance criteria
if 'acceptance_criteria' in requirements:
for criterion in requirements['acceptance_criteria']:
test_cases.extend(self._test_cases_from_criteria(criterion))
# Generate from API specs
if 'api_specs' in requirements:
for endpoint in requirements['api_specs']:
test_cases.extend(self._test_cases_from_api(endpoint))
self.test_cases = test_cases
return test_cases
def _test_cases_from_story(self, story: Dict[str, Any]) -> List[Dict[str, Any]]:
"""Generate test cases from user story."""
test_cases = []
# Happy path test
test_cases.append({
'name': f"should_{story.get('action', 'work')}_successfully",
'type': 'happy_path',
'description': story.get('description', ''),
'given': story.get('given', []),
'when': story.get('when', ''),
'then': story.get('then', ''),
'priority': 'P0'
})
# Error cases
if 'error_conditions' in story:
for error in story['error_conditions']:
test_cases.append({
'name': f"should_handle_{error.get('condition', 'error')}",
'type': 'error_case',
'description': error.get('description', ''),
'expected_error': error.get('error_type', ''),
'priority': 'P0'
})
# Edge cases
if 'edge_cases' in story:
for edge_case in story['edge_cases']:
test_cases.append({
'name': f"should_handle_{edge_case.get('scenario', 'edge_case')}",
'type': 'edge_case',
'description': edge_case.get('description', ''),
'priority': 'P1'
})
return test_cases
def _test_cases_from_criteria(self, criterion: Dict[str, Any]) -> List[Dict[str, Any]]:
"""Generate test cases from acceptance criteria."""
return [{
'name': f"should_meet_{criterion.get('id', 'criterion')}",
'type': 'acceptance',
'description': criterion.get('description', ''),
'verification': criterion.get('verification_steps', []),
'priority': 'P0'
}]
def _test_cases_from_api(self, endpoint: Dict[str, Any]) -> List[Dict[str, Any]]:
"""Generate test cases from API specification."""
test_cases = []
method = endpoint.get('method', 'GET')
path = endpoint.get('path', '/')
# Success case
test_cases.append({
'name': f"should_{method.lower()}_{path.replace('/', '_')}_successfully",
'type': 'api_success',
'method': method,
'path': path,
'expected_status': endpoint.get('success_status', 200),
'priority': 'P0'
})
# Validation errors
if 'required_params' in endpoint:
test_cases.append({
'name': f"should_return_400_for_missing_params",
'type': 'api_validation',
'method': method,
'path': path,
'expected_status': 400,
'priority': 'P0'
})
# Authorization
if endpoint.get('requires_auth', False):
test_cases.append({
'name': f"should_return_401_for_unauthenticated",
'type': 'api_auth',
'method': method,
'path': path,
'expected_status': 401,
'priority': 'P0'
})
return test_cases
def generate_test_stub(self, test_case: Dict[str, Any]) -> str:
"""
Generate test stub code for a test case.
Args:
test_case: Test case specification
Returns:
Test stub code as string
"""
if self.framework == TestFramework.JEST:
return self._generate_jest_stub(test_case)
elif self.framework == TestFramework.PYTEST:
return self._generate_pytest_stub(test_case)
elif self.framework == TestFramework.JUNIT:
return self._generate_junit_stub(test_case)
elif self.framework == TestFramework.VITEST:
return self._generate_vitest_stub(test_case)
else:
return self._generate_generic_stub(test_case)
def _generate_jest_stub(self, test_case: Dict[str, Any]) -> str:
"""Generate Jest test stub."""
name = test_case.get('name', 'test')
description = test_case.get('description', '')
stub = f"""
describe('{{Feature Name}}', () => {{
it('{name}', () => {{
// {description}
// Arrange
// TODO: Set up test data and dependencies
// Act
// TODO: Execute the code under test
// Assert
// TODO: Verify expected behavior
expect(true).toBe(true); // Replace with actual assertion
}});
}});
"""
return stub.strip()
def _generate_pytest_stub(self, test_case: Dict[str, Any]) -> str:
"""Generate Pytest test stub."""
name = test_case.get('name', 'test')
description = test_case.get('description', '')
stub = f"""
def test_{name}():
\"\"\"
{description}
\"\"\"
# Arrange
# TODO: Set up test data and dependencies
# Act
# TODO: Execute the code under test
# Assert
# TODO: Verify expected behavior
assert True # Replace with actual assertion
"""
return stub.strip()
def _generate_junit_stub(self, test_case: Dict[str, Any]) -> str:
"""Generate JUnit test stub."""
name = test_case.get('name', 'test')
description = test_case.get('description', '')
# Convert snake_case to camelCase for Java
method_name = ''.join(word.capitalize() if i > 0 else word
for i, word in enumerate(name.split('_')))
stub = f"""
@Test
public void {method_name}() {{
// {description}
// Arrange
// TODO: Set up test data and dependencies
// Act
// TODO: Execute the code under test
// Assert
// TODO: Verify expected behavior
assertTrue(true); // Replace with actual assertion
}}
"""
return stub.strip()
def _generate_vitest_stub(self, test_case: Dict[str, Any]) -> str:
"""Generate Vitest test stub (similar to Jest)."""
name = test_case.get('name', 'test')
description = test_case.get('description', '')
stub = f"""
describe('{{Feature Name}}', () => {{
it('{name}', () => {{
// {description}
// Arrange
// TODO: Set up test data and dependencies
// Act
// TODO: Execute the code under test
// Assert
// TODO: Verify expected behavior
expect(true).toBe(true); // Replace with actual assertion
}});
}});
"""
return stub.strip()
def _generate_generic_stub(self, test_case: Dict[str, Any]) -> str:
"""Generate generic test stub."""
name = test_case.get('name', 'test')
description = test_case.get('description', '')
return f"""
# Test: {name}
# Description: {description}
#
# TODO: Implement test
# 1. Arrange: Set up test data
# 2. Act: Execute code under test
# 3. Assert: Verify expected behavior
"""
def generate_test_file(
self,
module_name: str,
test_cases: Optional[List[Dict[str, Any]]] = None
) -> str:
"""
Generate complete test file with all test stubs.
Args:
module_name: Name of module being tested
test_cases: List of test cases (uses self.test_cases if not provided)
Returns:
Complete test file content
"""
cases = test_cases or self.test_cases
if self.framework == TestFramework.JEST:
return self._generate_jest_file(module_name, cases)
elif self.framework == TestFramework.PYTEST:
return self._generate_pytest_file(module_name, cases)
elif self.framework == TestFramework.JUNIT:
return self._generate_junit_file(module_name, cases)
elif self.framework == TestFramework.VITEST:
return self._generate_vitest_file(module_name, cases)
else:
return ""
def _generate_jest_file(self, module_name: str, test_cases: List[Dict[str, Any]]) -> str:
"""Generate complete Jest test file."""
imports = f"import {{ {module_name} }} from '../{module_name}';\n\n"
stubs = []
for test_case in test_cases:
stubs.append(self._generate_jest_stub(test_case))
return imports + "\n\n".join(stubs)
def _generate_pytest_file(self, module_name: str, test_cases: List[Dict[str, Any]]) -> str:
"""Generate complete Pytest test file."""
imports = f"import pytest\nfrom {module_name} import *\n\n\n"
stubs = []
for test_case in test_cases:
stubs.append(self._generate_pytest_stub(test_case))
return imports + "\n\n\n".join(stubs)
def _generate_junit_file(self, module_name: str, test_cases: List[Dict[str, Any]]) -> str:
"""Generate complete JUnit test file."""
class_name = ''.join(word.capitalize() for word in module_name.split('_'))
imports = """import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
"""
class_header = f"public class {class_name}Test {{\n\n"
stubs = []
for test_case in test_cases:
stubs.append(self._generate_junit_stub(test_case))
class_footer = "\n}"
return imports + class_header + "\n\n".join(stubs) + class_footer
def _generate_vitest_file(self, module_name: str, test_cases: List[Dict[str, Any]]) -> str:
"""Generate complete Vitest test file."""
imports = f"import {{ describe, it, expect }} from 'vitest';\nimport {{ {module_name} }} from '../{module_name}';\n\n"
stubs = []
for test_case in test_cases:
stubs.append(self._generate_vitest_stub(test_case))
return imports + "\n\n".join(stubs)
def suggest_missing_scenarios(
self,
existing_tests: List[str],
code_analysis: Dict[str, Any]
) -> List[Dict[str, Any]]:
"""
Suggest missing test scenarios based on code analysis.
Args:
existing_tests: List of existing test names
code_analysis: Analysis of code under test (branches, error paths, etc.)
Returns:
List of suggested test scenarios
"""
suggestions = []
# Check for untested error conditions
if 'error_handlers' in code_analysis:
for error_handler in code_analysis['error_handlers']:
error_name = error_handler.get('type', 'error')
if not self._has_test_for(existing_tests, error_name):
suggestions.append({
'name': f"should_handle_{error_name}",
'type': 'error_case',
'reason': 'Error handler exists but no corresponding test',
'priority': 'P0'
})
# Check for untested branches
if 'conditional_branches' in code_analysis:
for branch in code_analysis['conditional_branches']:
branch_name = branch.get('condition', 'condition')
if not self._has_test_for(existing_tests, branch_name):
suggestions.append({
'name': f"should_test_{branch_name}_branch",
'type': 'branch_coverage',
'reason': 'Conditional branch not fully tested',
'priority': 'P1'
})
# Check for boundary conditions
if 'input_validation' in code_analysis:
for validation in code_analysis['input_validation']:
param = validation.get('parameter', 'input')
if not self._has_test_for(existing_tests, f"{param}_boundary"):
suggestions.append({
'name': f"should_test_{param}_boundary_values",
'type': 'boundary',
'reason': 'Input validation exists but boundary tests missing',
'priority': 'P1'
})
return suggestions
def _has_test_for(self, existing_tests: List[str], keyword: str) -> bool:
"""Check if existing tests cover a keyword/scenario."""
keyword_lower = keyword.lower().replace('_', '').replace('-', '')
for test in existing_tests:
test_lower = test.lower().replace('_', '').replace('-', '')
if keyword_lower in test_lower:
return True
return False
Chất vấn của Chief Customer Officer với kế hoạch về giữ chân khách hàng, phân khúc, quy mô và tuyển dụng CS.
--- name: "cco-review" description: "/cs:cco-review <plan> — Retention-obsessed Chief Customer Officer interrogation of any plan that touches customer retention, segmentation, CS team sizing, or CS team hiring." --- # /cs:cco-review — CCO Forcing Questions **Command:** `/cs:cco-review <plan>` The retention-obsessed CCO pressure-tests any plan that touches customer experience. Six questions before any retention claim, segmentation change, CS team expansion, or major CS hire. ## When to Run - Before any board narrative that includes a retention number - Before approving a CS team headcount expansion - Before re-segmenting the customer base or changing tier definitions - Before launching a customer marketing or advocacy program - Before a major CS hire (CSM, AM, Implementation, Customer Marketing) - When NRR is "great" but churn complaints from CSMs are increasing - Before deciding whether to add an AM role separate from CSM ## The Six CCO Questions ### 1. What's the GROSS retention rate? **Not NRR. Gross.** NRR can hide a leaky bucket behind expansion. - GRR healthy ≥ 90% at growth stage, ≥ 95% at scale - If GRR < 85% but NRR > 100%, the product is failing for 15%+ of customers; expansion is masking the failure - Run `retention_decomposition_analyzer.py` ### 2. What's the #1 reason customers leave? **If you can't name it, you don't understand churn.** - 7-category taxonomy: product_fit / competitor_loss / no_value_realized / pricing / champion_left / company_event / tactical_failure - Preventable churn = product_fit + no_value_realized + tactical_failure - If preventable > 50%, CS has clear leverage; if < 30%, churn is structural (ICP, market, competition) ### 3. What's the median time-to-value (TTV) by segment? **Long TTV signals different problems by segment.** - Long TTV in low tier = ICP misfit; downgrade or kill - Long TTV in high tier = onboarding broken; fix the Implementation Manager handoff - TTV is a leading indicator of GRR ### 4. Which customer would you fire today? **If "none" — your segmentation is broken.** - Some accounts cost more than they earn (support cost > 50% of ARR + low ICP fit) - Run `customer_segmentation_designer.py` to surface kill list - The 3 paths for kill candidates: non-renewal / downgrade-to-tech-touch / raise-price-to-cost-recover ### 5. What's the ARR-per-CSM ratio, and is the model pooled or named? **Wrong model wastes capacity.** - Strategic: named + exec sponsor, $300K-$1M ARR/CSM - Enterprise: named, $500K-$2M - Mid-market: pooled, $2M-$5M - SMB: tech-touch, $5M+ - Run `cs_coverage_calculator.py` to size the team ### 6. Is CS in your comp plan, and how is it different from Sales comp? **Misalignment is the leading indicator of CS failure.** - CS comp: 70/30 base/variable typical - Variable: 50% gross retention + 30% net retention + 20% activity - Anti-pattern: comp CSMs on NPS — they game it - Anti-pattern: comp CSMs same as Sales — they sell instead of serve ## Workflow ```bash # 1. Retention decomposition (always start here) python ../../../skills/chief-customer-officer-advisor/scripts/retention_decomposition_analyzer.py cohorts.json # 2. Segmentation audit python ../../../skills/chief-customer-officer-advisor/scripts/customer_segmentation_designer.py customers.json # 3. Coverage sizing (if making CS team changes) python ../../../skills/chief-customer-officer-advisor/scripts/cs_coverage_calculator.py book.json ``` ## Output Format ```markdown # CCO Review: <plan> **Date:** YYYY-MM-DD ## The Decision Being Made [one sentence — retention | segmentation | coverage | next hire] ## Retention (if applicable) - GRR: X% (vs vanity NRR of Y%) - Top churn driver: <category> at X% of churn - Preventable churn: X% (CS-controllable) - Leaky-bucket pattern? yes/no ## Segmentation (if applicable) - Tier distribution: Strategic X / Enterprise X / Mid-market X / SMB X - Kill list size: N customers (X% of customers, Y% of ARR) - Upgrade candidates: N ## Coverage (if applicable) - Current CSMs: N | Required now: M | Required 12mo: P - Annual cost (12mo): $X - Manager trigger fired: yes/no ## Org (if applicable) - Next hire: <CSM | Support | AM | IM | CS Ops | Customer Marketing> - Why this, not the alternative: <one line> - Customer outcome unblocked: <specific> ## Verdict 🟢 SHIP | 🟡 SHARPEN | 🔴 BLOCK ## Next Steps [3 concrete actions] ``` ## Routing - `/cs:cpo-review` — if churn root cause is product_fit or no_value_realized - `/cs:cro-review` — if expansion math or comp alignment is in question - `/cs:cfo-review` — for CS cost commitments and retention-impact-on-revenue - `/cs:chro-review` — for CS hires, comp, ladder - `/cs:decide` — log the verdict - `/cs:freeze 30` — on multi-year CS comp plan changes ## Related - Agent: [`cs-cco-advisor`](../../agents/cs-cco-advisor.md) - Skill: [`chief-customer-officer-advisor`](../../../skills/chief-customer-officer-advisor/SKILL.md) - Adjacent: `../../../../business-growth/` (tactical CS execution) --- **Version:** 1.0.0
Tạo changelog từ lịch sử git và kiểm tra conventional commits.
--- name: changelog description: Generate changelogs from git history and validate conventional commits. Usage: /changelog <generate|lint> [options] --- # /changelog Generate Keep a Changelog entries from git history and validate commit message format. ## Usage ``` /changelog generate [--from-tag <tag>] [--to-tag <tag>] Generate changelog entries /changelog lint [--from-ref <ref>] [--to-ref <ref>] Lint commit messages ``` ## Examples ``` /changelog generate --from-tag v2.0.0 /changelog lint --from-ref main --to-ref dev /changelog generate --from-tag v2.0.0 --to-tag v2.1.0 --format markdown ``` ## Scripts - `engineering/changelog-generator/scripts/generate_changelog.py` — Parse commits, render changelog (`--from-tag`, `--to-tag`, `--from-ref`, `--to-ref`, `--format markdown|json`) - `engineering/changelog-generator/scripts/commit_linter.py` — Validate conventional commit format (`--from-ref`, `--to-ref`, `--strict`, `--format text|json`) ## Skill Reference → `engineering/changelog-generator/SKILL.md`
Tạo ghi chú phát hành nhất quán, có thể kiểm toán từ Conventional Commits, phục vụ phát hành và CI.
---
name: "changelog-generator"
description: "Produce consistent, auditable release notes from Conventional Commits. Separates commit parsing, semantic-bump logic, and changelog rendering for automated releases with editorial control. Use when cutting a release, generating CHANGELOG.md from git history, or automating release notes in CI."
---
# Changelog Generator
**Tier:** POWERFUL
**Category:** Engineering
**Domain:** Release Management / Documentation
## Overview
Use this skill to produce consistent, auditable release notes from Conventional Commits. It separates commit parsing, semantic bump logic, and changelog rendering so teams can automate releases without losing editorial control.
## Core Capabilities
- Parse commit messages using Conventional Commit rules
- Detect semantic bump (`major`, `minor`, `patch`) from commit stream
- Render Keep a Changelog sections (`Added`, `Changed`, `Fixed`, etc.)
- Generate release entries from git ranges or provided commit input
- Enforce commit format with a dedicated linter script
- Support CI integration via machine-readable JSON output
## When to Use
- Before publishing a release tag
- During CI to generate release notes automatically
- During PR checks to block invalid commit message formats
- In monorepos where package changelogs require scoped filtering
- When converting raw git history into user-facing notes
## Key Workflows
### 1. Generate Changelog Entry From Git
```bash
python3 scripts/generate_changelog.py \
--from-tag v1.3.0 \
--to-tag v1.4.0 \
--next-version v1.4.0 \
--format markdown
```
### 2. Generate Entry From stdin/File Input
```bash
git log v1.3.0..v1.4.0 --pretty=format:'%s' | \
python3 scripts/generate_changelog.py --next-version v1.4.0 --format markdown
python3 scripts/generate_changelog.py --input commits.txt --next-version v1.4.0 --format json
```
### 3. Update `CHANGELOG.md`
```bash
python3 scripts/generate_changelog.py \
--from-tag v1.3.0 \
--to-tag HEAD \
--next-version v1.4.0 \
--write CHANGELOG.md
```
### 4. Lint Commits Before Merge
```bash
python3 scripts/commit_linter.py --from-ref origin/main --to-ref HEAD --strict --format text
```
Or file/stdin:
```bash
python3 scripts/commit_linter.py --input commits.txt --strict
cat commits.txt | python3 scripts/commit_linter.py --format json
```
## Conventional Commit Rules
Supported types:
- `feat`, `fix`, `perf`, `refactor`, `docs`, `test`, `build`, `ci`, `chore`
- `security`, `deprecated`, `remove`
Breaking changes:
- `type(scope)!: summary`
- Footer/body includes `BREAKING CHANGE:`
SemVer mapping:
- breaking -> `major`
- non-breaking `feat` -> `minor`
- all others -> `patch`
## Script Interfaces
- `python3 scripts/generate_changelog.py --help`
- Reads commits from git or stdin/`--input`
- Renders markdown or JSON
- Optional in-place changelog prepend
- `python3 scripts/commit_linter.py --help`
- Validates commit format
- Returns non-zero in `--strict` mode on violations
## Common Pitfalls
1. Mixing merge commit messages with release commit parsing
2. Using vague commit summaries that cannot become release notes
3. Failing to include migration guidance for breaking changes
4. Treating docs/chore changes as user-facing features
5. Overwriting historical changelog sections instead of prepending
## Best Practices
1. Keep commits small and intent-driven.
2. Scope commit messages (`feat(api): ...`) in multi-package repos.
3. Enforce linter checks in PR pipelines.
4. Review generated markdown before publishing.
5. Tag releases only after changelog generation succeeds.
6. Keep an `[Unreleased]` section for manual curation when needed.
## References
- [references/ci-integration.md](references/ci-integration.md)
- [references/changelog-formatting-guide.md](references/changelog-formatting-guide.md)
- [references/monorepo-strategy.md](references/monorepo-strategy.md)
- [README.md](README.md)
## Release Governance
Use this release flow for predictability:
1. Lint commit history for target release range.
2. Generate changelog draft from commits.
3. Manually adjust wording for customer clarity.
4. Validate semver bump recommendation.
5. Tag release only after changelog is approved.
## Output Quality Checks
- Each bullet is user-meaningful, not implementation noise.
- Breaking changes include migration action.
- Security fixes are isolated in `Security` section.
- Sections with no entries are omitted.
- Duplicate bullets across sections are removed.
## CI Policy
- Run `commit_linter.py --strict` on all PRs.
- Block merge on invalid conventional commits.
- Auto-generate draft release notes on tag push.
- Require human approval before writing into `CHANGELOG.md` on main branch.
## Monorepo Guidance
- Prefer commit scopes aligned to package names.
- Filter commit stream by scope for package-specific releases.
- Keep infra-wide changes in root changelog.
- Store package changelogs near package roots for ownership clarity.
## Failure Handling
- If no valid conventional commits found: fail early, do not generate misleading empty notes.
- If git range invalid: surface explicit range in error output.
- If write target missing: create safe changelog header scaffolding.
FILE:README.md
# Changelog Generator
Automates release notes from Conventional Commits with Keep a Changelog output and strict commit linting. Designed for CI-friendly release workflows.
## Quick Start
```bash
# Generate entry from git range
python3 scripts/generate_changelog.py \
--from-tag v1.2.0 \
--to-tag v1.3.0 \
--next-version v1.3.0 \
--format markdown
# Lint commit subjects
python3 scripts/commit_linter.py --from-ref origin/main --to-ref HEAD --strict --format text
```
## Included Tools
- `scripts/generate_changelog.py`: parse commits, infer semver bump, render markdown/JSON, optional file prepend
- `scripts/commit_linter.py`: validate commit subjects against Conventional Commits rules
## References
- `references/ci-integration.md`
- `references/changelog-formatting-guide.md`
- `references/monorepo-strategy.md`
## Installation
### Claude Code
```bash
cp -R engineering/changelog-generator ~/.claude/skills/changelog-generator
```
### OpenAI Codex
```bash
cp -R engineering/changelog-generator ~/.codex/skills/changelog-generator
```
### OpenClaw
```bash
cp -R engineering/changelog-generator ~/.openclaw/skills/changelog-generator
```
FILE:references/changelog-formatting-guide.md
# Changelog Formatting Guide
Use Keep a Changelog section ordering:
1. Security
2. Added
3. Changed
4. Deprecated
5. Removed
6. Fixed
Rules:
- One bullet = one user-visible change.
- Lead with impact, not implementation detail.
- Keep bullets short and actionable.
- Include migration note for breaking changes.
FILE:references/ci-integration.md
# CI Integration Examples
## GitHub Actions
```yaml
name: Changelog Check
on: [pull_request]
jobs:
changelog:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: python3 engineering/changelog-generator/scripts/commit_linter.py \
--from-ref origin/main --to-ref HEAD --strict
```
## GitLab CI
```yaml
changelog_lint:
image: python:3.12
stage: test
script:
- python3 engineering/changelog-generator/scripts/commit_linter.py --to-ref HEAD --strict
```
FILE:references/monorepo-strategy.md
# Monorepo Changelog Strategy
## Approaches
| Strategy | When to use | Tradeoff |
|----------|-------------|----------|
| Single root changelog | Product-wide releases, small teams | Simple but loses package-level detail |
| Per-package changelogs | Independent versioning, large teams | Clear ownership but harder to see full picture |
| Hybrid model | Root summary + package-specific details | Best of both, more maintenance |
## Commit Scoping Pattern
Enforce scoped conventional commits to enable per-package filtering:
```
feat(payments): add Stripe webhook handler
fix(auth): handle expired refresh tokens
chore(infra): bump base Docker image
```
**Rules:**
- Scope must match a package/directory name exactly
- Unscoped commits go to root changelog only
- Multi-package changes get separate scoped commits (not one mega-commit)
## Filtering for Package Releases
```bash
# Generate changelog for 'payments' package only
git log v1.3.0..HEAD --pretty=format:'%s' | grep '^[a-z]*\(payments\)' | \
python3 scripts/generate_changelog.py --next-version v1.4.0 --format markdown
```
## Ownership Model
- Package maintainers own their scoped changelog
- Platform/infra team owns root changelog
- CI enforces scope presence on all commits touching package directories
- Root changelog aggregates breaking changes from all packages for visibility
FILE:scripts/commit_linter.py
#!/usr/bin/env python3
"""Lint commit messages against Conventional Commits.
Input sources (priority order):
1) --input file (one commit subject per line)
2) stdin lines
3) git range via --from-ref/--to-ref
Use --strict for non-zero exit on violations.
"""
import argparse
import json
import re
import subprocess
import sys
from dataclasses import dataclass, asdict
from pathlib import Path
from typing import List, Optional
CONVENTIONAL_RE = re.compile(
r"^(feat|fix|perf|refactor|docs|test|build|ci|chore|security|deprecated|remove)"
r"(\([a-z0-9._/-]+\))?(!)?:\s+.{1,120}$"
)
class CLIError(Exception):
"""Raised for expected CLI errors."""
@dataclass
class LintReport:
total: int
valid: int
invalid: int
violations: List[str]
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Validate conventional commit subjects.")
parser.add_argument("--input", help="File with commit subjects (one per line).")
parser.add_argument("--from-ref", help="Git ref start (exclusive).")
parser.add_argument("--to-ref", help="Git ref end (inclusive).")
parser.add_argument("--strict", action="store_true", help="Exit non-zero when violations exist.")
parser.add_argument("--format", choices=["text", "json"], default="text", help="Output format.")
return parser.parse_args()
def lines_from_file(path: str) -> List[str]:
try:
return [line.strip() for line in Path(path).read_text(encoding="utf-8").splitlines() if line.strip()]
except Exception as exc:
raise CLIError(f"Failed reading --input file: {exc}") from exc
def lines_from_stdin() -> List[str]:
if sys.stdin.isatty():
return []
data = sys.stdin.read()
return [line.strip() for line in data.splitlines() if line.strip()]
def lines_from_git(args: argparse.Namespace) -> List[str]:
if not args.to_ref:
return []
range_spec = f"{args.from_ref}..{args.to_ref}" if args.from_ref else args.to_ref
try:
proc = subprocess.run(
["git", "log", range_spec, "--pretty=format:%s", "--no-merges"],
text=True,
capture_output=True,
check=True,
)
except subprocess.CalledProcessError as exc:
raise CLIError(f"git log failed for range '{range_spec}': {exc.stderr.strip()}") from exc
return [line.strip() for line in proc.stdout.splitlines() if line.strip()]
def load_lines(args: argparse.Namespace) -> List[str]:
if args.input:
return lines_from_file(args.input)
stdin_lines = lines_from_stdin()
if stdin_lines:
return stdin_lines
git_lines = lines_from_git(args)
if git_lines:
return git_lines
raise CLIError("No commit input found. Use --input, stdin, or --to-ref.")
def lint(lines: List[str]) -> LintReport:
violations: List[str] = []
valid = 0
for idx, line in enumerate(lines, start=1):
if CONVENTIONAL_RE.match(line):
valid += 1
continue
violations.append(f"line {idx}: {line}")
return LintReport(total=len(lines), valid=valid, invalid=len(violations), violations=violations)
def format_text(report: LintReport) -> str:
lines = [
"Conventional commit lint report",
f"- total: {report.total}",
f"- valid: {report.valid}",
f"- invalid: {report.invalid}",
]
if report.violations:
lines.append("Violations:")
lines.extend([f"- {v}" for v in report.violations])
return "\n".join(lines)
def main() -> int:
args = parse_args()
lines = load_lines(args)
report = lint(lines)
if args.format == "json":
print(json.dumps(asdict(report), indent=2))
else:
print(format_text(report))
if args.strict and report.invalid > 0:
return 1
return 0
if __name__ == "__main__":
try:
raise SystemExit(main())
except CLIError as exc:
print(f"ERROR: {exc}", file=sys.stderr)
raise SystemExit(2)
FILE:scripts/generate_changelog.py
#!/usr/bin/env python3
"""Generate changelog entries from Conventional Commits.
Input sources (priority order):
1) --input file with one commit subject per line
2) stdin commit subjects
3) git log from --from-tag/--to-tag or --from-ref/--to-ref
Outputs markdown or JSON and can prepend into CHANGELOG.md.
"""
import argparse
import json
import re
import subprocess
import sys
from dataclasses import dataclass, asdict, field
from datetime import date
from pathlib import Path
from typing import Dict, List, Optional
COMMIT_RE = re.compile(
r"^(?P<type>feat|fix|perf|refactor|docs|test|build|ci|chore|security|deprecated|remove)"
r"(?:\((?P<scope>[^)]+)\))?(?P<breaking>!)?:\s+(?P<summary>.+)$"
)
SECTION_MAP = {
"feat": "Added",
"fix": "Fixed",
"perf": "Changed",
"refactor": "Changed",
"security": "Security",
"deprecated": "Deprecated",
"remove": "Removed",
}
class CLIError(Exception):
"""Raised for expected CLI failures."""
@dataclass
class ParsedCommit:
raw: str
ctype: str
scope: Optional[str]
summary: str
breaking: bool
@dataclass
class ChangelogEntry:
version: str
release_date: str
sections: Dict[str, List[str]] = field(default_factory=dict)
breaking_changes: List[str] = field(default_factory=list)
bump: str = "patch"
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Generate changelog from conventional commits.")
parser.add_argument("--input", help="Text file with one commit subject per line.")
parser.add_argument("--from-tag", help="Git tag start (exclusive).")
parser.add_argument("--to-tag", help="Git tag end (inclusive).")
parser.add_argument("--from-ref", help="Git ref start (exclusive).")
parser.add_argument("--to-ref", help="Git ref end (inclusive).")
parser.add_argument("--next-version", default="Unreleased", help="Version label for the generated entry.")
parser.add_argument("--date", dest="entry_date", default=str(date.today()), help="Release date (YYYY-MM-DD).")
parser.add_argument("--format", choices=["markdown", "json"], default="markdown", help="Output format.")
parser.add_argument("--write", help="Prepend generated markdown entry into this changelog file.")
return parser.parse_args()
def read_lines_from_file(path: str) -> List[str]:
try:
return [line.strip() for line in Path(path).read_text(encoding="utf-8").splitlines() if line.strip()]
except Exception as exc:
raise CLIError(f"Failed reading --input file: {exc}") from exc
def read_lines_from_stdin() -> List[str]:
if sys.stdin.isatty():
return []
payload = sys.stdin.read()
return [line.strip() for line in payload.splitlines() if line.strip()]
def read_lines_from_git(args: argparse.Namespace) -> List[str]:
if args.from_tag or args.to_tag:
if not args.to_tag:
raise CLIError("--to-tag is required when using tag range.")
start = args.from_tag
end = args.to_tag
elif args.from_ref or args.to_ref:
if not args.to_ref:
raise CLIError("--to-ref is required when using ref range.")
start = args.from_ref
end = args.to_ref
else:
return []
range_spec = f"{start}..{end}" if start else end
try:
proc = subprocess.run(
["git", "log", range_spec, "--pretty=format:%s", "--no-merges"],
text=True,
capture_output=True,
check=True,
)
except subprocess.CalledProcessError as exc:
raise CLIError(f"git log failed for range '{range_spec}': {exc.stderr.strip()}") from exc
return [line.strip() for line in proc.stdout.splitlines() if line.strip()]
def load_commits(args: argparse.Namespace) -> List[str]:
if args.input:
return read_lines_from_file(args.input)
stdin_lines = read_lines_from_stdin()
if stdin_lines:
return stdin_lines
git_lines = read_lines_from_git(args)
if git_lines:
return git_lines
raise CLIError("No commit input found. Use --input, stdin, or git range flags.")
def parse_commits(lines: List[str]) -> List[ParsedCommit]:
parsed: List[ParsedCommit] = []
for line in lines:
match = COMMIT_RE.match(line)
if not match:
continue
ctype = match.group("type")
scope = match.group("scope")
summary = match.group("summary")
breaking = bool(match.group("breaking")) or "BREAKING CHANGE" in line
parsed.append(ParsedCommit(raw=line, ctype=ctype, scope=scope, summary=summary, breaking=breaking))
return parsed
def determine_bump(commits: List[ParsedCommit]) -> str:
if any(c.breaking for c in commits):
return "major"
if any(c.ctype == "feat" for c in commits):
return "minor"
return "patch"
def build_entry(commits: List[ParsedCommit], version: str, entry_date: str) -> ChangelogEntry:
sections: Dict[str, List[str]] = {
"Security": [],
"Added": [],
"Changed": [],
"Deprecated": [],
"Removed": [],
"Fixed": [],
}
breaking_changes: List[str] = []
for commit in commits:
if commit.breaking:
breaking_changes.append(commit.summary)
section = SECTION_MAP.get(commit.ctype)
if section:
line = commit.summary if not commit.scope else f"{commit.scope}: {commit.summary}"
sections[section].append(line)
sections = {k: v for k, v in sections.items() if v}
return ChangelogEntry(
version=version,
release_date=entry_date,
sections=sections,
breaking_changes=breaking_changes,
bump=determine_bump(commits),
)
def render_markdown(entry: ChangelogEntry) -> str:
lines = [f"## [{entry.version}] - {entry.release_date}", ""]
if entry.breaking_changes:
lines.append("### Breaking")
lines.extend([f"- {item}" for item in entry.breaking_changes])
lines.append("")
ordered_sections = ["Security", "Added", "Changed", "Deprecated", "Removed", "Fixed"]
for section in ordered_sections:
items = entry.sections.get(section, [])
if not items:
continue
lines.append(f"### {section}")
lines.extend([f"- {item}" for item in items])
lines.append("")
lines.append(f"<!-- recommended-semver-bump: {entry.bump} -->")
return "\n".join(lines).strip() + "\n"
def prepend_changelog(path: Path, entry_md: str) -> None:
if path.exists():
original = path.read_text(encoding="utf-8")
else:
original = "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\n"
if original.startswith("# Changelog"):
first_break = original.find("\n")
head = original[: first_break + 1]
tail = original[first_break + 1 :].lstrip("\n")
combined = f"{head}\n{entry_md}\n{tail}"
else:
combined = f"# Changelog\n\n{entry_md}\n{original}"
path.write_text(combined, encoding="utf-8")
def main() -> int:
args = parse_args()
lines = load_commits(args)
parsed = parse_commits(lines)
if not parsed:
raise CLIError("No valid conventional commit messages found in input.")
entry = build_entry(parsed, args.next_version, args.entry_date)
if args.format == "json":
print(json.dumps(asdict(entry), indent=2))
else:
markdown = render_markdown(entry)
print(markdown, end="")
if args.write:
prepend_changelog(Path(args.write), markdown)
if args.format == "json" and args.write:
prepend_changelog(Path(args.write), render_markdown(entry))
return 0
if __name__ == "__main__":
try:
raise SystemExit(main())
except CLIError as exc:
print(f"ERROR: {exc}", file=sys.stderr)
raise SystemExit(2)
Trình hướng dẫn tương tác để thiết kế và xác thực một thử nghiệm chaos engineering.
--- description: Interactive wizard to design and validate a chaos engineering experiment --- # /chaos-experiment Step through the design of a chaos engineering experiment using the `chaos-engineering` skill. Produces a plan, calculates blast radius, validates abort criteria, and outputs a markdown plan ready for peer review. ## Usage ``` /chaos-experiment /chaos-experiment --target checkout-svc --attack latency ``` ## Implementation ```bash SKILL=engineering/chaos-engineering/skills/chaos-engineering # Step 1: gather inputs interactively (target, hypothesis, attack, magnitude, ...) # Step 2: run experiment_designer.py to produce the plan python "$SKILL/scripts/experiment_designer.py" \ --target "$TARGET" --hypothesis "$HYPOTHESIS" \ --attack "$ATTACK" --magnitude "$MAGNITUDE" \ --duration-min "$DURATION" \ --abort-if "$ABORT" --owner "$OWNER" \ --format json > .chaos-plan.json # Step 3: calculate blast radius against the team's error budget python "$SKILL/scripts/blast_radius_calculator.py" \ --traffic-share "$TRAFFIC_SHARE" \ --user-pop "$USER_POP" \ --duration-min "$DURATION" \ --baseline-availability "$BASELINE_AVAIL" \ --expected-impact-availability "$IMPACT_AVAIL" # Step 4: render the markdown plan for peer review python "$SKILL/scripts/experiment_designer.py" \ --target "$TARGET" --hypothesis "$HYPOTHESIS" \ --attack "$ATTACK" --abort-if "$ABORT" --owner "$OWNER" ``` ## Output A markdown plan with: - Hypothesis, steady-state metric, attack, magnitude, duration - Blast radius (calculated) with risk score (GREEN/YELLOW/RED) - Abort criteria parsed from `--abort-if` - Rollback procedure - Monitoring dashboard link - Learning question ## Pre-conditions - `chaos-engineering` skill installed - Target identified - Steady-state metric and dashboard available - On-call team available - Error budget known (or use defaults) ## Post-conditions - `.chaos-plan.json` written for use with `experiment_postmortem.py` later - Markdown plan streamed for review - Recommendation printed: PROCEED / REDUCE / ABORT
Tạo đặc tả Use Case bằng Markdown theo mẫu 13 trường chuẩn của BA IT, dùng khi cần phân tích, soạn thảo hoặc rà soát Use Case.
---
name: use-case-writer
description: Generate Use Case specifications in English Markdown following the IT BA standard 13-field template (Karl Wiegers / IIBA). Use whenever a BA needs to scope, analyze, document, refine, or review a Use Case. Triggers include "write a use case", "draft UC", "use case specification", "analyze UC scope", "split feature into use cases", "review my UC", "write normal course / alternative course / exceptions", "define actors", and Vietnamese equivalents like "viết use case", "viết UC", "đặc tả use case", "phân tích use case", "review UC". Also trigger when user pastes a feature/BRD/PRD and asks to turn it into UCs. Skill enforces Cockburn's guidelines (coffee-break test, goal levels) and runs a 20-point quality checklist. Output is English Markdown with 13 fields (Actor, Description, Pre/Postconditions, Priority, Frequency, Normal/Alternative Courses, Exceptions, Includes, Special Req, Assumptions, Notes). DO NOT use for Agile User Stories, PRD/URD/SRS, or UML diagrams.
author: Phúc NT @ BA Zone
source: https://github.com/phucnt-bazone-vietnam/use-case-writer
---
# Use Case Writer — Skill for IT Business Analysts
> by **Phúc NT** · BA Zone · Digital School
This skill helps IT BAs **scope, analyze, and document Use Cases** in English Markdown following the standard 13-field template (Karl Wiegers / IIBA style), with best practices from Alistair Cockburn's "Writing Effective Use Cases" and the IIBA BABOK Guide.
Developed by **Phúc NT** as part of the **Digital School** training program by **BA Zone** — Vietnam's Business Analyst & Product Owner community.
## When to use this skill
Trigger this skill whenever the user needs to:
- Draft a new UC from a feature description, BRD, or PRD
- Refine or review an existing UC (completeness, correctness)
- Split a large feature into multiple smaller UCs (scope identification)
- Write a specific section: Normal Course, Alternative Course, Exceptions
- Validate a UC against the quality checklist
## Output rules (non-negotiable)
1. **Language**: English. Even if the user types in Vietnamese, generate the UC document in English. Use Vietnamese only when chatting with the user about the process.
2. **Format**: Markdown (`.md`). Use the 2-column table layout that mirrors the original template.
3. **Mode**: Sequential — generate section by section, **stop and wait for the user to confirm** before moving on. Never dump a full UC in one shot unless the user explicitly says "give me the full UC at once".
---
## Workflow: 4 Steps
```
Step 1: CLASSIFY INPUT → identify which mode the user is in
Step 2: SCOPE THE UC → apply 4 scoping rules + coffee-break test
Step 3: WRITE THE UC → fill the 13 fields ONE SECTION AT A TIME
Step 4: VALIDATE → run the 20-point checklist before handover
```
---
## Step 1: Classify input and pick a mode
Before writing anything, identify which mode the user is in:
| Mode | Signals | Action |
|------|---------|--------|
| **Mode A: Write new from feature** | User pastes a feature description, BRD, PRD, or says "write UC for feature X" | Go to Step 2 (scope) → Step 3 (write sequentially) |
| **Mode B: Split large feature into UC list** | User says "split into UC list", "how many UCs does this feature need", uploads a large PRD | Go deep on Step 2 (apply 3 identification techniques), output the **UC List first**, then ask the user which UC to write in detail |
| **Mode C: Refine / review existing UC** | User pastes an existing UC and asks "review this", "is it complete", "what's missing" | Skip Step 2, go directly to Step 4 (validate checklist) |
| **Mode D: Write a specific section** | User says "write the Normal Course for this UC", "add Exceptions" | Read the UC context, jump to the relevant part of Step 3 |
**Golden rule**: If input is vague (just one line), **ASK before writing** — never make things up. Ask at most 3 questions:
1. Who is the primary actor? (specific role / user class)
2. What is the actor's concrete goal in this UC?
3. Which system / module does this UC belong to?
Communicate with the user in their language (Vietnamese or English), but the UC artifact is always English.
---
## Step 2: Scope the Use Case
This is the **most important and most error-prone** part of UC writing. Read carefully.
### 2.1. Four scoping rules
**Rule 1 - Coffee-break test (Alistair Cockburn)**
After completing the UC, can the actor take a coffee break without feeling the task is unfinished? If NO → the UC is too low-level (sub-function), merge it. If YES → scope is right (user-goal level).
**Rule 2 - Goal Level (Cockburn's 3 levels)**
- **Summary level (cloud)**: UC spans multiple sessions. E.g. "Manage course enrollment lifecycle" → too high, DO NOT write as a single UC.
- **User-goal level (sea level)** ✅: 1 actor, 1 session, achieves 1 business goal. E.g. "Enroll in a Digital School course" → right level for a UC.
- **Sub-function level (fish)**: A small step inside another UC. E.g. "Verify OTP" → too low, treat as Includes inside another UC.
**Rule 3 - One Actor, One Goal, One Session**
Each UC should have EXACTLY: 1 primary actor + 1 business goal + completion in 1 continuous session. If you see 2 different goals → split into 2 UCs.
**Rule 4 - System Boundary**
A UC describes the **interaction** between actor and system, NOT the system's internals. Each step must be one of:
- Actor does something to the system (input)
- System responds to the actor (output)
If a step has neither actor nor UI → it's a design detail, not part of the UC.
### 2.2. Three techniques to identify UCs (for Mode B)
When splitting a large feature into a UC list:
**Technique 1: Goal-driven (top-down)**
List all goals for each actor → each goal = 1 candidate UC.
**Technique 2: Event-driven (external + internal triggers)**
- External events: user actions (click, submit, scheduled time)
- Internal events: system-triggered (cron job, batch process)
Each event produces a system response → candidate UC.
**Technique 3: CRUD-driven (data-centric)**
For each business entity (Learner, Course, Enrollment, Certificate…), check whether the system needs Create / Read / Update / Delete. Each = 1 candidate UC (you can merge R-U-D for the same entity if logic is similar).
### 2.3. Output of Step 2
**Mode A**: One sentence confirming scope, then ASK USER TO CONFIRM before moving to Step 3:
> "Scope confirmed: this UC is at user-goal level. Primary actor: [X]. Goal: [Y]. System boundary: [Z]. Confirm to proceed to Step 3?"
**Mode B**: A UC List table:
```
| UC ID | UC Name (verb + noun) | Primary Actor | Goal | Priority |
| UC-01 | Enroll in Digital School course | Learner | ... | High |
| UC-02 | Book 1-on-1 mentor session | Learner | ... | High |
| UC-03 | Approve learner KYC application | BO Approver | ... | Medium |
```
Then ask: "Which UC do you want me to write in detail first?"
---
## Step 3: Write the Use Case — section by section
**CRITICAL**: Generate ONE SECTION GROUP at a time, then **STOP and ask the user to confirm** before continuing. Do not dump the whole UC at once.
Read `references/template-guide.md` for detailed guidance on filling each field.
Read `references/writing-style.md` for writing conventions (active voice, numbering, anti-patterns).
### 3.1. The template (output structure)
```markdown
| **Use Case ID:** | UC-XX-YY |
| **Use Case Name:** | [Action verb + noun] |
| **Created By:** | | **Last Updated By:** | |
| **Date Created:** | | **Date Last Updated:** | |
| **Actor:** | [Primary actor] / [Secondary actors] |
| **Description:** | [2-3 sentences: why + what + outcome] |
| **Preconditions:** | 1. ... 2. ... |
| **Postconditions:** | 1. ... 2. ... |
| **Priority:** | High / Medium / Low |
| **Frequency of Use:** | [X times / unit time] |
| **Normal Course of Events:** | 1. Actor... 2. System... 3. ... |
| **Alternative Courses:**| UC-XX-YY.AC.1: [name] |
| **Exceptions:** | UC-XX-YY.EX.1: [name] |
| **Includes:** | UC-AA-BB |
| **Special Requirements:**| [Non-functional: perf, security…] |
| **Assumptions:** | 1. ... |
| **Notes and Issues:** | TBD-1: [open question] / Owner / Due |
```
Copy-ready template lives in `assets/uc-template.md`.
### 3.2. Sequential generation — the 5 section groups
Generate **in this exact order**, pause and ask confirmation after each group:
> **Group 1 — Identification + Actor + Description**
> Output: Use Case ID, Name, History (Created By / Date), Actor, Description.
> Then say: *"Group 1 done. Confirm to proceed to preconditions, postconditions, priority, frequency?"*
> **Group 2 — Conditions + Priority + Frequency**
> Output: Preconditions, Postconditions, Priority, Frequency of Use.
> Then say: *"Group 2 done. Confirm to proceed to the Normal Course?"*
> **Group 3 — Normal Course of Events**
> Output: numbered, step-by-step happy path.
> Then say: *"Normal Course done. Confirm to proceed to Alternative Courses and Exceptions?"*
> **Group 4 — Alternative Courses + Exceptions**
> Output: AC.1, AC.2…, EX.1, EX.2…
> Then say: *"Group 4 done. Confirm to proceed to the final group (Includes, Special Req, Assumptions, Notes)?"*
> **Group 5 — Includes + Special Requirements + Assumptions + Notes and Issues**
> Output: the remaining fields.
> Then say: *"All sections done. Shall I run the 20-point quality validation now?"*
**If the user requests changes** to a previous group, apply them and reconfirm before continuing.
**If the user says "skip ahead"** or "give me everything at once", honor that — but warn briefly that sequential mode catches more issues.
### 3.3. CRITICAL field-filling rules (the most common mistakes)
**Use Case ID**: Format `UC-<module>-<seq>`, e.g. `UC-LEARN-01`. Hierarchical X.Y if you have UC groups.
**Use Case Name**: MUST be "**Verb + Object**" (active voice).
- ✅ "Enroll in Digital School course", "Book mentor session", "Issue course completion certificate"
- ❌ "Enrollment" (no verb), "Learner enrolls" (actor included), "Manage courses" (vague verb)
**Actor**: Distinguish:
- *Primary actor*: initiates the UC, benefits from the outcome
- *Secondary actor*: supporting system/person (payment gateway, OTP service, LMS)
Never write "User" — be specific (Learner, Mentor, BO Admin, HR Manager, Enterprise Partner…).
**Preconditions**: Conditions that **MUST be true** before the UC starts. Distinguish from business rules!
- ✅ "Learner has logged in and has an active Digital School subscription"
- ❌ "Learner is motivated to study" (motivation — not verifiable)
**Postconditions**: System state **AFTER** successful UC completion. Must be verifiable.
- ✅ "Enrollment record saved with status='Active'; learner gains access to all course materials"
- ❌ "Learner feels satisfied" (not verifiable)
**Normal Course of Events** (most important):
- Numbered list, one action per step
- Alternate Actor / System steps (subject must be explicit)
- Each step starts with a clear subject + active verb
- **NO embedded if/else, loops, or exceptions** — those go in Alternative/Exception sections
- Storytelling style: from trigger to goal achieved
- ✅ "1. Learner selects the course on the Digital School catalog. 2. System displays course details and enrollment options. 3. Learner clicks 'Enroll Now'."
- ❌ "1. If learner has voucher, enter code; otherwise proceed to payment…" (branching embedded)
**Alternative Courses**: Different paths that **still lead to success**. E.g. paying with enterprise voucher instead of personal wallet. Format `UC-XX.AC.N` + "At step Y of Normal Course, if [condition], execute the alternative: …"
**Exceptions**: Cases where **the goal fails** (errors, validation fails, timeouts). Format `UC-XX.EX.N`. Each exception needs: trigger condition + system response + final state.
**Includes**: List of sub-UCs "called" by this UC (common functionality). E.g. UC "Enroll in course" includes UC "Process payment".
**Special Requirements**: Non-functional requirements specific to this UC:
- Performance: "Course catalog page loads ≤ 2s for 5,000 concurrent learners"
- Security: "Payment data must be encrypted in transit (TLS 1.3)"
- Usability, Reliability, Compliance…
**Assumptions**: Things assumed during analysis. Different from Preconditions — a precondition is a hard requirement; an assumption is a belief that hasn't been verified.
**Notes and Issues**: List of TBDs with format `[TBD-N] | Owner | Due date | Resolution`.
---
## Step 4: Validate against the 20-point checklist
**ALWAYS run this checklist BEFORE handing over the UC.** If any item fails, fix it or flag it to the user.
Read `references/quality-checklist.md` for the full checklist with examples. The 20 items, grouped:
### Scope & Identification (5 items)
- [ ] **C1**: UC Name follows "verb + object", active voice
- [ ] **C2**: UC is at user-goal level (passes coffee-break test)
- [ ] **C3**: UC ID is unique and follows naming convention
- [ ] **C4**: Exactly 1 primary actor + 1 clear business goal
- [ ] **C5**: System boundary is clear (not mixed with other UCs)
### Actor & Context (3 items)
- [ ] **C6**: Actor is a specific role/class, not "User"
- [ ] **C7**: Description answers WHY (reason) + WHAT (action) + OUTCOME (result)
- [ ] **C8**: Frequency of Use is quantified (not "sometimes")
### Pre/Post Conditions (3 items)
- [ ] **C9**: Preconditions are verifiable (not disguised business rules)
- [ ] **C10**: Postconditions cover the success state and all system changes
- [ ] **C11**: Preconditions are not confused with Assumptions
### Normal Course (4 items)
- [ ] **C12**: Numbered list, one action per step
- [ ] **C13**: Alternates Actor / System with clear subjects
- [ ] **C14**: NO embedded if/else/loop in the Normal Course
- [ ] **C15**: Flow runs from trigger to postcondition (no dangling step)
### Alternative & Exception (3 items)
- [ ] **C16**: Each AC specifies "at step N" + condition
- [ ] **C17**: Each Exception has trigger + system response + final state
- [ ] **C18**: Common failure modes are covered (timeout, invalid input, network, permission denied, concurrency conflict)
### Completeness (2 items)
- [ ] **C19**: Includes (if any) point to existing UCs
- [ ] **C20**: Special Requirements don't duplicate functional requirements
**Validation output**: A `Item | Status | Note` table with ✅ ❌ ⚠️ markers.
---
## Output format details
- Always produce English Markdown
- Use the 2-column table layout that matches the original template
- Save final output as a `.md` file if the user wants a downloadable file; otherwise show inline in chat
- File naming convention: `<UC-ID>_<UC-Name-kebab>.md`, e.g. `UC-LEARN-01_enroll-digital-school-course.md`
---
## References
- `references/template-guide.md` - Detailed guidance for each field (with EdTech & Digital School examples)
- `references/writing-style.md` - Writing conventions (active voice, numbering, anti-patterns)
- `references/quality-checklist.md` - 20-point checklist with pass/fail examples
- `references/examples-edtech.md` - 2 complete EdTech UC examples (Course Enrollment, Mentor Session Approval)
- `assets/uc-template.md` - Copy-ready Markdown template
- `scripts/uc_quality_checker.py` - stdlib Python tool that runs the 20-point checklist (Step 4) as deterministic heuristic checks against a finished UC Markdown file. Repo-convention addition, not part of the upstream skill; use it as a mechanical first pass before the LLM-driven review.
---
## Anti-patterns (ABSOLUTELY avoid)
1. **UC = UI flow**: Describing every button click and popup → that's a wireframe spec, not a UC
2. **UC = User Story**: A UC describes detailed interactions; a US is a one-liner "As a… I want… So that…"
3. **UC = Business Process**: A BP covers an entire business process (many people, many systems); a UC covers 1 actor + 1 system
4. **Vague verbs in UC Name**: "Manage", "Handle", "Process" — too generic. Use specific action verbs
5. **Mixing concerns**: Cramming enrollment, payment, notification into one giant UC → split using Includes
6. **Forgetting exceptions**: Writing only the happy path with no failure modes → insufficient for dev/QA
7. **Vague preconditions**: "System is ready" → meaningless. Must be verifiable
---
*Skill developed by **Phúc NT** · BA Zone · Digital School*
*Please keep attribution intact when sharing or forking this repo.*
FILE:assets/uc-template.md
# Use Case Template — Copy-Ready Markdown
Copy the template below and replace the `<...>` placeholders with actual content.
> Template by **Phúc NT** · BA Zone · Digital School
---
## UC-XX-YY: \<Use Case Name\>
| **Use Case ID:** | UC-XX-YY |
| ---: | :--- |
| **Use Case Name:** | \<Action verb + Object\> |
| **Created By:** | \<Name - Role\> | **Last Updated By:** | \<Name - Role\> |
| **Date Created:** | YYYY-MM-DD | **Date Last Updated:** | YYYY-MM-DD |
| **Actor:** | **Primary:** \<specific role, e.g. Learner / Mentor / HR Manager\>. **Secondary:** \<supporting actors, e.g. Payment Gateway, LMS, Notification Service\>. |
| ---: | :--- |
| **Description:** | \<2-3 sentences: WHY + WHAT + OUTCOME\> |
| **Preconditions:** | 1. \<condition 1, verifiable\><br>2. \<condition 2\><br>3. \<...\> |
| **Postconditions:** | 1. \<state 1 after UC completes\><br>2. \<state 2\><br>3. \<...\> |
| **Priority:** | High / Medium / Low - \<brief justification\> |
| **Frequency of Use:** | \<count / unit time\>, peak: \<...\> |
| **Normal Course of Events:** | 1. \<Actor action\><br>2. \<System response\><br>3. \<...\><br>... |
| **Alternative Courses:** | **UC-XX-YY.AC.1: \<AC name\>**<br>At step \<N\>, if \<condition\>:<br>Na. \<step\><br>Nb. \<step\><br>... → continue from step \<M\> of the Normal Course. |
| **Exceptions:** | **UC-XX-YY.EX.1: \<exception name\>**<br>Trigger: \<when it happens\><br>Response: \<what the system does\><br>Final state: \<the end state\> |
| **Includes:** | UC-AA-BB: \<sub-UC name\> (invoked at step \<N\>) |
| **Special Requirements:** | **Performance**: \<...\><br>**Security**: \<...\><br>**Reliability**: \<...\><br>**Compliance**: \<...\> |
| **Assumptions:** | 1. \<assumption 1\><br>2. \<assumption 2\> |
| **Notes and Issues:** | [TBD-1] \<question\> \| Owner: \<...\> \| Due: \<...\> \| Resolution: \<...\> |
---
*Template by **Phúc NT** · BA Zone · Digital School*
*Please keep attribution intact when distributing this template.*
FILE:references/examples-edtech.md
# Examples — 2 Complete EdTech Use Cases
Use these as reference when writing UCs. Both examples have passed the full 20-point checklist.
Compiled by **Phúc NT** for the **Digital School** program · **BA Zone**
---
## Example 1: UC-LEARN-01 — Enroll in a Digital School course
| **Use Case ID:** | UC-LEARN-01 |
| ---: | :--- |
| **Use Case Name:** | Enroll in a Digital School course |
| **Created By:** | Phúc NT - BA Zone | **Last Updated By:** | Phúc NT - BA Zone |
| **Date Created:** | 2026-05-14 | **Date Last Updated:** | 2026-05-14 |
| **Actor:** | **Primary:** Learner (registered BA Zone account, email verified). **Secondary:** Payment Gateway, Notification Service, LMS (Learning Management System). |
| ---: | :--- |
| **Description:** | When a learner discovers a course on the Digital School catalog and decides to purchase it, the learner navigates to the course page and completes enrollment via the BA Zone platform. The UC ends when the payment is confirmed, the learner's account is granted access to all course materials, and a welcome notification is sent. |
| **Preconditions:** | 1. Learner has logged in to BA Zone with a verified email address.<br>2. The selected course status is 'Published' and still has available capacity.<br>3. Learner has not previously enrolled in this course (no duplicate enrollment).<br>4. Payment Gateway is available and responsive. |
| **Postconditions:** | 1. Enrollment record is created in the Enrollments table with status='Active' and the enrollment timestamp.<br>2. Learner's account is granted read access to all published lessons of the course.<br>3. Payment transaction is recorded with status='Completed' and a unique transaction reference.<br>4. Welcome email + in-app notification is sent to the learner within 60 seconds.<br>5. Course enrollment count is incremented by 1. |
| **Priority:** | High — Core revenue-generating feature; blocks Digital School MVP go-live. |
| **Frequency of Use:** | Estimated ~500 enrollments/day platform-wide; peak ~100/hour during promotional campaigns and new course launches. |
| **Normal Course of Events:** | 1. Learner browses the Digital School course catalog and clicks on a course card.<br>2. System displays the **Course Detail** page: syllabus, instructor profile, price, and an **Enroll Now** button.<br>3. Learner clicks **Enroll Now**.<br>4. System displays the **Order Summary** screen: course name, original price, applicable discount (if any), and total amount due.<br>5. Learner selects a payment method (wallet, card, or enterprise voucher) and clicks **Proceed to Payment**.<br>6. System invokes UC-PAY-01 to process the payment.<br>7. Upon successful payment, system creates the enrollment record in the LMS and grants course access.<br>8. System displays the **Enrollment Confirmed** screen with a **Go to Course** button and a download link for the receipt.<br>9. System invokes UC-NOTI-01 to send the welcome email and push notification to the learner. |
| **Alternative Courses:** | **UC-LEARN-01.AC.1: Enroll using an enterprise voucher code**<br>At step 5 of the Normal Course, if the learner selects **Enterprise Voucher** as the payment method:<br>5a. System displays a voucher code input field.<br>5b. Learner enters the voucher code and clicks **Apply**.<br>5c. System validates the voucher against the Enterprise_Vouchers table: checks expiry date, course applicability, and remaining usage count.<br>5d. System updates the total amount to 0 VND and displays "Voucher applied — fully covered".<br>5e. Learner clicks **Confirm Enrollment** → continue from step 7 of the Normal Course (no payment gateway call needed).<br><br>**UC-LEARN-01.AC.2: Re-enroll after course expiry**<br>At step 3 of the Normal Course, if the learner had a previous expired enrollment (status='Expired') in this course:<br>3a. System detects the expired enrollment and displays a banner: "You previously enrolled in this course. Your access expired on [date]. Re-enroll to regain access?"<br>3b. Learner clicks **Re-enroll** → continue from step 4 of the Normal Course. The original enrollment record is archived; a new enrollment record is created. |
| **Exceptions:** | **UC-LEARN-01.EX.1: Payment fails at gateway**<br>Trigger: At step 6, UC-PAY-01 returns a payment failure (insufficient funds, card declined, gateway timeout).<br>Response: System displays "Payment unsuccessful. Please check your payment details and try again." with a **Try Again** button and a **Choose Different Method** link.<br>Final state: No enrollment record is created. Learner's course access remains unchanged. The failed payment attempt is logged in Payment_Logs with reason code from the gateway.<br><br>**UC-LEARN-01.EX.2: Course reaches full capacity between page load and enrollment confirmation**<br>Trigger: At step 7, LMS returns a CAPACITY_EXCEEDED error because another learner filled the last slot milliseconds earlier.<br>Response: System displays "Sorry, this course just reached full capacity. Join the waitlist to be notified when a slot opens."<br>Final state: Payment is refunded automatically within 1 business day. No enrollment record is created. Learner is offered the option to join the waitlist.<br><br>**UC-LEARN-01.EX.3: Voucher code invalid or expired**<br>Trigger: At step 5c (AC.1), the voucher code does not exist, has expired, or has been fully redeemed.<br>Response: System displays an inline error: "Voucher code is invalid or has expired. Please check with your enterprise administrator."<br>Final state: No enrollment is created. Learner can retry with a different code or choose another payment method.<br><br>**UC-LEARN-01.EX.4: LMS unavailable during enrollment**<br>Trigger: At step 7, the LMS does not respond within 10 seconds when the system attempts to create the enrollment record.<br>Response: System displays "Your payment was successful. We are setting up your course access — this may take a few minutes. Check your email for confirmation."<br>Final state: Payment is recorded as 'Completed'. A background retry job attempts to create the LMS enrollment every 2 minutes for up to 30 minutes. Learner receives email once access is confirmed. If the retry fails after 30 minutes, the support team is alerted via an automated ticket. |
| **Includes:** | UC-PAY-01: Process payment (invoked at step 6 of the Normal Course)<br>UC-NOTI-01: Send enrollment notification (invoked at step 9 of the Normal Course) |
| **Special Requirements:** | **Performance**: Course Detail page loads ≤ 2s; Order Summary and Enrollment Confirmed screens load ≤ 1s. System supports 500 concurrent enrollments without degradation.<br>**Security**: Payment card data must never be stored on BA Zone servers; all card processing is delegated to the PCI-DSS certified Payment Gateway. Voucher codes must be validated server-side only — never expose remaining usage counts to the client.<br>**Reliability**: If the LMS is unavailable at step 7, payment must NOT be rolled back — the enrollment must be retried asynchronously. No learner should pay and receive no access without a support escalation path.<br>**Usability**: The enrollment flow must be completable in ≤ 3 taps on mobile (post-payment-method selection).<br>**Compliance**: Issue a VAT invoice for all transactions ≥ 200,000 VND per Vietnamese tax regulations. |
| **Assumptions:** | 1. Payment Gateway SLA is ≥ 99.5% uptime during business hours.<br>2. Enterprise vouchers are pre-loaded into the system by BA Zone's ops team before being distributed to partners.<br>3. Learner's email is verified — welcome emails will not bounce.<br>4. LMS access provisioning is synchronous under normal conditions (< 3s). |
| **Notes and Issues:** | [TBD-1] Should learners be able to gift a course enrollment to another BA Zone user? \| Owner: Product Team \| Due: 2026-06-01 \| Resolution: TBD — deferred to phase 2.<br>[TBD-2] What is the refund policy if a learner requests a refund within 7 days of enrollment? \| Owner: Phúc NT - BA Zone \| Due: 2026-05-25 \| Resolution: TBD.<br>[NOTE] Coordinate with the Marketing team on the welcome email template — must align with the current Digital School brand guidelines. |
---
## Example 2: UC-MENTOR-03 — Approve learner 1-on-1 mentor session request
| **Use Case ID:** | UC-MENTOR-03 |
| ---: | :--- |
| **Use Case Name:** | Approve learner 1-on-1 mentor session request |
| **Created By:** | Phúc NT - BA Zone | **Last Updated By:** | Phúc NT - BA Zone |
| **Date Created:** | 2026-05-14 | **Date Last Updated:** | 2026-05-14 |
| **Actor:** | **Primary:** Mentor (BA Zone certified mentor, active mentor account). **Secondary:** Notification Service, Calendar Integration Service. |
| ---: | :--- |
| **Description:** | When a learner submits a 1-on-1 session request through the Digital School platform, the request enters the assigned mentor's queue. The mentor accesses their Mentor Portal, reviews the request details, and makes a decision: Approve, Decline, or Propose an Alternative Slot. The UC ends when the request has a final status and the learner is notified of the outcome. |
| **Preconditions:** | 1. Mentor has logged in to the BA Zone Mentor Portal with an active mentor account.<br>2. At least one session request is in the mentor's queue with status='Pending_Mentor_Review'.<br>3. The requested time slot is within the mentor's published availability window.<br>4. Learner still has at least 1 unused session quota in their current subscription. |
| **Postconditions:** | 1. Session request has a final status: 'Confirmed' / 'Declined' / 'Rescheduled'.<br>2. A decision record is saved in the Session_Decisions table with: mentor_id, timestamp, decision, reason.<br>3. If Approved: a calendar invite (Google Calendar / Outlook) is sent to both parties; session slot is blocked in the mentor's availability calendar.<br>4. Learner's session quota is decremented by 1 if the decision is 'Confirmed'.<br>5. Push notification + email is sent to the learner with the outcome within 2 minutes of the mentor's decision. |
| **Priority:** | High — Core differentiator of the Digital School Premium subscription. Directly affects learner retention. |
| **Frequency of Use:** | ~15 session requests/day per mentor. Platform-wide ~300 requests/day across 20 active mentors. Peak on Sunday evenings (learners planning their week). |
| **Normal Course of Events:** | 1. Mentor selects the **Session Requests** tab in the BA Zone Mentor Portal.<br>2. System displays the pending request queue, sorted by request submission time (oldest first), showing: learner name, requested topic, preferred date/time, and subscription tier.<br>3. Mentor clicks a request to open the **Request Detail** view.<br>4. System displays full details: learner profile, course progress summary, submitted topic/question, preferred slot, and the learner's session history with this mentor (if any).<br>5. Mentor reviews the details and clicks one of three action buttons: **Approve** / **Decline** / **Propose Alternative Slot**.<br>6. System displays a confirmation popup requesting an optional note to the learner.<br>7. Mentor enters an optional note and clicks **Confirm Decision**.<br>8. System saves the decision to Session_Decisions, updates the request status, and performs the corresponding action (book slot / release slot / send alternative options).<br>9. System invokes UC-NOTI-02 to send the decision notification to the learner.<br>10. System displays a toast "Session request #[ID] processed" and returns the mentor to the queue. |
| **Alternative Courses:** | **UC-MENTOR-03.AC.1: Propose an alternative time slot**<br>At step 5 of the Normal Course, if the mentor is unavailable at the learner's preferred time but is willing to meet:<br>5a. Mentor clicks **Propose Alternative Slot**.<br>5b. System displays the mentor's availability calendar for the next 14 days.<br>5c. Mentor selects 1-3 alternative slots and clicks **Send Proposal**.<br>5d. System updates the request status to 'Awaiting_Learner_Confirmation' and sends the alternative slots to the learner.<br>5e. UC ends here for the mentor. Learner will confirm or decline the alternatives in a separate UC (UC-MENTOR-04).<br><br>**UC-MENTOR-03.AC.2: Approve with a pre-session resource attachment**<br>At step 7 of the Normal Course, if the mentor wants to share a preparation resource before the session:<br>7a. Mentor clicks **Attach Resource** and uploads a PDF or pastes a URL (max 10MB / 500 chars).<br>7b. System validates the attachment format and size.<br>7c. Mentor clicks **Confirm Decision** → continue from step 8 of the Normal Course.<br>7d. The attached resource is included in the learner's notification email. |
| **Exceptions:** | **UC-MENTOR-03.EX.1: Duplicate decision — concurrency conflict**<br>Trigger: At step 8, system detects that the request has already been processed (status changed) since the mentor opened the detail view — e.g. a system auto-cancellation or an admin override occurred concurrently.<br>Response: System displays "This session request was already processed or cancelled. Please refresh the queue."<br>Final state: The mentor's current decision attempt is discarded. The request retains its already-updated status. The concurrency event is logged in the audit trail.<br><br>**UC-MENTOR-03.EX.2: Session expired before mentor reviews**<br>Trigger: At step 3, the system detects that the learner's preferred slot is now in the past (request sat in the queue too long due to mentor inactivity).<br>Response: System displays a warning banner: "The learner's preferred slot has passed. You may still Decline or Propose a new slot — the Approve option is disabled."<br>Final state: Approve button is disabled. Mentor can only Decline or Propose Alternative Slot.<br><br>**UC-MENTOR-03.EX.3: Calendar Integration Service unavailable**<br>Trigger: At step 8, when the system attempts to create the calendar invite, the Calendar Integration Service times out or returns an error.<br>Response: System saves the decision as 'Confirmed' and displays a warning: "Session confirmed, but calendar invite could not be sent automatically. Please share the meeting link manually with the learner."<br>Final state: Session status is 'Confirmed'. Learner is notified of approval but without a calendar invite. A retry job attempts to send the calendar invite every 5 minutes for 30 minutes; if it fails, a support ticket is created.<br><br>**UC-MENTOR-03.EX.4: Learner's session quota already exhausted**<br>Trigger: At step 8, system checks and finds the learner's session quota = 0 (quota was consumed by another session between the request submission and the mentor's approval).<br>Response: System prevents the approval and displays: "This learner's session quota has been exhausted. Approve action is blocked. Please Decline and inform the learner to upgrade their subscription."<br>Final state: Request is auto-updated to status='Blocked_Quota_Exceeded'. Learner receives a notification explaining the situation and a link to upgrade. |
| **Includes:** | UC-NOTI-02: Send session decision notification (invoked at step 9 of the Normal Course) |
| **Special Requirements:** | **Performance**: Session request queue loads ≤ 2s with up to 50 pending requests. Request Detail view loads ≤ 1.5s.<br>**Security**: Mentor can only view and process requests assigned to their own queue (RBAC). Learner's personal data displayed in the mentor portal must comply with BA Zone's privacy policy.<br>**Audit**: Every mentor action (view, approve, decline, propose) must be logged with a second-level timestamp. Audit log is retained for 3 years.<br>**SLA**: 80% of session requests must receive a mentor decision within 24 hours of submission. Requests exceeding 48 hours without a decision trigger an automated escalation to the Mentor Success team. |
| **Assumptions:** | 1. Mentors have been onboarded and trained on using the BA Zone Mentor Portal.<br>2. Calendar Integration supports both Google Calendar and Microsoft Outlook (OAuth 2.0).<br>3. Session quota management is real-time — no batch reconciliation. |
| **Notes and Issues:** | [TBD-1] Should mentors be penalized (e.g. ranking score deduction) for declining more than 30% of session requests in a month? \| Owner: Phúc NT - BA Zone \| Due: 2026-06-10 \| Resolution: TBD.<br>[NOTE] Consider adding a mobile-optimized view for the Mentor Portal — several mentors have requested it for on-the-go approvals. Targeted for phase 2. |
---
## Lessons learned from the two examples
1. **UC-LEARN-01** illustrates a learner-facing enrollment UC with payment integration and async fallback handling when the LMS is unavailable.
2. **UC-MENTOR-03** illustrates a mentor-facing admin UC with concurrency, quota enforcement, and calendar integration edge cases.
3. **Both UCs share**:
- 1 primary actor + clear secondary actors
- Description covering WHY + WHAT + OUTCOME
- Verifiable preconditions (not motivations)
- Postconditions expressed as system state changes
- 9-10 step Normal Course alternating Actor/System
- 2 ACs + 3-4 Exceptions covering key failure modes
- Special Requirements that are non-functional only
- Notes with TBDs including owner + due date
4. **Patterns worth learning from these EdTech examples**:
- Async fallback pattern (EX.4 in UC-LEARN-01): payment succeeds but downstream system fails — never roll back payment, retry asynchronously
- Quota enforcement at decision time, not request time (EX.4 in UC-MENTOR-03)
- Concurrency conflict handling for multi-actor queues (EX.1 in UC-MENTOR-03)
- Voucher validation always server-side (security note in Special Requirements)
- Reference Business Rules by ID rather than inlining them in Normal Course steps
---
*Compiled by **Phúc NT** · BA Zone · Digital School*
*Please credit the source when sharing or adapting these examples.*
FILE:references/quality-checklist.md
# Quality Checklist — 20 Points to Validate a Use Case
Run this checklist BEFORE handing over a UC. Each item has: definition, how to check, pass/fail examples.
> Compiled by **Phúc NT** · BA Zone · Digital School
## How to use
1. After writing the UC, walk through items C1-C20
2. Mark Status: ✅ Pass / ❌ Fail / ⚠️ Needs review
3. If Fail → fix it or flag it to the user
4. Output a summary table at the end
```
| Item | Status | Note |
| C1 | ✅ | UC Name "Enroll in Digital School course" follows the format |
| C2 | ⚠️ | UC could be split further — confirm with PO |
| ... | ... | ... |
```
---
## GROUP A: Scope & Identification (C1-C5)
### C1. UC Name follows "verb + object", active voice
**Definition**: UC Name starts with an active verb + an object noun, with no actor name embedded.
**How to check**: Parse the UC Name → identify the leading verb → verify it's an action verb.
**Pass**: "Enroll in Digital School course", "Approve mentor session request", "Issue completion certificate"
**Fail**: "Enrollment" (no verb), "Learner books session" (actor included), "Manage courses" (vague verb)
---
### C2. UC is at user-goal level (passes the coffee-break test)
**Definition**: After completing the UC, the actor can stop and take a break — the goal is achieved.
**How to check**: Read the Postconditions → ask yourself "Is this a business-meaningful result?"
- If the result is just a sub-step (e.g. "OTP is verified") → UC is too small
- If the result spans multiple sessions → UC is too large
**Pass**:
- "Enroll in Digital School course" → postcondition: enrollment active, learner has course access
- "Book mentor session" → postcondition: session request submitted, mentor notified
**Fail**:
- "Verify OTP" (too small — just a sub-step of another UC) → should be an Includes
- "Manage entire learner lifecycle" (too large — spans many sessions) → split into many UCs
---
### C3. UC ID is unique and follows naming convention
**Definition**: ID is unique in the project and matches the standard format.
**How to check**:
- Check the master UC list — is the ID unique?
- Does the format match `UC-<module>-<seq>`?
**Pass**: "UC-LEARN-01" (unique, correct format), "UC-MENTOR-03"
**Fail**: "UC1" (no module), "UseCase_CourseEnroll" (name embedded)
---
### C4. Exactly 1 primary actor + clear business goal
**Definition**: One UC has 1 primary actor (the initiator) and 1 specific goal.
**How to check**:
- Actor field → is there "Primary: [X]"?
- Description → does it state the goal clearly?
- If you see 2 primary actors → flag for splitting
**Pass**: Primary: Learner. Goal: enroll in a Digital School course and gain immediate access to materials.
**Fail**: Primary: Learner + HR Manager (2 actors). → Split: "Learner self-enrolls" and "HR Manager assigns course to employee" as 2 UCs.
---
### C5. System boundary is clear
**Definition**: The UC describes interaction with one specific system, not multiple systems mixed together.
**How to check**: Read the Normal Course → do the "System..." steps consistently refer to one system?
**Pass**: All steps refer to "BA Zone Platform". LMS and Payment Gateway are secondary actors.
**Fail**: Mixing BA Zone web platform + mobile app + third-party LMS API as if they were one system. → Split by system boundary or clarify primary system.
---
## GROUP B: Actor & Context (C6-C8)
### C6. Actor is a specific role/class
**How to check**: Is the actor a specific role/class rather than "User"?
**Pass**: "Learner (Digital School Premium subscriber)", "Mentor (BA Zone certified, active account)"
**Fail**: "User", "Person", "Actor 1"
---
### C7. Description answers WHY + WHAT + OUTCOME
**How to check**: Read the Description → check that all 3 elements are present.
**Pass**:
"When a learner completes all lessons and passes the final assessment [WHY], the learner navigates to the Certificate section to request a completion certificate [WHAT]. The UC ends when a personalized certificate PDF is generated with a unique verification code and emailed to the learner [OUTCOME]."
**Fail**: "This UC is about issuing certificates." (missing WHY and OUTCOME)
---
### C8. Frequency of Use is quantified
**How to check**: Does the Frequency field contain a NUMBER?
**Pass**: "~500 enrollments/day platform-wide; peak ~100/hour during promotional campaigns"
**Fail**: "Frequent", "Often during course launches" (no volume)
⚠️ Acceptable: "TBD — awaiting analytics data from ops team" + logged in Notes as [TBD-N]
---
## GROUP C: Pre/Post Conditions (C9-C11)
### C9. Preconditions are verifiable
**How to check**: Can each precondition be verified by a query / boolean test?
**Pass**: "Learner has completed 100% of course lessons (progress = 100%)" (DB query), "Payment Gateway is available" (health check)
**Fail**: "Learner is motivated to learn" (motivation — not verifiable), "System is ready" (too vague)
---
### C10. Postconditions cover the success state + all changes
**How to check**: Do the postconditions describe all changes after the UC runs?
- Data changes (which records, which fields)
- External state (notification sent, calendar blocked, file generated)
- User-visible state (new screen, badge unlocked)
**Pass**:
```
1. Enrollment record created with status='Active'
2. Learner granted access to all published lessons
3. Payment transaction saved with status='Completed'
4. Welcome email + in-app notification sent within 60s
5. Course enrollment count incremented by 1
```
**Fail**: Only "Enrollment succeeded" → missing all state detail.
---
### C11. Preconditions are not confused with Assumptions
**How to check**: Distinguish:
- Precondition: MUST BE TRUE, system can check
- Assumption: BELIEVED to be true, not verified
**Common Digital School mistake**: Putting "Learner has basic computer literacy" in Precondition → WRONG, this is an Assumption. The system cannot check it.
---
## GROUP D: Normal Course (C12-C15)
### C12. Numbered list, one action per step
**How to check**: Does each step:
- Start with a number (1., 2., 3...)
- Contain only one main action
- Avoid "and" connecting two different-kind actions
**Pass**: "3. Learner enters the session topic, preferred date, and time slot." (same kind — input fields)
**Fail**: "3. Learner enters the topic and clicks Send and waits for confirmation." (3 actions in one step)
---
### C13. Alternates Actor / System with clear subjects
**How to check**: Read the steps — is there an alternating Actor/System pattern?
**Pass**:
```
1. Learner clicks Enroll Now ← Actor
2. System displays the Order Summary ← System
3. Learner selects a payment method ← Actor
4. Learner clicks Proceed to Payment ← Actor
5. System invokes the Payment Gateway ← System
```
(OK to have 2 consecutive Actor steps when both are input — still clear)
**Fail**: Only "Learner does X, then Y, then Z" with no system response anywhere.
---
### C14. NO embedded if/else/loop in the Normal Course
**How to check**: Search the Normal Course for "if", "in case", "otherwise" → flag.
**Pass**:
```
5. System validates the learner's remaining session quota.
6. System creates the session request with status='Pending_Mentor_Review'.
```
**Fail**:
```
5. If the learner has a Premium subscription, system shows all mentors; if Free, system shows only free-tier mentors; if quota is 0, system blocks the action.
```
→ Split into: Normal Course (default Premium flow) + AC (Free tier) + Exception (quota exhausted).
---
### C15. Flow runs from trigger to postcondition
**How to check**:
- Does step 1 match the trigger in the Description?
- Does the final step achieve the postcondition?
- Are there any "dangling" steps?
**Pass**: Step 1 "Learner clicks Enroll Now" (trigger) → step 9 "System sends welcome notification" (postcondition achieved).
**Fail**: Final step is "System saves enrollment" but postcondition says "Welcome notification is sent" → flow is incomplete.
---
## GROUP E: Alternative & Exception (C16-C18)
### C16. Each AC specifies "at step N" + condition
**How to check**: Does each Alternative Course have:
- ID format `UC-XX.AC.N`
- Opening sentence: "At step Y of the Normal Course, if [condition]..."
- Sub-steps numbered 5a, 5b...
- Closing sentence: "continue from step Z of the Normal Course"
**Pass**:
```
UC-LEARN-01.AC.1: Enroll using enterprise voucher
At step 5 of the Normal Course, if the learner selects Enterprise Voucher:
5a. System displays a voucher code field.
5b. Learner enters the code and clicks Apply.
5c. System validates the voucher → continue from step 7 of the Normal Course.
```
**Fail**:
```
AC1: If learner has a voucher, they can use it instead of paying.
```
(Too vague, no step reference, no sub-steps, no rejoining instruction)
---
### C17. Each Exception has trigger + response + final state
**How to check**: Does each exception have all 3 parts?
**Pass**:
```
UC-LEARN-01.EX.2: Course reaches full capacity mid-flow
Trigger: At step 7, LMS returns CAPACITY_EXCEEDED.
Response: System displays "This course just reached full capacity. Join the waitlist."
Final state: Payment refunded within 1 business day. No enrollment created. Waitlist offer shown.
```
**Fail**:
```
EX1: If an error occurs, system shows an error message.
```
(Vague — no trigger, no response detail, no final state)
---
### C18. Common failure modes are covered
**How to check**: Does the UC cover at least the common failure types relevant to Digital School?
| Failure type | Required for Digital School UC? |
|---|---|
| Validation error (invalid input) | ✅ |
| Business rule violation (quota exceeded, course at capacity) | ✅ |
| External service failure (payment gateway, LMS, calendar timeout) | ✅ |
| Authentication/Authorization failure | ✅ if UC has auth |
| Network/connectivity issue | ✅ for mobile flows |
| Concurrency conflict (two learners grabbing last slot) | ✅ for enrollment/booking UCs |
| Session timeout (mentor idle on detail view) | ✅ for UCs with long review flows |
**Tip**: If the UC has only 1-2 Exceptions → suspicious. Enrollment and booking UCs typically need 3-5.
---
## GROUP F: Completeness (C19-C20)
### C19. Includes (if any) point to existing UCs
**How to check**: Does each UC in the Includes field have a valid ID + does that UC actually exist?
**Pass**: "Includes: UC-PAY-01 (Process payment)" → UC-PAY-01 has been written and is in the UC register.
**Fail**: "Includes: Payment UC" → ID not specific, or referenced UC doesn't exist yet.
---
### C20. Special Requirements don't duplicate functional requirements
**How to check**: Is each item in Special Requirements a non-functional requirement?
**Pass** (non-functional):
- "Course catalog loads ≤ 2s under 5,000 concurrent users"
- "Audit log retained for 3 years"
- "Comply with Vietnamese VAT invoicing regulations"
**Fail** (functional — belongs in Normal Course / Business Rule):
- "Validate that the voucher code is 16 characters" → validation logic, belongs in a Normal Course step or BR
- "Learner can only enroll in 10 courses per month" → business rule, not a Special Requirement
---
## Validation Report
After checking all 20 items, output the report in this format:
```markdown
## Validation Result for UC-LEARN-01
| # | Item | Status | Note |
|---|------|--------|------|
| C1 | UC Name format | ✅ | "Enroll in Digital School course" — active verb + object |
| C2 | User-goal level | ✅ | Passes coffee-break test |
| C3 | UC ID unique | ✅ | Follows convention |
| C4 | 1 primary actor | ✅ | Learner |
| C5 | System boundary | ✅ | BA Zone Platform |
| C6 | Specific actor | ✅ | |
| C7 | Description WHY+WHAT+OUTCOME | ✅ | |
| C8 | Frequency quantified | ⚠️ | TBD — awaiting analytics from ops team |
| C9 | Preconditions verifiable | ✅ | 4/4 verifiable |
| C10 | Postconditions cover state | ✅ | 5 postconditions |
| C11 | No Pre/Assumption mix | ✅ | |
| C12 | Numbered, 1 action/step | ✅ | 9 steps |
| C13 | Actor/System alternating | ✅ | |
| C14 | No nested if/else | ✅ | |
| C15 | Flow complete | ✅ | |
| C16 | AC has "at step N" | ✅ | 2 ACs, all properly anchored |
| C17 | Exception has 3 parts | ✅ | 4 exceptions, all complete |
| C18 | Common failure modes covered | ✅ | Payment fail, capacity, LMS unavailable — all covered |
| C19 | Includes valid | ✅ | UC-PAY-01, UC-NOTI-01 |
| C20 | Special Req non-functional | ✅ | |
**Summary**: 19/20 ✅ + 1 ⚠️. UC is ready for stakeholder review.
**Follow-up**: C8 — Frequency of Use awaiting analytics data from the ops team [TBD-3].
```
---
*Compiled by **Phúc NT** · BA Zone · Digital School*
*Please credit the source when sharing or adapting this checklist.*
FILE:references/template-guide.md
# Template Guide — How to Fill Each Field
Detailed guidance for filling each of the 13 fields, with pass/fail examples in the EdTech & Digital School domain.
Compiled by **Phúc NT** · BA Zone · Digital School
## Table of Contents
1. [Use Case ID](#1-use-case-id)
2. [Use Case Name](#2-use-case-name)
3. [Use Case History](#3-use-case-history)
4. [Actor](#4-actor)
5. [Description](#5-description)
6. [Preconditions](#6-preconditions)
7. [Postconditions](#7-postconditions)
8. [Priority](#8-priority)
9. [Frequency of Use](#9-frequency-of-use)
10. [Normal Course of Events](#10-normal-course-of-events)
11. [Alternative Courses](#11-alternative-courses)
12. [Exceptions](#12-exceptions)
13. [Includes](#13-includes)
14. [Special Requirements](#14-special-requirements)
15. [Assumptions](#15-assumptions)
16. [Notes and Issues](#16-notes-and-issues)
---
## 1. Use Case ID
**Purpose**: Unique identifier so requirements can be traced back to the UC.
**Rules**:
- Format: `UC-<module>-<sequence>` or `UC-X.Y` (hierarchical)
- Use a consistent naming convention across the project
- For related UC groups, use X.Y (e.g. UC-3.1, UC-3.2 belong to the "Enrollment" group)
- Pad the sequence to 2-3 digits: `UC-LEARN-01`, `UC-MENTOR-003`
**Good examples**:
- `UC-LEARN-01` (Enrollment module, UC #1)
- `UC-MENTOR-03` (Mentoring module, UC #3)
- `UC-3.2` (hierarchical, 2nd sub-UC of group 3)
**Bad examples**:
- `UC1` (no scheme)
- `UseCase_CourseEnrollment` (mixes name into ID — hard to maintain when name changes)
---
## 2. Use Case Name
**Purpose**: Short label describing the UC's goal.
**CRITICAL rules**:
- MUST follow **"Action verb + Object"** form
- 3-7 words, not too long
- DO NOT start with the actor name
- DO NOT use vague verbs ("manage", "handle", "process", "do")
- Reflects the actor's goal, not the implementation
**Pattern**: `<Verb> <Direct Object> [<modifier>]`
**Good examples** (Digital School / BA Zone):
- ✅ "Enroll in Digital School course"
- ✅ "Book 1-on-1 mentor session"
- ✅ "Issue course completion certificate"
- ✅ "Approve learner KYC application"
- ✅ "Assign enterprise license to employee"
**Bad examples → how to fix**:
- ❌ "Enrollment" → ✅ "Enroll in course"
- ❌ "Learner books session" (actor included) → ✅ "Book mentor session"
- ❌ "Manage learning path" (vague verb) → split into "Create learning path", "Update learning path", "Archive learning path"
- ❌ "Certificate is issued" (passive voice) → ✅ "Issue completion certificate"
---
## 3. Use Case History
**Purpose**: Audit trail (Created By, Date Created, Last Updated By, Date Last Updated).
**Rules**:
- Created By: full name + role (e.g. "Phúc NT - BA Zone")
- Date Created: YYYY-MM-DD format
- Last Updated By + Date Last Updated: update on every edit
- If unknown, use the placeholder `<TBD>` instead of leaving blank
---
## 4. Actor
**Purpose**: Identify who/what interacts with the system.
**Actor types**:
- **Primary actor**: Initiates the UC, benefits from the outcome
- **Secondary actor**: Supporting system/person (payment gateway, LMS, calendar service)
- **Off-stage stakeholder**: Has interest but doesn't interact directly (regulators, auditors) — usually NOT listed in the Actor field
**Rules**:
- The primary actor MUST be a specific role/class — never write "User" generically
- A UC should have 1 primary actor (rarely 2+)
- If there's a secondary actor, label it clearly
**Good examples** (Digital School / BA Zone):
- ✅ "Primary: Learner (Digital School Premium subscriber, email verified)"
- ✅ "Primary: Mentor (BA Zone certified, active account)"
- ✅ "Primary: HR Manager (Enterprise Partner with license admin rights)"
- ✅ "Primary: BO Admin; Secondary: AML Service, Notification Service"
**Bad examples**:
- ❌ "User" (too generic)
- ❌ "Student" (ambiguous — is it the same as Learner?)
- ❌ "System" (the system is the target of the UC, not an actor)
---
## 5. Description
**Purpose**: Summarize the UC in 2-3 sentences so readers grasp what it's about quickly.
**Rules — must answer 3 questions**:
1. **WHY**: The reason/trigger that leads to this UC
2. **WHAT**: What the actor does with the system
3. **OUTCOME**: The final result (new system state / value for the actor)
**Pattern**: `[When/To] <trigger/reason>, <actor> <action> in order to <outcome>.`
**Good example** (Digital School):
> "When a learner completes all lessons and passes the final assessment of a Digital School course, the learner navigates to the Certificate section to download their completion certificate. The UC ends when a personalized certificate PDF is generated with a unique verification code, downloaded by the learner, and recorded in the Certificates table."
**Bad examples**:
> ❌ "This UC is about certificates." (too short, missing WHY and OUTCOME)
> ❌ "The certificate module has these steps: request, generate, download…" (describes flow, not a description)
---
## 6. Preconditions
**Purpose**: List conditions that MUST be true before the UC can start.
**CRITICAL rules**:
- Every precondition must be **verifiable** (boolean check)
- Number them: 1, 2, 3…
- Distinguish from Business Rules:
- Precondition: checked BEFORE the UC starts
- Business Rule: applied DURING the UC's flow
- Distinguish from Assumptions:
- Precondition: REQUIRED for the UC to run
- Assumption: BELIEVED to be true but not verified
**Good examples** (Digital School):
```
1. Learner has logged in to BA Zone with a verified email address
2. Learner has completed 100% of the course lessons (progress = 100%)
3. Learner has passed the final assessment with a score ≥ 70%
4. Certificate generation service is available
```
**Bad examples**:
- ❌ "System is operating" (too generic, not verifiable)
- ❌ "Learner wants a certificate" (motivation, not a condition)
- ❌ "Learner must have a valid payment method" (belongs in a payment UC, not a certificate UC)
---
## 7. Postconditions
**Purpose**: Describe the system state AFTER successful UC completion.
**Rules**:
- Verifiable (can be checked via DB query / API response)
- Cover all kinds of changes:
- Data state (new record, status change)
- User-facing state (notification sent, file available for download)
- External system state (API call succeeded, calendar blocked)
- Number them
**Important**: A postcondition is a **state**, not an **action**.
- ✅ State: "Certificate record is saved with a unique verification code"
- ❌ Action: "System saves the certificate record" (this is a step in the Normal Course)
**Good example** (Digital School):
```
1. Certificate record is created in the Certificates table with a unique verification code (format: CERT-BAZONE-YYYY-NNNNN)
2. Certificate PDF is generated and stored in cloud storage, accessible via a permanent URL
3. Learner's profile displays the certificate badge for the completed course
4. Certificate verification page is publicly accessible at verify.bazone.vn using the unique code
5. Achievement notification is sent to the learner's email and in-app notification center
```
---
## 8. Priority
**Purpose**: Define the implementation priority of the UC.
**Common schemes**:
- **MoSCoW**: Must / Should / Could / Won't
- **3-level**: High / Medium / Low
**Rules**:
- Use the SAME scheme as the project's SRS / PRD
- Justify (one sentence explaining why priority X)
**Good examples** (Digital School):
- "High — Core feature; directly tied to learner retention and completion rate metrics"
- "Medium — Enhances enterprise partner experience; planned for phase 2"
---
## 9. Frequency of Use
**Purpose**: Estimate how often the UC will be executed → input for performance/capacity planning.
**Rules**:
- Use SPECIFIC NUMBERS (not "occasionally", "frequently")
- Suitable time units: per second, per hour, per day, per month
- If there are peak times, state them explicitly
**Good examples** (Digital School):
- "~500 enrollments/day; peak ~100/hour during campaign launches and new course releases"
- "~15 session requests/day per mentor; system-wide ~300/day across 20 active mentors; peak Sunday evenings"
**Bad examples**:
- ❌ "Frequent"
- ❌ "Daily" (no volume)
---
## 10. Normal Course of Events
**Purpose**: Describe the happy path — steps from trigger to goal achieved.
**CRITICAL rules** (this is the most error-prone field):
### 10.1. Format
- Numbered list (1, 2, 3…)
- Each step: one single action
- Start with a clear subject (Actor / System)
- Active voice + present tense
- Short steps, 1-2 sentences each
### 10.2. Alternate Actor / System
Typical pattern: Actor → System → Actor → System…
- Odd steps: actor input
- Even steps: system response
### 10.3. DO NOT embed:
- ❌ If/else → move to Alternative Course
- ❌ Loops → use "Steps X-Y repeat until Z"
- ❌ Exceptions → move to Exceptions
- ❌ Internal system logic → that's design, not a UC
### 10.4. Start and end
- Step 1: Trigger (the event that activates the UC)
- Final step: Goal achieved (postcondition met)
**Good example** (Digital School — book a mentor session):
```
1. Learner navigates to the "My Mentors" section and selects a mentor profile.
2. System displays the mentor's profile: bio, expertise, average rating, and available time slots for the next 14 days.
3. Learner selects a preferred date and time slot.
4. Learner enters a topic or question for the session (max 500 characters) and clicks "Send Request".
5. System validates that the learner has at least 1 unused session quota in their current subscription.
6. System creates a session request with status='Pending_Mentor_Review' and sends a notification to the mentor.
7. System displays a confirmation screen: "Request sent! Your mentor will respond within 24 hours."
8. System invokes UC-NOTI-03 to send a confirmation email to the learner.
```
**Bad examples → how to fix**:
- ❌ "1. If the learner has a Premium subscription, they can select any mentor; otherwise they can only select from the free tier…" → Move the branching to an Alternative Course
- ❌ "3. System validates. If invalid, show error. If valid, continue." → Validation-pass continues in flow; validation-fail goes into an Exception
- ❌ "5. System calls POST /api/v1/sessions with body {learner_id, mentor_id, slot_id}" → Too technical. Say: "System creates the session request in the booking system"
---
## 11. Alternative Courses
**Purpose**: A DIFFERENT path that still leads to the goal (still success), just a different route.
**Rules**:
- ID format: `UC-XX.AC.N` (AC = Alternative Course)
- Each AC starts with: "At step Y of the Normal Course, if [condition], execute the alternative: …"
- After the AC, state explicitly which step of the Normal Course to continue from
**Good example** (Digital School):
```
UC-LEARN-01.AC.1: Enroll using an enterprise voucher
At step 5 of the Normal Course, if the learner selects "Enterprise Voucher" as the payment method:
5a. System displays a voucher code input field.
5b. Learner enters the code and clicks "Apply".
5c. System validates the voucher (expiry, applicability, remaining uses).
5d. System updates the total amount to 0 VND → continue from step 7 of the Normal Course (no payment gateway call).
```
---
## 12. Exceptions
**Purpose**: Cases where the UC FAILS (goal is not achieved).
**Rules**:
- ID format: `UC-XX.EX.N` (EX = Exception)
- Each exception needs 3 parts:
1. **Trigger condition**: When the exception occurs
2. **System response**: What the system does
3. **Final state**: The end state (rollback? partial? log?)
**Common failure modes to check** (don't forget):
- Validation errors (wrong format, missing field)
- Business rule violations (quota exceeded, course at capacity)
- External service failures (payment gateway timeout, LMS unavailable)
- Network/connectivity issues
- Permission denied / authorization failure
- Concurrency conflict (slot booked by another learner at the same time)
- Session timeout (mentor idle too long on the detail view)
**Good example** (Digital School):
```
UC-LEARN-01.EX.2: Course reaches full capacity between page load and enrollment
Trigger: At step 7, LMS returns CAPACITY_EXCEEDED because another learner filled the last slot milliseconds earlier.
Response: System displays "Sorry, this course just reached full capacity. Join the waitlist to be notified when a slot opens."
Final state: Payment is refunded automatically within 1 business day. No enrollment record is created. Learner is offered the waitlist option.
```
---
## 13. Includes
**Purpose**: Reuse common functionality across UCs.
**Rules**:
- List sub-UCs "called" by this UC (UML «include» semantics)
- The sub-UC must exist (have its own spec)
- DO NOT use Includes just to group minor steps — only for logic reused in other UCs
**Good example** (Digital School):
```
- UC-PAY-01: Process payment (called at step 6 of the Normal Course)
- UC-NOTI-01: Send enrollment notification (called at step 9)
```
---
## 14. Special Requirements
**Purpose**: Non-functional requirements specific to this UC.
**Categories to cover**:
- **Performance**: Response time, throughput, concurrent users
- **Security**: Authentication, encryption, data privacy
- **Usability**: Accessibility, mobile-first requirements
- **Reliability**: Uptime, async fallback strategy
- **Compliance**: Regulatory requirements (VAT invoicing, data retention)
**Rule**: DO NOT duplicate functional requirements — only list non-functional.
**Good example** (Digital School):
```
- Performance: Course catalog page loads ≤ 2s under 5,000 concurrent learners
- Security: Payment card data never stored on BA Zone servers; all card processing via PCI-DSS certified gateway
- Reliability: If LMS is unavailable during enrollment, payment must not be rolled back — retry asynchronously up to 30 min
- Compliance: Issue VAT invoice for all transactions ≥ 200,000 VND (Vietnamese tax law)
```
---
## 15. Assumptions
**Purpose**: Things assumed during analysis that haven't been verified.
**Difference vs Precondition**:
- Precondition: MUST BE TRUE, system can verify
- Assumption: BELIEVED TO BE TRUE, not required to verify
**Good example** (Digital School):
```
1. Learner's email address is verified and active — welcome emails will not bounce
2. Payment Gateway SLA is ≥ 99.5% uptime during business hours
3. Enterprise vouchers are pre-loaded by BA Zone's ops team before distribution to partners
4. LMS access provisioning completes synchronously in < 3s under normal load
```
---
## 16. Notes and Issues
**Purpose**: Open questions, TBDs, follow-up items.
**Format**:
```
[TBD-N] | Owner | Due Date | Resolution
```
**Good example** (Digital School):
```
- [TBD-1] Should learners be able to gift a course enrollment to another BA Zone user? | Owner: Product Team | Due: 2026-06-01 | Resolution: TBD — deferred to phase 2
- [TBD-2] What is the refund policy if a learner requests a refund within 7 days? | Owner: Phúc NT - BA Zone | Due: 2026-05-25 | Resolution: TBD
- [NOTE] Welcome email template must align with current Digital School brand guidelines — coordinate with Marketing team
```
---
*Compiled by **Phúc NT** · BA Zone · Digital School*
*Please credit the source when sharing or adapting this guide.*
FILE:references/writing-style.md
# Writing Style Guide
Compiled from Alistair Cockburn ("Writing Effective Use Cases") + IIBA BABOK + BA Zone practice in EdTech and enterprise software domains.
> Compiled by **Phúc NT** · BA Zone · Digital School
## Supreme principle: READABILITY FIRST
Cockburn's famous quote: "Write clearly. Readability is the most important thing."
A good UC is one where:
- Non-technical stakeholders can grasp the meaning
- Developers have enough to code from
- QA has enough to write test cases
- A new BA (or future Digital School grad) can update it when things change
---
## Rule 1: Active Voice + Present Tense
### Active voice
Use active sentences where the subject performs the action.
- ✅ "Learner clicks the **Enroll Now** button"
- ❌ "The **Enroll Now** button is clicked by the learner"
- ✅ "System saves the enrollment record to the database"
- ❌ "The enrollment record is saved to the database by the system"
### Present tense
Use simple present tense, avoid future/past.
- ✅ "System displays the course confirmation screen"
- ❌ "System will display the course confirmation screen"
- ❌ "System displayed the course confirmation screen"
---
## Rule 2: Clear subject — Subject + Verb + Object
Every step must start with a **specific subject**: an actor name or "System".
- ✅ "Learner selects a preferred mentor session slot"
- ❌ "Selects a preferred session slot" (no subject)
- ✅ "System validates the learner's remaining session quota"
- ❌ "Validates the remaining quota" (passive, unclear who's doing it)
---
## Rule 3: One step = one action
Each step in the Normal Course does exactly one thing. If you see "and" connecting different kinds of action → split the step.
- ✅ "3. Learner enters the destination account, preferred slot, and session topic." (same kind — filling a form)
- ❌ "3. Learner fills in the session topic and clicks Confirm." → split into 2 steps:
- "3. Learner enters the session topic (max 500 characters)."
- "4. Learner clicks the **Send Request** button."
**Why**: "Clicking Send Request" usually triggers system validation → it needs to be a separate step so an Exception "quota exceeded" can be attached to it.
---
## Rule 4: Avoid vague verbs
Vague verbs = verbs that don't convey a specific action.
| ❌ Vague | ✅ Specific |
|---------|------------|
| Manage | Create / Update / Archive / View |
| Handle | Validate / Process / Reject / Escalate |
| Do | Submit / Approve / Assign / Generate |
| Make | Issue / Build / Render / Compute |
| Get | Retrieve / Fetch / Query / Download |
| Use | Apply / Invoke / Execute / Redeem |
| Take care of | Specific verb |
**Apply this to both the UC Name and the step text.**
---
## Rule 5: Avoid implementation details
A UC describes **WHAT** (the action), not **HOW** (the mechanism). Leave HOW for the design phase.
- ❌ "System calls POST /api/v1/enrollments with header Authorization Bearer {token}, body {course_id, learner_id}…"
- ✅ "System creates the enrollment record in the LMS"
- ❌ "System inserts a row into the tbl_enrollments table with fields: enroll_id, course_id, learner_id, created_at…"
- ✅ "System saves the enrollment to the database"
- ❌ "System renders the <EnrollmentSuccessModal> React component with prop courseTitle='BA Fundamentals'…"
- ✅ "System displays the Enrollment Confirmed screen with the course name and access link"
**Exception**: If the UC is specifically an integration spec, it can be more detailed — but still use business language.
---
## Rule 6: Consistent numbering
### Normal Course
Numbered list starting at 1.
### Alternative Course
Sub-numbering by original step + letter:
- AC at step 5 → step 5a, 5b, 5c
- After the AC, state "continue from step N of the Normal Course"
### Exception ID
Format: `UC-XX.EX.N` (numbered independently, not tied to a step)
### Pre/Postconditions
Numbered list starting at 1.
---
## Rule 7: Naming UI elements
When mentioning a UI element in a step, use bold and the actual on-screen label:
- ✅ "Learner clicks the **Enroll Now** button"
- ✅ "System displays the **Order Summary** screen"
- ✅ "Learner selects **Enterprise Voucher** from the payment method dropdown"
Reason: Easy to trace back to wireframes/mockups during design handoff.
---
## Rule 8: Avoid vague words
| ❌ Vague | ✅ Specific |
|---------|------------|
| In some cases | When condition X occurs |
| May / can | When [condition], system [action] |
| Sometimes | X% of the time / Y times per Z |
| If needed | When [specific trigger] |
| Valid | Meets the criteria: … (list them) |
| Appropriate | Per BA Zone policy [reference] |
| Quickly | Within X seconds |
| User | Learner / Mentor / BO Admin / HR Manager |
---
## Rule 9: Don't embed business rules in steps
A Normal Course step describes **flow**. Business rules (validation rules, limits, business logic) should:
- Reference Special Requirements by rule ID
- Or live in a separate Business Rule document (BR-XX-YY)
- ❌ "5. System validates: session topic must be ≤ 500 chars, learner must have ≥ 1 unused quota, slot must be ≥ 2 hours in the future, mentor must not be on leave…"
- ✅ "5. System validates the session request according to business rule BR-MENTOR-001."
- (Then list BR-MENTOR-001 in Special Requirements or a separate BR document)
---
## Rule 10: Length guidelines
- **UC Name**: 3-7 words
- **Description**: 2-4 sentences, ~50-100 words
- **Normal Course**: 5-15 steps (usually 7-10 for Digital School UCs)
- **Each step**: 1 sentence, max 2 sentences, < 30 words
- **Alternative Courses**: 1-5 ACs per UC (more → consider splitting the UC)
- **Exceptions**: 3-7 for a typical UC
- **Total UC document**: 2-5 A4 pages
If you exceed the guideline:
- UC too long → split via Includes
- Too many ACs/EXs → review the scope, the UC might be carrying too much
---
## Rule 11: Consistency across the project
Be consistent across the whole document set:
- Actor names (don't switch between "Learner", "Student", "User", "Participant")
- System component names (LMS, Learning Management System, Moodle → pick one)
- Screen/menu names (must match the wireframe or product spec)
- Naming convention for UC IDs
Tip: Maintain a **Glossary** at the front of the document set. For Digital School, agree upfront: is it "Learner" or "Student"? "Mentor" or "Instructor"?
---
## Rule 12: Internationalization
If the Digital School platform has i18n requirements:
- Screen/button names in the UC can use keys instead of hard-coded text
- E.g. replace "clicks the **Enroll Now** button" with "clicks the {btn.enroll_now} button"
- For most BA Zone UC specs, plain English labels are fine
---
## Anti-patterns — the 10 most common mistakes
### 1. UC is a pixel-by-pixel UI spec
❌ "System displays a modal with a blue #1E88E5 header 'Enrollment Confirmed', a checkmark icon, and course thumbnail image on the left..."
→ That's a wireframe annotation. A UC says: "System displays the Enrollment Confirmed screen with the course name and a Go to Course button."
### 2. Mixing actor and system in one step
❌ "3. Learner selects the slot and system validates quota."
→ Split into 2 steps.
### 3. Skipping system response
❌ "1. Learner clicks Enroll Now. 2. Learner enters payment info. 3. Learner confirms."
→ System responses between steps are missing. A UC must show DIALOG actor ↔ system.
### 4. Embedded conditional logic
❌ "5. If the learner has a Premium subscription, system allows mentor selection; otherwise only free-tier mentors are shown."
→ Split into Normal Course (default case) + AC (Premium path) or Exception (unauthorized access).
### 5. Vague trigger
❌ "When the learner wants to get a certificate, they..."
→ Be specific: "When the learner navigates to the **Certificates** tab after completing the course..."
### 6. Postcondition is an action instead of a state
❌ "System sends a certificate to the learner" (action)
→ "A certificate email has been delivered to the learner's registered email address" (state) ← verifiable
### 7. UC with 2 primary actors
❌ Primary: Learner + HR Manager (both initiating the UC)
→ Split into 2 UCs: one for self-enrollment, one for HR-assigned enrollment.
### 8. Vague "System processes"
❌ "5. System processes the enrollment."
→ Be specific: "System creates the enrollment record in the LMS and grants the learner access to all published lessons."
### 9. Repeating the Description in the Normal Course
If the Description already states the full flow, don't copy it into the Normal Course. The Description is a 2-3 sentence summary; the Normal Course is the detailed step-by-step.
### 10. Forgetting failure modes
A UC with only a Normal Course + 1 generic "error" Exception → not enough.
For Digital School UCs, always cover: payment failures, quota exhaustion, external service timeouts, concurrency conflicts (two learners grabbing the last slot), and permission/role mismatches.
---
*Compiled by **Phúc NT** · BA Zone · Digital School*
*Please credit the source when sharing or adapting this guide.*
FILE:scripts/uc_quality_checker.py
#!/usr/bin/env python3
"""Deterministic first-pass checker for the use-case-writer 20-point quality checklist.
Parses a Use Case Markdown file written in the `assets/uc-template.md` table format
and runs heuristic checks for items C1-C20 from `references/quality-checklist.md`.
This is a mechanical pre-check, not a substitute for the LLM-driven Step 4 review in
SKILL.md -- several items (C2, C5, C11, C13, C15, C19) require judgment or an external
UC registry and are reported as MANUAL rather than PASS/FAIL.
Stdlib only. No network calls.
"""
import argparse
import json
import re
import sys
VAGUE_VERBS = {"manage", "handle", "process", "do", "make", "get", "use", "take"}
GENERIC_ACTORS = {"user", "users", "person", "actor", "system", "student", "customer"}
VAGUE_PRECONDITION_WORDS = ("motivated", "literacy", "wants", "ready", "interested", "willing")
FAILURE_MODE_KEYWORDS = (
"timeout", "invalid", "network", "permission", "quota", "capacity",
"concurrency", "unavailable", "denied", "expire", "conflict",
)
FIELD_ALIASES = {
"use case id": "uc_id",
"use case name": "uc_name",
"actor": "actor",
"description": "description",
"preconditions": "preconditions",
"postconditions": "postconditions",
"priority": "priority",
"frequency of use": "frequency",
"normal course of events": "normal_course",
"alternative courses": "alternative_courses",
"exceptions": "exceptions",
"includes": "includes",
"special requirements": "special_requirements",
"assumptions": "assumptions",
"notes and issues": "notes",
}
SAMPLE_UC = """## UC-LEARN-01: Enroll in Digital School course
| **Use Case ID:** | UC-LEARN-01 |
| ---: | :--- |
| **Use Case Name:** | Enroll in Digital School course |
| **Actor:** | **Primary:** Learner (Digital School Premium subscriber). **Secondary:** Payment Gateway, LMS. |
| **Description:** | When a learner selects a course from the catalog, the learner enrolls in order to gain access to all published lessons. The UC ends when the enrollment record is active and materials are accessible. |
| **Preconditions:** | 1. Learner has logged in with a verified email address.<br>2. Payment Gateway is available. |
| **Postconditions:** | 1. Enrollment record created with status='Active'.<br>2. Learner granted access to all published lessons. |
| **Priority:** | High - core retention feature |
| **Frequency of Use:** | ~500 enrollments/day; peak ~100/hour during campaign launches |
| **Normal Course of Events:** | 1. Learner selects the course on the catalog.<br>2. System displays course details and the Enroll Now button.<br>3. Learner clicks Enroll Now.<br>4. System creates the enrollment record.<br>5. System displays the confirmation screen. |
| **Alternative Courses:** | **UC-LEARN-01.AC.1: Enroll using enterprise voucher**<br>At step 3, if the learner selects Enterprise Voucher: 3a. System displays a voucher field. 3b. Learner enters the code. -> continue from step 4 of the Normal Course. |
| **Exceptions:** | **UC-LEARN-01.EX.1: Course reaches full capacity**<br>Trigger: At step 4, LMS returns CAPACITY_EXCEEDED.<br>Response: System displays a waitlist offer.<br>Final state: No enrollment record created; payment refunded. |
| **Includes:** | UC-PAY-01: Process payment (invoked at step 4) |
| **Special Requirements:** | **Performance**: Catalog page loads <= 2s under 5,000 concurrent learners. |
| **Assumptions:** | 1. Learner's email address is verified and active. |
| **Notes and Issues:** | [TBD-1] Should learners be able to gift enrollment? \\| Owner: Product \\| Due: 2026-06-01 \\| Resolution: TBD |
"""
def parse_uc(text):
fields = {}
for line in text.splitlines():
stripped = line.strip()
if not stripped.startswith("|"):
continue
m = re.match(r"\|\s*\*\*([^*:]+):?\*\*\s*\|\s*(.*)", stripped)
if not m:
continue
label = m.group(1).strip().lower()
key = FIELD_ALIASES.get(label)
if not key:
continue
value = m.group(2)
value = re.sub(r"\|\s*$", "", value).strip()
value = value.replace("<br>", "\n")
if key in fields:
fields[key] += "\n" + value
else:
fields[key] = value
return fields
def check(item, status, note):
return {"item": item, "status": status, "note": note}
def run_checks(fields, registry):
results = []
# C1: UC Name follows verb + object
name = fields.get("uc_name", "")
if not name:
results.append(check("C1", "FAIL", "Use Case Name is missing"))
else:
first_word = re.split(r"\s+", name.strip())[0].lower().rstrip(".,")
if first_word in VAGUE_VERBS:
results.append(check("C1", "FAIL", f"Vague leading verb '{first_word}' -- use a specific action verb"))
elif len(name.split()) < 2:
results.append(check("C1", "FAIL", "Name is a single word -- needs verb + object"))
elif re.match(r"^(the|a|an)\b", name.strip(), re.I):
results.append(check("C1", "WARN", "Name may embed an actor/article rather than starting with a verb"))
else:
results.append(check("C1", "PASS", f"'{name}' reads as verb + object"))
# C2: coffee-break test / goal level -- judgment call
results.append(check("C2", "MANUAL", "Goal-level scoping (coffee-break test) requires human/LLM judgment"))
# C3: UC ID format
uc_id = fields.get("uc_id", "")
if re.match(r"^UC-[A-Za-z0-9]+-\d+$", uc_id) or re.match(r"^UC-\d+(\.\d+)+$", uc_id):
results.append(check("C3", "PASS", f"ID '{uc_id}' matches UC-<module>-<seq> or UC-X.Y"))
else:
results.append(check("C3", "FAIL", f"ID '{uc_id}' does not match naming convention"))
# C4: exactly one primary actor
actor = fields.get("actor", "")
primary_count = len(re.findall(r"primary\s*:", actor, re.I))
if primary_count == 1:
results.append(check("C4", "PASS", "Exactly one Primary actor declared"))
elif primary_count == 0:
results.append(check("C4", "FAIL", "No 'Primary:' actor found"))
else:
results.append(check("C4", "FAIL", f"{primary_count} 'Primary:' actors found -- consider splitting the UC"))
# C5: system boundary -- judgment call
results.append(check("C5", "MANUAL", "System boundary consistency requires reading the full Normal Course"))
# C6: actor is specific, not generic
primary_match = re.search(r"primary\s*:\s*\**\s*([^.\n]+)", actor, re.I)
primary_role = primary_match.group(1).strip().strip("*").strip() if primary_match else ""
primary_first_word = re.split(r"\s+", primary_role)[0].lower().rstrip(".,()") if primary_role else ""
if primary_first_word and primary_first_word in GENERIC_ACTORS:
results.append(check("C6", "FAIL", f"Primary actor '{primary_role}' is a generic role"))
elif primary_role:
results.append(check("C6", "PASS", f"Primary actor '{primary_role}' is specific"))
else:
results.append(check("C6", "FAIL", "No Primary actor role found"))
# C7: description covers WHY + WHAT + OUTCOME (heuristic keyword presence + length)
description = fields.get("description", "")
words = len(description.split())
why_cue = re.search(r"\b(when|after|because|since)\b", description, re.I)
outcome_cue = re.search(r"\b(so that|in order to|ends when|until|outcome)\b", description, re.I)
if words < 12:
results.append(check("C7", "WARN", f"Description is short ({words} words) -- verify WHY/WHAT/OUTCOME are all present"))
elif why_cue and outcome_cue:
results.append(check("C7", "PASS", "Description contains WHY and OUTCOME cues"))
else:
missing = [n for n, c in (("WHY", why_cue), ("OUTCOME", outcome_cue)) if not c]
results.append(check("C7", "WARN", f"Description may be missing: {', '.join(missing)}"))
# C8: frequency quantified
frequency = fields.get("frequency", "")
if re.search(r"\d", frequency):
results.append(check("C8", "PASS", "Frequency of Use contains a number"))
elif re.search(r"tbd", frequency, re.I):
results.append(check("C8", "WARN", "Frequency marked TBD -- ensure it is logged in Notes and Issues"))
else:
results.append(check("C8", "FAIL", "Frequency of Use has no quantity"))
# C9: preconditions verifiable (heuristic banned-word scan)
preconditions = fields.get("preconditions", "")
if not preconditions:
results.append(check("C9", "FAIL", "Preconditions field is empty"))
else:
hits = [w for w in VAGUE_PRECONDITION_WORDS if w in preconditions.lower()]
if hits:
results.append(check("C9", "WARN", f"Possible unverifiable precondition language: {', '.join(hits)}"))
else:
results.append(check("C9", "PASS", "No obviously unverifiable language detected"))
# C10: postconditions present and non-trivial
postconditions = fields.get("postconditions", "")
items = [l for l in postconditions.split("\n") if re.match(r"^\s*\d+[.)]", l)]
if not items:
results.append(check("C10", "FAIL", "No numbered Postconditions found"))
else:
results.append(check("C10", "PASS", f"{len(items)} postcondition(s) listed"))
# C11: precondition/assumption confusion -- judgment call
results.append(check("C11", "MANUAL", "Precondition-vs-Assumption confusion needs semantic review"))
# C12: Normal Course numbered, one action per step
normal_course = fields.get("normal_course", "")
steps = [l for l in normal_course.split("\n") if re.match(r"^\s*\d+[.)]", l)]
if not steps:
results.append(check("C12", "FAIL", "Normal Course has no numbered steps"))
else:
multi_and = [s for s in steps if s.lower().count(" and ") >= 2]
if multi_and:
results.append(check("C12", "WARN", f"{len(multi_and)} step(s) chain multiple actions with 'and'"))
else:
results.append(check("C12", "PASS", f"{len(steps)} numbered step(s), no obvious multi-action steps"))
# C13: actor/system alternation -- weak heuristic
if re.search(r"\bsystem\b", normal_course, re.I) and re.search(r"\b(learner|actor|user|mentor|admin|manager)\b", normal_course, re.I):
results.append(check("C13", "PASS", "Both actor- and system-attributed steps appear"))
else:
results.append(check("C13", "WARN", "Could not confirm actor/system alternation -- review manually"))
# C14: no embedded conditionals in Normal Course
conditional_hits = re.findall(r"\b(if|in case|otherwise|unless)\b", normal_course, re.I)
if conditional_hits:
results.append(check("C14", "FAIL", f"Conditional language found in Normal Course: {', '.join(sorted(set(w.lower() for w in conditional_hits)))} -- move to Alternative/Exception"))
else:
results.append(check("C14", "PASS", "No embedded if/otherwise/unless in Normal Course"))
# C15: flow completeness -- judgment call
results.append(check("C15", "MANUAL", "Trigger-to-postcondition completeness needs semantic review"))
# C16: Alternative Courses format
ac = fields.get("alternative_courses", "")
if not ac.strip():
results.append(check("C16", "WARN", "No Alternative Courses provided (acceptable if none apply)"))
else:
ac_ids = re.findall(r"UC-[\w.\-]+\.AC\.\d+", ac)
has_at_step = "at step" in ac.lower()
has_continue = "continue from step" in ac.lower()
if ac_ids and has_at_step and has_continue:
results.append(check("C16", "PASS", f"{len(ac_ids)} AC(s) with 'at step' + 'continue from step'"))
else:
missing = []
if not ac_ids:
missing.append("UC-XX.AC.N id")
if not has_at_step:
missing.append("'at step N' anchor")
if not has_continue:
missing.append("'continue from step' rejoin")
results.append(check("C16", "WARN", f"Alternative Course missing: {', '.join(missing)}"))
# C17: Exceptions have trigger/response/final state
exceptions = fields.get("exceptions", "")
if not exceptions.strip():
results.append(check("C17", "FAIL", "No Exceptions found -- happy-path-only UCs are insufficient (see C18)"))
else:
ex_ids = re.findall(r"UC-[\w.\-]+\.EX\.\d+", exceptions)
has_trigger = "trigger" in exceptions.lower()
has_response = "response" in exceptions.lower()
has_final = "final state" in exceptions.lower()
if ex_ids and has_trigger and has_response and has_final:
results.append(check("C17", "PASS", f"{len(ex_ids)} exception(s) with trigger/response/final state"))
else:
missing = [n for n, c in (("id", ex_ids), ("Trigger:", has_trigger), ("Response:", has_response), ("Final state:", has_final)) if not c]
results.append(check("C17", "WARN", f"Exception(s) missing: {', '.join(missing)}"))
# C18: common failure modes covered
hits = [w for w in FAILURE_MODE_KEYWORDS if w in exceptions.lower()]
if len(hits) >= 2:
results.append(check("C18", "PASS", f"Failure-mode keywords present: {', '.join(hits)}"))
elif hits:
results.append(check("C18", "WARN", f"Only one failure-mode keyword present ({hits[0]}) -- consider more coverage"))
else:
results.append(check("C18", "FAIL", "No common failure-mode keywords found in Exceptions"))
# C19: Includes point to existing UCs
includes = fields.get("includes", "")
if not includes.strip():
results.append(check("C19", "WARN", "No Includes -- acceptable if this UC has no shared sub-flows"))
else:
inc_ids = re.findall(r"UC-[\w.\-]+", includes)
if not inc_ids:
results.append(check("C19", "FAIL", "Includes field has no recognizable UC-ID"))
elif registry:
unknown = [i for i in inc_ids if i not in registry]
if unknown:
results.append(check("C19", "FAIL", f"Included UC(s) not found in registry: {', '.join(unknown)}"))
else:
results.append(check("C19", "PASS", f"All {len(inc_ids)} included UC(s) found in registry"))
else:
results.append(check("C19", "MANUAL", f"Includes reference {', '.join(inc_ids)} -- pass --registry to verify existence"))
# C20: Special Requirements are non-functional
special = fields.get("special_requirements", "")
functional_leak = re.search(r"\b(validate|must be exactly|characters|business rule)\b", special, re.I)
if not special.strip():
results.append(check("C20", "WARN", "Special Requirements is empty"))
elif functional_leak:
results.append(check("C20", "WARN", f"Possible functional requirement leaking in: '{functional_leak.group(0)}'"))
else:
results.append(check("C20", "PASS", "No obvious functional-requirement language detected"))
return results
def format_table(uc_id, results):
lines = [f"## Validation Result for {uc_id or '(unknown UC ID)'}", "", "| # | Status | Note |", "|---|--------|------|"]
icon = {"PASS": "PASS", "FAIL": "FAIL", "WARN": "WARN", "MANUAL": "MANUAL"}
for r in results:
lines.append(f"| {r['item']} | {icon[r['status']]} | {r['note']} |")
passed = sum(1 for r in results if r["status"] == "PASS")
failed = sum(1 for r in results if r["status"] == "FAIL")
warned = sum(1 for r in results if r["status"] == "WARN")
manual = sum(1 for r in results if r["status"] == "MANUAL")
lines += ["", f"**Summary**: {passed} PASS / {warned} WARN / {failed} FAIL / {manual} MANUAL out of {len(results)}."]
if failed:
lines.append("**Verdict**: FIX FAILING ITEMS before handover.")
elif warned:
lines.append("**Verdict**: Review WARN items, then run the LLM-driven Step 4 checklist before handover.")
else:
lines.append("**Verdict**: Mechanical checks clean. Still run MANUAL items and the LLM-driven Step 4 checklist before handover.")
return "\n".join(lines)
def main():
parser = argparse.ArgumentParser(description="Mechanical first pass over the use-case-writer 20-point checklist.")
parser.add_argument("input", nargs="?", help="Path to a UC Markdown file (assets/uc-template.md format)")
parser.add_argument("--registry", help="Path to a text file listing known UC IDs (one per line), used to verify C19 Includes")
parser.add_argument("--json", action="store_true", help="Output JSON instead of a Markdown table")
parser.add_argument("--sample", action="store_true", help="Run against the bundled sample UC instead of a file")
args = parser.parse_args()
if args.sample:
text = SAMPLE_UC
elif args.input:
with open(args.input, "r", encoding="utf-8") as f:
text = f.read()
else:
parser.error("provide an input file or --sample")
return
registry = None
if args.registry:
with open(args.registry, "r", encoding="utf-8") as f:
registry = {line.strip() for line in f if line.strip()}
fields = parse_uc(text)
results = run_checks(fields, registry)
if args.json:
print(json.dumps({"uc_id": fields.get("uc_id", ""), "results": results}, indent=2))
else:
print(format_table(fields.get("uc_id", ""), results))
sys.exit(1 if any(r["status"] == "FAIL" for r in results) else 0)
if __name__ == "__main__":
main()
Phân tích và tối ưu vận hành TMĐT đa kênh: đơn hàng, tồn kho, sản phẩm và hiệu quả quảng cáo ROAS/CPO trên Shopee, TikTok Shop, website.
--- name: van-hanh-tmdt-da-kenh description: Phân tích và tối ưu vận hành thương mại điện tử đa kênh (Shopee, TikTok Shop, Website, Facebook), quản lý đơn hàng, tồn kho, sản phẩm và hiệu quả quảng cáo ROAS/CPO. Dùng khi nói "TMĐT", "vận hành sàn", "Shopee", "TikTok Shop", "đa kênh". --- # Vận hành thương mại điện tử đa kênh (E-Commerce Operations) ## Mục tiêu Phân tích và tối ưu vận hành TMĐT đa kênh — Shopee, TikTok Shop, Website, Facebook — với cơ cấu mỗi nhân sự phụ trách 1 kênh, đảm bảo hiệu suất đồng đều và phối hợp liên kênh hiệu quả. ## Bối cảnh Chargee - **Mô hình**: Phân phối hàng hóa trên sàn TMĐT - **Kênh**: Shopee / TikTok Shop / Website / Facebook - **Cơ cấu**: Mỗi người phụ trách 1 kênh → rủi ro siloed, thiếu phối hợp giữa các kênh - **Vai trò của bạn**: Chủ doanh nghiệp → cần nhìn tổng thể và phát hiện vấn đề từng kênh ## Khi nào dùng - Review hiệu suất định kỳ (tuần/tháng) từng kênh - Phát hiện kênh đang underperform cần can thiệp - Phân bổ lại ngân sách quảng cáo giữa các kênh - Phát sinh vấn đề tồn kho, logistics, hoặc đơn hàng - Ra quyết định về giá, khuyến mãi, hoặc listing sản phẩm mới ## Đầu vào cần cung cấp - **Kênh cần phân tích**: 1 kênh cụ thể hay toàn bộ - **Loại phân tích**: Hiệu suất / đơn hàng / sản phẩm / quảng cáo - **Dữ liệu hiện có**: Doanh thu, đơn hàng, ngân sách, tồn kho - **Kỳ phân tích**: Ngày / tuần / tháng - **Vấn đề đang gặp** (nếu có) ## Quy trình xử lý ### A. Hiệu suất kênh bán 1. Tổng hợp theo kênh: doanh thu, số đơn, AOV (giá trị đơn TB) 2. Tính conversion rate và traffic nguồn vào 3. So sánh kênh với kênh — kênh nào đang kéo, kênh nào đang kéo lùi 4. Xác định nguyên nhân: sản phẩm, giá, quảng cáo, hay vận hành 5. Đề xuất điều chỉnh ưu tiên theo kênh ### B. Vận hành đơn hàng 1. Tỷ lệ đơn thành công / hủy / hoàn — theo từng kênh 2. Thời gian xử lý đơn trung bình — có đang vượt SLA không 3. Xác định bottleneck: người xử lý, kho, đơn vị vận chuyển 4. Tồn kho: hàng nào sắp hết, hàng nào tồn quá lâu 5. Đề xuất điều chỉnh quy trình hoặc phân công ### C. Quản lý sản phẩm 1. Sản phẩm nào đang bán chạy / chậm theo từng kênh 2. Kiểm tra listing: hình ảnh, mô tả, từ khóa có tối ưu chưa 3. So sánh giá với đối thủ cùng kênh 4. Khuyến mãi hiện tại có phù hợp với biên lợi nhuận không 5. Đề xuất: sản phẩm nên đẩy, sản phẩm nên dừng, giá cần điều chỉnh ### D. Hiệu quả quảng cáo 1. Tổng hợp ngân sách và kết quả theo kênh 2. Tính ROAS, CPO, CPC theo từng kênh 3. Xác định kênh có ROI tốt nhất để tăng ngân sách 4. Xác định camp đang burn tiền không hiệu quả 5. Đề xuất phân bổ lại ngân sách tuần/tháng tới ## Tiêu chuẩn đầu ra | Loại phân tích | Định dạng | Nội dung bắt buộc | |---|---|---| | Hiệu suất kênh | Bảng so sánh đa kênh | Doanh thu, đơn, AOV, tăng trưởng | | Vận hành đơn | Bảng + danh sách vấn đề | Tỷ lệ thành công, bottleneck, đề xuất | | Sản phẩm | Danh sách ưu tiên | Top bán chạy, cần tối ưu, nên dừng | | Quảng cáo | Bảng ROAS/CPO theo kênh | Kênh hiệu quả, kênh cần cắt/điều chỉnh | ## Dashboard tổng hợp tuần (gợi ý) | Kênh | Doanh thu | Đơn | AOV | ROAS | Vấn đề nổi bật | |---|---|---|---|---|---| | Shopee | | | | | | | TikTok Shop | | | | | | | Website | | | | | | | Facebook | | | | | | | **Tổng** | | | | | | ## Rủi ro đặc thù cần theo dõi - **Siloed operation**: mỗi người 1 kênh → dễ mất đồng bộ giá, tồn kho, khuyến mãi giữa các kênh - **Phụ thuộc nhân sự**: 1 người nghỉ = 1 kênh tê liệt → cần SOP rõ ràng cho từng kênh - **Cạnh tranh chéo kênh**: cùng sản phẩm, giá khác nhau giữa các kênh gây mất niềm tin khách hàng ## Giả định luôn 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 số liệu một kênh: phân tích các kênh còn lại và nêu rõ kênh nào chưa có dữ liệu - Đề xuất mang tính tham khảo — quyết định cuối thuộc về bạn ## Tránh - Phân tích từng kênh riêng lẻ mà không nhìn tổng thể đa kênh - Đề xuất tăng ngân sách mà không kiểm tra biên lợi nhuận - Bỏ qua rủi ro tồn kho khi đẩy mạnh quảng cáo - Kết luận khi thiếu dữ liệu — phải nêu rõ giả định
Tạo pipeline CI/CD thực dụng từ tín hiệu công nghệ của dự án, gồm kiểm tra lặp lại và giai đoạn triển khai theo môi trường.
---
name: "ci-cd-pipeline-builder"
description: "Generate pragmatic CI/CD pipelines from detected project stack signals — fast baseline generation, repeatable checks, environment-aware deployment stages. Use when setting up CI for a new project, refactoring existing pipelines, or standardizing deployment workflows across multiple repos."
---
# CI/CD Pipeline Builder
**Tier:** POWERFUL
**Category:** Engineering
**Domain:** DevOps / Automation
## Overview
Use this skill to generate pragmatic CI/CD pipelines from detected project stack signals, not guesswork. It focuses on fast baseline generation, repeatable checks, and environment-aware deployment stages.
## Core Capabilities
- Detect language/runtime/tooling from repository files
- Recommend CI stages (`lint`, `test`, `build`, `deploy`)
- Generate GitHub Actions or GitLab CI starter pipelines
- Include caching and matrix strategy based on detected stack
- Emit machine-readable detection output for automation
- Keep pipeline logic aligned with project lockfiles and build commands
## When to Use
- Bootstrapping CI for a new repository
- Replacing brittle copied pipeline files
- Migrating between GitHub Actions and GitLab CI
- Auditing whether pipeline steps match actual stack
- Creating a reproducible baseline before custom hardening
## Key Workflows
### 1. Detect Stack
```bash
python3 scripts/stack_detector.py --repo . --format text
python3 scripts/stack_detector.py --repo . --format json > detected-stack.json
```
Supports input via stdin or `--input` file for offline analysis payloads.
### 2. Generate Pipeline From Detection
```bash
python3 scripts/pipeline_generator.py \
--input detected-stack.json \
--platform github \
--output .github/workflows/ci.yml \
--format text
```
Or end-to-end from repo directly:
```bash
python3 scripts/pipeline_generator.py --repo . --platform gitlab --output .gitlab-ci.yml
```
### 3. Validate Before Merge
1. Confirm commands exist in project (`test`, `lint`, `build`).
2. Run generated pipeline locally where possible.
3. Ensure required secrets/env vars are documented.
4. Keep deploy jobs gated by protected branches/environments.
### 4. Add Deployment Stages Safely
- Start with CI-only (`lint/test/build`).
- Add staging deploy with explicit environment context.
- Add production deploy with manual gate/approval.
- Keep rollout/rollback commands explicit and auditable.
## Script Interfaces
- `python3 scripts/stack_detector.py --help`
- Detects stack signals from repository files
- Reads optional JSON input from stdin/`--input`
- `python3 scripts/pipeline_generator.py --help`
- Generates GitHub/GitLab YAML from detection payload
- Writes to stdout or `--output`
## Common Pitfalls
1. Copying a Node pipeline into Python/Go repos
2. Enabling deploy jobs before stable tests
3. Forgetting dependency cache keys
4. Running expensive matrix builds for every trivial branch
5. Missing branch protections around prod deploy jobs
6. Hardcoding secrets in YAML instead of CI secret stores
## Best Practices
1. Detect stack first, then generate pipeline.
2. Keep generated baseline under version control.
3. Add one optimization at a time (cache, matrix, split jobs).
4. Require green CI before deployment jobs.
5. Use protected environments for production credentials.
6. Regenerate pipeline when stack changes significantly.
## References
- [references/github-actions-templates.md](references/github-actions-templates.md)
- [references/gitlab-ci-templates.md](references/gitlab-ci-templates.md)
- [references/deployment-gates.md](references/deployment-gates.md)
- [README.md](README.md)
## Detection Heuristics
The stack detector prioritizes deterministic file signals over heuristics:
- Lockfiles determine package manager preference
- Language manifests determine runtime families
- Script commands (if present) drive lint/test/build commands
- Missing scripts trigger conservative placeholder commands
## Generation Strategy
Start with a minimal, reliable pipeline:
1. Checkout and setup runtime
2. Install dependencies with cache strategy
3. Run lint, test, build in separate steps
4. Publish artifacts only after passing checks
Then layer advanced behavior (matrix builds, security scans, deploy gates).
## Platform Decision Notes
- GitHub Actions for tight GitHub ecosystem integration
- GitLab CI for integrated SCM + CI in self-hosted environments
- Keep one canonical pipeline source per repo to reduce drift
## Validation Checklist
1. Generated YAML parses successfully.
2. All referenced commands exist in the repo.
3. Cache strategy matches package manager.
4. Required secrets are documented, not embedded.
5. Branch/protected-environment rules match org policy.
## Scaling Guidance
- Split long jobs by stage when runtime exceeds 10 minutes.
- Introduce test matrix only when compatibility truly requires it.
- Separate deploy jobs from CI jobs to keep feedback fast.
- Track pipeline duration and flakiness as first-class metrics.
FILE:README.md
# CI/CD Pipeline Builder
Detects your repository stack and generates practical CI pipeline templates for GitHub Actions and GitLab CI. Designed as a fast baseline you can extend with deployment controls.
## Quick Start
```bash
# Detect stack
python3 scripts/stack_detector.py --repo . --format json > stack.json
# Generate GitHub Actions workflow
python3 scripts/pipeline_generator.py \
--input stack.json \
--platform github \
--output .github/workflows/ci.yml \
--format text
```
## Included Tools
- `scripts/stack_detector.py`: repository signal detection with JSON/text output
- `scripts/pipeline_generator.py`: generate GitHub/GitLab CI YAML from detection payload
## References
- `references/github-actions-templates.md`
- `references/gitlab-ci-templates.md`
- `references/deployment-gates.md`
## Installation
### Claude Code
```bash
cp -R engineering/ci-cd-pipeline-builder ~/.claude/skills/ci-cd-pipeline-builder
```
### OpenAI Codex
```bash
cp -R engineering/ci-cd-pipeline-builder ~/.codex/skills/ci-cd-pipeline-builder
```
### OpenClaw
```bash
cp -R engineering/ci-cd-pipeline-builder ~/.openclaw/skills/ci-cd-pipeline-builder
```
FILE:references/deployment-gates.md
# Deployment Gates
## Minimum Gate Policy
- `lint` must pass before `test`.
- `test` must pass before `build`.
- `build` artifact required for deploy jobs.
- Production deploy requires manual approval and protected branch.
## Environment Pattern
- `develop` -> auto deploy to staging
- `main` -> manual promote to production
## Rollback Requirement
Every deploy job should define a rollback command or procedure reference.
FILE:references/github-actions-templates.md
# GitHub Actions Templates
## Node.js Baseline
```yaml
name: Node CI
on: [push, pull_request]
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npm run lint
- run: npm test
- run: npm run build
```
## Python Baseline
```yaml
name: Python CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: python3 -m pip install -U pip
- run: python3 -m pip install -r requirements.txt
- run: python3 -m pytest
```
FILE:references/gitlab-ci-templates.md
# GitLab CI Templates
## Node.js Baseline
```yaml
stages:
- lint
- test
- build
node_lint:
image: node:20
stage: lint
script:
- npm ci
- npm run lint
node_test:
image: node:20
stage: test
script:
- npm ci
- npm test
```
## Python Baseline
```yaml
stages:
- test
python_test:
image: python:3.12
stage: test
script:
- python3 -m pip install -U pip
- python3 -m pip install -r requirements.txt
- python3 -m pytest
```
FILE:scripts/pipeline_generator.py
#!/usr/bin/env python3
"""Generate CI pipeline YAML from detected stack data.
Input sources:
- --input stack report JSON file
- stdin stack report JSON
- --repo path (auto-detect stack)
Output:
- text/json summary
- pipeline YAML written via --output or printed to stdout
"""
import argparse
import json
import sys
from dataclasses import dataclass, asdict
from pathlib import Path
from typing import Any, Dict, List, Optional
class CLIError(Exception):
"""Raised for expected CLI failures."""
@dataclass
class PipelineSummary:
platform: str
output: str
stages: List[str]
uses_cache: bool
languages: List[str]
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Generate CI/CD pipeline YAML from detected stack.")
parser.add_argument("--input", help="Stack report JSON file. If omitted, can read stdin JSON.")
parser.add_argument("--repo", help="Repository path for auto-detection fallback.")
parser.add_argument("--platform", choices=["github", "gitlab"], required=True, help="Target CI platform.")
parser.add_argument("--output", help="Write YAML to this file; otherwise print to stdout.")
parser.add_argument("--format", choices=["text", "json"], default="text", help="Summary output format.")
return parser.parse_args()
def load_json_input(input_path: Optional[str]) -> Optional[Dict[str, Any]]:
if input_path:
try:
return json.loads(Path(input_path).read_text(encoding="utf-8"))
except Exception as exc:
raise CLIError(f"Failed reading --input: {exc}") from exc
if not sys.stdin.isatty():
raw = sys.stdin.read().strip()
if raw:
try:
return json.loads(raw)
except json.JSONDecodeError as exc:
raise CLIError(f"Invalid JSON from stdin: {exc}") from exc
return None
def detect_stack(repo: Path) -> Dict[str, Any]:
scripts = {}
pkg_file = repo / "package.json"
if pkg_file.exists():
try:
pkg = json.loads(pkg_file.read_text(encoding="utf-8"))
raw_scripts = pkg.get("scripts", {})
if isinstance(raw_scripts, dict):
scripts = raw_scripts
except Exception:
scripts = {}
languages: List[str] = []
if pkg_file.exists():
languages.append("node")
if (repo / "pyproject.toml").exists() or (repo / "requirements.txt").exists():
languages.append("python")
if (repo / "go.mod").exists():
languages.append("go")
return {
"languages": sorted(set(languages)),
"signals": {
"pnpm_lock": (repo / "pnpm-lock.yaml").exists(),
"yarn_lock": (repo / "yarn.lock").exists(),
"npm_lock": (repo / "package-lock.json").exists(),
"dockerfile": (repo / "Dockerfile").exists(),
},
"lint_commands": ["npm run lint"] if "lint" in scripts else [],
"test_commands": ["npm test"] if "test" in scripts else [],
"build_commands": ["npm run build"] if "build" in scripts else [],
}
def select_node_install(signals: Dict[str, Any]) -> str:
if signals.get("pnpm_lock"):
return "pnpm install --frozen-lockfile"
if signals.get("yarn_lock"):
return "yarn install --frozen-lockfile"
return "npm ci"
def github_yaml(stack: Dict[str, Any]) -> str:
langs = stack.get("languages", [])
signals = stack.get("signals", {})
lint_cmds = stack.get("lint_commands", []) or ["echo 'No lint command configured'"]
test_cmds = stack.get("test_commands", []) or ["echo 'No test command configured'"]
build_cmds = stack.get("build_commands", []) or ["echo 'No build command configured'"]
lines: List[str] = [
"name: CI",
"on:",
" push:",
" branches: [main, develop]",
" pull_request:",
" branches: [main, develop]",
"",
"jobs:",
]
if "node" in langs:
lines.extend(
[
" node-ci:",
" runs-on: ubuntu-latest",
" steps:",
" - uses: actions/checkout@v4",
" - uses: actions/setup-node@v4",
" with:",
" node-version: '20'",
" cache: 'npm'",
f" - run: {select_node_install(signals)}",
]
)
for cmd in lint_cmds + test_cmds + build_cmds:
lines.append(f" - run: {cmd}")
if "python" in langs:
lines.extend(
[
" python-ci:",
" runs-on: ubuntu-latest",
" steps:",
" - uses: actions/checkout@v4",
" - uses: actions/setup-python@v5",
" with:",
" python-version: '3.12'",
" - run: python3 -m pip install -U pip",
" - run: python3 -m pip install -r requirements.txt || true",
" - run: python3 -m pytest || true",
]
)
if "go" in langs:
lines.extend(
[
" go-ci:",
" runs-on: ubuntu-latest",
" steps:",
" - uses: actions/checkout@v4",
" - uses: actions/setup-go@v5",
" with:",
" go-version: '1.22'",
" - run: go test ./...",
" - run: go build ./...",
]
)
return "\n".join(lines) + "\n"
def gitlab_yaml(stack: Dict[str, Any]) -> str:
langs = stack.get("languages", [])
signals = stack.get("signals", {})
lint_cmds = stack.get("lint_commands", []) or ["echo 'No lint command configured'"]
test_cmds = stack.get("test_commands", []) or ["echo 'No test command configured'"]
build_cmds = stack.get("build_commands", []) or ["echo 'No build command configured'"]
lines: List[str] = [
"stages:",
" - lint",
" - test",
" - build",
"",
]
if "node" in langs:
install_cmd = select_node_install(signals)
lines.extend(
[
"node_lint:",
" image: node:20",
" stage: lint",
" script:",
f" - {install_cmd}",
]
)
for cmd in lint_cmds:
lines.append(f" - {cmd}")
lines.extend(
[
"",
"node_test:",
" image: node:20",
" stage: test",
" script:",
f" - {install_cmd}",
]
)
for cmd in test_cmds:
lines.append(f" - {cmd}")
lines.extend(
[
"",
"node_build:",
" image: node:20",
" stage: build",
" script:",
f" - {install_cmd}",
]
)
for cmd in build_cmds:
lines.append(f" - {cmd}")
if "python" in langs:
lines.extend(
[
"",
"python_test:",
" image: python:3.12",
" stage: test",
" script:",
" - python3 -m pip install -U pip",
" - python3 -m pip install -r requirements.txt || true",
" - python3 -m pytest || true",
]
)
if "go" in langs:
lines.extend(
[
"",
"go_test:",
" image: golang:1.22",
" stage: test",
" script:",
" - go test ./...",
" - go build ./...",
]
)
return "\n".join(lines) + "\n"
def main() -> int:
args = parse_args()
stack = load_json_input(args.input)
if stack is None:
if not args.repo:
raise CLIError("Provide stack input via --input/stdin or set --repo for auto-detection.")
repo = Path(args.repo).resolve()
if not repo.exists() or not repo.is_dir():
raise CLIError(f"Invalid repo path: {repo}")
stack = detect_stack(repo)
if args.platform == "github":
yaml_content = github_yaml(stack)
else:
yaml_content = gitlab_yaml(stack)
output_path = args.output or "stdout"
if args.output:
out = Path(args.output)
out.parent.mkdir(parents=True, exist_ok=True)
out.write_text(yaml_content, encoding="utf-8")
else:
print(yaml_content, end="")
summary = PipelineSummary(
platform=args.platform,
output=output_path,
stages=["lint", "test", "build"],
uses_cache=True,
languages=stack.get("languages", []),
)
if args.format == "json":
print(json.dumps(asdict(summary), indent=2), file=sys.stderr if not args.output else sys.stdout)
else:
text = (
"Pipeline generated\n"
f"- platform: {summary.platform}\n"
f"- output: {summary.output}\n"
f"- stages: {', '.join(summary.stages)}\n"
f"- languages: {', '.join(summary.languages) if summary.languages else 'none'}"
)
print(text, file=sys.stderr if not args.output else sys.stdout)
return 0
if __name__ == "__main__":
try:
raise SystemExit(main())
except CLIError as exc:
print(f"ERROR: {exc}", file=sys.stderr)
raise SystemExit(2)
FILE:scripts/stack_detector.py
#!/usr/bin/env python3
"""Detect project stack/tooling signals for CI/CD pipeline generation.
Input sources:
- repository scan via --repo
- JSON via --input file
- JSON via stdin
Output:
- text summary or JSON payload
"""
import argparse
import json
import sys
from dataclasses import dataclass, asdict
from pathlib import Path
from typing import Dict, List, Optional
class CLIError(Exception):
"""Raised for expected CLI failures."""
@dataclass
class StackReport:
repo: str
languages: List[str]
package_managers: List[str]
ci_targets: List[str]
test_commands: List[str]
build_commands: List[str]
lint_commands: List[str]
signals: Dict[str, bool]
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Detect stack/tooling from a repository.")
parser.add_argument("--input", help="JSON input file (precomputed signal payload).")
parser.add_argument("--repo", default=".", help="Repository path to scan.")
parser.add_argument("--format", choices=["text", "json"], default="text", help="Output format.")
return parser.parse_args()
def load_payload(input_path: Optional[str]) -> Optional[dict]:
if input_path:
try:
return json.loads(Path(input_path).read_text(encoding="utf-8"))
except Exception as exc:
raise CLIError(f"Failed reading --input file: {exc}") from exc
if not sys.stdin.isatty():
raw = sys.stdin.read().strip()
if raw:
try:
return json.loads(raw)
except json.JSONDecodeError as exc:
raise CLIError(f"Invalid JSON from stdin: {exc}") from exc
return None
def read_package_scripts(repo: Path) -> Dict[str, str]:
pkg = repo / "package.json"
if not pkg.exists():
return {}
try:
data = json.loads(pkg.read_text(encoding="utf-8"))
except Exception:
return {}
scripts = data.get("scripts", {})
return scripts if isinstance(scripts, dict) else {}
def detect(repo: Path) -> StackReport:
signals = {
"package_json": (repo / "package.json").exists(),
"pnpm_lock": (repo / "pnpm-lock.yaml").exists(),
"yarn_lock": (repo / "yarn.lock").exists(),
"npm_lock": (repo / "package-lock.json").exists(),
"pyproject": (repo / "pyproject.toml").exists(),
"requirements": (repo / "requirements.txt").exists(),
"go_mod": (repo / "go.mod").exists(),
"dockerfile": (repo / "Dockerfile").exists(),
"vercel": (repo / "vercel.json").exists(),
"helm": (repo / "helm").exists() or (repo / "charts").exists(),
"k8s": (repo / "k8s").exists() or (repo / "kubernetes").exists(),
}
languages: List[str] = []
package_managers: List[str] = []
ci_targets: List[str] = ["github", "gitlab"]
if signals["package_json"]:
languages.append("node")
if signals["pnpm_lock"]:
package_managers.append("pnpm")
elif signals["yarn_lock"]:
package_managers.append("yarn")
else:
package_managers.append("npm")
if signals["pyproject"] or signals["requirements"]:
languages.append("python")
package_managers.append("pip")
if signals["go_mod"]:
languages.append("go")
scripts = read_package_scripts(repo)
lint_commands: List[str] = []
test_commands: List[str] = []
build_commands: List[str] = []
if "lint" in scripts:
lint_commands.append("npm run lint")
if "test" in scripts:
test_commands.append("npm test")
if "build" in scripts:
build_commands.append("npm run build")
if "python" in languages:
lint_commands.append("python3 -m ruff check .")
test_commands.append("python3 -m pytest")
if "go" in languages:
lint_commands.append("go vet ./...")
test_commands.append("go test ./...")
build_commands.append("go build ./...")
return StackReport(
repo=str(repo.resolve()),
languages=sorted(set(languages)),
package_managers=sorted(set(package_managers)),
ci_targets=ci_targets,
test_commands=sorted(set(test_commands)),
build_commands=sorted(set(build_commands)),
lint_commands=sorted(set(lint_commands)),
signals=signals,
)
def format_text(report: StackReport) -> str:
lines = [
"Detected stack",
f"- repo: {report.repo}",
f"- languages: {', '.join(report.languages) if report.languages else 'none'}",
f"- package managers: {', '.join(report.package_managers) if report.package_managers else 'none'}",
f"- lint commands: {', '.join(report.lint_commands) if report.lint_commands else 'none'}",
f"- test commands: {', '.join(report.test_commands) if report.test_commands else 'none'}",
f"- build commands: {', '.join(report.build_commands) if report.build_commands else 'none'}",
]
return "\n".join(lines)
def main() -> int:
args = parse_args()
payload = load_payload(args.input)
if payload:
try:
report = StackReport(**payload)
except TypeError as exc:
raise CLIError(f"Invalid input payload for StackReport: {exc}") from exc
else:
repo = Path(args.repo).resolve()
if not repo.exists() or not repo.is_dir():
raise CLIError(f"Invalid repo path: {repo}")
report = detect(repo)
if args.format == "json":
print(json.dumps(asdict(report), indent=2))
else:
print(format_text(report))
return 0
if __name__ == "__main__":
try:
raise SystemExit(main())
except CLIError as exc:
print(f"ERROR: {exc}", file=sys.stderr)
raise SystemExit(2)
Chất vấn thận trọng về rủi ro với mọi kế hoạch liên quan dữ liệu, tuân thủ hoặc truy cập hệ thống production.
--- name: "ciso-review" description: "/cs:ciso-review <plan> — Risk-paranoid interrogation of any plan that touches data, compliance, or production access." --- # /cs:ciso-review — CISO Forcing Questions **Command:** `/cs:ciso-review <plan>` The risk-paranoid threat-modeler. Six questions before any production change that touches customer data or compliance scope. ## When to Run - Before deploying any system that touches PII / PHI / cardholder data - Before signing a new vendor with data access - Before a compliance audit (SOC 2, ISO 27001, HIPAA, GDPR) - Before any architecture decision crossing trust boundaries - After any near-miss incident ## The Six CISO Questions ### 1. Threat Model **What's the STRIDE threat model for this system, and which threat is most likely?** - Spoofing, Tampering, Repudiation, Info Disclosure, DoS, Elevation of Privilege. - Pick the top 3 by likelihood × impact. ### 2. Blast Radius **If this is fully compromised, what data is exposed and how many users are affected?** - Worst case in plain English. - Quantify in dollars via FAIR-based ALE. ### 3. Detection **What signals indicate compromise, and how long until they're triggered (MTTD)?** - Logs alone are not detection. - Define the detection rule, the alert, and the on-call. ### 4. Response **Is there an IR runbook for this scenario, and has it been tabletop-tested?** - If no runbook: build one before ship. - If untested: tabletop before ship. ### 5. Regulatory Window **What's the regulator notification window if this scenario occurs?** - GDPR: 72h. HIPAA: 60d. State breach laws vary. - Pre-write the customer comms template. ### 6. Vendor & Supply Chain **Which third-party vendors are in scope, and what's their security posture?** - Subprocessor list current? - DPAs in place? - Last security review per vendor? ## Workflow ```bash python ../../../skills/ciso-advisor/scripts/risk_quantifier.py python ../../../skills/ciso-advisor/scripts/compliance_tracker.py ``` ## Output Format ```markdown # CISO Review: <plan> **Date:** YYYY-MM-DD ## Threat Model - Top threat: <STRIDE category> — <description> - Likelihood: H/M/L | Impact: H/M/L - ALE: $X / year ## Blast Radius - Data exposed (worst case): <description> - Users affected: N - Estimated cost: $X ## Detection - MTTD target: X hours - Current MTTD: X hours - Detection rule: <name> ## Response - IR runbook: ✅ / ❌ - Last tabletop: <date> ## Regulatory - Frameworks in scope: SOC 2 / ISO 27001 / HIPAA / GDPR - Notification window: X hours/days ## Vendors - New vendors added: N - DPAs signed: N / N - Security reviews complete: N / N ## Verdict 🟢 SHIP | 🟡 MITIGATE THEN SHIP | 🔴 BLOCK ``` ## Routing - `/cs:cto-review` — architecture alignment - `/cs:gc-review` — DPA, regulatory implications - `/cs:decide` — log risk acceptance - `/cs:boardroom` — for CRITICAL risks ## Related - Agent: [`cs-ciso-advisor`](../../agents/cs-ciso-advisor.md) - Skill: [`ciso-advisor`](../../../skills/ciso-advisor/SKILL.md) - Compliance: `../../../../ra-qm-team/` --- **Version:** 1.0.0
Phân tích codebase và tạo tài liệu hướng dẫn cho kỹ sư, tech lead và cộng tác viên mới.
---
name: "codebase-onboarding"
description: "Analyze a codebase and generate onboarding documentation for engineers, tech leads, and contractors. Fast fact-gathering and repeatable onboarding outputs. Use when onboarding a new engineer, writing architecture-overview docs for a new project, or producing tech-lead briefings for unfamiliar repos."
---
# Codebase Onboarding
**Tier:** POWERFUL
**Category:** Engineering
**Domain:** Documentation / Developer Experience
---
## Overview
Analyze a codebase and generate onboarding documentation for engineers, tech leads, and contractors. This skill is optimized for fast fact-gathering and repeatable onboarding outputs.
## Core Capabilities
- Architecture and stack discovery from repository signals
- Key file and config inventory for new contributors
- Local setup and common-task guidance generation
- Audience-aware documentation framing
- Debugging and contribution checklist scaffolding
---
## When to Use
- Onboarding a new team member or contractor
- Rebuilding stale project docs after large refactors
- Preparing internal handoff documentation
- Creating a standardized onboarding packet for services
---
## Quick Start
```bash
# 1) Gather codebase facts
python3 scripts/codebase_analyzer.py /path/to/repo
# 2) Export machine-readable output
python3 scripts/codebase_analyzer.py /path/to/repo --json
# 3) Use the template to draft onboarding docs
# See references/onboarding-template.md
```
---
## Recommended Workflow
1. Run `scripts/codebase_analyzer.py` against the target repository.
2. Capture key signals: file counts, detected languages, config files, top-level structure.
3. Fill the onboarding template in `references/onboarding-template.md`.
4. Tailor output depth by audience:
- Junior: setup + guardrails
- Senior: architecture + operational concerns
- Contractor: scoped ownership + integration boundaries
---
## Onboarding Document Template
Detailed template and section examples live in:
- `references/onboarding-template.md`
- `references/output-format-templates.md`
---
## Common Pitfalls
- Writing docs without validating setup commands on a clean environment
- Mixing architecture deep-dives into contractor-oriented docs
- Omitting troubleshooting and verification steps
- Letting onboarding docs drift from current repo state
## Best Practices
1. Keep setup instructions executable and time-bounded.
2. Document the "why" for key architectural decisions.
3. Update docs in the same PR as behavior changes.
4. Treat onboarding docs as living operational assets, not one-time deliverables.
FILE:references/onboarding-template.md
# Onboarding Document Template
## README.md - Full Template
```markdown
# [Project Name]
> One-sentence description of what this does and who uses it.
[](https://github.com/org/repo/actions/workflows/ci.yml)
[](https://codecov.io/gh/org/repo)
## What is this?
[2-3 sentences: problem it solves, who uses it, current state]
**Live:** https://myapp.com
**Staging:** https://staging.myapp.com
**Docs:** https://docs.myapp.com
---
## Quick Start
### Prerequisites
| Tool | Version | Install |
|------|---------|---------|
| Node.js | 20+ | `nvm install 20` |
| pnpm | 8+ | `npm i -g pnpm` |
| Docker | 24+ | [docker.com](https://docker.com) |
| PostgreSQL | 16+ | via Docker (see below) |
### Setup (5 minutes)
```bash
git clone https://github.com/org/repo
cd repo
pnpm install
docker compose up -d
cp .env.example .env
pnpm db:migrate
pnpm db:seed
pnpm dev
pnpm test
```
### Verify it works
- [ ] App loads on localhost
- [ ] Health endpoint returns ok
- [ ] Tests pass
---
## Architecture
### System Overview
```
Browser / Mobile
|
v
[Next.js App] <- [Auth]
|
+-> [PostgreSQL]
+-> [Redis]
+-> [S3]
```
### Tech Stack
| Layer | Technology | Why |
|-------|-----------|-----|
| Frontend | Next.js | SSR + routing |
| Styling | Tailwind + shadcn/ui | Rapid UI |
| API | Route handlers | Co-location |
| Database | PostgreSQL | Relational |
| Queue | BullMQ + Redis | Background jobs |
---
## Key Files
| Path | Purpose |
|------|---------|
| `app/` | Pages and route handlers |
| `src/db/` | Schema and migrations |
| `src/lib/` | Shared utilities |
| `tests/` | Test suites and helpers |
| `.env.example` | Required variables |
---
## Common Developer Tasks
### Add a new API endpoint
```bash
touch app/api/my-resource/route.ts
touch tests/api/my-resource.test.ts
```
### Run a database migration
```bash
pnpm db:generate
pnpm db:migrate
```
### Add a background job
```bash
# Create worker module and enqueue path
```
---
## Debugging Guide
### Common Errors
- Missing environment variable
- Database connectivity failure
- Expired auth token
- Generic 500 in local dev
### Useful SQL Queries
- Slow query checks
- Connection status
- Table bloat checks
### Log Locations
| Environment | Logs |
|-------------|------|
| Local dev | local terminal |
| Production | platform logs |
| Worker | worker process logs |
---
## Contribution Guidelines
### Branch Strategy
- `main` protected
- feature/fix branches with ticket IDs
### PR Requirements
- CI green
- Tests updated
- Why documented
- Self-review completed
### Commit Convention
- `feat(scope): ...`
- `fix(scope): ...`
- `docs: ...`
---
## Audience-Specific Notes
### Junior Developers
- Start with core auth/data modules
- Follow tests as executable examples
### Senior Engineers
- Read ADRs and scaling notes first
- Validate performance/security assumptions early
### Contractors
- Stay within scoped feature boundaries
- Use wrappers for external integrations
```
## Usage Notes
- Keep onboarding setup under 10 minutes where possible.
- Include executable verification checks after each setup phase.
- Prefer links to canonical docs instead of duplicating long content.
- Update this template when stack conventions or tooling change.
FILE:references/output-format-templates.md
# codebase-onboarding reference
## Output Formats
### Notion Export
```javascript
// Use Notion API to create onboarding page
const { Client } = require('@notionhq/client')
const notion = new Client({ auth: process.env.NOTION_TOKEN })
const blocks = markdownToNotionBlocks(onboardingMarkdown) // use notion-to-md
await notion.pages.create({
parent: { page_id: ONBOARDING_PARENT_PAGE_ID },
properties: { title: { title: [{ text: { content: 'Engineer Onboarding — MyApp' } }] } },
children: blocks,
})
```
### Confluence Export
```bash
# Using confluence-cli or REST API
curl -X POST \
-H "Content-Type: application/json" \
-u "user@example.com:$CONFLUENCE_TOKEN" \
"https://yourorg.atlassian.net/wiki/rest/api/content" \
-d '{
"type": "page",
"title": "Codebase Onboarding",
"space": {"key": "ENG"},
"body": {
"storage": {
"value": "<p>Generated content...</p>",
"representation": "storage"
}
}
}'
```
---
FILE:scripts/codebase_analyzer.py
#!/usr/bin/env python3
"""Generate a compact onboarding summary for a codebase (stdlib only)."""
from __future__ import annotations
import argparse
import json
import os
from collections import Counter
from pathlib import Path
from typing import Dict, Iterable, List
IGNORED_DIRS = {
".git",
"node_modules",
".next",
"dist",
"build",
"coverage",
"venv",
".venv",
"__pycache__",
}
EXT_TO_LANG = {
".py": "Python",
".ts": "TypeScript",
".tsx": "TypeScript",
".js": "JavaScript",
".jsx": "JavaScript",
".go": "Go",
".rs": "Rust",
".java": "Java",
".kt": "Kotlin",
".rb": "Ruby",
".php": "PHP",
".cs": "C#",
".c": "C",
".cpp": "C++",
".h": "C/C++",
".swift": "Swift",
".sql": "SQL",
".sh": "Shell",
}
KEY_CONFIG_FILES = [
"package.json",
"pnpm-workspace.yaml",
"turbo.json",
"nx.json",
"lerna.json",
"tsconfig.json",
"next.config.js",
"next.config.mjs",
"pyproject.toml",
"requirements.txt",
"go.mod",
"Cargo.toml",
"docker-compose.yml",
"Dockerfile",
".github/workflows",
]
def iter_files(root: Path) -> Iterable[Path]:
for dirpath, dirnames, filenames in os.walk(root):
dirnames[:] = [d for d in dirnames if d not in IGNORED_DIRS]
for name in filenames:
path = Path(dirpath) / name
if path.is_file():
yield path
def detect_languages(paths: Iterable[Path]) -> Dict[str, int]:
counts: Counter[str] = Counter()
for path in paths:
lang = EXT_TO_LANG.get(path.suffix.lower())
if lang:
counts[lang] += 1
return dict(sorted(counts.items(), key=lambda item: (-item[1], item[0])))
def find_key_configs(root: Path) -> List[str]:
found: List[str] = []
for rel in KEY_CONFIG_FILES:
if (root / rel).exists():
found.append(rel)
return found
def top_level_structure(root: Path, max_depth: int) -> List[str]:
lines: List[str] = []
for dirpath, dirnames, filenames in os.walk(root):
rel = Path(dirpath).relative_to(root)
depth = 0 if str(rel) == "." else len(rel.parts)
if depth > max_depth:
dirnames[:] = []
continue
if any(part in IGNORED_DIRS for part in rel.parts):
dirnames[:] = []
continue
indent = " " * depth
if str(rel) != ".":
lines.append(f"{indent}{rel.name}/")
visible_files = [f for f in sorted(filenames) if not f.startswith(".")]
for filename in visible_files[:10]:
lines.append(f"{indent} {filename}")
dirnames[:] = sorted([d for d in dirnames if d not in IGNORED_DIRS])
return lines
def build_report(root: Path, max_depth: int) -> Dict[str, object]:
files = list(iter_files(root))
languages = detect_languages(files)
total_files = len(files)
file_count_by_ext: Counter[str] = Counter(p.suffix.lower() or "<no-ext>" for p in files)
largest = sorted(
((str(p.relative_to(root)), p.stat().st_size) for p in files),
key=lambda item: item[1],
reverse=True,
)[:20]
return {
"root": str(root),
"file_count": total_files,
"languages": languages,
"key_config_files": find_key_configs(root),
"top_extensions": dict(file_count_by_ext.most_common(12)),
"largest_files": largest,
"directory_structure": top_level_structure(root, max_depth),
}
def format_size(num_bytes: int) -> str:
units = ["B", "KB", "MB", "GB"]
value = float(num_bytes)
for unit in units:
if value < 1024 or unit == units[-1]:
return f"{value:.1f}{unit}"
value /= 1024
return f"{num_bytes}B"
def print_text(report: Dict[str, object]) -> None:
print("Codebase Onboarding Summary")
print(f"Root: {report['root']}")
print(f"Total files: {report['file_count']}")
print("")
print("Languages detected")
if report["languages"]:
for lang, count in report["languages"].items():
print(f"- {lang}: {count}")
else:
print("- No recognized source file extensions")
print("")
print("Key config files")
configs = report["key_config_files"]
if configs:
for cfg in configs:
print(f"- {cfg}")
else:
print("- None found from default checklist")
print("")
print("Largest files")
for rel, size in report["largest_files"][:10]:
print(f"- {rel}: {format_size(size)}")
print("")
print("Directory structure")
for line in report["directory_structure"][:200]:
print(line)
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Scan a repository and generate onboarding summary facts.")
parser.add_argument("path", help="Path to project directory")
parser.add_argument("--max-depth", type=int, default=2, help="Max depth for structure output (default: 2)")
parser.add_argument("--json", action="store_true", help="Print JSON output")
return parser.parse_args()
def main() -> int:
args = parse_args()
root = Path(args.path).expanduser().resolve()
if not root.exists() or not root.is_dir():
raise SystemExit(f"Path is not a directory: {root}")
report = build_report(root, max_depth=max(1, args.max_depth))
if args.json:
print(json.dumps(report, indent=2))
else:
print_text(report)
return 0
if __name__ == "__main__":
raise SystemExit(main())
Phân tích ngược codebase frontend để tạo tài liệu yêu cầu sản phẩm (PRD).
---
name: code-to-prd
description: Reverse-engineer a frontend codebase into a PRD. Usage: /code-to-prd [path]
---
# /code-to-prd
Reverse-engineer a frontend codebase into a complete Product Requirements Document.
## Usage
```bash
/code-to-prd # Analyze current project
/code-to-prd ./src # Analyze specific directory
/code-to-prd /path/to/project # Analyze external project
```
## What It Does
1. **Scan** — Run `codebase_analyzer.py` to detect framework, routes, APIs, enums, and project structure
2. **Scaffold** — Run `prd_scaffolder.py` to create `prd/` directory with README.md, per-page stubs, and appendix files
3. **Analyze** — Walk through each page following the Phase 2 workflow: fields, interactions, API dependencies, page relationships
4. **Generate** — Produce the final PRD with all pages, enum dictionary, API inventory, and page relationship map
## Steps
### Step 1: Analyze
Determine the project path (default: current directory). Run the frontend analyzer:
```bash
python3 {skill_path}/scripts/codebase_analyzer.py {project_path} -o .code-to-prd-analysis.json
```
Display a summary of findings: framework, page count, API count, enum count.
### Step 2: Scaffold
Generate the PRD directory skeleton:
```bash
python3 {skill_path}/scripts/prd_scaffolder.py .code-to-prd-analysis.json -o prd/
```
### Step 3: Fill
For each page in the inventory, follow the SKILL.md Phase 2 workflow:
- Read the page's component files
- Document fields, interactions, API dependencies, page relationships
- Fill in the corresponding `prd/pages/` stub
Work in batches of 3-5 pages for large projects (>15 pages). Ask the user to confirm after each batch.
### Step 4: Finalize
Complete the appendix files:
- `prd/appendix/enum-dictionary.md` — all enums and status codes found
- `prd/appendix/api-inventory.md` — consolidated API reference
- `prd/appendix/page-relationships.md` — navigation and data coupling map
Clean up the temporary analysis file:
```bash
rm .code-to-prd-analysis.json
```
## Output
A `prd/` directory containing:
- `README.md` — system overview, module map, page inventory
- `pages/*.md` — one file per page with fields, interactions, APIs
- `appendix/*.md` — enum dictionary, API inventory, page relationships
## Skill Reference
- `product-team/code-to-prd/SKILL.md`
- `product-team/code-to-prd/scripts/codebase_analyzer.py`
- `product-team/code-to-prd/scripts/prd_scaffolder.py`
- `product-team/code-to-prd/references/prd-quality-checklist.md`
Tạo tệp CodeTour hướng dẫn từng bước, liên kết tới tệp và dòng mã thật cho từng nhóm đối tượng.
---
name: "code-tour"
description: "Use when the user asks to create a CodeTour .tour file — persona-targeted, step-by-step walkthroughs that link to real files and line numbers. Trigger for: create a tour, onboarding tour, architecture tour, PR review tour, explain how X works, vibe check, RCA tour, contributor guide, or any structured code walkthrough request."
---
# Code Tour
Create **CodeTour** files — persona-targeted, step-by-step walkthroughs of a codebase that link directly to files and line numbers. CodeTour files live in `.tours/` and work with the [VS Code CodeTour extension](https://github.com/microsoft/codetour).
## Overview
A great tour is a **narrative** — a story told to a specific person about what matters, why it matters, and what to do next. Only create `.tour` JSON files. Never modify source code.
## When to Use This Skill
- User asks to create a code tour, onboarding tour, or architecture walkthrough
- User says "tour for this PR", "explain how X works", "vibe check", "RCA tour"
- User wants a contributor guide, security review, or bug investigation walkthrough
- Any request for a structured walkthrough with file/line anchors
## Core Workflow
### 1. Discover the repo
Before asking anything, explore the codebase:
In parallel: list root directory, read README, check config files.
Then: identify language(s), framework(s), project purpose. Map folder structure 1-2 levels deep. Find entry points — every path in the tour must be real.
If the repo has fewer than 5 source files, create a quick-depth tour regardless of persona — there's not enough to warrant a deep one.
### 2. Infer the intent
One message should be enough. Infer persona, depth, and focus silently.
| User says | Persona | Depth |
|-----------|---------|-------|
| "tour for this PR" | pr-reviewer | standard |
| "why did X break" / "RCA" | rca-investigator | standard |
| "onboarding" / "new joiner" | new-joiner | standard |
| "quick tour" / "vibe check" | vibecoder | quick |
| "architecture" | architect | deep |
| "security" / "auth review" | security-reviewer | standard |
| (no qualifier) | new-joiner | standard |
When intent is ambiguous, default to **new-joiner** persona at **standard** depth — it's the most generally useful.
### 3. Read actual files
**Every file path and line number must be verified.** A tour pointing to the wrong line is worse than no tour.
### 4. Write the tour
Save to `.tours/<persona>-<focus>.tour`.
```json
{
"$schema": "https://aka.ms/codetour-schema",
"title": "Descriptive Title — Persona / Goal",
"description": "Who this is for and what they'll understand after.",
"ref": "<current-branch-or-commit>",
"steps": []
}
```
### Step types
| Type | When to use | Example |
|------|-------------|---------|
| **Content** | Intro/closing only (max 2) | `{ "title": "Welcome", "description": "..." }` |
| **Directory** | Orient to a module | `{ "directory": "src/services", "title": "..." }` |
| **File + line** | The workhorse | `{ "file": "src/auth.ts", "line": 42, "title": "..." }` |
| **Selection** | Highlight a code block | `{ "file": "...", "selection": {...}, "title": "..." }` |
| **Pattern** | Regex match (volatile files) | `{ "file": "...", "pattern": "class App", "title": "..." }` |
| **URI** | Link to PR, issue, doc | `{ "uri": "https://...", "title": "..." }` |
### Step count
| Depth | Steps | Use for |
|-------|-------|---------|
| Quick | 5-8 | Vibecoder, fast exploration |
| Standard | 9-13 | Most personas |
| Deep | 14-18 | Architect, RCA |
### Writing descriptions — SMIG formula
- **S — Situation**: What is the reader looking at?
- **M — Mechanism**: How does this code work?
- **I — Implication**: Why does this matter for this persona?
- **G — Gotcha**: What would a smart person get wrong?
### 5. Validate
- [ ] Every `file` path relative to repo root (no leading `/` or `./`)
- [ ] Every `file` confirmed to exist
- [ ] Every `line` verified by reading the file
- [ ] First step has `file` or `directory` anchor
- [ ] At most 2 content-only steps
- [ ] `nextTour` matches another tour's `title` exactly if set
## Personas
| Persona | Goal | Must cover |
|---------|------|------------|
| **Vibecoder** | Get the vibe fast | Entry point, main modules. Max 8 steps. |
| **New joiner** | Structured ramp-up | Directories, setup, business context |
| **Bug fixer** | Root cause fast | Trigger -> fault points -> tests |
| **RCA investigator** | Why did it fail | Causality chain, observability anchors |
| **Feature explainer** | End-to-end | UI -> API -> backend -> storage |
| **PR reviewer** | Review correctly | Change story, invariants, risky areas |
| **Architect** | Shape and rationale | Boundaries, tradeoffs, extension points |
| **Security reviewer** | Trust boundaries | Auth flow, validation, secret handling |
| **Refactorer** | Safe restructuring | Seams, hidden deps, extraction order |
| **External contributor** | Contribute safely | Safe areas, conventions, landmines |
## Narrative Arc
1. **Orientation** — `file` or `directory` step (never content-only first step — blank in VS Code)
2. **High-level map** — 1-3 directory steps showing major modules
3. **Core path** — file/line steps, the heart of the tour
4. **Closing** — what the reader can now do, suggested follow-ups
## Anti-Patterns
| Anti-pattern | Fix |
|---|---|
| **File listing** — "this file contains the models" | Tell a story. Each step depends on the previous. |
| **Generic descriptions** | Name the specific pattern unique to this codebase. |
| **Line number guessing** | Never write a line you didn't verify by reading. |
| **Too many steps** for quick depth | Actually cut steps. |
| **Hallucinated files** | If it doesn't exist, skip the step. |
| **Recap closing** — "we covered X, Y, Z" | Tell the reader what they can now *do*. |
| **Content-only first step** | Anchor step 1 to a file or directory. |
## Cross-References
- Related: `engineering/codebase-onboarding` — for broader onboarding beyond tours
- Related: `engineering/pr-review-expert` — for automated PR review workflows
- CodeTour extension: [microsoft/codetour](https://github.com/microsoft/codetour)
- Real-world tours: [coder/code-server](https://github.com/coder/code-server/blob/main/.tours/contributing.tour)
Xây ma trận phân tích cạnh tranh với chấm điểm và phân tích khoảng cách.
---
name: competitive-matrix
description: Build competitive analysis matrices with scoring and gap analysis. Usage: /competitive-matrix <analyze> [options]
---
# /competitive-matrix
Build competitive matrices with weighted scoring, gap analysis, and market positioning insights.
## Usage
```
/competitive-matrix analyze <competitors.json> Full analysis
/competitive-matrix analyze <competitors.json> --weights pricing=2,ux=1.5 Custom weights
```
## Input Format
```json
{
"your_product": { "name": "MyApp", "scores": {"ux": 8, "pricing": 7, "features": 9} },
"competitors": [
{ "name": "Competitor A", "scores": {"ux": 7, "pricing": 9, "features": 6} }
],
"dimensions": ["ux", "pricing", "features"]
}
```
## Examples
```
/competitive-matrix analyze competitors.json
/competitive-matrix analyze competitors.json --format json --output matrix.json
```
## Scripts
- `product-team/competitive-teardown/scripts/competitive_matrix_builder.py` — Matrix builder
## Skill Reference
→ `product-team/competitive-teardown/SKILL.md`